PR #301 · pramaan-io/pramaan-functions · merged 2026-06-10
A matter is not one court. But the old vocab pretended it was.
Story nine-eighty-eight — Matter data-model v3
One row, one vocab, one proceeding — the moment you add a second, you are stuck.
The fix — One matter, one frozen snapshot, one place for parties — the vocab lives with the proceeding it serves.
sequenceDiagram autonumber participant matter as Matter participant main as Main participant appeal as Appeal participant snapshot as Snapshot matter->>snapshot: freeze template_snapshot matter->>main: seed filing types + labels matter-->>appeal: seed its own forms + labels main->>main: Statement of Claim ✓ appeal->>appeal: Memorandum of Appeal ✓
| Method | in → out | what it does |
|---|---|---|
_build_template_snapshot | () → dict[str, Any] | freezes the template at matter-create so the snapshot never drifts |
_nest_template_vocab | (table, source_column) → None | nests filing types and labels under proceeding type so uniqueness is per proceeding |
_write_parties | async def _write_parties( session: AsyncSession, firm_id: UUID, matter: Matter, main_proceeding: Proceeding, """Write parties to matter_participants + firm labels on the Main proceeding.""" if not parties: our_count = 0 labels_by_name: dict | writes parties once to matter_participants and records firm labels on the Main proceeding |
_assign_lead_counsel | (session, firm_id, matter, user_id) → None | assigns the opener as lead_counsel and enforces the at-least-one lead invariant |
0090 migration that nests filing types and party labels under proceeding type, creates proceeding-scoped vocab tables, and repoints foreign keys.
| new method | signature |
|---|---|
_create_updated_at_trigger | (table_name) → None |
_drop_updated_at_trigger | (table_name) → None |
_drop_policies | (table) → None |
_enable_matter_rls | (table, matter_expression) → None |
_add_staff_with_grant_select_policy | (table) → None |
_disable_rls_for_drop | (table) → None |
_create_proceeding_vocab_table | (table, source_column, source_table) → None |
_nest_template_vocab | (table, source_column) → None |
upgrade | () → None |
_recreate_matter_vocab_table | (table, source_column, source_table) → None |
_unnest_template_vocab | (table, source_column) → None |
downgrade | () → None |
revision : strdown_revision : str | Sequence[str] | Nonebranch_labels : str | Sequence[str] | Nonedepends_on : str | Sequence[str] | Nonesrc/domains/matter/service.py+377 −183Matter create now writes the immutable template_snapshot, seeds matter_proceeding_types, sole-sources parties to matter_participants, and assigns the opener as lead_counsel.
| new method | signature |
|---|---|
_side_to_role_type | (role, our_index) → tuple[str, bool] |
_write_parties | async def _write_parties( session: AsyncSession, firm_id: UUID, matter: Matter, main_proceeding: Proceeding, """Write parties to matter_participants + firm labels on the Main proceeding.""" if not parties: our_count = 0 labels_by_name: dict |
_assign_lead_counsel | (session, firm_id, matter, user_id) → None |
_party_views | (session, matter) → list[MatterPartyView] |
_party_count | (session, firm_id, matter_id) → int |
_template_name | (matter) → str | None |
_matter_type_display | (matter) → str |
_list_item | (matter, client_name, party_count) → MatterListItem |
_party_counts | (session, firm_id, matter_ids) → dict[UUID, int] |
_main_proceeding | (session, firm_id, matter) → Proceeding |
_build_template_snapshot | () → dict[str, Any] |
_seed_matter_proceeding_types | (session, firm_id, matter, snapshot) → None |
New SQLAlchemy models ProceedingFilingType and ProceedingParticipantLabel scoped by proceeding_id, with the same RLS and updated_at trigger pattern as migration 0085.
| new class | signature |
|---|---|
ProceedingFilingType | (class) |
ProceedingParticipantLabel | (class) |
id : UUIDfirm_id : UUIDmatter_id : UUIDproceeding_id : UUIDsource_filing_type_id : UUID | Nonename : strdescription : str | Nonestatus : strdisplay_order : intsource_participant_label_id : UUID | NoneProceeding create now seeds its own proceeding_filing_types and proceeding_participant_labels from the snapshot's block for that proceeding type.
| new method | signature |
|---|---|
_seed_proceeding_vocab | (session, firm_id, matter, proceeding, proceeding_type) → None |
_filing_view | (row, filing_type) → FilingView |
src/domains/matter/entity.py+42 −91Matter entity gains template_snapshot jsonb and matter_frame_text, and the assignment gains team_role; type, parties, and matter_template_name columns are dropped.
matter_template_id : UUID | Nonematter_frame_text : str | Noneteam_role : strNew shared constants MatterPartySide (our/their/neutral) and MatterTeamRole (lead_counsel/member) that drive party role mapping and assignment team_role.
| new class | signature |
|---|---|
MatterPartySide | (class) |
MatterTeamRole | (class) |
matter_template_filing_types and matter_template_participant_labels now carry a NOT NULL proceeding_type_id foreign key, so template uniqueness nests per proceeding type.
proceeding_type_id : UUIDSnapshot DTO reshaped to nest filing types and party labels under each proceeding type, exposed as templateSnapshot in the matter snapshot view.
matter_template_id : UUID | Nonetemplate_name : str | NoneMatter DTO gains matter_frame_text to carry the human-readable matter frame on read.
matter_frame_text : str | None