Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c723598
Add basic regex to map roles from ETDA API
jvitorbarros15 Feb 4, 2026
9ab610c
Fix committee_role_normalizer to pass Rspec
jvitorbarros15 Feb 4, 2026
4d4a22c
Fix Dockerfile with updated image version and rubocop corrections
jvitorbarros15 Feb 4, 2026
f2fbf9d
Rubocop
jvitorbarros15 Feb 5, 2026
8eb2396
Add edta api importer
jvitorbarros15 Feb 11, 2026
9ae71a2
Fix name to etda_committee_memberships_xml_builder.rb
jvitorbarros15 Feb 11, 2026
804bc0f
Add rspec tests for the api importer
jvitorbarros15 Feb 11, 2026
f887c48
Refactor ETDA committee memberships XML builder to use Nokogiri
jvitorbarros15 Feb 12, 2026
e898cc7
Setup skeleton for the api client with initialize
usmannsiddiqui Feb 17, 2026
15f502a
Skeleton initialized, didnt save file initially
usmannsiddiqui Feb 17, 2026
f488a2b
Finished setting up the parameters for faculty committee
madhurakhandkar Feb 19, 2026
eb0e15c
Created rspec tests for committee records client
madhurakhandkar Feb 19, 2026
21063fa
Add .envrc.example for environment configuration
usmannsiddiqui Feb 23, 2026
e8df657
Add environment variables to .envrc.example
usmannsiddiqui Feb 23, 2026
2bbed27
Fix API client to use env variable methods
usmannsiddiqui Feb 24, 2026
f76696c
changed explanation in envr.example
usmannsiddiqui Feb 24, 2026
a40040c
Complete RSpec tests for CommitteeRecordsClient and fix API key header
usmannsiddiqui Feb 27, 2026
740d5bc
Add Committee.delete_all to ApplicationJob cleanup
usmannsiddiqui Feb 27, 2026
70bff3c
Finished rspec tests for committee_records_client
madhurakhandkar Mar 2, 2026
54279de
WIP: Add EtdaImporter skeleton for pulling committee data from ETDA API
usmannsiddiqui Mar 2, 2026
8b3fd0a
Merge branch 'api-client-etda' into etda-post-receiver
usmannsiddiqui Mar 2, 2026
be3fc7a
Wire EtdaImporter into ActivityInsightCommitteeJob and fix importer
usmannsiddiqui Mar 5, 2026
8fe4f47
Modified the etda importer and created rspec tests for the etda importer
madhurakhandkar Mar 5, 2026
ebc0229
adding rubocop fixes for now
madhurakhandkar Mar 17, 2026
3a0ef49
Rspec tests passed - Fix integrate method to accept target directly i…
usmannsiddiqui Mar 17, 2026
3011030
Fix etda importer spec to correctly test committee creation and error…
usmannsiddiqui Mar 17, 2026
aa0093a
Fix rspec tests for committee records client
usmannsiddiqui Mar 17, 2026
a832109
deleted 2 redundant files
usmannsiddiqui Mar 19, 2026
74c5441
fixed up regex in committe normalizer and updated envrc.example
usmannsiddiqui Mar 19, 2026
4c30e5a
Rubocop corrections
jvitorbarros15 Mar 19, 2026
d086479
Fix Activity Insight dropdown mappings for committee data
usmannsiddiqui Mar 24, 2026
092e2f4
Fix XML tag COMP -> COMPSTAGE to match Activity Insight schema
usmannsiddiqui Mar 24, 2026
7cfbafb
Remove wihtdrawn from determine_comp method
usmannsiddiqui Mar 26, 2026
1f78a66
Remove degree_type and role_other_explanation from committee data pip…
usmannsiddiqui Mar 26, 2026
1442e91
Add migration to drop degree_type and role_other_explanation from com…
usmannsiddiqui Mar 26, 2026
1691ad8
Address PR feedback: remove type_other_explanation, fix migration, up…
usmannsiddiqui Mar 31, 2026
2e902d9
Filter committee imports to last 6 months, remove .irb_history from t…
usmannsiddiqui Apr 1, 2026
0eb5b3b
Update base Docker image to ruby-3.4.9-node-22
usmannsiddiqui Apr 2, 2026
5366429
Merge remote-tracking branch 'origin/main' into etda-post-receiver
usmannsiddiqui Apr 2, 2026
4dca339
Removed type_other_explantion from schema
usmannsiddiqui Apr 2, 2026
1e7b753
Removed submission status from etda_importer and fixed rails logger e…
usmannsiddiqui Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .envrc.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Example environment variables for FAMS Tools
# Copy this file to .envrc and replace with actual values

