Transformers

Shared helpers for validating vertical-form workbook fields.

acmad_uploader.transformers.forms.is_supported_version(version)

Return whether a source version denotes template version 3.

Return type:

bool

acmad_uploader.transformers.forms.single_field(form, key, issues, *, code_prefix, required=True)

Read one source field and report missing or duplicate keys.

Some workbook variants don’t declare every key at all (for example site_id exists on physical_exam.xlsx but not on function_proms.xlsx/mocap.xlsx). Pass required=False to treat an entirely absent key as silently optional rather than an error; a duplicate key is still always reported.

Return type:

SourceField | None

acmad_uploader.transformers.forms.required_text(form, key, issues, *, code_prefix)

Read a non-blank text field without coercing identifiers.

Return type:

str | None

acmad_uploader.transformers.forms.coerce_whole_number(value)

Coerce a whole number from an int, a whole float, or a digit string.

Different sites export the same logically-numeric field with different Excel cell types (e.g. gaitass_age as text "3733" at one site, an int elsewhere) — this tolerates either, returning None for anything that doesn’t cleanly represent a whole number (booleans, fractional floats, non-numeric text).

Return type:

int | None

acmad_uploader.transformers.forms.required_int(form, key, issues, *, code_prefix)

Read a whole-number field, rejecting text and fractional values.

Return type:

int | None

acmad_uploader.transformers.forms.optional_int(form, key, issues, *, code_prefix, required=True)

Read an optional whole-number field, distinguishing blank from invalid.

required controls whether the key itself must be declared by the workbook at all; see single_field().

Return type:

int | None

acmad_uploader.transformers.forms.optional_text(form, key, issues, *, code_prefix, required=True)

Read optional text, distinguishing blank values from invalid types.

required controls whether the key itself must be declared by the workbook at all; see single_field().

Return type:

str | None

acmad_uploader.transformers.forms.coerce_float(value)

Coerce a float from an int, a float, or a numeric string.

Mirrors coerce_whole_number() but permits fractional values — for real-valued measurement fields (height, mass, …) rather than whole-number fields (age, year, …).

Return type:

float | None

acmad_uploader.transformers.forms.required_float(form, key, issues, *, code_prefix)

Read a numeric field, rejecting non-numeric text.

Return type:

float | None

acmad_uploader.transformers.forms.optional_float(form, key, issues, *, code_prefix, required=True)

Read an optional numeric field, distinguishing blank from invalid.

required controls whether the key itself must be declared by the workbook at all; see single_field().

Return type:

float | None

acmad_uploader.transformers.forms.choice_text(form, key, issues, *, code_prefix, required=True)

Read a choice-code field, tolerating digit codes stored as numbers.

Some sites enter digit choice codes (e.g. "0".."5") as numbers rather than text (Excel auto-converts a numeric-looking cell), so this reads the raw value rather than going through optional_text()’s text-only requirement. Callers look the returned text up against their own StrEnum.

Return type:

str | None

acmad_uploader.transformers.forms.field_location(form, key)

Return the first source location for a field key.

Return type:

SourceLocation | None

acmad_uploader.transformers.forms.cross_check_site_id(workbook_value, override, issues, *, code_prefix, location)

Resolve the effective site abbreviation for one workbook.

The workbook’s own site_id wins whenever it declares one - a supplied override must agree with it (case-insensitively) or this appends a {code_prefix}.site_override_conflict error, but the workbook’s value is still returned so a caller’s own required-field check doesn’t also (redundantly) fire; the conflict issue alone already fails the enclosing form. The override only fills the gap when the workbook doesn’t declare a value at all, returning None in that case only if no override was supplied either - callers already have their own required-field handling for that (see required_text/ optional_text).

Return type:

str | None

Shared extraction of PatientEvent metadata across event-creating slices.

acmad_uploader.transformers.patient_event.extract_patient_event_details(form, issues, *, code_prefix, year_field, region_field, state_field, country_field, irsd_field, irsad_field, ieo_field, ier_field)

Read the PatientEvent fields a slice’s anchor workbook may declare.

Field names differ between workbooks (gaitass_* vs. surg_res*), so each call site names its own eight source keys explicitly rather than assuming a shared prefix. Every field is read tolerantly (required=False): a workbook variant that doesn’t carry this group at all (e.g. function_proms.xlsx/mocap.xlsx) simply yields an empty PatientEventDetails.

