Skip to content

Trades and units

Audience: domain, product, backend
Status: implemented
Owns: product + backend
Depends on: Domain entities, Lifecycle, API overview

  • Applications target a Trade and related Units from NOS documents.
  • Concurrent application policy: one active non-draft path at a time (see lifecycle and policies).
  • Centres review applications in the context of the selected trade/units.
  • Each trade has one active NOS document; superseded versions remain for admin history.
  • Platform admin UI treats the active NOS as the editable document (version switching is not exposed in v1 admin screens). Mutating levels/units/LOs and trade NOS top-level fields always resolve (or create) the active NOS.
  • Trade soft-archive (status: active | archived) backs bulk “delete” in the UI. Sector hard-delete is allowed only when the sector has no trades.
  • UI “Title” maps to API name. There is no tradeCode — use auto-generated slug as the stable identifier. Purpose and objectives live on qualification levels, not on the trade.
  • Platform admin can list the catalogue as one row per qualification level on each trade’s active NOS (GET /admin/levels) so a table can look like trades while unitCount is per level. Row id is the level id; navigation and bulk archive still use tradeId. GET /admin/trades remains one row per trade.
Entity Attributes (v1) Relationship
Sector id, slug (auto from name), name, description? Has many Trades
Trade id, sectorId, slug (auto from name), name, description?, status (active | archived) Has many NOS versions; one active
NosDocument version, title, issuedBy, issuedDate, status (active | superseded) Belongs to Trade; owns qualification levels
QualificationLevel level (positive int, commonly 1–3), slug, purpose, objectives[], requirements[] Under active/superseded NOS; owns Units
Unit referenceNumber, title, isMandatory, credits/GLH, structure (LO → PC tree), … Under a qualification level

Learning outcomes are not a separate table — they live in Unit.structure.learningObjectives[].

Catalogue (application draft — authenticated, not /admin):

Method Path Use
GET /sectors Sector picker (unpaginated); each row includes tradeCount (active trades only)
GET /sectors/{id}/trades Trade picker (active trades only)
GET /trades/{id} Trade + active NOS tree (levels → units); 404 if archived or no active NOS
GET /trades/{id}/units Flat unit list for POST /applications (?level= optional)
GET /trades/{id}/evidence-types Union of unit assessmentMethods for Evidence Vault

Admin (platform staff — platform.catalogue.read / .write):

Method Path Use
GET /admin/sectors Paginated list (cursor, limit, q, sort name|slug, order); each row includes tradeCount (active + archived)
POST /admin/sectors Create (name, description?); slug auto-generated
PATCH /admin/sectors/{id} Update name/description (rename regenerates slug)
DELETE /admin/sectors/{id} Hard-delete; 409 catalogue.sector_has_trades if any trades exist
PATCH /admin/sectors/bulk { ids, action: "delete" } — all-or-nothing; same block if any have trades
GET /admin/trades Paginated enriched list (sector, levelCount, unitCount; filter sectorId, q, includeArchived)
GET /admin/levels Paginated flattened list: one row per qualification level on each trade’s active NOS (tradeId, trade name/slug/status/sector, level, per-level unitCount). id is the level id — not for bulk archive. Filter sectorId, level, q, includeArchived
POST /admin/trades Create (name, sectorId, description?, levelCount positive int) → trade + active NOS v1 + stub levels
GET /admin/trades/{id} Admin detail; active NOS optional
PATCH /admin/trades/{id} Trade fields and/or active NOS top-level (nosTitle, issuedBy, issuedDate)
PATCH /admin/trades/bulk { ids, action: "archive" } — soft-delete
GET/POST /admin/trades/{tradeId}/levels List / add level on active NOS (ensureActiveNos on create)
PATCH/DELETE /admin/levels/{id} Update / delete level; 409 catalogue.level_in_use if any unit has ApplicationUnit refs
GET/POST /admin/levels/{levelId}/units List / create unit (full unit body incl. LO structure)
GET/PATCH/DELETE /admin/units/{id} Unit detail / patch / delete; 409 catalogue.unit_in_use if referenced
POST /admin/units/{id}/learning-outcomes Append LO to structure
PATCH/DELETE /admin/units/{id}/learning-outcomes/{code} Replace / remove LO; 422 if removing the last LO
POST /admin/nos-documents Bulk NOS import (supersedes prior active; empty stub at same version may be replaced)
GET /admin/nos-documents/{id} Any NOS version with full tree

POST /admin/sectors/{id}/trades remains as a deprecated alias of POST /admin/trades (sector from path). Prefer the global create path.

See openapi/cap.yaml / Scalar — CAP REST.