Core concepts

Data model

Organization, project, assembly, equipment, record, and two places that nest into themselves.

Contents

Five levels, and the shape is not arbitrary: it mirrors how a substation is actually walked. You are on a job, at a lineup, in front of a device, writing down a test.

Hierarchy
Organization                    created at registration; the tenant boundary
└── Project                    a job: customer, site, job number
    └── Assembly               a lineup, MCC, panel   ← nests into itself
        └── Equipment          a device                ← nests into itself
            └── Record         one test on one device, on one date

The two self-references#

An assembly may contain sub-assemblies via parent_assembly_id, and equipment may contain sub-equipment via parent_equipment_id. Both are optional and both exist because real gear does.

NestingWhat it is for
Sub-assemblyA section of a lineup, or an MCC inside a switchgear room, that has its own designation on the drawings but is not a separate job.
Sub-equipmentA device that is tested as part of another: a trip unit inside a breaker, a CT on a bus, a relay on a cubicle. It gets its own records without becoming a peer of the parent.
Listings return only the top level, with children inlined

GET /db/projects/{pid}/assemblies returns assemblies whose parent_assembly_id is null, each carrying its descendants in sub_assemblies and equipment. Walk the tree from the response; do not expect a flat list. When you do want everything flat, use GET /db/projects/{pid}/all-equipment.

Roll-up counts#

Projects and assemblies are returned with pre-computed status counts, so a dashboard does not have to fetch every record to draw a progress bar. The four are disjoint and sum to the equipment count at that level.

FieldCounts equipment whose most recent record…
pass_counthas status "pass"
fail_counthas status "fail"
incomplete_counthas any other status
untested_countdoes not exist. The device has no records at all

The counts are computed over all equipment beneath the node, including sub-equipment, while assembly_count on a project counts only top-level assemblies. That asymmetry is deliberate. One answers "how much work is left", the other "how many lineups am I looking at".

Tenancy#

Every query is scoped to the caller's organization by the session itself, not by a filter each endpoint remembers to apply. There is no parameter that widens it. A request for an id belonging to another organization returns 404, not 403. The resource does not exist as far as that caller is concerned, and saying otherwise would confirm the id.