Return type:

PatientEventDetails

acmad_uploader.transformers.patient_event.merge_patient_event_details(details)

Coalesce PatientEvent fields from every contributing form.

Only one workbook type ever declares this field group in practice, so this is a simple first-non-None-wins merge rather than a conflict-checked one (contrast GaitAssessmentTransformer’s stricter handling of site_id, which every contributing workbook can independently declare).

Return type:

PatientEventDetails

Transformation of patient workbooks into canonical patient records.

class acmad_uploader.transformers.patient.PatientTransformer(*, site_override=None)

Bases: object

Validate version 3 patient forms and map them to domain records.

transform(extracted)

Transform all forms as one all-or-nothing upload batch.

Return type:

StageResult[tuple[PatientRecord, ...]]

Transformation of patient record fields into canonical diagnosis records.

class acmad_uploader.transformers.diagnosis.DiagnosisTransformer(*, site_override=None)

Bases: object

Validate version 3 patient records and extract their diagnosis fields.

A blank diagnosis group is a valid state (a participant recruited to the normative comparison group has no primary diagnosis at all), so absent fields are treated as “no diagnosis of this type”, not an error.

transform(extracted)

Extract every primary and secondary diagnosis, one batch at a time.

Return type:

StageResult[tuple[DiagnosisRecord, ...]]

Transformation of gait assessment workbooks into canonical event records.

class acmad_uploader.transformers.gait_assessment.GaitAssessmentTransformer(*, site_override=None)

Bases: object

Validate version 3 gait assessment forms and join them by natural key.

transform(extracted)

Join physical exam, function/PROMs and mocap forms by natural key.

Return type:

StageResult[tuple[GaitAssessmentRecord, ...]]

Transformation of function/PROMs workbooks into canonical records.

class acmad_uploader.transformers.function_assessment.FunctionAssessmentTransformer

Bases: object

Validate version 3 function/PROMs forms and map them to domain records.

transform(extracted)

Transform all forms as one all-or-nothing upload batch.

Return type:

StageResult[tuple[FunctionAssessmentRecord, ...]]

Transformation of mocap workbooks into canonical mocap session records.

class acmad_uploader.transformers.mocap_data.MocapDataTransformer

Bases: object

Validate version 3 mocap forms and map them to domain records.

transform(extracted)

Transform all forms as one all-or-nothing upload batch.

Return type:

StageResult[tuple[MocapDataRecord, ...]]

Transformation of mocap session file inventories into artifact records.

class acmad_uploader.transformers.biomechanics_artifact.BiomechanicsArtifactTransformer(mocap_transformer=None)

Bases: object

Map every file in a mocap session folder to a biomechanics artifact record.

Every file is a plain upload with no ETL-side processing beyond metadata (role/checksum/content-type) — the destination’s own hooks parse the three “normalised” CSVs into their array-field models server-side (see CLAUDE.md).

transform(extracted)

Transform every discovered mocap session folder’s files.

Return type:

StageResult[tuple[BiomechanicsArtifactRecord, ...]]

Transformation of physical exam workbooks into canonical records.

class acmad_uploader.transformers.physical_examination.PhysicalExaminationTransformer

Bases: object

Validate version 3 physical exam forms and map them to domain records.

transform(extracted)

Transform all forms as one all-or-nothing upload batch.

Return type:

StageResult[tuple[PhysicalExaminationRecord, ...]]

Transformation of function/PROMs workbooks into canonical PROMs records.

class acmad_uploader.transformers.proms.PromsTransformer

Bases: object

Validate version 3 function/PROMs forms and map them to domain records.

transform(extracted)

Transform all forms as one all-or-nothing upload batch.

Return type:

StageResult[tuple[PromsRecord, ...]]

Transformation of surgery workbooks into canonical intervention episodes.

class acmad_uploader.transformers.surgery.SurgeryTransformer(*, site_override=None)

Bases: object

Validate version 3 surgery forms and join them by natural key.

Every numbered procedure slot found across contributing surgery.xlsx workbooks for the same (site_patient_id, age_in_days) is aggregated into one episode, the same “multiple entries per day” behaviour GaitAssessmentTransformer already applies to its own siblings.

transform(extracted)

Join every contributing workbook’s procedures by natural key.

Return type:

StageResult[tuple[SurgeryRecord, ...]]