You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement RRMS/Molnix joins for the 4-table ingest model
Summary
Implement the join model for the four RRMS/Molnix ingest tables and the optional bridge to the existing GO database deployments_personnel table.
This ticket builds on the earlier 4-table ingest proposal and adds the missing join contract:
what data should be fetched from each Molnix endpoint,
which fields should be stored in each table,
which keys should be used to join the four tables,
how the four-table model joins to the existing database,
which joins are core and which fields are optional enrichment.
The key correction is that deployments_molnixappraisal.target_id is not a person key. The appraisal-to-person join requires a new Molnix-sourced field:
The follow-up join analysis verified that the 4-table shape is still correct, but the initial schema needs one important fix: deployments_molnixappraisal must store the appraised person's RRMS person id as appraised_person_id.
The analysis also verified that the local GO database table deployments_personnel can be joined to RRMS people through deployments_personnel.molnix_id, but only as a partial analytical bridge. It should not be enforced directly as a hard foreign key because some historical molnix_id values no longer resolve in live RRMS.
Field Consistency Check Against Earlier Proposal
The target fields below were checked against latest_analysis/molnix_rrms_comprehensive_data_requirements_and_schema_proposal.md.
Table
Earlier proposal field count
This ticket field count
Difference
rrms_person_snapshot
12
12
No target-field difference
deployments_molnixappraisal
18
19
Adds appraised_person_id from the later join analysis
deployments_molnixappraiser
12
12
No target-field difference
rrms_event_participation
16
16
No target-field difference
The field tables below keep the original target field names. The only target-field addition beyond the original proposal is deployments_molnixappraisal.appraised_person_id, which is required for the corrected appraisal-to-person join.
There is one source-mapping clarification for appraisers: the earlier proposal listed top-level /api/api/appraisals appraiser fields, but the later live join analysis found that the complete appraiser set is nested under appraisal.appraisers[]. This ticket keeps the same deployments_molnixappraiser target columns as the earlier proposal, while sourcing the full child set from sanitized nested appraiser rows and using top-level self-row fields only for optional enrichment.
Scope
In scope
Implement or update the four RRMS/Molnix ingest tables.
Load person, appraisal, appraiser, and event participation data from the Molnix API endpoints listed below.
Add indexes and join keys needed for downstream reporting.
Add a safe nullable bridge from deployments_personnel to rrms_person_snapshot if database-level FK integrity is required.
Validate joins after ingest.
Out of scope
Do not collapse the four tables into one flat table.
Do not merge deployments_molnixappraisal and deployments_molnixappraiser.
Do not store appraisal PII fields such as names, emails, tokens, or free-text responses.
Do not treat target_id as a person id.
Do not add a hard FK directly from deployments_personnel.molnix_id to rrms_person_snapshot.person_id.
Target Data Sources By Table
1. rrms_person_snapshot
One row represents one RRMS/Molnix person snapshot.
Primary key:
person_id
Endpoints:
Endpoint
Use
People cohort endpoint selected for RRMS and standby extraction, for example /api/api/people/...
Initial person id discovery once exact cohort filters are confirmed
/api/api/people/{id}
Person-level non-PII attributes
/api/api/people/{id}/tags
Person tags used for RRMS member/standby classification and sector/status markers
/api/api/people/{id}/roles
Person roles
/api/api/people/{id}/languages
Person languages
/api/api/system/organizations
Organization id-to-name enrichment
/api/api/system/organizations/{orgid}
Organization detail enrichment where needed
Fields to store:
Target field
Source field or rule
Notes
person_id
data.id
RRMS person id
person_status
Derived from cohort selection and /api/api/people/{id}/tagsdata[]
Keep the original field name; status is derived rather than directly stored as one source field
sex
data.sex
Non-PII reporting dimension
current_availability
data.current_availability
Where present
outofscope
data.outofscope
Boolean
organization_id
data.organization
Partially confirmed; keep nullable
organization_name
/api/api/system/organizations and /api/api/system/organizations/{orgid}
Organization name enrichment as described in the proposal
roles_json
/api/api/people/{id}/rolesdata[]
Proposal-aligned person roles payload
languages_json
/api/api/people/{id}/languagesdata[]
Proposal-aligned person languages payload
tags_json
/api/api/people/{id}/tagsdata[]
Proposal-aligned person tags payload
personnel_id
Local GO DB key resolved during ingest
Optional local bridge, as already described in the proposal
source_updated_at
People endpoint update timestamp, where present
Source freshness marker
Population rule:
Build or refresh person snapshots for all RRMS cohort people in scope.
Also refresh any person ids referenced by appraisals and event participation in the same run so joins do not fail because the cached person range is stale.
2. deployments_molnixappraisal
One row represents one Molnix appraisal.
Primary keys and keys:
id local surrogate PK
molnix_id unique source appraisal id
Do not add a strict unique constraint on (event_id, person_id, event_person_role) in the first migration because the source currently has duplicate participant rows.
Add deduplication rules deliberately after reviewing duplicate source rows.
Core Join Schematic
This is the schematic that should be included directly in the GitHub issue. GitHub issues support Mermaid code blocks, so this should render as a diagram on GitHub.com.
Current analytical bridge only. Use LEFT JOIN. Not every RRMS person has a local personnel row, and some historical molnix_id values no longer resolve in live RRMS.
Verified for non-null values; optional for most rows
The database metadata queried during analysis did not expose declared FK constraints for these tables, even though the data behaves consistently for these joins.
Validation And Acceptance Criteria
Endpoint ingestion
Appraisals ingestion follows pagination until next_page_url is null or current_page == last_page.
Events ingestion follows pagination until next_page_url is null or current_page == last_page.
Person-scoped endpoints are called for required people and do not assume every RRMS person already exists in a cached range.
Organization lookup is nullable and defensive.
Table population
rrms_person_snapshot.person_id is populated for all referenced person ids.
deployments_molnixappraisal.molnix_id is unique and populated.
deployments_molnixappraisal.appraised_person_id is populated from appraisal.deployment.person_id.
deployments_molnixappraiser.appraisal_molnix_id points to a loaded deployments_molnixappraisal.molnix_id.
rrms_event_participation.person_id matches the event participant pivot person id.
Continuation of ticket #2633
@szabozoltan69 @tovari
Implement RRMS/Molnix joins for the 4-table ingest model
Summary
Implement the join model for the four RRMS/Molnix ingest tables and the optional bridge to the existing GO database
deployments_personneltable.This ticket builds on the earlier 4-table ingest proposal and adds the missing join contract:
The key correction is that
deployments_molnixappraisal.target_idis not a person key. The appraisal-to-person join requires a new Molnix-sourced field:Background
The earlier proposal defined four tables:
rrms_person_snapshotdeployments_molnixappraisaldeployments_molnixappraiserrrms_event_participationThe follow-up join analysis verified that the 4-table shape is still correct, but the initial schema needs one important fix:
deployments_molnixappraisalmust store the appraised person's RRMS person id asappraised_person_id.The analysis also verified that the local GO database table
deployments_personnelcan be joined to RRMS people throughdeployments_personnel.molnix_id, but only as a partial analytical bridge. It should not be enforced directly as a hard foreign key because some historicalmolnix_idvalues no longer resolve in live RRMS.Field Consistency Check Against Earlier Proposal
The target fields below were checked against
latest_analysis/molnix_rrms_comprehensive_data_requirements_and_schema_proposal.md.rrms_person_snapshot1212deployments_molnixappraisal1819appraised_person_idfrom the later join analysisdeployments_molnixappraiser1212rrms_event_participation1616The field tables below keep the original target field names. The only target-field addition beyond the original proposal is
deployments_molnixappraisal.appraised_person_id, which is required for the corrected appraisal-to-person join.There is one source-mapping clarification for appraisers: the earlier proposal listed top-level
/api/api/appraisalsappraiser fields, but the later live join analysis found that the complete appraiser set is nested underappraisal.appraisers[]. This ticket keeps the samedeployments_molnixappraisertarget columns as the earlier proposal, while sourcing the full child set from sanitized nested appraiser rows and using top-level self-row fields only for optional enrichment.Scope
In scope
deployments_molnixappraisal.appraised_person_id.deployments_personneltorrms_person_snapshotif database-level FK integrity is required.Out of scope
deployments_molnixappraisalanddeployments_molnixappraiser.target_idas a person id.deployments_personnel.molnix_idtorrms_person_snapshot.person_id.Target Data Sources By Table
1.
rrms_person_snapshotOne row represents one RRMS/Molnix person snapshot.
Primary key:
Endpoints:
/api/api/people/.../api/api/people/{id}/api/api/people/{id}/tags/api/api/people/{id}/roles/api/api/people/{id}/languages/api/api/system/organizations/api/api/system/organizations/{orgid}Fields to store:
person_iddata.idperson_status/api/api/people/{id}/tagsdata[]sexdata.sexcurrent_availabilitydata.current_availabilityoutofscopedata.outofscopeorganization_iddata.organizationorganization_name/api/api/system/organizationsand/api/api/system/organizations/{orgid}roles_json/api/api/people/{id}/rolesdata[]languages_json/api/api/people/{id}/languagesdata[]tags_json/api/api/people/{id}/tagsdata[]personnel_idsource_updated_atPopulation rule:
2.
deployments_molnixappraisalOne row represents one Molnix appraisal.
Primary keys and keys:
Endpoints:
/api/api/appraisals?page=n/api/api/deployments/{deployment_id}/api/api/system/organizations/api/api/system/organizations/{orgid}Fields to store:
idmolnix_idoriginal.data[].appraisal.idtarget_idoriginal.data[].appraisal.target_iddeployment_molnix_idoriginal.data[].appraisal.deployment.idappraised_person_idoriginal.data[].appraisal.deployment.person_idstageoriginal.data[].appraisal.stageappraisers_countoriginal.data[].appraisal.appraisers_countscoreoriginal.data[].appraisal.scoredeployment_country_idoriginal.data[].appraisal.deployment.country_iddeployment_startoriginal.data[].appraisal.deployment.startdeployment_endoriginal.data[].appraisal.deployment.enddeployment_titleoriginal.data[].appraisal.deployment.titlesending_organization_id/api/api/deployments/{deployment_id}sending organization idreceiving_organization_id/api/api/deployments/{deployment_id}receiving organization iddeployment_tags_jsonoriginal.data[].appraisal.deployment.tags[]competencies_jsonoriginal.data[].appraisal.competencies[]personnel_idcreated_atoriginal.data[].appraisal.created_atupdated_atoriginal.data[].appraisal.updated_atReference verification from the join audit:
3620appraised_person_idrows:00target_idvsdeployment.idmismatches:03.
deployments_molnixappraiserOne row represents one appraiser/reviewer assignment attached to an appraisal.
Primary keys and keys:
Endpoint:
/api/api/appraisals?page=nappraisal.appraisers[]Fields to store:
idmolnix_idoriginal.data[].idappraisal_molnix_idoriginal.data[].appraisal_idappraisal_iddeployments_molnixappraisal.idappraiser_typeoriginal.data[].appraiser_typeperson_idoriginal.data[].person_idpersonnel_idrequiredoriginal.data[].requiredcompleted_atoriginal.data[].completed_atnotified_atoriginal.data[].notified_atcreated_atoriginal.data[].created_atupdated_atoriginal.data[].updated_atPrivacy exclusions:
Do not persist these fields from appraiser payloads:
Reference verification from the join audit:
79600appraisers_countmismatches:0person_idfor the full appraiser set.Decision:
deployments_molnixappraisal.4.
rrms_event_participationOne row represents one RRMS event-person participation record.
Primary keys and keys:
Endpoint:
/api/api/events?page=nFields to store:
idevent_idevents.data[].idevent_nameevents.data[].nameperson_idevents.data[].person[].idevent_person_roleevents.data[].person[].pivot.roleevent_typeevents.data[].event_typeevent_scale_typeevents.data[].typeevent_fromevents.data[].fromevent_toevents.data[].toparticipant_startevents.data[].person[].pivot.startparticipant_endevents.data[].person[].pivot.endrequestedevents.data[].person[].pivot.requestedevent_organization_idevents.data[].organizationevent_organization_name/api/api/system/organizationsand/api/api/system/organizations/{orgid}venueevents.data[].venuetags_jsonevents.data[].tagsReference verification from the join audit:
205750person[].id = person[].pivot.person_idmatches:20575000(event_id, person_id)keys observed:14(event_id, person_id, role)keys observed:1413Constraint decision:
id.(event_id, person_id, event_person_role)in the first migration because the source currently has duplicate participant rows.Core Join Schematic
This is the schematic that should be included directly in the GitHub issue. GitHub issues support Mermaid code blocks, so this should render as a diagram on GitHub.com.
flowchart LR person["rrms_person_snapshot<br/>PK: person_id<br/>Fields: person_status, sex, current_availability, outofscope, organization_id, roles_json, languages_json, tags_json"] appraisal["deployments_molnixappraisal<br/>UK: molnix_id<br/>Join field: appraised_person_id<br/>Context: target_id, deployment_molnix_id, stage, score, competencies_json"] appraiser["deployments_molnixappraiser<br/>UK: molnix_id<br/>Parent join: appraisal_molnix_id<br/>Fields: appraiser_type, required, completed_at"] event["rrms_event_participation<br/>Join field: person_id<br/>Fields: event_id, event_name, event_person_role, event_type, event_scale_type, participant_start, participant_end"] personnel["deployments_personnel<br/>Current bridge: molnix_id<br/>Local fields: deployedperson_ptr_id, deployment_id, molnix_status"] person -->|"person_id = appraised_person_id"| appraisal appraisal -->|"molnix_id = appraisal_molnix_id"| appraiser person -->|"person_id = person_id"| event person -->|"current: person_id = molnix_id (LEFT JOIN)"| personnelJoin Contract
Core joins and current GO bridge
rrms_person_snapshot.person_id = deployments_molnixappraisal.appraised_person_idappraised_person_idfielddeployments_molnixappraisal.molnix_id = deployments_molnixappraiser.appraisal_molnix_idrrms_person_snapshot.person_id = rrms_event_participation.person_idrrms_person_snapshot.person_id = deployments_personnel.molnix_idLEFT JOIN. Not every RRMS person has a local personnel row, and some historicalmolnix_idvalues no longer resolve in live RRMS.Local GO Database Bridge
Current bridge
Current analytical join:
Use
LEFT JOINfrom RRMS people intodeployments_personnelbecause:deployments_personnelrows have nomolnix_id.molnix_idvalues no longer resolve in live RRMS.Reference verification:
deployments_personnelrows4024molnix_id3400molnix_idvalues3400molnix_idkeys0molnix_id3192molnix_id208active=187,deleted=12,hidden=9Existing local joins
These joins were verified against the existing database and can be used when local GO context is needed:
deployments_personnel.deployedperson_ptr_id = deployments_deployedperson.iddeployments_personnel.deployment_id = deployments_personneldeployment.iddeployments_personnel.surge_alert_id = notifications_surgealert.idThe database metadata queried during analysis did not expose declared FK constraints for these tables, even though the data behaves consistently for these joins.
Validation And Acceptance Criteria
Endpoint ingestion
next_page_urlis null orcurrent_page == last_page.next_page_urlis null orcurrent_page == last_page.Table population
rrms_person_snapshot.person_idis populated for all referenced person ids.deployments_molnixappraisal.molnix_idis unique and populated.deployments_molnixappraisal.appraised_person_idis populated fromappraisal.deployment.person_id.deployments_molnixappraiser.appraisal_molnix_idpoints to a loadeddeployments_molnixappraisal.molnix_id.rrms_event_participation.person_idmatches the event participant pivot person id.Join checks
Validation must report:
0duplicatedeployments_molnixappraisal.molnix_idvalues.0nulldeployments_molnixappraisal.appraised_person_idvalues, unless Molnix sends malformed rows that are deliberately quarantined.0orphandeployments_molnixappraisal.appraised_person_idvalues after same-run people refresh.0parent mismatches fromdeployments_molnixappraiser.appraisal_molnix_idtodeployments_molnixappraisal.molnix_id.0event participant rows whereperson[].id != person[].pivot.person_id.0orphanrrms_event_participation.person_idvalues after same-run people refresh.deployments_personnel.molnix_idvalues separately.