export ETDA_API_URL="http://localhost:3000"
export ETDA_API_TOKEN="generate_a_token_in_ETDA_console"
export FAMS_WEBSERVICES_USERNAME
export FAMS_WEBSERVICES_PASSWORD
export FAMS_MAIN_USERNAME
export FAMS_MAIN_PASSWORD
export FAMS_BACKUPS_SERVICE_USERNAME
export FAMS_BACKUPS_SERVICE_PASSWORD
export FAMS_METADATA_DB_KEY
export FAMS_S3_BUCKET_API_KEY
export FAMS_LP_SFTP_USERNAME
export FAMS_LP_SFTP_HOST
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ spec/examples.txt
.envrc
.irb_history
.DS_Store
CLAUDE.md
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ GEM
builder (3.3.0)
bundle-audit (0.1.0)
bundler-audit
bundler-audit (0.9.1)
bundler (>= 1.2.0, < 3)
bundler-audit (0.9.3)
bundler (>= 1.2.0)
thor (~> 1.0)
byebug (11.1.3)
capistrano (3.18.0)
Expand Down Expand Up @@ -526,4 +526,4 @@ DEPENDENCIES
whenever

BUNDLED WITH
4.0.9
4.0.9
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ This app parses data from faculty CVs, and integrates data into Activity Insight
## Setup

Add the following untracked files/folders:
* `config/database.yml` This will be similar to `config/database.yml.sample`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should be added back, I just wanted you to remove the extra string quotes that were added.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added back the config/database.yml line

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About .irb_history file. Removed it from git tracking with git rm --cached .irb_history


* `config/database.yml` This will be similar to `config/database.yml.sample`
* `config/activity_insight.yml`
* `config/integration_passcode.yml`
* `public/log`
Expand Down
5 changes: 4 additions & 1 deletion app/importers/committee_data/committee_xml_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ def build_xml(batch)
committees.each do |committee|
xml.DSL do
xml.ROLE committee.role
xml.TYPE committee.type_of_work
xml.COMPSTAGE committee.stage_of_completion
xml.DTY_START committee.start_year
xml.DTY_END committee.completion_year if committee.completion_year

xml.DSL_STUDENT do
xml.FNAME committee.student_fname
xml.LNAME committee.student_lname
xml.DEG committee.degree_type
xml.TITLE committee.thesis_title
end
end
Expand Down
81 changes: 81 additions & 0 deletions app/importers/committee_data/etda_importer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
require 'etda/committee_records_client'

module CommitteeData
class EtdaImporter
class DegreeTypeError < RuntimeError; end

def import_all
Faculty.find_each do |faculty|
import_for_faculty(faculty)
rescue Etda::CommitteeRecordsClient::CommitteeRecordsClientError => e
Rails.logger.error("Failed to import committees for #{faculty.access_id}: #{e.message}")
end
end

private

def import_for_faculty(faculty)
result = Etda::CommitteeRecordsClient.new.faculty_committees(faculty.access_id)
committees_data = result[:data]['committees']

committees_data.each do |committee|
next unless within_last_six_months?(committee['approval_started_at'])

normalized_role = CommitteeRoleNormalizer.normalize(committee['role'])

faculty.committees.create!(
student_fname: committee['student_fname'],
student_lname: committee['student_lname'],
role: normalized_role,
thesis_title: committee['title'],
type_of_work: map_type_of_work(committee['degree_type']),
stage_of_completion: determine_completion_stage(
committee['final_submission_approved_at']
),
start_year: extract_year(committee['approval_started_at']),
completion_year: extract_year(committee['final_submission_approved_at'])
)
end

Rails.logger.info("Imported #{committees_data.length} committees for #{faculty.access_id}")
end

def map_type_of_work(degree_type)
return nil if degree_type.blank?

case degree_type.strip
when 'Master Thesis'
"Master's Committee"
when 'Dissertation'
'Dissertation Committee'
when 'Thesis'
'Undergraduate Honors Thesis'
when 'Final Paper'
"Master's Paper Committee"

