System Diagram

The DEOH data model, mapped — 46 models, 125 relationships

🔍 For serious exploration, open the Interactive Diagram Viewer — pan, mouse-wheel zoom, fit-to-screen, full-screen, per-module tabs, the full 125-relationship ERD, and a focus mode that draws one model and its neighbours at a chosen depth. Person (32 links), Site (15) and OperatingArea (10) are context columns that fan out to nearly everything, so the viewer hides those by default — a toggle brings them back. The diagrams below are the readable, static summary of the principal links only.


Architecture Overview

Ten areas mirror the sidebar menu. Every arrow means "one row of the left model is referenced by many rows of the right model".

flowchart LR
    subgraph REF["📚 Reference"]
        Authority --> SourceDocument --> ExposureStandard
    end

    subgraph HAZ["☣️ Hazards & sources"]
        HazardousSubstance
        MaterialProduct --> ProductConstituent
        ExposureSource --> ExposureSourceSubstance
    end

    subgraph SEG["👥 Exposure groups & risk"]
        SimilarExposureGroup --> SegMember
        SimilarExposureGroup --> SegTask
        HygieneRiskAssessment --> RiskAssessmentControl --> Control
    end

    subgraph RPE["😷 Respiratory protection"]
        RespiratoryProtectionProgram --> RespiratorType --> FitTest
    end

    subgraph SMP["🧪 Sampling & field work"]
        SamplingProgram --> SamplingPlan --> SamplingEvent --> Sample
        Sample --> SampleTaskInterval
        SamplingMethod
        SamplingEquipment --> EquipmentCalibration
    end

    subgraph LAB["📦 Custody & laboratory"]
        ChainOfCustody --> ChainOfCustodySample
        LaboratoryResult
    end

    subgraph ASM["📈 Exposure assessment"]
        StatisticalExposureAssessment --> ExposureAssessment
    end

    subgraph RSP["🚨 Exceedance & response"]
        ExposureExceedance --> CorrectiveAction --> ControlVerification
        HygieneInvestigation
        ComplaintObservation
    end

    subgraph WRK["🧑‍🏭 Worker health & training"]
        WorkerNotification
        HealthSurveillanceReferral
        TrainingRequirement --> TrainingCompletion
    end

    HazardousSubstance --> ExposureStandard
    HazardousSubstance --> ExposureSourceSubstance
    HazardousSubstance --> SamplingPlan
    MaterialProduct --> ExposureSource
    ExposureSource --> SegTask
    SimilarExposureGroup --> SamplingPlan
    SimilarExposureGroup --> HygieneRiskAssessment
    SimilarExposureGroup --> StatisticalExposureAssessment
    SamplingMethod --> SamplingPlan
    SamplingEquipment --> Sample
    RespiratorType --> Sample
    Sample --> ChainOfCustodySample
    Sample --> LaboratoryResult
    SamplingEvent --> ChainOfCustody
    LaboratoryResult --> ExposureAssessment
    ExposureStandard --> ExposureAssessment
    ExposureAssessment --> ExposureExceedance
    ExposureAssessment --> WorkerNotification
    HygieneInvestigation --> ExposureExceedance
    HygieneInvestigation --> CorrectiveAction
    ComplaintObservation --> HygieneInvestigation
    Control --> ControlVerification

(ComplaintObservation → HygieneInvestigation is drawn in the causal direction; the foreign key sits on ComplaintObservation pointing at the investigation it fed.)


The Core Chain

Measurement on the left, consequence on the right. This is the diagram the application exists to make true.

