Domain models

Structured issues emitted by each ETL stage.

class acmad_uploader.domain.issues.IssueSeverity(*values)

Bases: StrEnum

Severity of a pipeline issue.

ERROR = 'error'
WARNING = 'warning'
INFO = 'info'
class acmad_uploader.domain.issues.PipelineIssue(code, severity, message, location=None)

Bases: object

Machine-readable issue with an optional source location.

code: str
severity: IssueSeverity
message: str
location: SourceLocation | None
display()

Return a concise representation suitable for CLI output.

Return type:

str

acmad_uploader.domain.issues.has_errors(issues)

Return whether an issue collection contains an error.

Return type:

bool

Infrastructure-independent provenance attached to clinical values.

class acmad_uploader.domain.provenance.SourceLocation(path, sheet=None, cell=None)

Bases: object

Location of a value in a source package.

path: Path
sheet: str | None
cell: str | None
display()

Return a concise, human-readable representation.

Return type:

str

Canonical value for an out-of-band site acronym supplied by the caller.

class acmad_uploader.domain.site_override.SiteOverride(acronym, source)

Bases: object

A site acronym supplied via a CLI flag or package config file.

Cross-checked against (or used to fill in) each contributing workbook’s own site_id field - see transformers.forms.cross_check_site_id.

acronym: str
source: str

Canonical PatientEvent metadata shared by every event-creating ETL slice.

class acmad_uploader.domain.patient_event.PatientEventDetails(year_of_assessment, region, state, country, irsd_decile, irsad_decile, ieo_decile, ier_decile)

Bases: object

Optional demographic fields common to every PatientEvent.

Every event-creating slice (gait assessment, surgery) extracts these from its own anchor workbook and merges them into whichever PatientEvent it resolves or creates. All fields are optional at the API: the data dictionary marks year_of_assessment/country as required for clinics filling out the sheet, but the destination model itself allows them blank, so the ETL doesn’t enforce that here.

year_of_assessment: int | None
region: str | None
state: str | None
country: str | None
irsd_decile: int | None
irsad_decile: int | None
ieo_decile: int | None
ier_decile: int | None
static empty()

Return an instance with every field absent.

Return type:

PatientEventDetails

Canonical patient values shared by ETL stages.

class acmad_uploader.domain.patient.BiologicalSex(*values)

Bases: StrEnum

Biological sex values accepted by the ACMAD API.

FEMALE = 'female'
MALE = 'male'
NOT_SPECIFIED = 'not specified'
NOT_APPLICABLE = 'not applicable'
class acmad_uploader.domain.patient.Ethnicity(*values)

Bases: StrEnum

Ethnicity values accepted by the ACMAD API.

EUROPEAN = 'European'
AUSTRALIAN = 'Australian'
ABORIGINAL_TORRES_STRAIT_ISLANDER = 'Aboriginal/Torres Strait Islander'
NEW_ZEALANDER = 'New Zealander'
MAORI = 'Māori'
PACIFIC_POLYNESIAN = 'Pacific/Polynesian'
ASIAN = 'Asian'
MIDDLE_EASTERN_LATIN_AMERICAN_AFRICAN = 'Middle Eastern/Latin American/African'
OTHER = 'Other'
UNKNOWN = 'Unknown'
NOT_RECORDED = 'Not Recorded'
class acmad_uploader.domain.patient.PatientRecord(site_patient_id, site_abbreviation, biological_sex, ethnicity, patient_hash, source)

Bases: object

Validated patient master data independent of its source and destination.

site_patient_id: str
site_abbreviation: str
biological_sex: BiologicalSex
ethnicity: Ethnicity
patient_hash: str | None
source: SourceLocation

Canonical diagnosis values shared by ETL stages.

class acmad_uploader.domain.diagnosis.DiagnosisType(*values)

Bases: StrEnum

Whether a diagnosis is a patient’s primary or a secondary diagnosis.

PRIMARY = 'primary'
SECONDARY = 'secondary'
class acmad_uploader.domain.diagnosis.DiagnosisLaterality(*values)

Bases: StrEnum

Laterality values accepted by the ACMAD API.

Values match the API’s stored codes, not the data dictionary’s human labels (for example the workbook’s “not applicable” maps to NA).

