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 reportFinal report
Sent whenEvening before, as soon as parsedMorning of, as soon as the court's final list is parsed
Labelled"Tentative — subject to change""Final listing for today"
If item numbers shiftedBefore → after per matter, not a silent overwrite
If a court never publishes a final listTentative stands, still labelled tentativen/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.