Briefings & Toolbox Talks¶
Status: Specified — ready to build Build order: 11 of 13 — depends on Projects & Sites, Personnel & Attendance
Purpose¶
Briefings is the daily pre-work communication record for a site. It covers Toolbox Talks (TBTs), Point of Work Risk Assessments (POWRAs), plant familiarisation briefings, COSHH briefings, daily task briefings, and any other structured team communication requiring an attendee sign-off record.
A briefing event is opened from a template (or ad hoc), the supervisor runs through the content on site, and each attendee signs digitally. The completed record is locked and forms part of the auditable project record. Under UK H&S legislation, evidence that workers have been briefed on risks before commencing work is a mandatory compliance requirement.
Entities¶
BriefingTemplate¶
A reusable briefing script. Templates can be defined at company level (shared across all projects) or project level (project-specific content).
| Field | Type | Required | Notes |
|---|---|---|---|
| id | UUID | System | PK, generated |
| project_id | UUID | FK → projects.id — NULL = company-wide template | |
| title | VARCHAR(255) | ✓ | Template name |
| category | VARCHAR(50) | ✓ | See categories below |
| content_points | JSONB | ✓ | Ordered list of briefing points — see structure below |
| estimated_duration_mins | INTEGER | Typical delivery time | |
| status | VARCHAR(20) | ✓ | Default: active |
| custom_fields | JSONB | ✓ | Default {} |
| created_at | TIMESTAMPTZ | System | |
| updated_at | TIMESTAMPTZ | System | Trigger-maintained |
| created_by | UUID | System | FK → users.id |
| updated_by | UUID | System | |
| deleted_at | TIMESTAMPTZ |
CREATE INDEX idx_briefing_template_project
ON briefing_templates(project_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_briefing_template_category
ON briefing_templates(category) WHERE deleted_at IS NULL;
Briefing categories:
| Category | Typical use |
|---|---|
tbt |
Toolbox Talk — topic-based safety briefing (working at height, excavations, etc.) |
daily_briefing |
Morning pre-work briefing — tasks for the day, hazards, changes |
powra |
Point of Work Risk Assessment — immediate environment check before task |
plant_familiarisation |
Introduction to specific plant or equipment before first use |
coshh_briefing |
Hazardous substance briefing before use of a specific product |
induction_refresher |
Refresher of site induction content |
other |
General briefing |
Template status values:
| Status | Meaning |
|---|---|
active |
Available for use |
archived |
No longer available for new events |
BriefingEvent¶
A single instance of a briefing delivered on site on a given date.
| Field | Type | Required | Notes |
|---|---|---|---|
| id | UUID | System | PK, generated |
| site_id | UUID | ✓ | FK → sites.id |
| project_id | UUID | ✓ | Denormalised |
| template_id | UUID | FK → briefing_templates.id — NULL = ad hoc briefing | |
| title | VARCHAR(255) | ✓ | Auto-filled from template; editable for ad hoc |
| category | VARCHAR(50) | ✓ | Copied from template or set directly for ad hoc |
| content_points | JSONB | ✓ | Copied from template at creation; editable before sign-off |
| briefed_at | TIMESTAMPTZ | ✓ | Date and time the briefing was delivered |
| conducted_by | UUID | ✓ | FK → users.id — person who delivered the briefing |
| location | VARCHAR(255) | Where on site the briefing was held | |
| attendee_count | INTEGER | System | Denormalised; kept in sync with attendee records |
| status | VARCHAR(20) | ✓ | Default: draft |
| signed_off_at | TIMESTAMPTZ | Set when status transitions to completed |
|
| custom_fields | JSONB | ✓ | Default {} |
| created_at | TIMESTAMPTZ | System | |
| updated_at | TIMESTAMPTZ | System | Trigger-maintained |
| created_by | UUID | System | FK → users.id |
| updated_by | UUID | System | |
| deleted_at | TIMESTAMPTZ |
CREATE INDEX idx_briefing_event_site
ON briefing_events(site_id, briefed_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_briefing_event_project
ON briefing_events(project_id, briefed_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_briefing_event_status
ON briefing_events(site_id, status) WHERE deleted_at IS NULL;
CREATE INDEX idx_briefing_event_template
ON briefing_events(template_id) WHERE deleted_at IS NULL AND template_id IS NOT NULL;
Briefing event status values:
| Status | Meaning |
|---|---|
draft |
Created; attendees can still be added and content edited |
completed |
Locked — all attendees have signed or the conductor has closed it |
BriefingAttendee¶
One record per person who attended a briefing event. Attendance is confirmed by a digital signature.
| Field | Type | Required | Notes |
|---|---|---|---|
| id | UUID | System | PK, generated |
| event_id | UUID | ✓ | FK → briefing_events.id |
| project_id | UUID | ✓ | Denormalised |
| personnel_id | UUID | FK → personnel.id — NULL if not in the system | |
| name | VARCHAR(255) | ✓ | Display name; auto-filled from Personnel if linked |
| company | VARCHAR(255) | Employer or subcontractor name | |
| signature_key | VARCHAR(500) | S3 object key for signature image — NULL until signed | |
| signed_at | TIMESTAMPTZ | NULL until signed | |
| created_at | TIMESTAMPTZ | System | |
| deleted_at | TIMESTAMPTZ |
CREATE INDEX idx_briefing_attendee_event
ON briefing_attendees(event_id) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX idx_briefing_attendee_personnel
ON briefing_attendees(event_id, personnel_id) WHERE deleted_at IS NULL AND personnel_id IS NOT NULL;
JSONB Field Structures¶
content_points¶
[
{
"order": 1,
"point": "Working at height — all operatives must use fall arrest harnesses above 2m. Lanyards to be inspected before use."
},
{
"order": 2,
"point": "Excavation at Grid E–F — exclusion zone in place. Do not approach within 2m of the edge without a banksman present."
},
{
"order": 3,
"point": "Traffic management — site road restricted to one-way from 08:00 today. Refer to TMP drawing ref TM-026."
}
]
Business Rules¶
-
A
BriefingEventcan be created from a template or ad hoc. When created from a template,title,category, andcontent_pointsare copied from the template at creation time. Subsequent changes to the template do not affect existing events. -
content_pointson aBriefingEventcan be edited while status isdraft. Oncecompleted, the event is locked — content and attendees cannot be modified. -
Attendees can be added as linked Personnel records or as free-text names (for visitors or workers not yet in the system). A linked attendee can only appear once per event.
-
Transitioning to
completedsetssigned_off_atfrom the server timestamp. The conductor does not need to wait for all attendees to sign — they can close the event and it will record which attendees signed and which did not. -
Soft deleting an event soft-deletes all its attendee records in the same transaction.
-
Company-wide templates (
project_id = NULL) are read-only from within a project — they cannot be edited by Project Managers or Site Managers. Only Tenant Admins and Platform Admins can manage company-wide templates. -
briefed_atcan be set to any past time on the current day, or to an earlier date by Project Manager+ (for retrospective records). Site Managers can only record for today.
API Endpoints¶
Templates¶
| Method | Path | Description |
|---|---|---|
| GET | /projects/{projectId}/briefing-templates |
List project + company-wide templates |
| POST | /projects/{projectId}/briefing-templates |
Create project-level template |
| GET | /projects/{projectId}/briefing-templates/{id} |
Template detail |
| PATCH | /projects/{projectId}/briefing-templates/{id} |
Update template |
| DELETE | /projects/{projectId}/briefing-templates/{id} |
Soft delete |
| GET | /briefing-templates |
List company-wide templates (Tenant Admin+) |
| POST | /briefing-templates |
Create company-wide template (Tenant Admin+) |
| PATCH | /briefing-templates/{id} |
Update company-wide template (Tenant Admin+) |
Events¶
| Method | Path | Description |
|---|---|---|
| GET | /sites/{siteId}/briefings |
List events (paginated, filterable) |
| POST | /sites/{siteId}/briefings |
Create event |
| GET | /sites/{siteId}/briefings/{id} |
Full detail with attendees |
| PATCH | /sites/{siteId}/briefings/{id} |
Update event (draft only) |
| DELETE | /sites/{siteId}/briefings/{id} |
Soft delete |
| POST | /sites/{siteId}/briefings/{id}/complete |
Transition to completed |
| GET | /projects/{projectId}/briefings |
Events across all sites |
Attendees¶
| Method | Path | Description |
|---|---|---|
| GET | /sites/{siteId}/briefings/{id}/attendees |
List attendees |
| POST | /sites/{siteId}/briefings/{id}/attendees |
Add attendee |
| POST | /sites/{siteId}/briefings/{id}/attendees/{attendeeId}/sign |
Capture signature (returns presigned PUT URL) |
| DELETE | /sites/{siteId}/briefings/{id}/attendees/{attendeeId} |
Remove attendee (draft only) |
Request / Response Shapes¶
POST /sites/{siteId}/briefings¶
// Request — create from template
{
"template_id": "uuid-template",
"briefed_at": "2026-05-28T07:30:00Z",
"location": "Site compound — welfare unit",
"custom_fields": {}
}
// Request — ad hoc
{
"template_id": null,
"title": "Concrete pour — pad foundations P7–P12",
"category": "daily_briefing",
"content_points": [
{ "order": 1, "point": "Concrete delivery expected 09:00 — banksman to manage vehicle access." },
{ "order": 2, "point": "Vibration exposure — rotate operatives every 30 minutes on vibrating poker." }
],
"briefed_at": "2026-05-28T07:30:00Z",
"location": "Grid E–F compound",
"custom_fields": {}
}
// Response 201
{
"id": "uuid-event",
"site_id": "uuid-site",
"project_id": "uuid-project",
"template_id": "uuid-template",
"title": "Working at Height — General Briefing",
"category": "tbt",
"content_points": [...],
"briefed_at": "2026-05-28T07:30:00Z",
"conducted_by": "uuid-user",
"conducted_by_name": "James Fletcher",
"location": "Site compound — welfare unit",
"attendee_count": 0,
"status": "draft",
"custom_fields": {},
"created_at": "2026-05-28T07:28:00Z"
}
POST /sites/{siteId}/briefings/{id}/attendees¶
// Request — linked to Personnel
{
"personnel_id": "uuid-person",
"company": null
}
// Request — free text (not in system)
{
"personnel_id": null,
"name": "Dave Kowalski",
"company": "Hanson Groundworks Ltd"
}
// Response 201
{
"id": "uuid-attendee",
"event_id": "uuid-event",
"personnel_id": "uuid-person",
"name": "James Fletcher",
"company": "Construo Civil Ltd",
"signed_at": null,
"signature_key": null
}
GET /sites/{siteId}/briefings¶
Query params: category, status, from_date, to_date,
conducted_by, sort (default -briefed_at), limit, cursor
Permission Matrix¶
| Action | Platform Admin | Tenant Admin | Project Manager | Site Manager | Viewer |
|---|---|---|---|---|---|
| List / view events | ✓ | ✓ | ✓ assigned | ✓ own site | ✓ assigned |
| Create event | ✓ | ✓ | ✓ assigned | ✓ own site | ✗ |
| Edit draft event | ✓ | ✓ | ✓ assigned | ✓ own site | ✗ |
| Complete event | ✓ | ✓ | ✓ assigned | ✓ own site | ✗ |
| Delete event | ✓ | ✓ | ✓ assigned | ✗ | ✗ |
| Add / remove attendees | ✓ | ✓ | ✓ assigned | ✓ own site (draft only) | ✗ |
| Capture signature | ✓ | ✓ | ✓ assigned | ✓ own site | ✗ |
| View attendee list | ✓ | ✓ | ✓ assigned | ✓ own site | ✓ assigned |
| Manage company-wide templates | ✓ | ✓ | ✗ | ✗ | ✗ |
| Create project-level templates | ✓ | ✓ | ✓ assigned | ✗ | ✗ |
Notifications Triggered¶
None. The Briefings module does not fire automated notifications in V1. Alerts for missed daily briefings (e.g. "no briefing recorded for today") are a V2 feature.
Offline Sync Scope¶
| Data | Syncs to | Notes |
|---|---|---|
| BriefingTemplates | Site Manager | Company-wide + project templates |
| BriefingEvents | Site Manager | Last 90 days for assigned sites |
| BriefingAttendees | Site Manager | Last 90 days |
| Signature files | Not synced | Uploaded directly from device on reconnect |
Offline briefing flow: - Site Manager opens event on device, adds attendees (linked or free-text) - Attendees sign on device screen — signature stored in device storage - On reconnect: event, attendees, and signatures upload sequentially - If another device has already created an event with the same template on the same date, a duplicate is allowed (multiple briefings may run in parallel)
Cross-Module Dependencies¶
Upstream¶
| Module | Dependency |
|---|---|
| Projects & Sites | site_id, project_id |
| Personnel | personnel_id on attendees (optional link) |
Downstream¶
None. Briefings is a terminal module.
Tests Required¶
Templates¶
- Create company-wide template — Tenant Admin → 201
- Create project-level template — Project Manager → 201
- Project Manager cannot create company-wide template → 403
- List templates includes both company-wide and project-level
Events¶
- Create event from template — content_points copied → 201
- Create ad hoc event — no template → 201
- Create event with backdated
briefed_atby Site Manager to yesterday → 422 - Create event with backdated
briefed_atby Project Manager → 201 - Update draft event — content editable
- Update completed event → 422
- Complete event —
signed_off_atset - Delete event — soft deletes event and all attendees
- List events — filter by
category,status,from_date,to_date
Attendees¶
- Add linked Personnel attendee → 201
- Add free-text attendee → 201
- Add duplicate Personnel attendee to same event → 409
- Capture signature — returns presigned PUT URL
- Remove attendee — only on draft event
- Remove attendee from completed event → 422
Cross-tenant isolation (mandatory)¶
- GET event from another tenant's site → 404
- POST attendee to another tenant's event → 404
Permission boundaries¶
- Site Manager can create and complete events → 201
- Site Manager cannot delete events → 403
- Viewer cannot create events → 403
- Viewer can list events → 200
Default Tenant Labels¶
| Key | Default |
|---|---|
module.briefings |
Briefings |
field.briefing.title |
Briefing Title |
field.briefing.category |
Category |
field.briefing.briefed_at |
Briefing Date & Time |
field.briefing.conducted_by |
Conducted By |
field.briefing.content_points |
Briefing Points |
field.briefing.location |
Location |
status.briefing.draft |
Draft |
status.briefing.completed |
Completed |
category.briefing.tbt |
Toolbox Talk |
category.briefing.daily_briefing |
Daily Briefing |
category.briefing.powra |
POWRA |
category.briefing.plant_familiarisation |
Plant Familiarisation |
category.briefing.coshh_briefing |
COSHH Briefing |
category.briefing.induction_refresher |
Induction Refresher |
Foundation Dependencies¶
| Component | Location | Used for |
|---|---|---|
TenantMiddleware |
core/middleware.py |
Sets request.state.tenant |
get_db |
core/db.py |
Schema-scoped AsyncSession |
get_current_user |
core/security.py |
Auth |
require_permission |
core/security.py |
RBAC |
EntityMixin |
core/models.py |
Universal columns |
PaginatedResponse |
core/pagination.py |
List endpoint wrapper |
ConstruoError hierarchy |
core/exceptions.py |
Typed exceptions |
presigned_put_url |
core/storage.py |
Signature upload |
audit_entity_change trigger |
DB foundation | Applied to briefing_events, briefing_attendees |
factories.py |
tests/factories.py |
create_briefing_template, create_briefing_event, create_briefing_attendee |