LEFT = 'left'
RIGHT = 'right'
BILATERAL = 'bilateral'
NOT_SPECIFIED = 'not specified'
NOT_APPLICABLE = 'NA'
class acmad_uploader.domain.diagnosis.DiagnosisRecord(site_patient_id, site_abbreviation, diagnosis_type, age_at_diagnosis_days, diagnosis_group, diagnosis_subgroup, diagnosis_detail, laterality, source)

Bases: object

Validated diagnosis independent of its source and destination.

definition (the matching admin-maintained diagnosis reference record) is deliberately not resolved here: the API matches and validates it server-side from diagnosis_group/diagnosis_subgroup/ diagnosis_detail, and reference data can change independently of this package’s release cycle.

site_patient_id: str
site_abbreviation: str
diagnosis_type: DiagnosisType
age_at_diagnosis_days: int
diagnosis_group: str
diagnosis_subgroup: str | None
diagnosis_detail: str | None
laterality: DiagnosisLaterality | None
source: SourceLocation

Canonical gait assessment event values shared by ETL stages.

class acmad_uploader.domain.gait_assessment.GaitAssessmentRecord(site_patient_id, site_abbreviation, age_in_days, event_details, sources)

Bases: object

Linkage between a patient and one gait assessment event.

age_in_days together with site_patient_id is the natural key shared by every workbook belonging to the same assessment (physical exam, function/PROMs and mocap), so records extracted from separate workbooks are joined on this pair rather than by folder position.

site_patient_id: str
site_abbreviation: str
age_in_days: int
event_details: PatientEventDetails
sources: tuple[SourceLocation, ...]

Canonical function assessment values shared by ETL stages.

class acmad_uploader.domain.function_assessment.Gmfcs(*values)

Bases: StrEnum

Gross Motor Function Classification System levels.

LEVEL_I = 'I'
LEVEL_II = 'II'
LEVEL_III = 'III'
LEVEL_IV = 'IV'
LEVEL_V = 'V'
NOT_SPECIFIED = 'not specified'
class acmad_uploader.domain.function_assessment.FunctionalMobilityScale(*values)

Bases: StrEnum

Functional Mobility Scale rating, used at 5/50/500 metre distances.

N = 'N'
C = 'C'
ONE = '1'
TWO = '2'
THREE = '3'
FOUR = '4'
FIVE = '5'
SIX = '6'
class acmad_uploader.domain.function_assessment.FunctionalAmbulationClassification(*values)

Bases: StrEnum

Functional Ambulation Classification (FAC) level.

ZERO = '0'
ONE = '1'
TWO = '2'
THREE = '3'
FOUR = '4'
FIVE = '5'
class acmad_uploader.domain.function_assessment.FunctionAssessmentRecord(site_patient_id, age_in_days, gmfcs, fms_5, fms_50, fms_500, fac, source)

Bases: object

Validated function assessment independent of its source and destination.

site_patient_id: str
age_in_days: int
gmfcs: Gmfcs | None
fms_5: FunctionalMobilityScale | None
fms_50: FunctionalMobilityScale | None
fms_500: FunctionalMobilityScale | None
fac: FunctionalAmbulationClassification | None
source: SourceLocation

Canonical mocap session values shared by ETL stages.

class acmad_uploader.domain.mocap_data.SessionCondition(*values)

Bases: StrEnum

Footwear/aid condition of the mocap session.

BAREFOOT = 'barefoot'
AFO = 'AFO'
SHOES = 'shoes'
PROSTHETIC = 'prosthetic'
OTHER = 'other'
class acmad_uploader.domain.mocap_data.GaitAid(*values)

Bases: StrEnum

Gait aid used during the mocap session.

UNASSISTED = 'unassisted'
HAND_HELD = 'hand held'
ANTERIOR_WALKER = 'anterior walker'
POSTERIOR_WALKER = 'posterior walker'
STICKS = 'sticks'
CRUTCHES = 'crutches'
OTHER = 'other'
class acmad_uploader.domain.mocap_data.ConditionDetail(*values)

Bases: StrEnum

Detailed per-side condition (e.g. orthosis/prosthetic type).

