});
// Watch form fields for changes
- ['record-type', 'modality'].forEach(id => {
+ ['record-type'].forEach(id => {
document.getElementById(id).addEventListener('change', updateSubmitButton);
});
diff --git a/bfd9000_web/archive/tests/test_api_flows.py b/bfd9000_web/archive/tests/test_api_flows.py
index c0baca7..59e916e 100644
--- a/bfd9000_web/archive/tests/test_api_flows.py
+++ b/bfd9000_web/archive/tests/test_api_flows.py
@@ -33,8 +33,8 @@ def setUp(self):
# Ensure codings exist
self.rt, _ = Coding.objects.get_or_create(
system=SYSTEM_RECORD_TYPE,
- code='201456002',
- defaults={'display': 'Cephalogram'},
+ code='L',
+ defaults={'display': 'Lateral Cephalogram'},
)
self.orient, _ = Coding.objects.get_or_create(
system=SYSTEM_ORIENTATION,
diff --git a/bfd9000_web/archive/tests/test_records.py b/bfd9000_web/archive/tests/test_records.py
index 01794dc..2f433bf 100644
--- a/bfd9000_web/archive/tests/test_records.py
+++ b/bfd9000_web/archive/tests/test_records.py
@@ -85,8 +85,8 @@ def setUp(self):
# Create codings
self.rt_lateral, _ = Coding.objects.get_or_create(
system=SYSTEM_RECORD_TYPE,
- code='201456002',
- defaults={'display': 'Cephalogram'}
+ code='L',
+ defaults={'display': 'Lateral Cephalogram'}
)
self.orient_left, _ = Coding.objects.get_or_create(
system=SYSTEM_ORIENTATION,
diff --git a/bfd9000_web/archive/tests/test_role_permissions.py b/bfd9000_web/archive/tests/test_role_permissions.py
index aea3e83..7e34928 100644
--- a/bfd9000_web/archive/tests/test_role_permissions.py
+++ b/bfd9000_web/archive/tests/test_role_permissions.py
@@ -38,8 +38,8 @@ def setUp(self):
)
self.record_type, _ = Coding.objects.get_or_create(
system=SYSTEM_RECORD_TYPE,
- code="201456002",
- defaults={"display": "Cephalogram"},
+ code="L",
+ defaults={"display": "Lateral Cephalogram"},
)
self.orientation, _ = Coding.objects.get_or_create(
system=SYSTEM_ORIENTATION,
diff --git a/bfd9000_web/archive/tests/test_valuesets.py b/bfd9000_web/archive/tests/test_valuesets.py
index 8fe227d..6644075 100644
--- a/bfd9000_web/archive/tests/test_valuesets.py
+++ b/bfd9000_web/archive/tests/test_valuesets.py
@@ -33,9 +33,9 @@ def setUp(self):
self.record_types_valueset, _ = ValueSet.objects.get_or_create(
slug="record_types",
defaults={
- "url": "https://orthodontics.case.edu/fhir/ValueSet/record-types",
- "name": "RecordTypes",
- "title": "Record types",
+ "url": "https://orthodontics.case.edu/fhir/cwru-ortho-record-types",
+ "name": "CWRUOrthoRecordTypes",
+ "title": "CWRU Ortho Record Types",
},
)
self.orientations_valueset, _ = ValueSet.objects.get_or_create(
@@ -71,11 +71,11 @@ def setUp(self):
},
)
- # Record types (using SNOMED codes from migration)
+ # Record types (CWRU codes)
self.rt_lateral, _ = Coding.objects.get_or_create(
system=SYSTEM_RECORD_TYPE,
- code='201456002',
- defaults={'display': 'Cephalogram'},
+ code='L',
+ defaults={'display': 'Lateral Cephalogram'},
)
ValueSetConcept.objects.get_or_create(
valueset=self.record_types_valueset,
@@ -83,8 +83,8 @@ def setUp(self):
)
self.rt_pa, _ = Coding.objects.get_or_create(
system=SYSTEM_RECORD_TYPE,
- code='268425006',
- defaults={'display': 'Pelvis X-ray'},
+ code='F',
+ defaults={'display': 'Frontal Cephalogram'},
)
ValueSetConcept.objects.get_or_create(
valueset=self.record_types_valueset,
@@ -227,10 +227,10 @@ def test_record_types(self):
self.assertIn('display', item)
self.assertEqual(len(item), 2, "Should only have 'id' and 'display' fields")
- # Verify expected values (SNOMED codes)
+ # Verify expected values (CWRU codes)
ids = [item['id'] for item in response.data]
- self.assertIn('201456002', ids) # Cephalogram
- self.assertIn('268425006', ids) # Pelvis X-ray
+ self.assertIn('L', ids) # Lateral Cephalogram
+ self.assertIn('F', ids) # Frontal Cephalogram
def test_orientations(self):
"""Should return orientations with correct structure"""
diff --git a/bfd9000_web/docker-compose.yml b/bfd9000_web/docker-compose.yml
index 2dc89f4..5b955bd 100644
--- a/bfd9000_web/docker-compose.yml
+++ b/bfd9000_web/docker-compose.yml
@@ -29,7 +29,18 @@ services:
# Enable BOTH lines below to emulate a proxy-mounted prefix locally.
- DJANGO_FORCE_SCRIPT_NAME=/bfd9000
- SCRIPT_NAME=/bfd9000
- - BFD9020_BASE_URL=https://wingate.case.edu/bfd9020
+ - BFD9020_BASE_URL=http://localhost:9020
+ # - BFD9020_BASE_URL=https://wingate.case.edu/bfd9020
+
+ bfd9020:
+ image: ghcr.io/open-ortho/edu.case.bfd9020:local
+ container_name: bfd9020
+ ports:
+ - "9020:9020"
+ environment:
+ LOG_LEVEL: "INFO"
+ ROOT_PATH: ""
+ ENABLE_DOCS: "true"
volumes:
media_volume:
diff --git a/bfd9000_web/docs/api_requirements.md b/bfd9000_web/docs/api_requirements.md
index 612e45a..7476451 100644
--- a/bfd9000_web/docs/api_requirements.md
+++ b/bfd9000_web/docs/api_requirements.md
@@ -222,7 +222,7 @@ Based on the use cases defined in `use_cases.md`, the following API endpoints ar
**Supported Valueset Types**:
-- **`record_types`**: Available record type options
+- **`record_types`**: Available record type options (CWRU Ortho Record Types ValueSet: `https://orthodontics.case.edu/fhir/cwru-ortho-record-types`)
- **`orientations`**: Available orientation options
- **`collections`**: Available collection names
- **`sex_options`**: Available sex/gender options
@@ -232,9 +232,9 @@ Based on the use cases defined in `use_cases.md`, the following API endpoints ar
```json
[
- {"id": "lateral", "display": "Lateral"},
- {"id": "pa", "display": "PA"},
- {"id": "hand", "display": "Hand"}
+ {"id": "L", "display": "Lateral Cephalogram"},
+ {"id": "F", "display": "Frontal Cephalogram"},
+ {"id": "H", "display": "Radiograph of Hand & Wrist"}
]
```
@@ -321,7 +321,7 @@ Based on the use cases defined in `use_cases.md`, the following API endpoints ar
- `thumbnail_preview` (file upload, optional): preprocessed preview PNG from UI pipeline; used as thumbnail source when provided
- `record_type` (string, required): value from `/api/valuesets/?type=record_types`
- `orientation` (string, required): value from `/api/valuesets/?type=orientations`
- - `modality` (string, required): value from `/api/valuesets/?type=modalities`
+ - `modality` (string, optional): value from `/api/valuesets/?type=modalities` (if omitted, inferred from `record_type`)
- `operator` (string, optional - defaults to authenticated user)
- `acquisition_date` (date, optional - defaults to today)
- `notes` (string, optional)
@@ -614,7 +614,7 @@ All error responses follow this structure:
- Maximum file size: 100MB (configurable)
- `record_type` must be a valid value from `/api/valuesets/?type=record_types`
- `orientation` must be a valid value from `/api/valuesets/?type=orientations`
-- `modality` must be a valid value from `/api/valuesets/?type=modalities`
+- `modality` must be a valid value from `/api/valuesets/?type=modalities` when provided; otherwise it is inferred from `record_type`
- Encounter subject must already belong to a valid collection; uploads are rejected otherwise
- `acquisition_date` cannot be in the future
- File content must match file extension (validated via magic bytes)
diff --git a/bfd9000_web/docs/data_model.md b/bfd9000_web/docs/data_model.md
index ee75d59..9e65d6d 100644
--- a/bfd9000_web/docs/data_model.md
+++ b/bfd9000_web/docs/data_model.md
@@ -2,7 +2,6 @@
This document defines the archive concepts and rationale used by the Django models.
-
## Core hierarchy
Imaging data is organized as:
@@ -103,7 +102,7 @@ Owns per-instance/acquisition fields:
These are different concepts and must never be substituted.
- `record_type`:
- - SNOMED clinical study type.
+ - CWRU record type code (ValueSet: `https://orthodontics.case.edu/fhir/cwru-ortho-record-types`).
- Owned by `Series`.
- Used for clinical grouping/filtering.
- `image_type`:
@@ -111,6 +110,18 @@ These are different concepts and must never be substituted.
- Owned by `Record`.
- Used for source compatibility/import semantics.
+## Codes, Valuesets, and Dropdowns
+
+Clinical codes (record types, modalities, orientations, procedures) are stored as `Coding` rows and grouped into `ValueSet`s. The UI uses the valueset API (`/api/valuesets/?type=...`) to populate dropdowns, and the same codes are used when exporting to open standards (FHIR/DICOM) so integrations stay interoperable.
+
+Valuesets are refreshed from canonical FHIR sources using the generic importer:
+
+```
+python manage.py import_valuesets --all
+```
+
+This is idempotent: it updates existing codes, adds new ones, and removes valueset links for retired codes so they no longer appear in dropdowns. Update valueset sources in `bfd9000_web/archive/constants.py` if a terminology endpoint changes or a new valueset should be managed by the importer.
+
## Clarifications from the refactor
- `Record.sop_class_uid` is removed; instance identity is `Record.sop_instance_uid`.