Central Verification Dashboard
This commit is contained in:
169
docs/er-diagram.md
Normal file
169
docs/er-diagram.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Central DB — ER diagram
|
||||
|
||||
Schema for the `central/` verification dashboard (Postgres). Source of truth:
|
||||
[`central/db/init.sql`](../db/init.sql) (re-run idempotently on every server boot).
|
||||
This diagram was generated from the **live** database and verified against it.
|
||||
|
||||
> Render: GitHub renders the Mermaid block below directly; in VS Code use the
|
||||
> *Markdown Preview Mermaid* extension (or any Mermaid viewer).
|
||||
|
||||
## Hierarchy at a glance
|
||||
|
||||
```
|
||||
clients ──1:N──▶ projects ──1:N──▶ videos ──1:N──▶ annotations
|
||||
├──1:N──▶ video_remarks (remark thread)
|
||||
└──1:N──▶ video_events (claim/push log)
|
||||
|
||||
users — provisioned workers/admins (token auth)
|
||||
audit_events — org-level action log
|
||||
(both link to the hierarchy only "softly", by username string — no FK)
|
||||
```
|
||||
|
||||
## Entity–relationship diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
clients ||--o{ projects : "owns"
|
||||
projects ||--o{ videos : "contains"
|
||||
videos ||--o{ annotations : "has"
|
||||
videos ||--o{ video_remarks : "thread"
|
||||
videos ||--o{ video_events : "logs"
|
||||
|
||||
clients {
|
||||
int id PK
|
||||
text name UK "unique client name"
|
||||
timestamptz created_at
|
||||
}
|
||||
|
||||
projects {
|
||||
int id PK
|
||||
int client_id FK "→ clients.id (CASCADE); nullable"
|
||||
text name "UK(client_id, name)"
|
||||
text source_path "NAS export path only — host lives in NFS_HOST env"
|
||||
text source_kind "'local' | 'nfs'"
|
||||
timestamptz created_at
|
||||
}
|
||||
|
||||
videos {
|
||||
int id PK "= serverVideoId on the desktop"
|
||||
int project_id FK "→ projects.id (CASCADE)"
|
||||
text file_name
|
||||
text rel_path "UK(project_id, rel_path); keeps subfolders"
|
||||
bool has_json "sibling export-JSON exists"
|
||||
int width
|
||||
int height
|
||||
float8 fps
|
||||
int frame_count
|
||||
int annotation_count
|
||||
bigint annotation_time_ms
|
||||
text primary_annotator
|
||||
text status "pending | annotated | verified"
|
||||
timestamptz annotated_at "max(annotation.created_at)"
|
||||
jsonb raw_json "full imported export doc (Phase-3 pull)"
|
||||
timestamptz ingested_at
|
||||
text claimed_by "claim dimension — orthogonal to status"
|
||||
timestamptz claimed_at
|
||||
timestamptz lease_expires_at
|
||||
text completed_by "set on push (verify pass)"
|
||||
timestamptz completed_at
|
||||
bigint verify_time_ms
|
||||
}
|
||||
|
||||
annotations {
|
||||
int id PK
|
||||
int video_id FK "→ videos.id (CASCADE)"
|
||||
int frame_number "0-indexed"
|
||||
text label
|
||||
text side
|
||||
text shape_type "bbox | polygon"
|
||||
jsonb vertices
|
||||
text annotated_by "username (soft link to users)"
|
||||
text review_status "default 'none'"
|
||||
text remark "per-annotation note (NOT the thread)"
|
||||
text subclass
|
||||
text created_at
|
||||
}
|
||||
|
||||
video_remarks {
|
||||
int id PK
|
||||
int video_id FK "→ videos.id (CASCADE)"
|
||||
text username "author (soft link to users)"
|
||||
text body
|
||||
timestamptz created_at
|
||||
}
|
||||
|
||||
video_events {
|
||||
int id PK
|
||||
int video_id FK "→ videos.id (CASCADE)"
|
||||
text username
|
||||
text event "claim | release | push | auto_release | reopen"
|
||||
timestamptz at
|
||||
jsonb meta
|
||||
}
|
||||
|
||||
users {
|
||||
int id PK
|
||||
text username UK
|
||||
text display_name
|
||||
text token_hash "SHA-256 of issued token (never clear)"
|
||||
text role "worker | admin"
|
||||
bool active
|
||||
timestamptz created_at
|
||||
}
|
||||
|
||||
audit_events {
|
||||
int id PK
|
||||
timestamptz at
|
||||
text username "actor"
|
||||
text action "client_created | project_created | project_source_changed | synced | user_created | user_token_reset"
|
||||
jsonb detail
|
||||
}
|
||||
```
|
||||
|
||||
## Relationships
|
||||
|
||||
| Parent | Child | Cardinality | On delete | Constraint |
|
||||
|---|---|---|---|---|
|
||||
| `clients.id` | `projects.client_id` | 1 : N | CASCADE | FK |
|
||||
| `projects.id` | `videos.project_id` | 1 : N | CASCADE | FK |
|
||||
| `videos.id` | `annotations.video_id` | 1 : N | CASCADE | FK |
|
||||
| `videos.id` | `video_remarks.video_id` | 1 : N | CASCADE | FK |
|
||||
| `videos.id` | `video_events.video_id` | 1 : N | CASCADE | FK |
|
||||
|
||||
**Soft links (by `username` string — no FK):** `users.username` is referenced as text by
|
||||
`annotations.annotated_by`, `videos.claimed_by` / `completed_by` / `primary_annotator`,
|
||||
`video_remarks.username`, `video_events.username`, and `audit_events.username`. These are
|
||||
intentionally not foreign keys so historical attribution survives a user being removed.
|
||||
|
||||
## Unique constraints & key indexes
|
||||
|
||||
- **Unique:** `clients(name)`, `projects(client_id, name)`, `videos(project_id, rel_path)`, `users(username)`.
|
||||
- **Indexes:** `idx_videos_project`, `idx_videos_status(project_id,status)`, `idx_videos_claim(project_id,claimed_by,lease_expires_at)`,
|
||||
`idx_annotations_video`, `idx_annotations_user`, `idx_users_token`, `idx_video_remarks_video(video_id,created_at)`,
|
||||
`idx_events_video`, `idx_events_at`, `idx_audit_at`, `idx_projects_client`.
|
||||
|
||||
## Notes
|
||||
|
||||
- **Two orthogonal dimensions on `videos`:** annotation `status` (`pending`/`annotated`/`verified`)
|
||||
vs. the claim/lease columns (`claimed_by` + `lease_expires_at`). Claimable = `status IN ('pending','annotated')`
|
||||
and unclaimed-or-lease-expired; push → `verified` + `completed_by`/`verify_time_ms`.
|
||||
- **A range annotation** in the export doc expands to **1–2 rows** here (start frame, and end frame if present).
|
||||
- The NAS host/IP is never persisted — `projects.source_path` holds only the export path; the host lives in the `NFS_HOST` server env.
|
||||
- Everything cascades from `clients`: deleting a client removes its projects → videos → annotations/remarks/events.
|
||||
|
||||
---
|
||||
|
||||
### Regenerate / verify against the live DB
|
||||
|
||||
```bash
|
||||
# columns
|
||||
docker exec central-db-1 psql -U central -d central -c "\d+ videos"
|
||||
# foreign keys
|
||||
docker exec central-db-1 psql -U central -d central -Atc "
|
||||
SELECT tc.table_name, kcu.column_name, ccu.table_name, rc.delete_rule
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu USING (constraint_name)
|
||||
JOIN information_schema.constraint_column_usage ccu USING (constraint_name)
|
||||
JOIN information_schema.referential_constraints rc USING (constraint_name)
|
||||
WHERE tc.constraint_type='FOREIGN KEY';"
|
||||
```
|
||||
Reference in New Issue
Block a user