BAREFOOT = 'barefoot'
SOLID_AFO = 'solid_AFO'
HINGED_AFO = 'hinged_AFO'
GRAFO = 'GRAFO'
ENERGY_RESTORING_AFO = 'energy_restoring_AFO'
KAFO = 'KAFO'
SHOE_WITH_RAISE = 'shoe_with_raise'
IN_SHOE_ORTHOSIS = 'in_shoe_orthosis'
PROSTHETIC = 'prosthetic'
OTHER = 'other'
class acmad_uploader.domain.mocap_data.DesiredSpeed(*values)

Bases: StrEnum

Requested walking speed for the trial.

The API stores this hyphenated (self-selected); the workbook spells it with a space (self selected) — the transformer maps between them.

SELF_SELECTED = 'self-selected'
SLOW = 'slow'
FAST = 'fast'
NOT_APPLICABLE = 'not applicable'
class acmad_uploader.domain.mocap_data.MocapDataRecord(site_patient_id, age_in_days, session_condition, gait_aid, left_condition_detail, right_condition_detail, desired_speed, source)

Bases: object

Validated mocap session independent of its source and destination.

trial_name/trial_activity/trial_side (per-trial detail) have no source in mocap.xlsx at all — those come from the separate C3D processing pipeline (MocapTrial, out of scope for this ETL), so this record only carries the session-level fields the workbook does have.

left_condition_detail/right_condition_detail are consistently left blank across real sample data (matching the data dictionary, which doesn’t mark them required) even for non-barefoot conditions, so they are optional here.

site_patient_id: str
age_in_days: int
session_condition: SessionCondition
gait_aid: GaitAid
left_condition_detail: ConditionDetail | None
right_condition_detail: ConditionDetail | None
desired_speed: DesiredSpeed | None
source: SourceLocation

Canonical biomechanics artifact values shared by ETL stages.

class acmad_uploader.domain.biomechanics_artifact.ArtifactRole(*values)

Bases: StrEnum

Kind of biomechanics file, matching the destination’s own role choices.

Only normalised_kinematics_csv/normalised_kinetics_csv/ spatiotemporal_csv are “derived” — the destination parses these three into OpenSimKinematics/OpenSimKinetics/ OpenSimSpatiotemporalMetrics server-side and requires an OpenSimProcessingRun to attach them to. Every other role is a plain file upload with no further processing.

RAW_C3D = 'raw_c3d'
DEIDENTIFIED_C3D = 'deidentified_c3d'
TRC = 'trc'
GRF_MOT = 'grf_mot'
IK_MOT = 'ik_mot'
ID_STO = 'id_sto'
OSIM_MODEL = 'osim_model'
SCALE_SETTINGS = 'scale_settings'
MESH_STL = 'mesh_stl'
MESH_VTP = 'mesh_vtp'
NORMALISED_KINEMATICS_CSV = 'normalised_kinematics_csv'
NORMALISED_KINETICS_CSV = 'normalised_kinetics_csv'
SPATIOTEMPORAL_CSV = 'spatiotemporal_csv'
MODEL_FIT_AUXILIARY = 'model_fit_auxiliary'
UPLOADED_PACKAGE = 'uploaded_package'
OTHER = 'other'
class acmad_uploader.domain.biomechanics_artifact.BiomechanicsArtifactRecord(site_patient_id, age_in_days, session_condition, gait_aid, left_condition_detail, right_condition_detail, file_path, original_path, filename, role, extension, content_type, size_bytes, sha256)

Bases: object

One raw or derived biomechanics file belonging to a mocap session.

original_path (relative to the session’s own mocap_<condition> folder) together with the resolved MocapData row is the destination’s own natural key (unique_together = (mocap_data, original_path)) — no client-side matching needed, unlike MocapData itself. The session-identifying fields mirror MocapDataRecord’s own natural key, needed to resolve the owning MocapData row via MocapDataLookup.

site_patient_id: str
age_in_days: int
session_condition: SessionCondition
gait_aid: GaitAid
left_condition_detail: ConditionDetail | None
right_condition_detail: ConditionDetail | None
file_path: Path
original_path: str
filename: str
role: ArtifactRole
extension: str
content_type: str
size_bytes: int
sha256: str

Canonical physical examination values shared by ETL stages.

class acmad_uploader.domain.physical_examination.AbdominalStrength(*values)

Bases: StrEnum

Degree of trunk flexion graded during abdominal strength testing.