else
raise DegreeTypeError, "Unexpected Degree Type: #{degree_type.strip}"
end
end

def extract_year(date_string)
return nil if date_string.blank?

Date.parse(date_string).year
rescue ArgumentError
nil
end

def within_last_six_months?(date_string)
return false if date_string.blank?

Date.parse(date_string) >= 6.months.ago
end

def determine_completion_stage(final_submission_approved_at)
return 'Completed' if final_submission_approved_at.present?

'In Process'
end
end
end
6 changes: 3 additions & 3 deletions app/jobs/activity_insight_committee_job.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class ActivityInsightCommitteeJob < ApplicationJob
def integrate(params)
target = params[:target]
def integrate(target)
CommitteeData::EtdaImporter.new.import_all

builder = CommitteeData::CommitteeXmlBuilder.new
xml_enum = builder.xmls_enumerator
Expand All @@ -12,6 +12,6 @@ def integrate(params)
private

def name
'Committee Membership Integration'
'Committee Integration'
end
end
1 change: 1 addition & 0 deletions app/jobs/application_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,6 @@ def delete_all_data
Yearly.delete_all
ComEffort.delete_all
ComQuality.delete_all
Committee.delete_all
end
end
25 changes: 25 additions & 0 deletions app/services/committee_role_normalizer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class CommitteeRoleNormalizer
PRIORITY_REGEX = [
['Co-Chairperson', %r{(co[-\s]?chair|co[-\s]?chairperson|committee chair/co-chair)}i],
['Chairperson', /(chairperson|chair of committee|committee chair|chair.)/i],
['Co-Advisor', /(co[-\s]?dissertation\s*advis(or|er)|co[-\s]?advisor)/i],
['Advisor', /(dissertation\s*advis(?:o?r|er)|advis(?:o?r|er))/i],
['Supervisor', /supervisor/i],
['Mentor', /mentor/i],
['Second Reader', /second\s+reader/i],
['Reader', /reader/i],
['Member', /(member|rep|represent|representative|substitute)/i]

].freeze

def self.normalize(raw_name)
text = raw_name.to_s.strip
return 'Other' if text.empty?

PRIORITY_REGEX.each do |label, regex|
return label if text.match?(regex)
end

'Other'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class AddActivityInsightFieldsToCommittees < ActiveRecord::Migration[7.2]
def change
change_table :committees, bulk: true do |t|
t.string :type_of_work
t.string :stage_of_completion
t.integer :start_year
t.integer :completion_year
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class RemoveDegreeTypeAndRoleOtherFromCommittees < ActiveRecord::Migration[7.0]
def change
remove_column :committees, :degree_type, :string
end
end
7 changes: 5 additions & 2 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.2].define(version: 2025_12_05_172036) do
ActiveRecord::Schema[7.2].define(version: 2026_03_26_000000) do
create_table "authors", charset: "utf8mb4", force: :cascade do |t|
t.string "f_name"
t.string "m_name"
Expand Down Expand Up @@ -58,9 +58,12 @@
t.string "student_lname"
t.string "role"
t.string "thesis_title"
t.string "degree_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "type_of_work"
t.string "stage_of_completion"
t.integer "start_year"
t.integer "completion_year"
t.index ["faculty_id"], name: "index_committees_on_faculty_id"
end

Expand Down
53 changes: 53 additions & 0 deletions lib/etda/committee_records_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
require './lib/etda/committee_records_client'
require 'httparty'
module Etda
class CommitteeRecordsClient
class CommitteeRecordsClientError < StandardError; end

def faculty_committees(access_id)
response = HTTParty.post(
"#{base_url}/api/v1/committee_records/faculty_committees",
headers: headers,
body: body(access_id)
)

handle_response(response)

# Tells us the error with committe records client with the error itself
rescue StandardError => e
raise CommitteeRecordsClientError, e.message
end

private

def base_url
@base_url ||= ENV.fetch('ETDA_API_URL', 'http://localhost:3000')
end

def api_token
@api_token ||= ENV.fetch('ETDA_API_TOKEN', 'abc123')
end

def headers
{
'X-API-KEY' => api_token,
'Content-Type' => 'application/json'
}
end

def body(access_id)
{
access_id: access_id
}.to_json
end

