19 KiB
Flat JSON Bodies — All SatuSehat Use Cases
Flat (non-nested) POST body for every FHIR resource currently exposed by
service-satusehat.Status: ✅ Implemented. As of 2026-05-13, all 19 FHIR usecases + EpisodeOfCare + QuestionnaireResponse accept the flat shape below. Internal mappers compose the nested FHIR R4 payload before submission to SATUSEHAT. No
*DTOtypes frominternal/satusehat/commonare exposed at the API surface anymore.Conventions used in every body below:
*_idcarries the raw identifier; the mapper composesPatient/{id},Practitioner/{id},Encounter/{id}, etc.*_name(or*_display) is the human-readable display.*_code+*_display(+ optional*_system) replace nestedCodeableConceptDTO.*_value+*_unit(+ optional*_system,*_code) replace nestedQuantityDTO.- Date-times use ISO-8601
2026-05-13T08:00:00+07:00(RFC 3339). Encounter alone accepts the simplerYYYY-MM-DD HH:MM:SSform forperiod_start/period_endvia the project'sCustomTime(WIB).- Status / class / intent values match the validator
oneof=lists in the DTOs verbatim.organization_idis optional — if omitted, the service injectscfg.SatuSehat.OrgIDat the boundary.
1. Encounter
POST /satusehat/encounter
{
"encounter_id": "ENC-0012345",
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_name": "Budi Santoso",
"practitioner_id": "N10000001",
"practitioner_name": "Dr. Andi",
"location_id": "a6bab5d0-ba3c-4f73-8450-f44d6ca8e9d4",
"location_name": "Poli Umum",
"status": "arrived",
"class": "AMB",
"period_start": "2026-05-13 08:00:00",
"period_end": "2026-05-13 09:30:00",
"diagnosis_condition_id": "C-0001",
"diagnosis_use_code": "AD",
"diagnosis_use_display": "Admission diagnosis",
"diagnosis_rank": 1
}
2. EpisodeOfCare
POST /satusehat/episodeofcare
{
"episode_of_care_id": "EOC-12345",
"organization_id": "10000004",
"managing_organization_id": "10000004",
"patient_id": "P03647103112",
"patient_name": "Budi Santoso",
"care_manager_id": "N10000001",
"care_manager_name": "Dr. Andi",
"status": "active",
"type_code": "HACC",
"type_display": "Home and Community Care",
"period_start": "2026-05-13T08:00:00+07:00",
"period_end": "2026-08-13T17:00:00+07:00",
"diagnosis_condition_id": "C-0001",
"diagnosis_role_code": "CC",
"diagnosis_role_display": "Chief complaint",
"diagnosis_rank": 1
}
3. Condition
POST /satusehat/condition
{
"condition_id": "COND-9001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_name": "Budi Santoso",
"encounter_id": "ENC-0012345",
"clinical_status": "active",
"category_code": "encounter-diagnosis",
"category_display": "Encounter Diagnosis",
"code_system": "http://hl7.org/fhir/sid/icd-10",
"code": "J06.9",
"code_display": "Acute upper respiratory infection, unspecified",
"onset_date_time": "2026-05-13T08:15:00+07:00",
"recorded_date": "2026-05-13T08:20:00+07:00"
}
4. Observation
POST /satusehat/observation
{
"observation_id": "OBS-7001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"encounter_id": "ENC-0012345",
"performer_id": "N10000001",
"performer_name": "Dr. Andi",
"status": "final",
"category_code": "vital-signs",
"category_display": "Vital Signs",
"code_system": "http://loinc.org",
"code": "8867-4",
"code_display": "Heart rate",
"effective_datetime": "2026-05-13T08:10:00+07:00",
"issued": "2026-05-13T08:12:00+07:00",
"value_quantity_value": 80,
"value_quantity_unit": "beats/minute",
"value_quantity_system": "http://unitsofmeasure.org",
"value_quantity_code": "/min",
"body_site_code": "40983000",
"body_site_display": "Arm",
"interpretation_code": "N",
"interpretation_display": "Normal",
"reference_range_low_value": 60,
"reference_range_high_value": 100,
"reference_range_unit": "beats/minute",
"reference_range_text": "Normal adult resting heart rate"
}
5. AllergyIntolerance
POST /satusehat/allergyintolerance
{
"allergy_id": "ALG-0001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_name": "Budi Santoso",
"encounter_id": "ENC-0012345",
"encounter_display": "Poli Umum 13 Mei 2026",
"recorder_id": "N10000001",
"recorder_display": "Dr. Andi",
"clinical_status": "active",
"verification_status": "confirmed",
"category": "medication",
"code_system": "http://snomed.info/sct",
"code": "294505008",
"code_display": "Allergy to amoxicillin",
"recorded_date": "2026-05-13T08:25:00+07:00"
}
6. CarePlan
POST /satusehat/careplan
{
"care_plan_id": "CP-0001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_display": "Budi Santoso",
"encounter_id": "ENC-0012345",
"encounter_display": "Poli Umum 13 Mei 2026",
"author_id": "N10000001",
"author_display": "Dr. Andi",
"status": "active",
"intent": "plan",
"category_code": "assess-plan",
"category_display": "Assessment and Plan of Treatment",
"title": "Rencana perawatan ISPA",
"description": "Antibiotik 5 hari, kontrol H+3",
"created_date": "2026-05-13T08:30:00+07:00",
"goal_ids": ["GOAL-001", "GOAL-002"]
}
7. ClinicalImpression
POST /satusehat/clinicalimpression
{
"clinical_impression_id": "CI-0001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_display": "Budi Santoso",
"encounter_id": "ENC-0012345",
"encounter_display": "Poli Umum 13 Mei 2026",
"assessor_id": "N10000001",
"assessor_display": "Dr. Andi",
"status": "completed",
"code_system": "http://snomed.info/sct",
"code": "162673000",
"code_display": "General examination of patient",
"description": "Pasien sadar, demam, batuk produktif",
"effective_datetime": "2026-05-13T08:35:00+07:00",
"date": "2026-05-13T08:40:00+07:00",
"summary": "ISPA non-pneumonia",
"problem_condition_ids": ["COND-9001"],
"finding_code": "386661006",
"finding_display": "Fever",
"prognosis_code": "170968001",
"prognosis_display": "Prognosis good"
}
8. Composition
POST /satusehat/composition
{
"composition_id": "COMP-0001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_display": "Budi Santoso",
"encounter_id": "ENC-0012345",
"encounter_display": "Poli Umum 13 Mei 2026",
"author_id": "N10000001",
"author_display": "Dr. Andi",
"status": "final",
"type_system": "http://loinc.org",
"type_code": "11488-4",
"type_display": "Consult note",
"category_code": "LP173421-1",
"category_display": "Report",
"title": "Catatan Konsultasi Poli Umum",
"date": "2026-05-13T09:00:00+07:00",
"section_title": "Anamnesis & Pemeriksaan",
"section_code": "55109-3",
"section_display": "Reason for visit Narrative",
"section_text": "Pasien datang dengan keluhan batuk dan demam selama 3 hari."
}
9. DiagnosticReport
POST /satusehat/diagnosticreport
{
"diagnostic_id": "DR-5001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"encounter_id": "ENC-0012345",
"performer_id": "N10000001",
"performer_name": "Dr. Andi",
"status": "final",
"category_code": "LAB",
"category_display": "Laboratory",
"code_system": "http://loinc.org",
"code": "58410-2",
"code_display": "Complete blood count (hemogram) panel",
"effective_datetime": "2026-05-13T09:10:00+07:00",
"issued": "2026-05-13T09:30:00+07:00",
"result_observation_ids": ["OBS-7001", "OBS-7002"],
"specimen_ids": ["SPC-3001"],
"imaging_study_ids": [],
"conclusion_code": "162673000",
"conclusion_display": "General examination of patient",
"conclusion": "Hasil dalam batas normal"
}
10. ImagingStudy
POST /satusehat/imagingstudy
{
"accession_number": "ACC-0001",
"organization_id": "10000004",
"service_request_id": "SR-0001",
"patient_id": "P03647103112",
"patient_name": "Budi Santoso",
"encounter_id": "ENC-0012345",
"practitioner_id": "N10000001",
"practitioner_name": "Dr. Andi",
"status": "available",
"started": "2026-05-13T09:00:00+07:00",
"number_of_series": 1,
"number_of_instances": 24,
"procedure_code": "168731009",
"procedure_display": "Chest X-ray",
"description": "Thorax PA"
}
11. Immunization
POST /satusehat/immunization
{
"immunization_id": "IMM-0001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_name": "Budi Santoso",
"encounter_id": "ENC-0012345",
"encounter_display": "Poli Umum 13 Mei 2026",
"performer_id": "N10000001",
"performer_name": "Dr. Andi",
"location_id": "a6bab5d0-ba3c-4f73-8450-f44d6ca8e9d4",
"location_name": "Poli Umum",
"status": "completed",
"vaccine_code_system": "http://sys-ids.kemkes.go.id/vaccine",
"vaccine_code": "1010101010",
"vaccine_display": "Sinovac",
"occurrence_date_time": "2026-05-13T09:05:00+07:00",
"primary_source": true,
"lot_number": "BATCH-XYZ-2026-001",
"dose_quantity_value": 0.5,
"dose_quantity_unit": "mL",
"dose_quantity_system": "http://unitsofmeasure.org",
"dose_quantity_code": "mL",
"route_code": "IM",
"route_display": "Intramuscular"
}
12. Medication
POST /satusehat/medication
{
"medication_id": "MED-0001",
"organization_id": "10000004",
"manufacturer_id": "100099999",
"status_code": "active",
"kfa_code": "92000001",
"kfa_display": "Paracetamol 500 mg tablet",
"form_code": "TAB",
"form_display": "Tablet",
"batch_number": "BTC-XYZ-2026-09",
"expiration_date": "2027-12-31T00:00:00+07:00"
}
13. MedicationDispense
POST /satusehat/medicationdispense
{
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_name": "Budi Santoso",
"encounter_id": "ENC-0012345",
"practitioner_id": "N10000001",
"practitioner_name": "Apt. Sari",
"location_id": "a6bab5d0-ba3c-4f73-8450-f44d6ca8e9d4",
"location_name": "Apotek Rawat Jalan",
"medication_id": "MED-0001",
"medication_display": "Paracetamol 500 mg tablet",
"medication_request_id": "MR-0001",
"prescription_id": "RX-2026-000123",
"prescription_item_id": "RX-2026-000123-1",
"status": "completed",
"category": "outpatient",
"prepared_date": "2026-05-13 09:40:00",
"handed_over_date": "2026-05-13 09:45:00",
"quantity_value": 10,
"quantity_unit": "TAB",
"days_supply_value": 5,
"dosage_text": "1 tablet, 3x sehari sesudah makan",
"timing_frequency": 3,
"timing_period": 1,
"timing_period_unit": "d",
"dose_quantity_value": 1,
"dose_quantity_unit": "TAB"
}
14. MedicationRequest
POST /satusehat/medicationrequest
{
"medicationrequest_id": "MR-0001",
"prescription_item_id": "RX-2026-000123-1",
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_name": "Budi Santoso",
"encounter_id": "ENC-0012345",
"practitioner_id": "N10000001",
"practitioner_name": "Dr. Andi",
"medication_id": "MED-0001",
"medication_display": "Paracetamol 500 mg tablet",
"category": "outpatient",
"priority": "routine",
"status": "active",
"intent": "order",
"authored_on": "2026-05-13T09:35:00+07:00",
"reason_code": "J06.9",
"reason_display": "Acute upper respiratory infection",
"course_of_therapy_code": "acute",
"course_of_therapy_display": "Short course (acute) therapy",
"dosage_text": "1 tablet, 3x sehari sesudah makan",
"additional_instruction": "Habiskan",
"patient_instruction": "Minum dengan air putih",
"timing_frequency": 3,
"timing_period": 1,
"timing_period_unit": "d",
"route_code": "O",
"route_display": "Oral",
"dose_quantity_value": 1,
"dose_quantity_unit": "TAB",
"dispense_interval": 0,
"dispense_value": 15,
"dispense_unit": "TAB",
"supply_duration": 5,
"validity_period_start": "2026-05-13T09:35:00+07:00",
"validity_period_end": "2026-05-20T23:59:59+07:00"
}
15. MedicationStatement
POST /satusehat/medicationstatement
{
"statement_id": "MS-0001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_name": "Budi Santoso",
"encounter_id": "ENC-0012345",
"information_source_id": "P03647103112",
"information_source_name": "Budi Santoso",
"status": "active",
"category_code": "outpatient",
"category_display": "Outpatient",
"medication_code_system": "http://sys-ids.kemkes.go.id/kfa",
"medication_code": "92000001",
"medication_display": "Paracetamol 500 mg tablet",
"effective_date_time": "2026-05-13T09:35:00+07:00",
"date_asserted": "2026-05-13T09:36:00+07:00",
"dosage_text": "1 tablet, 3x sehari sesudah makan",
"dosage_patient_instruction": "Minum dengan air putih",
"dosage_route_code": "O",
"dosage_route_display": "Oral",
"dose_quantity_value": 1,
"dose_quantity_unit": "TAB"
}
16. Procedure
POST /satusehat/procedure
{
"procedure_id": "PROC-0001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"encounter_id": "ENC-0012345",
"performer_id": "N10000001",
"performer_name": "Dr. Andi",
"status": "completed",
"category_code": "103693007",
"category_display": "Diagnostic procedure",
"code_system": "http://hl7.org/fhir/sid/icd-9-cm",
"code": "89.61",
"code_display": "Continuous blood gas monitoring",
"performed_date_time": "2026-05-13T09:15:00+07:00",
"performed_start": "2026-05-13T09:15:00+07:00",
"performed_end": "2026-05-13T09:25:00+07:00",
"reason_code": "J06.9",
"reason_display": "Acute upper respiratory infection",
"body_site_code": "302551006",
"body_site_display": "Entire thorax",
"note": "Pasien kooperatif, prosedur selesai tanpa komplikasi"
}
17. QuestionnaireResponse
POST /satusehat/questionnaireresponse
{
"questionnaire_response_id": "QR-0001",
"organization_id": "10000004",
"questionnaire_url": "https://fhir.kemkes.go.id/Questionnaire/Q0007",
"patient_id": "P03647103112",
"encounter_id": "ENC-0012345",
"author_id": "N10000001",
"author_name": "Dr. Andi",
"source_id": "P03647103112",
"source_name": "Budi Santoso",
"status": "completed",
"authored": "2026-05-13T09:00:00+07:00",
"items": [
{
"link_id": "1",
"text": "Apakah Anda merokok?",
"answer_boolean": false
},
{
"link_id": "2",
"text": "Berapa suhu tubuh Anda hari ini?",
"answer_quantity_value": 38.2,
"answer_quantity_unit": "Cel"
}
]
}
Note:
itemsremains an array because QuestionnaireResponse is intrinsically list-shaped — but each item is itself flat.
18. ServiceRequest
POST /satusehat/servicerequest
{
"organization_id": "10000004",
"patient_id": "P03647103112",
"patient_name": "Budi Santoso",
"encounter_id": "ENC-0012345",
"requester_id": "N10000001",
"requester_name": "Dr. Andi",
"performer_id": "N10000099",
"performer_name": "Lab Pathology Unit",
"status": "active",
"intent": "order",
"code": "58410-2",
"display": "Complete blood count panel",
"authored_on": "2026-05-13T09:05:00+07:00"
}
19. Specimen
POST /satusehat/specimen
{
"specimen_id": "SPC-3001",
"organization_id": "10000004",
"patient_id": "P03647103112",
"status": "available",
"type_system": "http://terminology.hl7.org/CodeSystem/v2-0487",
"type_code": "BLD",
"type_display": "Whole blood",
"received_date_time": "2026-05-13T09:20:00+07:00",
"collected_date_time": "2026-05-13T09:10:00+07:00",
"collector_id": "N10000001",
"collector_name": "Perawat Sari",
"collection_quantity_value": 5,
"collection_quantity_unit": "mL",
"collection_method_code": "129316008",
"collection_method_display": "Aspiration - action",
"body_site_code": "368208006",
"body_site_display": "Left upper arm",
"fasting_status_code": "F",
"fasting_status_display": "Fasting",
"processing_procedure_code": "9718006",
"processing_procedure_display": "Centrifugation",
"processing_time_datetime": "2026-05-13T09:25:00+07:00",
"conditions": ["refrigerated"],
"request_service_request_ids": ["SR-0001"]
}
20. Studies (DICOM upload)
The studies module today is a thin DICOM tarball uploader and does not
take a structured body. The recommended request is multipart/form-data:
POST /satusehat/dicom/studies/upload
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXyz
------WebKitFormBoundaryXyz
Content-Disposition: form-data; name="organization_id"
10000004
------WebKitFormBoundaryXyz
Content-Disposition: form-data; name="patient_id"
P03647103112
------WebKitFormBoundaryXyz
Content-Disposition: form-data; name="accession_number"
ACC-0001
------WebKitFormBoundaryXyz
Content-Disposition: form-data; name="file"; filename="study.tar.gz"
Content-Type: application/gzip
<binary DICOM tarball>
------WebKitFormBoundaryXyz--
If a JSON metadata-only endpoint is added later, the flat shape would be:
{
"organization_id": "10000004",
"patient_id": "P03647103112",
"accession_number": "ACC-0001",
"study_instance_uid": "1.2.840.113619.2.5.1762583153.1762583153.1762583153.1",
"modality": "CR",
"started": "2026-05-13T09:00:00+07:00",
"description": "Thorax PA"
}
Implementation notes
All 20 usecases now share the same internal pattern:
dto.go— pure flat struct withbinding:"required,..."tags only on scalar fields. Nocommon.ReferenceDTO/common.CodeableConceptDTOimports at the request boundary.mapper.go—MapRequestToFHIR(req)builds the nested FHIR R4 payload. Mapper is pure: no env reads, no I/O.repository.go— thin wrapper overSatuSehatClient.DoRequest. The oldhiddenCtxwrapper has been removed;context.Contextflows through unchanged for proper deadline / cancellation propagation.service.go—NewService(repo, orgID string)takes the default organisation identifier fromcfg.SatuSehat.OrgIDat boot. When a caller omitsorganization_idin the request body, the service fills it in.
The full audit trail is in docs/DEVLOG.md.
Field-shape cheat-sheet (nested → flat)
| Nested FHIR field | Flat replacement |
|---|---|
subject.reference = "Patient/X" |
patient_id = "X" (+ optional patient_name) |
encounter.reference = "Encounter/X" |
encounter_id = "X" (+ encounter_display) |
performer[].reference = "Practitioner/X" |
performer_id = "X" (+ performer_name) |
location[].location.reference = "Location/X" |
location_id = "X" (+ location_name) |
managingOrganization.reference = "Organization/X" |
managing_organization_id = "X" |
code.system / .code / .display |
code_system / code / code_display |
category.code / .display |
category_code / category_display |
valueQuantity.value / .unit / .system / .code |
value_quantity_value / _unit / _system / _code |
period.start / .end |
period_start / period_end |
dosage.route.code |
route_code / dosage_route_code |
When a list of references exists (e.g. result_observation_ids,
specimen_ids), keep it as a flat array of IDs — the mapper turns them
into ["Observation/{id}", …].