HEAD = 'head'
HEAD_SHOULDERS = 'head_shoulders'
HEAD_SHOULDER_BLADES = 'head_shoulder_blades'
class acmad_uploader.domain.physical_examination.Side(*values)

Bases: StrEnum

Body side a physical exam detail row was measured on.

LEFT = 'left'
RIGHT = 'right'
class acmad_uploader.domain.physical_examination.OxfordStrength(*values)

Bases: StrEnum

Oxford manual muscle testing (MMT) grade, 0 (none) to 5 (max resistance).

ZERO = '0'
ONE = '1'
TWO = '2'
THREE = '3'
FOUR = '4'
FIVE = '5'
class acmad_uploader.domain.physical_examination.SelectivityScale(*values)

Bases: StrEnum

Whether a muscle group’s movement can be selectively isolated.

CANNOT_ISOLATE = '0'
CAN_ISOLATE = '1'
class acmad_uploader.domain.physical_examination.SelectiveMotorControl(*values)

Bases: StrEnum

Selective motor control grade for dorsiflexion, 0 (none) to 4 (isolated).

ZERO = '0'
ONE = '1'
TWO = '2'
THREE = '3'
FOUR = '4'
class acmad_uploader.domain.physical_examination.ConfusionTest(*values)

Bases: StrEnum

Result of confusion testing for ankle dorsiflexion substitution.

POSITIVE_INVERTED = 'positive_inverted'
POSITIVE_NEUTRAL = 'positive_neutral'
POSITIVE_EVERTED = 'positive_everted'
NEGATIVE = 'negative'
class acmad_uploader.domain.physical_examination.ToneScale(*values)

Bases: StrEnum

Scale used for a tone measurement — shared across both sides.

DUNCAN_ELY = 'DE'
MODIFIED_TARDIEU = 'MTS'
MODIFIED_ASHWORTH = 'MAS'
AUSTRALIAN_SPASTICITY = 'ASAS'
class acmad_uploader.domain.physical_examination.Clonus(*values)

Bases: StrEnum

Severity of clonus observed.

NONE = 'none'
MILD = 'mild'
MODERATE = 'moderate'
SEVERE = 'severe'
class acmad_uploader.domain.physical_examination.HindfootSagittal(*values)

Bases: StrEnum

Hindfoot sagittal plane alignment.

MODERATE_SEVERE_CALCANEUS = 'moderate_severe_calcaneus'
MILD_CALCANEUS = 'mild_calcaneus'
NORMAL = 'normal'
MILD_EQUINUS = 'mild_equinus'
MODERATE_SEVERE_EQUINUS = 'moderate_severe_equinus'
class acmad_uploader.domain.physical_examination.HindfootCoronal(*values)

Bases: StrEnum

Hindfoot coronal plane alignment.

MODERATE_SEVERE_VARUS = 'moderate_severe_varus'
MILD_VARUS = 'mild_varus'
NORMAL = 'normal'
MILD_VALGUS = 'mild_valgus'
MODERATE_SEVERE_VALGUS = 'moderate_severe_valgus'
class acmad_uploader.domain.physical_examination.MidfootTransverse(*values)

Bases: StrEnum

Midfoot transverse plane alignment.

MODERATE_SEVERE_CAVUS = 'moderate_severe_cavus'
MILD_CAVUS = 'mild_cavus'
NORMAL = 'normal'
MILD_PLANUS = 'mild_planus'
MODERATE_SEVERE_PLANUS = 'moderate_severe_planus'
class acmad_uploader.domain.physical_examination.ForefootHorizontal(*values)

Bases: StrEnum

Forefoot horizontal plane alignment.

MODERATE_SEVERE_ADDUCTUS = 'moderate_severe_adductus'
MILD_ADDUCTUS = 'mild_adductus'
NORMAL = 'normal'
MILD_ABDUCTUS = 'mild_abductus'
MODERATE_SEVERE_ABDUCTUS = 'moderate_severe_abductus'
class acmad_uploader.domain.physical_examination.AnthropometricsRecord(height_mm, mass_kg, asis_distance_mm)

Bases: object

Non-sided anthropometric measurements for one physical examination.

height_mm: float | None
mass_kg: float | None
asis_distance_mm: float | None
class acmad_uploader.domain.physical_examination.AnthropometricSideRecord(side, leg_length_mm, troc_asis_distance_mm, knee_width_mm, ankle_width_mm, orthosis_ankle_width_mm, orthosis_knee_width_mm, shoe_sole_delta_mm)

