@startuml db-relationships
' Schema source: Flyway V1–V69 (excl. V37, V43 — intentionally removed)
' Schema as of:  V69 (2026-05-27)
' ⚠ This is a versioned snapshot. Update when the schema changes significantly.
' Note: V69 adds columns only (persons.source_ref, tag.source_ref, document
' precision/attribution fields); no new FK relationships, so this diagram is unchanged.
' Note: V76 swaps persons.birth_year/death_year for birth_date/death_date +
' precision columns; columns only, no new FK relationships, diagram unchanged.
' Note: V77 adds the timeline_events table + two join tables (Timeline package below).
' Note: V78 swaps person_relationships.from_year/to_year for from_date/to_date +
' precision columns; columns only, no new FK relationships, diagram unchanged.

hide circle
skinparam linetype ortho

left to right direction

' ── Auth ──
package "Auth" {
  entity app_users
  entity user_groups
  entity app_users_groups
  entity group_permissions
  entity password_reset_tokens
  entity invite_tokens
  entity invite_token_group_ids
}

' ── Documents ──
package "Documents" {
  entity documents
  entity document_receivers
  entity document_tags
  entity document_versions
  entity document_annotations
  entity document_comments
  entity document_training_labels
  entity comment_mentions
}

' ── Persons ──
package "Persons" {
  entity persons
  entity person_name_aliases
  entity person_relationships
}

' ── Tags ──
package "Tags" {
  entity tag
}

' ── Transcription ──
package "Transcription" {
  entity transcription_blocks
  entity transcription_block_versions
  entity transcription_block_mentioned_persons
}

' ── OCR ──
package "OCR" {
  entity ocr_jobs
  entity ocr_job_documents
  entity ocr_training_runs
  entity sender_models
}

' ── Supporting ──
package "Supporting" {
  entity notifications
  entity audit_log
  entity geschichten
  entity geschichten_persons
  entity journey_items
}

' ── Timeline (Zeitstrahl) ──
package "Timeline" {
  entity timeline_events
  entity timeline_event_persons
  entity timeline_event_documents
}

' Auth relationships
app_users_groups }o--|| app_users : app_user_id
app_users_groups }o--|| user_groups : group_id
group_permissions }o--|| user_groups : group_id
password_reset_tokens }o--|| app_users : app_user_id
invite_tokens }o--|| app_users : created_by
invite_token_group_ids }o--|| invite_tokens : invite_token_id
invite_token_group_ids }o--|| user_groups : group_id

' Document relationships
documents }o--o| persons : sender_id (ON DELETE SET NULL)
document_receivers }o--|| documents : document_id
document_receivers }o--|| persons : person_id (ON DELETE CASCADE)
document_tags }o--|| documents : document_id
document_tags }o--|| tag : tag_id
document_versions }o--|| documents : document_id
document_versions }o--o| app_users : editor_id
document_annotations }o--|| documents : document_id
document_annotations }o--o| app_users : created_by
document_comments }o--|| documents : document_id
document_comments }o--o| document_annotations : annotation_id
document_comments }o--o| transcription_blocks : block_id
document_comments }o--o| app_users : author_id
document_comments }o--o| document_comments : parent_id
document_training_labels }o--|| documents : document_id
comment_mentions }o--|| document_comments : comment_id
comment_mentions }o--|| app_users : app_user_id

' Person relationships
person_name_aliases }o--|| persons : person_id
person_relationships }o--|| persons : person_id
person_relationships }o--|| persons : related_person_id

' Tag self-reference
tag }o--o| tag : parent_id

' Transcription relationships
transcription_blocks }o--|| document_annotations : annotation_id
transcription_blocks }o--|| documents : document_id
transcription_blocks }o--o| app_users : created_by
transcription_blocks }o--o| app_users : updated_by
transcription_block_versions }o--|| transcription_blocks : block_id
transcription_block_versions }o--o| app_users : changed_by
transcription_block_mentioned_persons }o--|| transcription_blocks : block_id
transcription_block_mentioned_persons }o--|| persons : person_id (ON DELETE CASCADE)

' OCR relationships
ocr_job_documents }o--|| ocr_jobs : job_id
ocr_job_documents }o--|| documents : document_id
ocr_training_runs }o--o| app_users : triggered_by
ocr_training_runs }o--o| persons : person_id
sender_models ||--|| persons : person_id

' Supporting relationships
notifications }o--|| app_users : recipient_id
audit_log }o--o| app_users : actor_id
audit_log }o--o| documents : document_id
geschichten }o--o| app_users : author_id
geschichten_persons }o--|| geschichten : geschichte_id
geschichten_persons }o--|| persons : person_id
journey_items }o--|| geschichten : geschichte_id (ON DELETE CASCADE)
journey_items }o--o| documents : document_id (ON DELETE SET NULL)
note right of journey_items : partial UNIQUE (geschichte_id, document_id)\nWHERE document_id IS NOT NULL (V74)
note right of geschichten : CHECK length(body) <= 4000\nfor type = JOURNEY (V75)

' Timeline relationships (V77)
timeline_event_persons }o--|| timeline_events : timeline_event_id (ON DELETE CASCADE)
timeline_event_persons }o--|| persons : person_id (ON DELETE CASCADE)
timeline_event_documents }o--|| timeline_events : timeline_event_id (ON DELETE CASCADE)
timeline_event_documents }o--|| documents : document_id (ON DELETE CASCADE)
note right of timeline_events : CHECK event_date_end non-null IFF RANGE\nCHECK date_precision <> 'UNKNOWN' (V77)

@enduml
