Section 06
Core Flows
The sequences that constitute the product, traced end to end.
Flow 1 — Adding a matter
sequenceDiagram
actor U as Advocate
participant App as Flutter app
participant API as API
participant Q as Queue
participant W as Worker
participant C as Court site
U->>App: select court, case type, number, year
App->>API: POST /matters
API->>API: validate, check duplicate
API-->>App: 201 matter, status Pending
API->>Q: enqueue first_poll, high priority
App-->>U: "Verifying with the court..."
Q->>W: dispatch
W->>C: search
C-->>W: result
W->>W: parse, normalise
alt case found
W->>API: matter to Active, store details
API-->>App: push — case confirmed
else not found
W->>API: matter to Invalid
API-->>App: push — check the details
end
Fig 6.1 — Matter onboarding
The first poll is synchronous from the user's point of view but asynchronous in the system. Confirming the case exists at the court before promising to watch it is what prevents a user believing they are covered when a typo means they are not.
Flow 2 — The daily poll
sequenceDiagram
participant S as Scheduler
participant Q as Queue
participant W as Worker
participant C as Court site
participant DB as Postgres
participant N as Notifier
S->>DB: select matters due, grouped by court
S->>Q: enqueue per court, honour concurrency
loop each matter
Q->>W: dispatch
W->>C: search
C-->>W: response
W->>W: parse, normalise
W->>DB: read last known state
alt changed
W->>DB: update matter, append hearing
W->>DB: insert CHANGE_EVENT
DB->>N: event
N->>N: apply user preferences
N-->>W: queued for delivery
else unchanged
W->>DB: record CONNECTOR_RUN only
end
end
Fig 6.2 — Nightly poll cycle
Polls run in an overnight window, per court, respecting each site's concurrency limit. Diffing against last known state is what makes notifications meaningful — the old system's push copy ("the cause list has been updated") is sent to every enabled user regardless of whether anything relevant to them changed, which trains users to ignore it.
Flow 3 — Cause list matching
flowchart TB
A[Court publishes
tomorrow's cause list] --> B[Connector fetches]
B --> C[Parse to entries:
item no, case no, court no, judge]
C --> C2{Publication kind}
C2 -->|evening before| C3[Tag CAUSELIST: tentative]
C2 -->|morning of| C4[Tag CAUSELIST: final]
C3 --> D[Store CAUSELIST]
C4 --> D
D --> E{Match against
tracked matters}
E -->|exact case number| F[High confidence]
E -->|fuzzy party name| G[Low confidence]
E -->|no match| H[Ignore]
F --> I[Link entry to matter]
G --> J[Link, flag for review]
I --> K[CHANGE_EVENT: listed tomorrow]
J --> K
K --> L[Per-user cause list PDF
one per court the user practises in]
L --> M[Upload to R2]
M --> N[WhatsApp + push + email]
Fig 6.3 — Cause list to notification
This is the flow users value most: "am I in court tomorrow, and where." The per-user PDF — a personalised cause list covering only their matters, with item numbers and court numbers — is the product's most tangible daily artifact, and the old system was right to build it.
Exact-versus-fuzzy matching matters. Some courts publish cause lists with party names but malformed case numbers. A fuzzy match must be visibly marked as such rather than presented with false confidence.
Tentative vs final — two reports, not one
Many courts publish a tentative list the evening before and a final (sometimes "revised") list the morning of the hearing day, with items renumbered, added, or struck. Treating these as the same event and sending one PDF hides the fact that item numbers can move overnight — which is exactly the kind of silent change this product exists to surface.
| Tentative report | Final report | |
|---|---|---|
| Sent when | Evening before, as soon as parsed | Morning of, as soon as the court's final list is parsed |
| Labelled | "Tentative — subject to change" | "Final listing for today" |
| If item numbers shifted | — | Before → after per matter, not a silent overwrite |
| If a court never publishes a final list | Tentative stands, still labelled tentative | n/a |
Both reports reuse the Flow 4 dedupe window and channel fallback — a final report is a distinct CHANGE_EVENT from the tentative one, not a resend of it, so both are delivered even though they concern the same hearing day.
Flow 4 — Notification fan-out
flowchart LR
A[CHANGE_EVENT] --> B{Event type}
B -->|next hearing date| C[Always notify]
B -->|listed tomorrow| C
B -->|new order| C
B -->|stage change| D{User opted in?}
B -->|cosmetic diff| E[Suppress]
C --> F[Build message]
D -->|yes| F
D -->|no| E
F --> G[Dedupe window]
G --> H{Channels}
H --> I[WhatsApp / Interakt]
H --> J[Push / FCM]
H --> K[Email]
I --> L[Record NOTIFICATION
with delivery status]
J --> L
K --> L
L --> M{Delivery failed?}
M -->|yes| N[Retry, then fall back
to next channel]
Fig 6.4 — Notification decision path
Two things the old system lacks: a dedupe window, so a court that republishes the same update three times produces one message; and delivery-status tracking with channel fallback, so a failed WhatsApp send escalates to push rather than vanishing. For a product whose promise is "you will not miss a hearing", an undelivered notification is a product failure, not a transport detail.
Flow 5 — AI order review
sequenceDiagram
participant W as Worker
participant R2 as Object store
participant AI as AI service
participant LLM as LLM
participant DB as Postgres
participant U as Advocate
W->>R2: store order PDF
W->>AI: review job, matter + document ref
AI->>R2: fetch PDF
AI->>AI: extract text, chunk
AI->>LLM: summarise, extract next date and directions
LLM-->>AI: structured JSON
AI->>AI: validate against schema
alt low confidence
AI->>DB: store, flag for human review
else confident
AI->>DB: store summary + extracted dates
DB->>U: "New order — summary ready"
end
Fig 6.5 — Order summarisation
The AI service is the one component of the old system built correctly — Python 3.12, FastAPI, LangGraph, real package structure. It should be ported largely intact.
One architectural note: it is pinned to langchain-openai. Long-context extraction from Indian legal orders — which are scanned, multilingual, and inconsistently formatted — is exactly the workload where models differ most. Evaluate Claude and Gemini against a real corpus before locking in, because swapping after prompts are tuned is expensive.
Never let AI output silently become a hearing date
An extracted date that is wrong is worse than no date at all, because the user acts on it. Model-derived dates must be visibly attributed as such, shown alongside the source text, and never overwrite a date parsed from the court's own structured fields. Low-confidence extractions go to human review, not to the user.
Flow 6 — Hearing date precedence
flowchart TB
A[Read: what is the
next hearing date?] --> B{next_hearing_override
set?}
B -->|yes| C[Show override
label: your date]
B -->|no| D{next_hearing
from court?}
D -->|yes| E[Show court date
label: from court]
D -->|no| F{next_hearing_suggested
from AI?}
F -->|yes| G[Show as suggestion only
label: AI, needs confirmation]
F -->|no| H[Show: no date yet]
I[Advocate types or confirms a date] --> J[Write next_hearing_override]
J --> C
K[Next poll returns next_hearing] --> L{Override already set
and disagrees?}
L -->|yes| M[Do not overwrite override.
Surface reconciliation prompt]
L -->|no| N[Write next_hearing normally]
Fig 6.6 — Precedence and the conflict case
Every screen that shows a next-hearing date — Today, Case detail, the display board, cause-list reports — reads through this same precedence, via the COALESCE index in the domain model. The label ("your date" / "from court" / "AI, needs confirmation") is not decoration: it is the difference between a fact and a suggestion, and it must travel with the date everywhere the date travels.
The conflict case is the one worth designing deliberately: an advocate sets an override, and the court's site later reports something different. Silently keeping the override risks acting on a date the court has moved on from; silently taking the court's value defeats the entire point of letting the advocate override it. The system does neither — it keeps showing the override, and raises a reconciliation prompt so the advocate makes the call with both values in front of them.
Flow 7 — Hearing report across multiple courts
An advocate practising across several courts or tribunals — see ORG_COURT — gets one report, not one per court.
sequenceDiagram
participant S as Scheduler
participant DB as Postgres
participant R as Report builder
participant R2 as Object store
participant N as Notifier
S->>DB: for this user, list all ORG_COURT
DB-->>S: Patna HC, Patna DRT, District eCourts...
S->>DB: fetch matters listed tomorrow, all courts, tentative + final as available
DB-->>S: rows grouped by court
S->>R: build one report
R->>R: group by court, then by item/court number
R->>R2: render PDF, upload
R2-->>N: link
N->>N: apply user's channel preferences
N-->>N: WhatsApp and/or email, per user setting
Fig 6.7 — One report, many courts
A firm covering five tribunals does not want five PDFs at five different times. Grouping by court within a single document is what makes the report usable in the twenty seconds someone actually reads it before leaving for the day.