Bases: object

Sided anthropometric measurements, incl. orthosis/shoe modifications.

side: Side
leg_length_mm: float | None
troc_asis_distance_mm: float | None
knee_width_mm: float | None
ankle_width_mm: float | None
orthosis_ankle_width_mm: float | None
orthosis_knee_width_mm: float | None
shoe_sole_delta_mm: float | None
class acmad_uploader.domain.physical_examination.RangeOfMotionRecord(side, psoas_length, hip_adductor_ext_length, hip_adductor_flex_length, hamstring_mod_popliteal_length, hamstring_popliteal_length, hamstring_spasticity, knee_ext_range, soleus_length, soleus_spasticity, gastroc_length, gastroc_spasticity, plantarflex_range, knee_flex_range, hip_rot_int_range, hip_rot_ext_range, tpat, bimalleolar, thigh_heel, foot_thigh)

Bases: object

Sided joint range-of-motion and muscle length measurements, in degrees.

The backend also has a *_is_wnl (within normal limits) flag paired with every measurement, but no workbook field maps to it — left unset.

side: Side
psoas_length: float | None
hip_adductor_ext_length: float | None
hip_adductor_flex_length: float | None
hamstring_mod_popliteal_length: float | None
hamstring_popliteal_length: float | None
hamstring_spasticity: float | None
knee_ext_range: float | None
soleus_length: float | None
soleus_spasticity: float | None
gastroc_length: float | None
gastroc_spasticity: float | None
plantarflex_range: float | None
knee_flex_range: float | None
hip_rot_int_range: float | None
hip_rot_ext_range: float | None
tpat: float | None
bimalleolar: float | None
thigh_heel: float | None
foot_thigh: float | None
class acmad_uploader.domain.physical_examination.StrengthRecord(side, hip_extensors_strength_knee_90, hip_extensors_selectivity_knee_90, hip_extensors_strength_knee_0, hip_extensors_selectivity_knee_0, hip_flexors_strength_knee_90, hip_flexors_selectivity_knee_90, hip_flexors_strength_knee_0, hip_flexors_selectivity_knee_0, hip_abductors_strength, hip_abductors_selectivity, knee_flexors_strength, knee_flexors_selectivity, knee_extensors_strength, knee_extensors_selectivity, plantarflexors_strength, plantarflexors_selectivity, dorsiflexors_strength_knee_0, dorsiflexors_selectivity_knee_0, dorsiflexors_strength_knee_90, dorsiflexors_selectivity_knee_90, ankle_invertors_strength, ankle_invertors_selectivity, ankle_evertors_strength, ankle_evertors_selectivity, quadriceps_lag, selective_motor_control, confusion_testing)

Bases: object

Sided manual muscle strength/selectivity testing.

side: Side
hip_extensors_strength_knee_90: OxfordStrength | None
hip_extensors_selectivity_knee_90: SelectivityScale | None
hip_extensors_strength_knee_0: OxfordStrength | None
hip_extensors_selectivity_knee_0: SelectivityScale | None
hip_flexors_strength_knee_90: OxfordStrength | None
hip_flexors_selectivity_knee_90: SelectivityScale | None
hip_flexors_strength_knee_0: OxfordStrength | None
hip_flexors_selectivity_knee_0: SelectivityScale | None
hip_abductors_strength: OxfordStrength | None
hip_abductors_selectivity: SelectivityScale | None
knee_flexors_strength: OxfordStrength | None
knee_flexors_selectivity: SelectivityScale | None
knee_extensors_strength: OxfordStrength | None
knee_extensors_selectivity: SelectivityScale | None
plantarflexors_strength: OxfordStrength | None
plantarflexors_selectivity: SelectivityScale | None
dorsiflexors_strength_knee_0: OxfordStrength | None
dorsiflexors_selectivity_knee_0: SelectivityScale | None
dorsiflexors_strength_knee_90: OxfordStrength | None
dorsiflexors_selectivity_knee_90: SelectivityScale | None
ankle_invertors_strength: OxfordStrength | None
ankle_invertors_selectivity: SelectivityScale | None
ankle_evertors_strength: OxfordStrength | None
ankle_evertors_selectivity: SelectivityScale | None
quadriceps_lag: float | None
selective_motor_control: SelectiveMotorControl | None
confusion_testing: ConfusionTest | None
class acmad_uploader.domain.physical_examination.ToneValue(scale, value)

