From 648e8955bb5ff0e565add55f4b391958bc89423f Mon Sep 17 00:00:00 2001 From: usmannsiddiqui Date: Thu, 23 Apr 2026 13:23:30 -0400 Subject: [PATCH 1/7] fix: extract months from ETDA dates and add same-date logic to avoid 2025-2025 duplication (#issue2) --- .../committee_data/committee_xml_builder.rb | 11 +- app/importers/committee_data/etda_importer.rb | 12 +- ...20260423000000_add_months_to_committees.rb | 8 ++ db/schema.rb | 46 +++---- .../committee_xml_builder_spec.rb | 116 +++++++++++++++++- .../committee_data/etda_importer_spec.rb | 50 ++++++++ 6 files changed, 218 insertions(+), 25 deletions(-) create mode 100644 db/migrate/20260423000000_add_months_to_committees.rb diff --git a/app/importers/committee_data/committee_xml_builder.rb b/app/importers/committee_data/committee_xml_builder.rb index 5d66223..c237f2a 100644 --- a/app/importers/committee_data/committee_xml_builder.rb +++ b/app/importers/committee_data/committee_xml_builder.rb @@ -27,7 +27,16 @@ def build_xml(batch) xml.ROLE committee.role xml.TYPE committee.type_of_work xml.COMPSTAGE committee.stage_of_completion - xml.DTY_START committee.start_year + + same_date = committee.start_year == committee.completion_year && + committee.start_month == committee.completion_month + + unless same_date + xml.DTM_START Date::MONTHNAMES[committee.start_month] if committee.start_month + xml.DTY_START committee.start_year if committee.start_year + end + + xml.DTM_END Date::MONTHNAMES[committee.completion_month] if committee.completion_month xml.DTY_END committee.completion_year if committee.completion_year xml.DSL_STUDENT do diff --git a/app/importers/committee_data/etda_importer.rb b/app/importers/committee_data/etda_importer.rb index 4c14be8..42a52bb 100644 --- a/app/importers/committee_data/etda_importer.rb +++ b/app/importers/committee_data/etda_importer.rb @@ -33,7 +33,9 @@ def import_for_faculty(faculty) committee['final_submission_approved_at'] ), start_year: extract_year(committee['approval_started_at']), - completion_year: extract_year(committee['final_submission_approved_at']) + start_month: extract_month(committee['approval_started_at']), + completion_year: extract_year(committee['final_submission_approved_at']), + completion_month: extract_month(committee['final_submission_approved_at']) ) end @@ -66,6 +68,14 @@ def extract_year(date_string) nil end + def extract_month(date_string) + return nil if date_string.blank? + + Date.parse(date_string).month + rescue ArgumentError + nil + end + def within_last_six_months?(date_string) return false if date_string.blank? diff --git a/db/migrate/20260423000000_add_months_to_committees.rb b/db/migrate/20260423000000_add_months_to_committees.rb new file mode 100644 index 0000000..c067d30 --- /dev/null +++ b/db/migrate/20260423000000_add_months_to_committees.rb @@ -0,0 +1,8 @@ +class AddMonthsToCommittees < ActiveRecord::Migration[7.2] + def change + change_table :committees, bulk: true do |t| + t.integer :start_month + t.integer :completion_month + end + end +end diff --git a/db/schema.rb b/db/schema.rb index acc3a25..56af42c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,8 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2026_03_26_000000) do - create_table "authors", charset: "utf8mb4", force: :cascade do |t| +ActiveRecord::Schema[7.2].define(version: 2026_04_23_000000) do + create_table "authors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "f_name" t.string "m_name" t.string "l_name" @@ -19,7 +19,7 @@ t.index ["work_id"], name: "fk_rails_ef7807179c" end - create_table "com_efforts", charset: "utf8mb4", force: :cascade do |t| + create_table "com_efforts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "com_id" @@ -35,7 +35,7 @@ t.index ["faculty_id"], name: "fk_rails_c1c0816923" end - create_table "com_qualities", charset: "utf8mb4", force: :cascade do |t| + create_table "com_qualities", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "com_id" @@ -51,7 +51,7 @@ t.index ["faculty_id"], name: "fk_rails_5da34f5b2e" end - create_table "committees", charset: "utf8mb4", force: :cascade do |t| + create_table "committees", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "faculty_id", null: false t.string "student_fname" t.string "student_mname" @@ -64,10 +64,12 @@ t.string "stage_of_completion" t.integer "start_year" t.integer "completion_year" + t.integer "start_month" + t.integer "completion_month" t.index ["faculty_id"], name: "index_committees_on_faculty_id" end - create_table "contract_faculty_links", charset: "utf8mb4", force: :cascade do |t| + create_table "contract_faculty_links", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "role" t.integer "pct_credit" t.bigint "contract_id" @@ -76,7 +78,7 @@ t.index ["faculty_id"], name: "fk_rails_7f7c136a9d" end - create_table "contracts", charset: "utf8mb4", force: :cascade do |t| + create_table "contracts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.integer "osp_key" t.string "title" t.bigint "sponsor_id" @@ -98,7 +100,7 @@ t.index ["sponsor_id"], name: "fk_rails_918599a14c" end - create_table "courses", charset: "utf8mb4", force: :cascade do |t| + create_table "courses", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.integer "academic_course_id" t.string "term" t.integer "calendar_year" @@ -107,7 +109,7 @@ t.index ["academic_course_id", "term", "calendar_year"], name: "index_courses_on_academic_course_id_and_term_and_calendar_year", unique: true end - create_table "editors", charset: "utf8mb4", force: :cascade do |t| + create_table "editors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "f_name" t.string "m_name" t.string "l_name" @@ -115,7 +117,7 @@ t.index ["work_id"], name: "fk_rails_6c877ed7df" end - create_table "external_authors", charset: "utf8mb4", force: :cascade do |t| + create_table "external_authors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "publication_id" t.string "f_name" t.string "m_name" @@ -127,7 +129,7 @@ t.index ["publication_id"], name: "fk_rails_eb03e1acd5" end - create_table "faculties", charset: "utf8mb4", force: :cascade do |t| + create_table "faculties", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "access_id" t.bigint "user_id" t.string "f_name" @@ -141,14 +143,14 @@ t.index ["access_id"], name: "index_faculties_on_access_id", unique: true end - create_table "integrations", charset: "utf8mb4", force: :cascade do |t| + create_table "integrations", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "process_type" t.boolean "is_active" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "personal_contacts", charset: "utf8mb4", force: :cascade do |t| + create_table "personal_contacts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "faculty_id", null: false t.string "telephone_number" t.string "postal_address" @@ -167,7 +169,7 @@ t.index ["faculty_id"], name: "index_personal_contacts_on_faculty_id", unique: true end - create_table "presentation_contributors", charset: "utf8mb4", force: :cascade do |t| + create_table "presentation_contributors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "presentation_id", null: false t.string "f_name" t.string "m_name" @@ -175,7 +177,7 @@ t.index ["presentation_id"], name: "index_presentation_contributors_on_presentation_id" end - create_table "presentations", charset: "utf8mb4", force: :cascade do |t| + create_table "presentations", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "faculty_id", null: false t.string "title" t.string "dty_date" @@ -185,7 +187,7 @@ t.index ["faculty_id"], name: "index_presentations_on_faculty_id" end - create_table "publication_faculty_links", charset: "utf8mb4", force: :cascade do |t| + create_table "publication_faculty_links", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "faculty_id" t.bigint "publication_id" t.string "category" @@ -197,14 +199,14 @@ t.index ["publication_id"], name: "fk_rails_7abcf28acb" end - create_table "publication_listings", charset: "utf8mb4", force: :cascade do |t| + create_table "publication_listings", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "name" t.string "type" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "publications", charset: "utf8mb4", force: :cascade do |t| + create_table "publications", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.text "title" t.integer "volume" t.integer "dty" @@ -231,7 +233,7 @@ t.bigint "rmd_id" end - create_table "sections", charset: "utf8mb4", force: :cascade do |t| + create_table "sections", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "class_campus_code" t.string "cross_listed_flag" t.string "course_number" @@ -253,13 +255,13 @@ t.index ["faculty_id", "course_id", "class_campus_code", "subject_code", "course_number", "course_suffix", "class_section_code", "course_component"], name: "pkey", unique: true, length: { class_campus_code: 50, subject_code: 50, course_suffix: 50, class_section_code: 50, course_component: 50 } end - create_table "sponsors", charset: "utf8mb4", force: :cascade do |t| + create_table "sponsors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "sponsor_name" t.string "sponsor_type" t.index ["sponsor_name"], name: "index_sponsors_on_sponsor_name", unique: true end - create_table "works", charset: "utf8mb4", force: :cascade do |t| + create_table "works", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "publication_listing_id" t.text "title" t.string "journal" @@ -290,7 +292,7 @@ t.index ["publication_listing_id"], name: "index_works_on_publication_listing_id" end - create_table "yearlies", charset: "utf8mb4", force: :cascade do |t| + create_table "yearlies", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "faculty_id" t.string "academic_year" t.string "campus" diff --git a/spec/importers/committee_data/committee_xml_builder_spec.rb b/spec/importers/committee_data/committee_xml_builder_spec.rb index 0e4cfa5..3dd77f4 100644 --- a/spec/importers/committee_data/committee_xml_builder_spec.rb +++ b/spec/importers/committee_data/committee_xml_builder_spec.rb @@ -16,7 +16,9 @@ type_of_work: 'Ph.D. Dissertation Committee', stage_of_completion: 'Completed', start_year: 2024, - completion_year: 2026 + start_month: 8, + completion_year: 2026, + completion_month: 1 ) expect(xml_builder_obj.xmls_enumerator.first).to eq( @@ -27,7 +29,9 @@ Mentor Ph.D. Dissertation Committee Completed + August 2024 + January 2026 Test @@ -62,6 +66,116 @@ expect(xml).to include('In Process') end + it 'emits only end-date tags when start and end dates are the same year and month' do + faculty = FactoryBot.create(:faculty, access_id: 'test123') + + Committee.create!( + faculty: faculty, + student_fname: 'Test', + student_lname: 'User', + role: 'Mentor', + thesis_title: 'Test Title', + start_year: 2025, + start_month: 3, + completion_year: 2025, + completion_month: 3 + ) + + xml = xml_builder_obj.xmls_enumerator.first + expect(xml).not_to include('') + expect(xml).not_to include('') + expect(xml).to include('March') + expect(xml).to include('2025') + end + + it 'emits all four date tags when year matches but months differ' do + faculty = FactoryBot.create(:faculty, access_id: 'test123') + + Committee.create!( + faculty: faculty, + student_fname: 'Test', + student_lname: 'User', + role: 'Mentor', + thesis_title: 'Test Title', + start_year: 2025, + start_month: 3, + completion_year: 2025, + completion_month: 11 + ) + + xml = xml_builder_obj.xmls_enumerator.first + expect(xml).to include('March') + expect(xml).to include('2025') + expect(xml).to include('November') + expect(xml).to include('2025') + end + + it 'emits all four date tags when years differ' do + faculty = FactoryBot.create(:faculty, access_id: 'test123') + + Committee.create!( + faculty: faculty, + student_fname: 'Test', + student_lname: 'User', + role: 'Mentor', + thesis_title: 'Test Title', + start_year: 2024, + start_month: 8, + completion_year: 2026, + completion_month: 1 + ) + + xml = xml_builder_obj.xmls_enumerator.first + expect(xml).to include('August') + expect(xml).to include('2024') + expect(xml).to include('January') + expect(xml).to include('2026') + end + + it 'emits only DTY_END when months are nil and years are the same' do + faculty = FactoryBot.create(:faculty, access_id: 'test123') + + Committee.create!( + faculty: faculty, + student_fname: 'Test', + student_lname: 'User', + role: 'Mentor', + thesis_title: 'Test Title', + start_year: 2025, + start_month: nil, + completion_year: 2025, + completion_month: nil + ) + + xml = xml_builder_obj.xmls_enumerator.first + expect(xml).not_to include('') + expect(xml).not_to include('') + expect(xml).not_to include('') + expect(xml).to include('2025') + end + + it 'emits both years and no month tags when months are nil and years differ' do + faculty = FactoryBot.create(:faculty, access_id: 'test123') + + Committee.create!( + faculty: faculty, + student_fname: 'Test', + student_lname: 'User', + role: 'Mentor', + thesis_title: 'Test Title', + start_year: 2024, + start_month: nil, + completion_year: 2026, + completion_month: nil + ) + + xml = xml_builder_obj.xmls_enumerator.first + expect(xml).to include('2024') + expect(xml).to include('2026') + expect(xml).not_to include('') + expect(xml).not_to include('') + end + it 'handles faculty with no committees' do FactoryBot.create(:faculty, access_id: 'test123') diff --git a/spec/importers/committee_data/etda_importer_spec.rb b/spec/importers/committee_data/etda_importer_spec.rb index 86819b0..91a01e5 100644 --- a/spec/importers/committee_data/etda_importer_spec.rb +++ b/spec/importers/committee_data/etda_importer_spec.rb @@ -38,7 +38,29 @@ expect(Committee.last.type_of_work).to eq('Dissertation Committee') expect(Committee.last.stage_of_completion).to eq('Completed') expect(Committee.last.start_year).to eq(1.month.ago.year) + expect(Committee.last.start_month).to eq(1.month.ago.month) expect(Committee.last.completion_year).to eq(2026) + expect(Committee.last.completion_month).to eq(1) + end + end + + context 'when final_submission_approved_at is nil' do + let(:api_response) do + { data: { 'committees' => [ + { 'student_fname' => 'Spider', 'student_lname' => 'Man', + 'role' => 'advisor', 'title' => 'My Thesis', 'degree_type' => 'Dissertation', + 'approval_started_at' => 1.month.ago.iso8601, + 'final_submission_approved_at' => nil, + 'submission_status' => 'waiting for publication release' } + ] } } + end + + before { allow(client).to receive(:faculty_committees).and_return(api_response) } + + it 'stores nil for completion_year and completion_month' do + importer.import_all + expect(Committee.last.completion_year).to be_nil + expect(Committee.last.completion_month).to be_nil end end end @@ -98,6 +120,34 @@ end end + describe '#extract_month' do + subject(:extract) { importer.send(:extract_month, date_string) } + + context 'with a valid ISO8601 date string' do + let(:date_string) { '2026-01-15T10:30:00Z' } + + it { is_expected.to eq(1) } + end + + context 'with nil' do + let(:date_string) { nil } + + it { is_expected.to be_nil } + end + + context 'with an empty string' do + let(:date_string) { '' } + + it { is_expected.to be_nil } + end + + context 'with an invalid date string' do + let(:date_string) { 'not-a-date' } + + it { is_expected.to be_nil } + end + end + describe '#within_last_six_months?' do it 'returns true for a date within the last 6 months' do recent_date = 1.month.ago.iso8601 From ed3772c4defb16dee8f24cec4fe72223b1b31fa1 Mon Sep 17 00:00:00 2001 From: usmannsiddiqui Date: Thu, 23 Apr 2026 13:31:22 -0400 Subject: [PATCH 2/7] chore: remove local-env collation noise from schema.rb dump --- db/schema.rb | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 56af42c..f0706f0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema[7.2].define(version: 2026_04_23_000000) do - create_table "authors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "authors", charset: "utf8mb4", force: :cascade do |t| t.string "f_name" t.string "m_name" t.string "l_name" @@ -19,7 +19,7 @@ t.index ["work_id"], name: "fk_rails_ef7807179c" end - create_table "com_efforts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "com_efforts", charset: "utf8mb4", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "com_id" @@ -35,7 +35,7 @@ t.index ["faculty_id"], name: "fk_rails_c1c0816923" end - create_table "com_qualities", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "com_qualities", charset: "utf8mb4", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "com_id" @@ -51,7 +51,7 @@ t.index ["faculty_id"], name: "fk_rails_5da34f5b2e" end - create_table "committees", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "committees", charset: "utf8mb4", force: :cascade do |t| t.bigint "faculty_id", null: false t.string "student_fname" t.string "student_mname" @@ -69,7 +69,7 @@ t.index ["faculty_id"], name: "index_committees_on_faculty_id" end - create_table "contract_faculty_links", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "contract_faculty_links", charset: "utf8mb4", force: :cascade do |t| t.string "role" t.integer "pct_credit" t.bigint "contract_id" @@ -78,7 +78,7 @@ t.index ["faculty_id"], name: "fk_rails_7f7c136a9d" end - create_table "contracts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "contracts", charset: "utf8mb4", force: :cascade do |t| t.integer "osp_key" t.string "title" t.bigint "sponsor_id" @@ -100,7 +100,7 @@ t.index ["sponsor_id"], name: "fk_rails_918599a14c" end - create_table "courses", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "courses", charset: "utf8mb4", force: :cascade do |t| t.integer "academic_course_id" t.string "term" t.integer "calendar_year" @@ -109,7 +109,7 @@ t.index ["academic_course_id", "term", "calendar_year"], name: "index_courses_on_academic_course_id_and_term_and_calendar_year", unique: true end - create_table "editors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "editors", charset: "utf8mb4", force: :cascade do |t| t.string "f_name" t.string "m_name" t.string "l_name" @@ -117,7 +117,7 @@ t.index ["work_id"], name: "fk_rails_6c877ed7df" end - create_table "external_authors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "external_authors", charset: "utf8mb4", force: :cascade do |t| t.bigint "publication_id" t.string "f_name" t.string "m_name" @@ -129,7 +129,7 @@ t.index ["publication_id"], name: "fk_rails_eb03e1acd5" end - create_table "faculties", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "faculties", charset: "utf8mb4", force: :cascade do |t| t.string "access_id" t.bigint "user_id" t.string "f_name" @@ -143,14 +143,14 @@ t.index ["access_id"], name: "index_faculties_on_access_id", unique: true end - create_table "integrations", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "integrations", charset: "utf8mb4", force: :cascade do |t| t.string "process_type" t.boolean "is_active" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "personal_contacts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "personal_contacts", charset: "utf8mb4", force: :cascade do |t| t.bigint "faculty_id", null: false t.string "telephone_number" t.string "postal_address" @@ -169,7 +169,7 @@ t.index ["faculty_id"], name: "index_personal_contacts_on_faculty_id", unique: true end - create_table "presentation_contributors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "presentation_contributors", charset: "utf8mb4", force: :cascade do |t| t.bigint "presentation_id", null: false t.string "f_name" t.string "m_name" @@ -177,7 +177,7 @@ t.index ["presentation_id"], name: "index_presentation_contributors_on_presentation_id" end - create_table "presentations", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "presentations", charset: "utf8mb4", force: :cascade do |t| t.bigint "faculty_id", null: false t.string "title" t.string "dty_date" @@ -187,7 +187,7 @@ t.index ["faculty_id"], name: "index_presentations_on_faculty_id" end - create_table "publication_faculty_links", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "publication_faculty_links", charset: "utf8mb4", force: :cascade do |t| t.bigint "faculty_id" t.bigint "publication_id" t.string "category" @@ -199,14 +199,14 @@ t.index ["publication_id"], name: "fk_rails_7abcf28acb" end - create_table "publication_listings", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "publication_listings", charset: "utf8mb4", force: :cascade do |t| t.string "name" t.string "type" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "publications", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "publications", charset: "utf8mb4", force: :cascade do |t| t.text "title" t.integer "volume" t.integer "dty" @@ -233,7 +233,7 @@ t.bigint "rmd_id" end - create_table "sections", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "sections", charset: "utf8mb4", force: :cascade do |t| t.string "class_campus_code" t.string "cross_listed_flag" t.string "course_number" @@ -255,13 +255,13 @@ t.index ["faculty_id", "course_id", "class_campus_code", "subject_code", "course_number", "course_suffix", "class_section_code", "course_component"], name: "pkey", unique: true, length: { class_campus_code: 50, subject_code: 50, course_suffix: 50, class_section_code: 50, course_component: 50 } end - create_table "sponsors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "sponsors", charset: "utf8mb4", force: :cascade do |t| t.string "sponsor_name" t.string "sponsor_type" t.index ["sponsor_name"], name: "index_sponsors_on_sponsor_name", unique: true end - create_table "works", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "works", charset: "utf8mb4", force: :cascade do |t| t.bigint "publication_listing_id" t.text "title" t.string "journal" @@ -292,7 +292,7 @@ t.index ["publication_listing_id"], name: "index_works_on_publication_listing_id" end - create_table "yearlies", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| + create_table "yearlies", charset: "utf8mb4", force: :cascade do |t| t.bigint "faculty_id" t.string "academic_year" t.string "campus" From 38ab63dea04d84015992fd74d3fb0fd50963ebe4 Mon Sep 17 00:00:00 2001 From: Joao Vitor Barros da Silva Date: Tue, 28 Apr 2026 10:34:51 -0400 Subject: [PATCH 3/7] Add ROLE_OTHER support for unrecognized committee roles --- .../committee_data/committee_xml_builder.rb | 1 + app/importers/committee_data/etda_importer.rb | 5 ++- app/services/committee_role_normalizer.rb | 6 +-- ...0428000000_add_role_other_to_committees.rb | 5 +++ db/schema.rb | 45 ++++++++++--------- .../committee_xml_builder_spec.rb | 41 +++++++++++++++++ .../committee_role_normalizer_spec.rb | 18 ++++---- 7 files changed, 85 insertions(+), 36 deletions(-) create mode 100644 db/migrate/20260428000000_add_role_other_to_committees.rb diff --git a/app/importers/committee_data/committee_xml_builder.rb b/app/importers/committee_data/committee_xml_builder.rb index c237f2a..9ce87fc 100644 --- a/app/importers/committee_data/committee_xml_builder.rb +++ b/app/importers/committee_data/committee_xml_builder.rb @@ -25,6 +25,7 @@ def build_xml(batch) committees.each do |committee| xml.DSL do xml.ROLE committee.role + xml.ROLE_OTHER committee.role_other if committee.role_other.present? xml.TYPE committee.type_of_work xml.COMPSTAGE committee.stage_of_completion diff --git a/app/importers/committee_data/etda_importer.rb b/app/importers/committee_data/etda_importer.rb index 42a52bb..3a10f85 100644 --- a/app/importers/committee_data/etda_importer.rb +++ b/app/importers/committee_data/etda_importer.rb @@ -21,12 +21,13 @@ def import_for_faculty(faculty) committees_data.each do |committee| next unless within_last_six_months?(committee['approval_started_at']) - normalized_role = CommitteeRoleNormalizer.normalize(committee['role']) + role, role_other = CommitteeRoleNormalizer.normalize(committee['role']) faculty.committees.create!( student_fname: committee['student_fname'], student_lname: committee['student_lname'], - role: normalized_role, + role: role, + role_other: role_other, thesis_title: committee['title'], type_of_work: map_type_of_work(committee['degree_type']), stage_of_completion: determine_completion_stage( diff --git a/app/services/committee_role_normalizer.rb b/app/services/committee_role_normalizer.rb index 9df716c..9e8e5e9 100644 --- a/app/services/committee_role_normalizer.rb +++ b/app/services/committee_role_normalizer.rb @@ -14,12 +14,12 @@ class CommitteeRoleNormalizer def self.normalize(raw_name) text = raw_name.to_s.strip - return 'Other' if text.empty? + return ['Other', nil] if text.empty? PRIORITY_REGEX.each do |label, regex| - return label if text.match?(regex) + return [label, nil] if text.match?(regex) end - 'Other' + ['Other', text] end end diff --git a/db/migrate/20260428000000_add_role_other_to_committees.rb b/db/migrate/20260428000000_add_role_other_to_committees.rb new file mode 100644 index 0000000..898e2fe --- /dev/null +++ b/db/migrate/20260428000000_add_role_other_to_committees.rb @@ -0,0 +1,5 @@ +class AddRoleOtherToCommittees < ActiveRecord::Migration[7.2] + def change + add_column :committees, :role_other, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index f0706f0..eadd038 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,8 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2026_04_23_000000) do - create_table "authors", charset: "utf8mb4", force: :cascade do |t| +ActiveRecord::Schema[7.2].define(version: 2026_04_28_000000) do + create_table "authors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "f_name" t.string "m_name" t.string "l_name" @@ -19,7 +19,7 @@ t.index ["work_id"], name: "fk_rails_ef7807179c" end - create_table "com_efforts", charset: "utf8mb4", force: :cascade do |t| + create_table "com_efforts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "com_id" @@ -35,7 +35,7 @@ t.index ["faculty_id"], name: "fk_rails_c1c0816923" end - create_table "com_qualities", charset: "utf8mb4", force: :cascade do |t| + create_table "com_qualities", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "com_id" @@ -51,7 +51,7 @@ t.index ["faculty_id"], name: "fk_rails_5da34f5b2e" end - create_table "committees", charset: "utf8mb4", force: :cascade do |t| + create_table "committees", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "faculty_id", null: false t.string "student_fname" t.string "student_mname" @@ -66,10 +66,11 @@ t.integer "completion_year" t.integer "start_month" t.integer "completion_month" + t.string "role_other" t.index ["faculty_id"], name: "index_committees_on_faculty_id" end - create_table "contract_faculty_links", charset: "utf8mb4", force: :cascade do |t| + create_table "contract_faculty_links", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "role" t.integer "pct_credit" t.bigint "contract_id" @@ -78,7 +79,7 @@ t.index ["faculty_id"], name: "fk_rails_7f7c136a9d" end - create_table "contracts", charset: "utf8mb4", force: :cascade do |t| + create_table "contracts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.integer "osp_key" t.string "title" t.bigint "sponsor_id" @@ -100,7 +101,7 @@ t.index ["sponsor_id"], name: "fk_rails_918599a14c" end - create_table "courses", charset: "utf8mb4", force: :cascade do |t| + create_table "courses", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.integer "academic_course_id" t.string "term" t.integer "calendar_year" @@ -109,7 +110,7 @@ t.index ["academic_course_id", "term", "calendar_year"], name: "index_courses_on_academic_course_id_and_term_and_calendar_year", unique: true end - create_table "editors", charset: "utf8mb4", force: :cascade do |t| + create_table "editors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "f_name" t.string "m_name" t.string "l_name" @@ -117,7 +118,7 @@ t.index ["work_id"], name: "fk_rails_6c877ed7df" end - create_table "external_authors", charset: "utf8mb4", force: :cascade do |t| + create_table "external_authors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "publication_id" t.string "f_name" t.string "m_name" @@ -129,7 +130,7 @@ t.index ["publication_id"], name: "fk_rails_eb03e1acd5" end - create_table "faculties", charset: "utf8mb4", force: :cascade do |t| + create_table "faculties", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "access_id" t.bigint "user_id" t.string "f_name" @@ -143,14 +144,14 @@ t.index ["access_id"], name: "index_faculties_on_access_id", unique: true end - create_table "integrations", charset: "utf8mb4", force: :cascade do |t| + create_table "integrations", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "process_type" t.boolean "is_active" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "personal_contacts", charset: "utf8mb4", force: :cascade do |t| + create_table "personal_contacts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "faculty_id", null: false t.string "telephone_number" t.string "postal_address" @@ -169,7 +170,7 @@ t.index ["faculty_id"], name: "index_personal_contacts_on_faculty_id", unique: true end - create_table "presentation_contributors", charset: "utf8mb4", force: :cascade do |t| + create_table "presentation_contributors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "presentation_id", null: false t.string "f_name" t.string "m_name" @@ -177,7 +178,7 @@ t.index ["presentation_id"], name: "index_presentation_contributors_on_presentation_id" end - create_table "presentations", charset: "utf8mb4", force: :cascade do |t| + create_table "presentations", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "faculty_id", null: false t.string "title" t.string "dty_date" @@ -187,7 +188,7 @@ t.index ["faculty_id"], name: "index_presentations_on_faculty_id" end - create_table "publication_faculty_links", charset: "utf8mb4", force: :cascade do |t| + create_table "publication_faculty_links", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "faculty_id" t.bigint "publication_id" t.string "category" @@ -199,14 +200,14 @@ t.index ["publication_id"], name: "fk_rails_7abcf28acb" end - create_table "publication_listings", charset: "utf8mb4", force: :cascade do |t| + create_table "publication_listings", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "name" t.string "type" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "publications", charset: "utf8mb4", force: :cascade do |t| + create_table "publications", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.text "title" t.integer "volume" t.integer "dty" @@ -233,7 +234,7 @@ t.bigint "rmd_id" end - create_table "sections", charset: "utf8mb4", force: :cascade do |t| + create_table "sections", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "class_campus_code" t.string "cross_listed_flag" t.string "course_number" @@ -255,13 +256,13 @@ t.index ["faculty_id", "course_id", "class_campus_code", "subject_code", "course_number", "course_suffix", "class_section_code", "course_component"], name: "pkey", unique: true, length: { class_campus_code: 50, subject_code: 50, course_suffix: 50, class_section_code: 50, course_component: 50 } end - create_table "sponsors", charset: "utf8mb4", force: :cascade do |t| + create_table "sponsors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "sponsor_name" t.string "sponsor_type" t.index ["sponsor_name"], name: "index_sponsors_on_sponsor_name", unique: true end - create_table "works", charset: "utf8mb4", force: :cascade do |t| + create_table "works", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "publication_listing_id" t.text "title" t.string "journal" @@ -292,7 +293,7 @@ t.index ["publication_listing_id"], name: "index_works_on_publication_listing_id" end - create_table "yearlies", charset: "utf8mb4", force: :cascade do |t| + create_table "yearlies", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.bigint "faculty_id" t.string "academic_year" t.string "campus" diff --git a/spec/importers/committee_data/committee_xml_builder_spec.rb b/spec/importers/committee_data/committee_xml_builder_spec.rb index 3dd77f4..1bd6c31 100644 --- a/spec/importers/committee_data/committee_xml_builder_spec.rb +++ b/spec/importers/committee_data/committee_xml_builder_spec.rb @@ -203,5 +203,46 @@ xml = xml_builder_obj.xmls_enumerator.first expect(xml.scan('').count).to eq(2) end + + it 'emits ROLE_OTHER when role is Other and role_other is present' do + faculty = FactoryBot.create(:faculty, access_id: 'test123') + + Committee.create!( + faculty: faculty, + student_fname: 'Jane', + student_lname: 'Doe', + role: 'Other', + role_other: 'Support Faculty', + thesis_title: 'Some Title', + type_of_work: 'Dissertation Committee', + stage_of_completion: 'In Process', + start_year: 2025, + start_month: 1 + ) + + xml = xml_builder_obj.xmls_enumerator.first + expect(xml).to include('Other') + expect(xml).to include('Support Faculty') + end + + it 'does not emit ROLE_OTHER when role_other is blank' do + faculty = FactoryBot.create(:faculty, access_id: 'test123') + + Committee.create!( + faculty: faculty, + student_fname: 'Jane', + student_lname: 'Doe', + role: 'Mentor', + role_other: nil, + thesis_title: 'Some Title', + type_of_work: 'Dissertation Committee', + stage_of_completion: 'In Process', + start_year: 2025, + start_month: 1 + ) + + xml = xml_builder_obj.xmls_enumerator.first + expect(xml).not_to include('') + end end end diff --git a/spec/services/committee_role_normalizer_spec.rb b/spec/services/committee_role_normalizer_spec.rb index e02833c..23827da 100644 --- a/spec/services/committee_role_normalizer_spec.rb +++ b/spec/services/committee_role_normalizer_spec.rb @@ -5,43 +5,43 @@ it 'maps co-chair roles correctly' do expect( described_class.normalize('Co-Chair & Dissertation Advisor') - ).to eq('Co-Chairperson') + ).to eq(['Co-Chairperson', nil]) end it 'maps chair roles correctly' do expect( described_class.normalize('Chair of Committee') - ).to eq('Chairperson') + ).to eq(['Chairperson', nil]) end it 'prioritizes chair over advisor when both appear' do expect( described_class.normalize('Chair & Dissertation Advisor') - ).to eq('Chairperson') + ).to eq(['Chairperson', nil]) end it 'maps advisor roles' do expect( described_class.normalize('Dissertation Advisr') - ).to eq('Advisor') + ).to eq(['Advisor', nil]) end it 'maps member and representative roles' do expect( described_class.normalize('Committee Member & Dean Grad Sch Rep') - ).to eq('Member') + ).to eq(['Member', nil]) end - it 'returns Other for unknown roles' do + it 'returns Other with original text for unknown roles' do expect( described_class.normalize('Some Weird ETDA Thing') - ).to eq('Other') + ).to eq(['Other', 'Some Weird ETDA Thing']) end - it 'returns Other for blank values' do + it 'returns Other with nil text for blank values' do expect( described_class.normalize(nil) - ).to eq('Other') + ).to eq(['Other', nil]) end end end From 71dcfdfa4b3d2b691c1ad3170fae04e33647fb74 Mon Sep 17 00:00:00 2001 From: Joao Vitor Barros da Silva Date: Tue, 28 Apr 2026 10:36:43 -0400 Subject: [PATCH 4/7] Ignore superpowers plans directory --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8f89fd1..96a7618 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,5 @@ spec/examples.txt .envrc .irb_history .DS_Store -CLAUDE.md \ No newline at end of file +CLAUDE.md +/docs/superpowers/ \ No newline at end of file From 92fea6ce9e4cfe4fab17c194008db9ae80034714 Mon Sep 17 00:00:00 2001 From: usmannsiddiqui Date: Tue, 28 Apr 2026 12:27:25 -0400 Subject: [PATCH 5/7] Mark all imported committee fields as read-only in Activity Insight XML (#issue6) --- .../committee_data/committee_xml_builder.rb | 22 +++--- .../committee_xml_builder_spec.rb | 72 +++++++++---------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/app/importers/committee_data/committee_xml_builder.rb b/app/importers/committee_data/committee_xml_builder.rb index 9ce87fc..58577a5 100644 --- a/app/importers/committee_data/committee_xml_builder.rb +++ b/app/importers/committee_data/committee_xml_builder.rb @@ -24,26 +24,26 @@ def build_xml(batch) xml.Record(username: faculty.access_id) do committees.each do |committee| xml.DSL do - xml.ROLE committee.role - xml.ROLE_OTHER committee.role_other if committee.role_other.present? - xml.TYPE committee.type_of_work - xml.COMPSTAGE committee.stage_of_completion + xml.ROLE committee.role, access: 'READ_ONLY' + xml.ROLE_OTHER committee.role_other, access: 'READ_ONLY' if committee.role_other.present? + xml.TYPE committee.type_of_work, access: 'READ_ONLY' + xml.COMPSTAGE committee.stage_of_completion, access: 'READ_ONLY' same_date = committee.start_year == committee.completion_year && committee.start_month == committee.completion_month unless same_date - xml.DTM_START Date::MONTHNAMES[committee.start_month] if committee.start_month - xml.DTY_START committee.start_year if committee.start_year + xml.DTM_START Date::MONTHNAMES[committee.start_month], access: 'READ_ONLY' if committee.start_month + xml.DTY_START committee.start_year, access: 'READ_ONLY' if committee.start_year end - xml.DTM_END Date::MONTHNAMES[committee.completion_month] if committee.completion_month - xml.DTY_END committee.completion_year if committee.completion_year + xml.DTM_END Date::MONTHNAMES[committee.completion_month], access: 'READ_ONLY' if committee.completion_month + xml.DTY_END committee.completion_year, access: 'READ_ONLY' if committee.completion_year xml.DSL_STUDENT do - xml.FNAME committee.student_fname - xml.LNAME committee.student_lname - xml.TITLE committee.thesis_title + xml.FNAME committee.student_fname, access: 'READ_ONLY' + xml.LNAME committee.student_lname, access: 'READ_ONLY' + xml.TITLE committee.thesis_title, access: 'READ_ONLY' end end end diff --git a/spec/importers/committee_data/committee_xml_builder_spec.rb b/spec/importers/committee_data/committee_xml_builder_spec.rb index 1bd6c31..c08c6d4 100644 --- a/spec/importers/committee_data/committee_xml_builder_spec.rb +++ b/spec/importers/committee_data/committee_xml_builder_spec.rb @@ -26,17 +26,17 @@ - Mentor - Ph.D. Dissertation Committee - Completed - August - 2024 - January - 2026 + Mentor + Ph.D. Dissertation Committee + Completed + August + 2024 + January + 2026 - Test - User - Test Title + Test + User + Test Title @@ -61,9 +61,9 @@ ) xml = xml_builder_obj.xmls_enumerator.first - expect(xml).to include('') - expect(xml).not_to include('') - expect(xml).to include('In Process') + expect(xml).to include('') + expect(xml).not_to include('In Process') end it 'emits only end-date tags when start and end dates are the same year and month' do @@ -82,10 +82,10 @@ ) xml = xml_builder_obj.xmls_enumerator.first - expect(xml).not_to include('') - expect(xml).not_to include('') - expect(xml).to include('March') - expect(xml).to include('2025') + expect(xml).not_to include('March') + expect(xml).to include('2025') end it 'emits all four date tags when year matches but months differ' do @@ -104,10 +104,10 @@ ) xml = xml_builder_obj.xmls_enumerator.first - expect(xml).to include('March') - expect(xml).to include('2025') - expect(xml).to include('November') - expect(xml).to include('2025') + expect(xml).to include('March') + expect(xml).to include('2025') + expect(xml).to include('November') + expect(xml).to include('2025') end it 'emits all four date tags when years differ' do @@ -126,10 +126,10 @@ ) xml = xml_builder_obj.xmls_enumerator.first - expect(xml).to include('August') - expect(xml).to include('2024') - expect(xml).to include('January') - expect(xml).to include('2026') + expect(xml).to include('August') + expect(xml).to include('2024') + expect(xml).to include('January') + expect(xml).to include('2026') end it 'emits only DTY_END when months are nil and years are the same' do @@ -148,10 +148,10 @@ ) xml = xml_builder_obj.xmls_enumerator.first - expect(xml).not_to include('') - expect(xml).not_to include('') - expect(xml).not_to include('') - expect(xml).to include('2025') + expect(xml).not_to include('2025') end it 'emits both years and no month tags when months are nil and years differ' do @@ -170,10 +170,10 @@ ) xml = xml_builder_obj.xmls_enumerator.first - expect(xml).to include('2024') - expect(xml).to include('2026') - expect(xml).not_to include('') - expect(xml).not_to include('') + expect(xml).to include('2024') + expect(xml).to include('2026') + expect(xml).not_to include('Other') - expect(xml).to include('Support Faculty') + expect(xml).to include('Other') + expect(xml).to include('Support Faculty') end it 'does not emit ROLE_OTHER when role_other is blank' do @@ -242,7 +242,7 @@ ) xml = xml_builder_obj.xmls_enumerator.first - expect(xml).not_to include('') + expect(xml).not_to include(' Date: Tue, 28 Apr 2026 13:18:47 -0400 Subject: [PATCH 6/7] Capture degree name from ETDA and put DEG tag inside DSL_STUDENT (#issue1) --- .../committee_data/committee_xml_builder.rb | 1 + app/importers/committee_data/etda_importer.rb | 1 + db/schema.rb | 3 +- .../committee_xml_builder_spec.rb | 34 +++++++++++++++++++ .../committee_data/etda_importer_spec.rb | 22 ++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/app/importers/committee_data/committee_xml_builder.rb b/app/importers/committee_data/committee_xml_builder.rb index 58577a5..549ada4 100644 --- a/app/importers/committee_data/committee_xml_builder.rb +++ b/app/importers/committee_data/committee_xml_builder.rb @@ -43,6 +43,7 @@ def build_xml(batch) xml.DSL_STUDENT do xml.FNAME committee.student_fname, access: 'READ_ONLY' xml.LNAME committee.student_lname, access: 'READ_ONLY' + xml.DEG committee.degree_name, access: 'READ_ONLY' if committee.degree_name.present? xml.TITLE committee.thesis_title, access: 'READ_ONLY' end end diff --git a/app/importers/committee_data/etda_importer.rb b/app/importers/committee_data/etda_importer.rb index 3a10f85..3f83fd9 100644 --- a/app/importers/committee_data/etda_importer.rb +++ b/app/importers/committee_data/etda_importer.rb @@ -30,6 +30,7 @@ def import_for_faculty(faculty) role_other: role_other, thesis_title: committee['title'], type_of_work: map_type_of_work(committee['degree_type']), + degree_name: committee['degree_name'], stage_of_completion: determine_completion_stage( committee['final_submission_approved_at'] ), diff --git a/db/schema.rb b/db/schema.rb index eadd038..f82564d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2026_04_28_000000) do +ActiveRecord::Schema[7.2].define(version: 2026_04_28_120000) do create_table "authors", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "f_name" t.string "m_name" @@ -67,6 +67,7 @@ t.integer "start_month" t.integer "completion_month" t.string "role_other" + t.string "degree_name" t.index ["faculty_id"], name: "index_committees_on_faculty_id" end diff --git a/spec/importers/committee_data/committee_xml_builder_spec.rb b/spec/importers/committee_data/committee_xml_builder_spec.rb index c08c6d4..a30e0b4 100644 --- a/spec/importers/committee_data/committee_xml_builder_spec.rb +++ b/spec/importers/committee_data/committee_xml_builder_spec.rb @@ -14,6 +14,7 @@ role: 'Mentor', thesis_title: 'Test Title', type_of_work: 'Ph.D. Dissertation Committee', + degree_name: 'PhD', stage_of_completion: 'Completed', start_year: 2024, start_month: 8, @@ -36,6 +37,7 @@ Test User + PhD Test Title @@ -45,6 +47,38 @@ ) end + it 'emits DEG when degree_name is present' do + faculty = FactoryBot.create(:faculty, access_id: 'test123') + + Committee.create!( + faculty: faculty, + student_fname: 'Jane', + student_lname: 'Doe', + role: 'Mentor', + thesis_title: 'A Title', + degree_name: 'MS' + ) + + xml = xml_builder_obj.xmls_enumerator.first + expect(xml).to include('MS') + end + + it 'omits DEG when degree_name is nil' do + faculty = FactoryBot.create(:faculty, access_id: 'test123') + + Committee.create!( + faculty: faculty, + student_fname: 'Jane', + student_lname: 'Doe', + role: 'Mentor', + thesis_title: 'A Title', + degree_name: nil + ) + + xml = xml_builder_obj.xmls_enumerator.first + expect(xml).not_to include(' [ { 'student_fname' => 'Spider', 'student_lname' => 'Man', 'role' => 'advisor', 'title' => 'My Thesis', 'degree_type' => 'Dissertation', + 'degree_name' => 'PhD', 'approval_started_at' => 1.month.ago.iso8601, 'final_submission_approved_at' => '2026-01-15T10:30:00Z', 'submission_status' => 'released for publication' } @@ -36,6 +37,7 @@ expect(Committee.last.role).to eq('Advisor') expect(Committee.last.thesis_title).to eq('My Thesis') expect(Committee.last.type_of_work).to eq('Dissertation Committee') + expect(Committee.last.degree_name).to eq('PhD') expect(Committee.last.stage_of_completion).to eq('Completed') expect(Committee.last.start_year).to eq(1.month.ago.year) expect(Committee.last.start_month).to eq(1.month.ago.month) @@ -44,6 +46,26 @@ end end + context 'when degree_name is nil in the API response' do + let(:api_response) do + { data: { 'committees' => [ + { 'student_fname' => 'Spider', 'student_lname' => 'Man', + 'role' => 'advisor', 'title' => 'My Thesis', 'degree_type' => 'Dissertation', + 'degree_name' => nil, + 'approval_started_at' => 1.month.ago.iso8601, + 'final_submission_approved_at' => '2026-01-15T10:30:00Z', + 'submission_status' => 'released for publication' } + ] } } + end + + before { allow(client).to receive(:faculty_committees).and_return(api_response) } + + it 'stores nil for degree_name' do + importer.import_all + expect(Committee.last.degree_name).to be_nil + end + end + context 'when final_submission_approved_at is nil' do let(:api_response) do { data: { 'committees' => [ From 388165a25b3cfaf6ad299b93368c01f2bda404eb Mon Sep 17 00:00:00 2001 From: usmannsiddiqui Date: Tue, 28 Apr 2026 13:20:05 -0400 Subject: [PATCH 7/7] Migration adds degree_name to committees --- db/migrate/20260428120000_add_degree_name_to_committees.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20260428120000_add_degree_name_to_committees.rb diff --git a/db/migrate/20260428120000_add_degree_name_to_committees.rb b/db/migrate/20260428120000_add_degree_name_to_committees.rb new file mode 100644 index 0000000..832fba7 --- /dev/null +++ b/db/migrate/20260428120000_add_degree_name_to_committees.rb @@ -0,0 +1,5 @@ +class AddDegreeNameToCommittees < ActiveRecord::Migration[7.2] + def change + add_column :committees, :degree_name, :string + end +end