Yes. Below is a modification to your implementation plan that adds an explicit SurveyJS → CDISC ODM-XML interoperability workstream, without derailing the core prototype. The intent is:
- Keep SurveyJS JSON as your authoring/runtime format (fast iteration).
- Add ODM as an interchange/archival layer (vendor-neutral, future-proof).
- Implement the mapping incrementally (minimum viable first, then deepen).
Added Workstream: SurveyJS → ODM Interoperability¶
Design principle¶
- Internal canonical for UI/runtime: SurveyJS JSON (Creator/Runner).
- Interchange/archival canonical: CDISC ODM-XML.
- Mapping is versioned, deterministic, and testable.
- ODM export/import must preserve traceability: form version, item OIDs, code lists, and auditability.
Phase 0 (New): Metadata Standards Baseline¶
Deliverables¶
- Add a small “standards module” with:
- ODM object model (Pydantic types) sufficient for v1 export.
- XML serializer/deserializer for ODM.
- Mapping registry: SurveyJS question types → ODM ItemDefs.
Acceptance criteria¶
- Given a stored SurveyJS form version, you can produce a valid ODM XML document representing:
- Study
- Protocol + StudyEventDef(s)
- FormDef(s)
- ItemGroupDef (minimal)
- ItemDef(s)
- CodeList(s) where applicable
Phase 2 (Modified): Form Definition Model Changes for ODM Compatibility¶
Data model adjustments¶
FormDefinition¶
** (or **¶
FormVersion¶
)¶
Add fields to support stable ODM identity across versions:
- form_oid (stable across versions; e.g., F.
. ) - form_version (integer/semver)
- surveyjs_schema (JSONB)
- odm_formdef_snapshot (optional XML text; stored at “release” time)
- mapping_profile (e.g., odm_v1_minimal, odm_v1_extended)
- released_at, released_by (to support “design lock” semantics)
QuestionMap¶
** (new table)**¶
A question-level mapping table that stabilizes OIDs and supports evolution:
- id
- form_version_id
- surveyjs_name (SurveyJS name property for the question)
- item_oid (stable per question identity; can persist across versions if semantically same)
- item_name (ODM ItemDef Name; usually same as surveyjs_name)
- datatype (ODM DataType)
- codelist_oid (nullable)
- unit_oid (nullable)
- is_identifier (for de-id / exports)
- is_required
- notes (mapping warnings, manual overrides)
This table is the anchor that prevents “silent flips” when a designer renames a question in SurveyJS.
CodeListMap¶
** (new table)**¶
- codelist_oid
- surveyjs_choices_hash (so you can detect choice changes)
- items (JSONB: code + decode)
Phase 3 (Modified): Study Schedule Model → ODM StudyEventDef Mapping¶
ODM needs study structure expressed via StudyEventDef (visits/events), FormRef, and optionally ItemGroupRef.
New fields / tables¶
- Ensure Visit has:
- event_oid (stable)
- order_number (ODM OrderNumber)
- repeating (optional)
- Ensure VisitFormAssignment has:
- form_oid reference
- order_number
Phase 4 (New): ODM Export Service¶
Service: ****¶
odm_exporter¶
Responsibilities¶
- Generate ODM-XML from:
- Study definition (arms/visits/forms)
- Form versions + mappings
- Produce deterministic output:
- stable OIDs
- consistent ordering
- Validate output against a lightweight ODM validation step (at minimum: internal structural checks).
API endpoints¶
- GET /studies/{study_id}/odm/metadata
- returns ODM-XML of metadata (Study/Protocol/Events/Forms/Items).
- GET /studies/{study_id}/odm/metadata?version=latest|released
- if released, export only released (locked) versions.
- POST /studies/{study_id}/odm/metadata/release
- creates an immutable “release snapshot” of ODM for the current study design + form versions.
- audit log entry: who released, hash of ODM XML.
Internal outputs¶
- Store:
- odm_metadata_xml
- odm_metadata_sha256
- released_at/by
Phase 5 (New): ODM Data Export (ClinicalData)¶
Once you have responses, ODM can also carry data (ClinicalData → SubjectData → StudyEventData → FormData → ItemGroupData → ItemData).
Minimal viable data export¶
- One participant = one SubjectData node
- Completed visits = StudyEventData
- Each submitted form = FormData
- Each question answer = ItemData using ItemOID
API endpoints¶
- GET /studies/{study_id}/odm/clinicaldata
- exports ODM including both metadata + clinical data (or provide two endpoints if you prefer separation).
- GET /studies/{study_id}/participants/{pid}/odm
- export a single subject in ODM.
Required mapping rules¶
- SurveyJS question name must map to a stable ItemOID
- Multi-select answers map either to:
- repeated ItemData entries, or
- delimited string (avoid if possible; choose a consistent policy and document it).
Audit requirements¶
- Export requests should be audited:
- who exported
- what scope (study, participant)
- what version (released snapshot hash)
Phase 6 (New): ODM Import (Optional, but recommended as a stub)¶
Even if you do not fully support ODM import, implement an ODM intake stub now:
API endpoints¶
- POST /studies/{study_id}/odm/import/metadata
- accepts ODM-XML
- parses and creates a new “imported” study definition or validates and reports mapping feasibility.
Why do this early¶
- It forces you to keep your internal model compatible with external systems.
- It’s a future pathway for migration or interoperability.
Mapping Specification (Agent-Coder Ready)¶
1) SurveyJS Form → ODM FormDef¶
- SurveyJS: title → FormDef/@Name
- Internal form_oid → FormDef/@OID
2) SurveyJS Pages → ODM ItemGroupDef (recommended)¶
- Each page becomes one ItemGroupDef:
- Page title → ItemGroupDef Name
- Stable itemgroup_oid derived from page name/index
- Each question on the page becomes an ItemDef and referenced via ItemRef inside that ItemGroupDef.
If you want a simpler v1:
- Create a single ItemGroupDef per form and put all items into it.
3) SurveyJS Questions → ODM ItemDef¶
Use SurveyJS name as the stable key; never rely on display title.
Type mapping (minimum viable)¶
- text → string
- comment → text
- boolean → boolean
- radiogroup → text + CodeListRef
- dropdown → text + CodeListRef
- checkbox → text + CodeListRef (policy needed for multi-select)
- rating → integer
- expression / calculated → either:
- not exported (v1), or
- exported as derived item with flag in metadata notes
Store mapping warnings when:
- a SurveyJS question lacks a stable name
- a choice list changes in a released form version
- a question is deleted (keep old ItemOID reserved; do not reuse)
4) Choice lists → ODM CodeList¶
- Each choice list becomes a CodeList with CodeListItem:
- choice value → CodedValue
- choice text → Decode/TranslatedText (support multilingual later)
5) Schedule → ODM StudyEventDef + FormRef¶
- Visit/event → StudyEventDef with OID, Name, OrderNumber
- VisitFormAssignment → FormRef with FormOID and OrderNumber
Versioning and “Release” Policy (Critical)¶
Draft vs Released¶
- Draft: designers can edit forms; ODM export is available but marked draft.
- Released: freeze ODM snapshot hash; form versions are locked for new data collection instances.
Rules¶
- Never mutate a released FormVersion.
- New changes create a new version and require a new release snapshot.
- Participant tasks are bound to a specific released form version (prevents ambiguity in analysis).
Audit Logging Extensions¶
Add the following audited events:
- FORM_MAPPING_AUTOGENERATED
- FORM_MAPPING_OVERRIDDEN_MANUAL
- ODM_METADATA_EXPORTED
- ODM_METADATA_RELEASED
- ODM_CLINICALDATA_EXPORTED
- ODM_IMPORT_ATTEMPTED
Each log entry should include:
- study_id
- form_version_id(s)
- odm hash (if export)
- user_id
- timestamp
- warnings count
Testing Strategy (Non-Optional)¶
-
Golden files
-
For a known SurveyJS schema, produce ODM-XML and compare against a committed “golden” ODM output (byte-for-byte or canonicalized XML compare).
-
Round-trip constraints
-
SurveyJS → ODM → (optional) rehydrate internal model should preserve:
- counts (forms/items)
- OID stability
-
schedule structure
-
Version drift tests
-
Update a form (add/remove/rename question) and verify:
- old ItemOIDs persist
- new ItemOIDs created
- deleted questions do not reuse OIDs
Where this fits into your current prototype timeline¶
- Implement ODM metadata export immediately after you have:
- Study + Visit schedule model
- Form versioning
- QuestionMap table
- Implement ODM clinical data export once survey submissions are stored.
- ODM import can remain a stub until you have a concrete need.
Agent-Coder Prompt (drop-in)¶
Use this as the first instruction block for your coder agent:
You are implementing an EDC prototype (FastAPI + PostgreSQL) using SurveyJS Creator/Runner for form design and participant-facing surveys. Extend the implementation plan to include CDISC ODM-XML interoperability.
Requirements:
1) Keep SurveyJS JSON as internal authoring/runtime schema.
2) Implement SurveyJS → ODM-XML mapping for study metadata:
- StudyEventDef for visits, FormDef for forms, ItemGroupDef for pages (or single group), ItemDef for questions, CodeList for choice questions.
3) Add stable OIDs and mapping tables:
- FormDefinition/FormVersion: form_oid, version, released_at/by, odm_snapshot(optional).
- QuestionMap: surveyjs_name → item_oid, datatype, codelist_oid, required, identifier flag.
4) Add ODM export endpoints:
- GET /studies/{id}/odm/metadata
- POST /studies/{id}/odm/metadata/release (stores immutable snapshot + sha256)
5) Add ODM clinical data export endpoint once responses exist:
- GET /studies/{id}/odm/clinicaldata
6) All exports and mapping operations must be audit logged with hash, user_id, timestamps.
7) Add golden-file tests for ODM export determinism and version drift behavior.
Deliverables:
- Data model migrations
- ODM exporter module (Pydantic types + XML serialization)
- Mapping service that generates QuestionMap/CodeListMap from SurveyJS schema and stores warnings
- API endpoints + RBAC enforcement
- Tests (golden files + version drift)
If you want the next iteration, I can turn this into a concrete repo-ready structure (folders, modules, Alembic migration checklist, and a minimal ODM XML template generator), but the above is sufficient to modify your existing plan and guide implementation cleanly.