Bases: object

A tone measurement and the scale it was recorded against.

The scale is shared between both sides in the source workbook (one scale column, two per-side value columns), but the destination stores it once per side-keyed Tone row, so it’s carried alongside each side’s own value here rather than hoisted onto PhysicalExaminationRecord.

scale: ToneScale | None
value: str | None
class acmad_uploader.domain.physical_examination.ToneRecord(side, rectus_tone_slow, rectus_tone_fast, hip_ext_tone, hip_flex_tone, hip_add_tone, hip_introt_tone, hip_extrot_tone, knee_flexor_tone, knee_extensor_tone, soleus_tone, gastroc_tone, tib_post_tone, clonus)

Bases: object

Sided muscle tone assessment across multiple scales.

side: Side
rectus_tone_slow: ToneValue
rectus_tone_fast: ToneValue
hip_ext_tone: ToneValue
hip_flex_tone: ToneValue
hip_add_tone: ToneValue
hip_introt_tone: ToneValue
hip_extrot_tone: ToneValue
knee_flexor_tone: ToneValue
knee_extensor_tone: ToneValue
soleus_tone: ToneValue
gastroc_tone: ToneValue
tib_post_tone: ToneValue
clonus: Clonus | None
class acmad_uploader.domain.physical_examination.FootStructureRecord(side, hindfoot_sagittal, hindfoot_coronal, midfoot_transverse, forefoot_horizontal)

Bases: object

Sided foot structure assessment across three planes.

side: Side
hindfoot_sagittal: HindfootSagittal | None
hindfoot_coronal: HindfootCoronal | None
midfoot_transverse: MidfootTransverse | None
forefoot_horizontal: ForefootHorizontal | None
class acmad_uploader.domain.physical_examination.PhysicalExaminationRecord(site_patient_id, age_in_days, abdominal_strength, anthropometrics, anthropometric_sides, range_of_motion, strength, tone, foot_structure, source)

Bases: object

One physical examination and its non-sided and sided detail rows.

gait_assessment (1:1) is the natural key, resolved from (site_patient_id, age_in_days) the same way FunctionAssessment and MocapData resolve their owning gait assessment. Each side-keyed tuple holds exactly one left and one right entry, matching the destination’s unique_together = (physical_examination, side) on each detail model.

site_patient_id: str
age_in_days: int
abdominal_strength: AbdominalStrength | None
anthropometrics: AnthropometricsRecord
anthropometric_sides: tuple[AnthropometricSideRecord, ...]
range_of_motion: tuple[RangeOfMotionRecord, ...]
strength: tuple[StrengthRecord, ...]
tone: tuple[ToneRecord, ...]
foot_structure: tuple[FootStructureRecord, ...]
source: SourceLocation

Canonical PROMs (patient-reported outcome measure) values shared by ETL stages.

class acmad_uploader.domain.proms.Respondent(*values)

Bases: StrEnum

Which respondent completed a dual-respondent questionnaire slot.

Workbook slot _1 is always the “other” respondent (parent for GOAL/OFAQ/PODCI, proxy for MGaitEfficiency/Walk12); slot _2 is always self-report — confirmed with the user, not a data-entry duplicate. The literal destination string differs per model ("parent" vs "proxy"), so each adapter maps this to its own model’s completed_by choice value.

SELF_REPORT = 'self_report'
OTHER = 'other'
class acmad_uploader.domain.proms.FaqRecord(function_score, activity_score)

Bases: object

Functional Assessment Questionnaire response.

Single row, no respondent split.

function_score: int | None
activity_score: int | None
class acmad_uploader.domain.proms.OfaqRecord(respondent, version, physical, school_play, emotional, shoe)

Bases: object

Oxford Foot and Ankle Questionnaire response for one respondent.

respondent: Respondent
version: str | None
physical: int
school_play: int
emotional: int
shoe: int
class acmad_uploader.domain.proms.PodciRecord(respondent, version, upper_extremity, transfer_mobility, physical_functioning, pain_comfort, happiness, global_functioning)

Bases: object

Paediatric Outcomes Data Collection Instrument response for one respondent.

