Trayectorias: how Nammu renders the right procedural roadmap for each case
Once a case is classified and its documents are segmented, Trayectorias overlays the correct statutory roadmap for that specific jurisdiction and process type with actual evidence from the case. Here is how the templates, status inference, two-view split, and seven empty states all fit together.
Inventum P5
Engineering · Galictis-Legal
July 10, 2026
10 min read
Every legal process type has its own statutory sequence of stages, its own article citations, its own deadlines, its own “what happens if X does not occur.” A civil ordinario's audiencia preliminar has nothing in common procedurally with a violencia doméstica case's plazo de vigencia window. Trayectorias exists to render the correct statutory roadmap for this specific case's jurisdiction and process type — and then overlay it with actual evidence from this case's own documents to show where it really stands today. This is not a generic five-stage progress bar.
Two views over one model
The same CaseTimeline data structure feeds two deliberately distinct renderings. Keeping them separate prevents two genuinely different questions from being conflated.
What has actually happened
- Filtered through _reached() — only COMPLETED and IN_PROGRESS milestones
- Evidence-gated: a stage not yet proven in the documents simply does not appear
- Dates attached when extraction succeeded
- Never shows what hasn't happened — that would misrepresent the case
What the process generally looks like
- Full statutory template — all milestones including PENDING and UNKNOWN
- Available the moment process_type is classified, regardless of documents
- Dates and plazos intentionally omitted — they only mean something once reached
- Answers: what does this type of case look like, not where is this one
The data model
Three dataclasses in timeline_model.py carry everything a builder produces:
Milestone
One procedural stage. Carries a bilingual label, a symbol (see below), a phase, a status (completed / in_progress / pending / unknown), the governing article and law, an optional statutory deadline in business days, and free-text notes — which often carry safety-critical legal guidance, not just descriptions.
JurisdictionalHinge
Populated only for a bisagra (jurisdictional transfer) case. Carries origin and receiving jurisdiction, court names, and the transfer document's date and title.
CaseTimeline
The case-level wrapper: process_type, has_bisagra flag, phase labels, the hinge if any, and the full milestone list. Every builder returns exactly one of these — the UI layer never touches jurisdiction-specific logic directly.
Milestone symbols
Each milestone carries a symbol that communicates its structural role in the procedural sequence at a glance:
Standard milestone
A regular procedural stage
Pivot
A stage that changes the case's direction or track
Terminal
An end state — the case can stop here
Conditional
Only applies under specific circumstances
One builder per process type — not per jurisdiction
load_case_timeline() reads process_type from case_index.json and routes to exactly one build_*_timeline() function. This is process-type-level dispatch, not jurisdiction-level: a jurisdiction being fully classified does not guarantee every one of its process types has a template. Civil's monitorio, penal's procedimiento_abreviado and flagrancia, and contencioso's proceso_de_lesividad are all classified correctly but have no builder yet — they show a purpose-built “template missing” state, not a generic error.
Each builder is self-contained. It owns its own milestone list, article citations, and status-inference rules — grounded in a specific legal-domain review, cited in-code. There is no shared generic milestone structure that jurisdictions customize. A civil ordinario's 13 milestones and a familia violencia-doméstica case's 8 milestones do not share a single stage name or structure, because they are governed by different codes with different procedural clocks.
Phases — communicating jurisdictional change mid-process
MilestonePhase has four values. Which ones a builder uses says something legally meaningful about whether the case's jurisdiction can change mid-process.
| Phase | Meaning | Used by |
|---|---|---|
| SINGLE | Process never changes jurisdiction | Penal, laboral, familia (all 6), notarial, tránsito |
| PHASE1 | Pre-transfer stages (muted styling) — contencioso-administrativo origin | Civil ordinario builder only, bisagra path |
| BISAGRA | The jurisdictional transfer act itself (gold-highlighted, distinct node) | Civil ordinario builder only, bisagra path |
| PHASE2 | Post-transfer stages (active styling) — civil ordinario track that follows | Civil ordinario builder only, bisagra path |
build_ordinario_timeline() is the one builder that must handle all three possible shapes of a civil ordinario case. It picks between them explicitly: bisagra detected → emit both phases plus the gold-highlighted hinge node; non-civil jurisdiction with no bisagra yet → still show both phase templates so the mini-view roadmap is visible before a transfer; purely civil from the start → drop phase labels and stamp every milestone SINGLE.
How milestone status is inferred
Every builder inspects the pooled doc_kind set from document_index.json — the output of Smart Document Segmentation — and derives each milestone's status from which document types are present. The patterns range from simple to legally nuanced:
Simple presence check
Civil ordinario's notificación al demandado:
COMPLETED if "notificacion" in doc_kinds else UNKNOWN
Three-state waterfall with a meaningful middle state
Penal querella's admisibilidad:
COMPLETED if a court admission resolution exists
IN_PROGRESS if defects flagged and/or subsanación filed,
but no court ruling yet
PENDING if no signal at allThe IN_PROGRESS state is deliberate and legally meaningful: it distinguishes “the defect/cure cycle happened and we are waiting on the court” from “nothing has happened yet” — a real procedural distinction, not a progress-bar artifact.
Logically inferred from surrounding evidence
Civil sumario's admisibilidad and audiencia única:
Sumario has no dedicated admisibilidad filing type in the current taxonomy. Rather than leaving these stages permanently UNKNOWN, the builder infers them from what must legally be true given later evidence: “admisibilidad” is COMPLETED once a contestación or sentencia exists (a case cannot legally reach either without having cleared it), IN_PROGRESS once a demanda exists with nothing further, PENDING otherwise. This is explicitly commented in-code as a deliberate, legally-sound inference — not a guess.
Dual-terminal, computed window
Familia violencia doméstica:
This template has two TERMINAL milestones — sentencia_recursos and archivo_proceso — because a VD case can legitimately end either way: a final ruling with measures still in force, or an administrative archival after the protective measures simply expire unrenewed. The plazo_vigencia milestone (whether protective measures are still legally in force) has no doc_kind of its own — its status is derived entirely from whether a granting resolution exists and whether any terminal event has since fired.
Milestone copy carries legal-safety guidance
The notes field on a milestone is not decorative. In several builders, it carries direct, load-bearing legal-safety instructions embedded at exactly the point in the UI where they are operationally relevant.
From the familia violencia-doméstica builder:
Resolución de otorgamiento
"NUNCA exigir estándar probatorio elevado en esta etapa — acceso rápido por diseño." Never demand a high evidentiary standard here. Rapid access is the legal design intent of the law.
Señalamiento de audiencia
"PROHIBICIÓN DE CONCILIACIÓN (art. 9 CPF): relación desigual de poder se presume en violencia doméstica; NUNCA sugerir, preparar ni facilitar conciliación en esta audiencia." A hard legal prohibition, not a suggestion.
Control del plazo de vigencia
Flagged as "ALERTA DE SEVERIDAD ALTA, NO COSMÉTICA" — a missed protective-measures expiration is a physical-safety risk, not an ordinary procedural deadline.
Levantamiento anticipado
"NUNCA inducir, sugerir conveniencia, ni evaluar el mérito." The decision to request early lifting of protective measures is explicitly marked as the protected person's alone to make.
Date enrichment — best effort, never a status determinant
timeline_dates.py runs a separate later pass that tries to attach a real calendar date to milestones already marked reached by the status logic. It never determines whether a milestone happened — only when.
For each milestone ID, a lookup table says which doc_kind(s) to search; the first matching segment's anchor page is scanned with Spanish-date regexes covering both numeral dates (28/01/2026) and Costa Rica's canonical spelled-out judicial self-dating formula (“del veintiocho de enero de dos mil veintiséis”), including a word-to-number table for spelled-out days and years.
A date extraction failure just means a milestone shows as reached with no date — never a reason to downgrade or hide a status the doc_kind evidence already established.
Seven distinct empty states
Trayectorias deliberately does not collapse every “nothing visible” situation into one generic empty state. The reason a case has nothing to show is itself useful information, and conflating the reasons would mislead the lawyer about what is actually going on.
| State | Meaning | Why it is not just “empty” |
|---|---|---|
| Empty | Nothing processed yet at all | The true default baseline state. |
| Processing | Transient — the ~7s gap between the fast-path read and the background rewrite finishing | Purely momentary. A permanent-looking empty state here would look like a stall. |
| Quality gate | Manifest reports completed, but too few pages yielded usable text (below 80% extraction floor) | Without this, a low-OCR case would misleadingly look "still processing" forever. |
| Out of scope | Jurisdiction detected, but no loaded legal schema (agrario, constitucional) | Tells the lawyer why — a real known jurisdiction with no template yet, not a processing failure. |
| Unbuilt (pending) | Correctly classified as sumario — a real NCPC category — but no timeline builder exists yet | Distinct from OUT_OF_SCOPE: classification succeeded, only the template is missing. |
| Unrecognized | Classified as abreviado — a label the current civil schema mapping does not recognize | Recommends manual attorney verification explicitly, rather than pretending confidence. |
| Manual classification | Tier 3 of the waterfall — both carátula probe and doc_kind roll-up were ambiguous | Interactive: presents jurisdiction / process_type dropdowns and persists the lawyer's pick. |
| No milestones reached | Process type known, full template exists (visible in mini-view) — case is genuinely early-stage | Without this, a correctly-classified early-stage case rendered the same message as a genuine failure. |
Guiding principles
One statutory template per process type, hand-grounded in its own governing code
Never a shared generic progress structure reused across branches.
Two distinct views, deliberately not merged
What a process generally looks like (mini-view, always available once classified) vs. what this case has actually proven so far (Trayectorias tab, evidence-gated).
Every status is evidence-derived or logically inferred from other evidence — never guessed
Where no dedicated evidence exists yet (civil sumario), the fallback is an honest, legally-sound inference from surrounding milestones, explicitly reasoned about in-code, not a silent default.
A missing template is reported as a missing template
Distinct from an unclassifiable case, distinct from a genuinely early-stage case, distinct from a transient processing gap. Collapsing any of these into a generic empty would misinform the lawyer about what is actually true.
Legal-safety guidance lives at the exact milestone it applies to
Including honest confidence caveats on any citation that is not yet fully verified — the same DRAFT discipline carried from the schema level all the way down into individual UI copy.
Dates are a best-effort enrichment, never a status determinant
A date-extraction miss never downgrades a status the underlying doc_kind evidence already earned.
Want to see Trayectorias running on real expedientes?
We can walk through the timeline for your jurisdiction and process types — including which milestones exist, which statuses are live, and what the roadmap looks like for a case you know.
Request a demo →