erDiagram
    SimilarExposureGroup ||--o{ SamplingPlan : "seg_id"
    SamplingProgram ||--o{ SamplingPlan : "sampling_program_id"
    SamplingMethod ||--o{ SamplingPlan : "sampling_method_id"
    SamplingPlan ||--o{ SamplingEvent : "sampling_plan_id"
    SamplingEvent ||--o{ Sample : "sampling_event_id"
    Sample ||--o{ SampleTaskInterval : "sample_id"
    SamplingEvent ||--o{ ChainOfCustody : "sampling_event_id"
    ChainOfCustody ||--o{ ChainOfCustodySample : "chain_of_custody_id"
    Sample ||--o{ ChainOfCustodySample : "sample_id"
    Sample ||--o{ LaboratoryResult : "sample_id"
    LaboratoryResult ||--o{ ExposureAssessment : "laboratory_result_id"
    ExposureStandard ||--o{ ExposureAssessment : "exposure_standard_id"
    StatisticalExposureAssessment ||--o{ ExposureAssessment : "statistical_group_result_id"
    ExposureAssessment ||--o{ ExposureExceedance : "exposure_assessment_id"
    HygieneInvestigation ||--o{ ExposureExceedance : "investigation_id"
    ExposureExceedance ||--o{ CorrectiveAction : "exceedance_id"
    CorrectiveAction ||--o{ ControlVerification : "corrective_action_id"
    Control ||--o{ ControlVerification : "control_id"

Note that Sample reaches ExposureAssessment two ways: directly (sample_id) and through the laboratory result. The direct link is what keeps a direct-reading measurement — the post-blast gas peak in the demo — assessable even though no filter was ever sent to a laboratory.


Hazards, Sources and Exposure Groups

What is emitted, from where, and who is standing in it.

erDiagram
    Site ||--o{ OperatingArea : "site_id"
    OperatingArea ||--o{ OperatingArea : "parent_area_id"
    Site ||--o{ MaterialProduct : "site_id"
    MaterialProduct ||--o{ ProductConstituent : "material_product_id"
    HazardousSubstance ||--o{ ProductConstituent : "substance_id"
    Site ||--o{ ExposureSource : "site_id"
    OperatingArea ||--o{ ExposureSource : "operating_area_id"
    MaterialProduct ||--o{ ExposureSource : "material_product_id"
    ExposureSource ||--o{ ExposureSourceSubstance : "exposure_source_id"
    HazardousSubstance ||--o{ ExposureSourceSubstance : "substance_id"
    Site ||--o{ SimilarExposureGroup : "site_id"
    WorkRole ||--o{ SimilarExposureGroup : "primary_work_role_id"
    SimilarExposureGroup ||--o{ SegMember : "seg_id"
    SimilarExposureGroup ||--o{ SegTask : "seg_id"
    ExposureSource ||--o{ SegTask : "exposure_source_id"

SegTask is the join that matters: it links a group's tasks to the specific sources those tasks put them near, which is what makes a sampling plan defensible rather than arbitrary.


Standards, Risk and Controls

erDiagram
    Authority ||--o{ SourceDocument : "authority_id"
    SourceDocument ||--o{ ExposureStandard : "source_document_id"
    HazardousSubstance ||--o{ ExposureStandard : "substance_id"
    ExposureStandard ||--o{ StatisticalExposureAssessment : "exposure_standard_id"
    SimilarExposureGroup ||--o{ HygieneRiskAssessment : "seg_id"
    HazardousSubstance ||--o{ HygieneRiskAssessment : "substance_id"
    ExposureSource ||--o{ HygieneRiskAssessment : "exposure_source_id"
    HygieneRiskAssessment ||--o{ RiskAssessmentControl : "risk_assessment_id"
    Control ||--o{ RiskAssessmentControl : "control_id"
    HygieneRiskAssessment ||--o{ CorrectiveAction : "risk_assessment_id"
    Control ||--o{ CorrectiveAction : "control_id"

An exposure standard is never a bare number: it hangs off the document that published it, which hangs off the authority that issued it, and it carries its own effective dates.


Equipment, Respiratory Protection and the Field Record

erDiagram
    Organisation ||--o{ SamplingEquipment : "organisation_id"
    SamplingEquipment ||--o{ EquipmentCalibration : "equipment_id"
    SamplingEquipment ||--o{ Sample : "pump_equipment_id"
    SamplingEquipment ||--o{ Sample : "calibrator_equipment_id"
    Site ||--o{ RespiratoryProtectionProgram : "site_id"
    RespiratoryProtectionProgram ||--o{ RespiratorType : "program_id"
    RespiratorType ||--o{ FitTest : "respirator_type_id"
    Person ||--o{ FitTest : "person_id"
    RespiratorType ||--o{ Sample : "rpe_type_id"
    Person ||--o{ Sample : "person_id"
    SimilarExposureGroup ||--o{ Sample : "seg_id"

Two edges run from SamplingEquipment into Sample — the pump that drew the air and the calibrator that checked its flow. Both are recorded because the validity of the result depends on both.


Response, Notification and Health Surveillance

erDiagram
    ExposureAssessment ||--o{ ExposureExceedance : "exposure_assessment_id"
    HygieneInvestigation ||--o{ CorrectiveAction : "investigation_id"
    HygieneInvestigation ||--o{ ComplaintObservation : "investigation_id"
    ExposureSource ||--o{ ComplaintObservation : "exposure_source_id"
    ExposureAssessment ||--o{ WorkerNotification : "exposure_assessment_id"
    ExposureExceedance ||--o{ WorkerNotification : "exceedance_id"
    Person ||--o{ WorkerNotification : "person_id"
    Person ||--o{ HealthSurveillanceReferral : "person_id"
    HazardousSubstance ||--o{ HealthSurveillanceReferral : "substance_id"
    TrainingRequirement ||--o{ TrainingCompletion : "training_requirement_id"
    Person ||--o{ TrainingCompletion : "person_id"

HealthSurveillanceReferral connects to a person and a substance and stops there. There is no edge into any clinical record, because there is no clinical record in this system.


Reading the Diagrams

  • A ||--o{ B means one row of A is referenced by many rows of B (the label is the foreign-key column on B).
  • These static diagrams show principal links only — the context columns present on nearly every model (*_person_id, site_id, operating_area_id) are omitted for readability. The interactive viewer's Full ERD shows all 125, with the noise toggleable.
  • Person has 32 of the 125 relationships because the model records who at every step: samples, calibrates, assesses, approves, verifies, investigates, owns, delivers, is exposed. That accountability trail is the substance of hygiene evidence, not incidental metadata.
  • Sample (4 outbound links) and ExposureAssessment (2) are narrow on paper but sit at the waist of the chain — the viewer's Focus mode defaults to Sample.
  • No foreign key was dropped in translation. The pack has no true reference cycles; models were only reordered so every ref points at an already-defined table (HazardousSubstance before ExposureStandard, SamplingMethod before SamplingPlan, StatisticalExposureAssessment before ExposureAssessment, HygieneInvestigation before ExposureExceedance).
  • The pack's AuditLog model was dropped — the framework provides audit history — and DocumentEvidence keeps a deliberately polymorphic entity_type / entity_id pair rather than a foreign key, so it can attach to any record.

Diagrams are derived from the generated schema metadata (apps/deoh/generated/json/deoh_relationship_metadata.json). If the DSL schema changes, regenerate the metadata (python -m codegen.cli all deoh) and update this page and the viewer's embedded data.