respondent: Respondent
version: str | None
upper_extremity: int
transfer_mobility: int
physical_functioning: int
pain_comfort: int
happiness: int
global_functioning: int
class acmad_uploader.domain.proms.MGaitEfficiencyRecord(respondent, version, score)

Bases: object

Modified Gait Efficiency Scale response for one respondent.

respondent: Respondent
version: str | None
score: int
class acmad_uploader.domain.proms.Walk12Record(respondent, version, score)

Bases: object

Walk-12 response for one respondent.

respondent: Respondent
version: str | None
score: int
class acmad_uploader.domain.proms.GoalAssessmentRecord(respondent, version, items, domain_scores)

Bases: object

GOAL questionnaire response for one respondent.

items and domain_scores are keyed by the destination GoalAssessment model’s own field names (e.g. "adli_1_difficulty", "global_score") — a dict rather than ~57 explicit dataclass fields, mirroring the backend’s own declarative GOAL_ITEM_FIELD_SPECS/ GOAL_SCORE_FIELD_SPECS tables (models/goal.py), which generate the model’s fields the same way rather than hand-declaring each one. Only populated items are included; a question’s absent metrics are simply not keys in the mapping.

respondent: Respondent
version: str | None
items: Mapping[str, int]
domain_scores: Mapping[str, float]
class acmad_uploader.domain.proms.PromsRecord(site_patient_id, age_in_days, faq, ofaq, podci, mgait_efficiency, walk12, goal_assessment, source)

Bases: object

Every PROMs family response recorded for one gait assessment.

gait_assessment (1:1) is the natural key for the parent PROMs row, resolved from (site_patient_id, age_in_days). Each child questionnaire is optional/repeatable per its own destination shape: faq is a single 1:1 row (or absent); the dual-respondent questionnaires (ofaq/podci/mgait_efficiency/walk12/ goal_assessment) each carry 0-2 entries, one per respondent slot actually present in the workbook.

site_patient_id: str
age_in_days: int
faq: FaqRecord | None
ofaq: tuple[OfaqRecord, ...]
podci: tuple[PodciRecord, ...]
mgait_efficiency: tuple[MGaitEfficiencyRecord, ...]
walk12: tuple[Walk12Record, ...]
goal_assessment: tuple[GoalAssessmentRecord, ...]
source: SourceLocation

Canonical surgery/intervention episode values shared by ETL stages.

class acmad_uploader.domain.surgery.SurgeryLaterality(*values)

Bases: StrEnum

Laterality of one surgical procedure.

LEFT = 'left'
RIGHT = 'right'
BILATERAL = 'bilateral'
NOT_SPECIFIED = 'not specified'
NOT_APPLICABLE = 'not applicable'
class acmad_uploader.domain.surgery.SurgicalProcedureRecord(procedure_index, laterality, surgery_type, surgery_region, surgery_intervention, surgery_intervention_detail, source)

Bases: object

One intervention performed during a surgery episode.

surgery_type/surgery_region/surgery_intervention/ surgery_intervention_detail are submitted as raw text and matched server-side against admin-editable SurgicalInterventionDefinition reference data — the ETL never resolves or sends a definition itself, the same pattern as Diagnosis.definition. procedure_index is the numbered workbook slot (surg_laterality_<procedure_index> etc.) and is the destination’s natural key alongside its owning surgery (SurgicalProcedure.procedure_index, unique per surgery).

procedure_index: int
laterality: SurgeryLaterality
surgery_type: str
surgery_region: str
surgery_intervention: str
surgery_intervention_detail: str | None
source: SourceLocation
class acmad_uploader.domain.surgery.SurgeryRecord(site_patient_id, site_abbreviation, age_in_days, hospital_abbreviation, event_details, procedures, sources)

Bases: object

One surgery/intervention episode and every procedure performed in it.

age_in_days together with site_patient_id is the natural key: every numbered procedure slot found across contributing surgery.xlsx workbooks for the same day is aggregated into one episode, mirroring how GaitAssessmentRecord joins its own contributing workbooks.

site_patient_id: str
site_abbreviation: str
age_in_days: int
hospital_abbreviation: str | None
event_details: PatientEventDetails
procedures: tuple[SurgicalProcedureRecord, ...]
sources: tuple[SourceLocation, ...]