def handle_response(response)
raise CommitteeRecordsClientError, response.parsed_response['error'] || 'Unknown error' unless response.success?

{
success: true,
data: response.parsed_response
}
end
end
end
41 changes: 34 additions & 7 deletions spec/importers/committee_data/committee_xml_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
let(:xml_builder_obj) { described_class.new }

describe '#xmls_enumerator' do
it 'returns an xml of DSL records' do
it 'returns an xml of DSL records with all Activity Insight fields' do
faculty = FactoryBot.create(:faculty, access_id: 'test123')

Committee.create!(
Expand All @@ -13,7 +13,10 @@
student_lname: 'User',
role: 'Mentor',
thesis_title: 'Test Title',
degree_type: 'PhD'
type_of_work: 'Ph.D. Dissertation Committee',
stage_of_completion: 'Completed',
start_year: 2024,
completion_year: 2026
)

expect(xml_builder_obj.xmls_enumerator.first).to eq(
Expand All @@ -22,10 +25,13 @@
<Record username="test123">
<DSL>
<ROLE>Mentor</ROLE>
<TYPE>Ph.D. Dissertation Committee</TYPE>
<COMPSTAGE>Completed</COMPSTAGE>
<DTY_START>2024</DTY_START>
<DTY_END>2026</DTY_END>
<DSL_STUDENT>
<FNAME>Test</FNAME>
<LNAME>User</LNAME>
<DEG>PhD</DEG>
<TITLE>Test Title</TITLE>
</DSL_STUDENT>
</DSL>
Expand All @@ -35,6 +41,27 @@
)
end

it 'omits DTY_END when completion year is nil' do
faculty = FactoryBot.create(:faculty, access_id: 'test123')

Committee.create!(
faculty: faculty,
student_fname: 'Test',
student_lname: 'User',
role: 'Mentor',
thesis_title: 'Test Title',
type_of_work: 'Ph.D. Dissertation Committee',
stage_of_completion: 'In Process',
start_year: 2024,
completion_year: nil
)

xml = xml_builder_obj.xmls_enumerator.first
expect(xml).to include('<DTY_START>')
expect(xml).not_to include('<DTY_END>')
expect(xml).to include('<COMPSTAGE>In Process</COMPSTAGE>')
end

it 'handles faculty with no committees' do
FactoryBot.create(:faculty, access_id: 'test123')

Expand All @@ -46,8 +73,8 @@
faculty1 = FactoryBot.create(:faculty, access_id: 'fac1')
faculty2 = FactoryBot.create(:faculty, access_id: 'fac2')

Committee.create!(faculty: faculty1, student_fname: 'John', student_lname: 'Doe', role: 'Mentor', thesis_title: 'Title 1', degree_type: 'PhD')
Committee.create!(faculty: faculty2, student_fname: 'Jane', student_lname: 'Smith', role: 'Chair', thesis_title: 'Title 2', degree_type: 'MS')
Committee.create!(faculty: faculty1, student_fname: 'John', student_lname: 'Doe', role: 'Mentor', thesis_title: 'Title 1')
Committee.create!(faculty: faculty2, student_fname: 'Jane', student_lname: 'Smith', role: 'Chair', thesis_title: 'Title 2')

result = xml_builder_obj.xmls_enumerator.to_a
expect(result.length).to eq(1)
Expand All @@ -56,8 +83,8 @@
it 'handles faculty member with multiple committees' do
faculty = FactoryBot.create(:faculty, access_id: 'test123')

Committee.create!(faculty: faculty, student_fname: 'John', student_lname: 'Doe', role: 'Mentor', thesis_title: 'Title 1', degree_type: 'PhD')
Committee.create!(faculty: faculty, student_fname: 'Jane', student_lname: 'Smith', role: 'Member', thesis_title: 'Title 2', degree_type: 'MS')
Committee.create!(faculty: faculty, student_fname: 'John', student_lname: 'Doe', role: 'Mentor', thesis_title: 'Title 1')
Committee.create!(faculty: faculty, student_fname: 'Jane', student_lname: 'Smith', role: 'Member', thesis_title: 'Title 2')

xml = xml_builder_obj.xmls_enumerator.first
expect(xml.scan('<DSL>').count).to eq(2)
Expand Down
Loading
Loading