Case study · live system · July 2026
CaseLens
I built CaseLens to help support teams understand why a ticket received its priority, which evidence supports the result, and how to recover failed processing.
Live demo ↗Code ↗Architecture notes ↗
114backend tests12frontend tests2browser journeysliveon AWS, ca-central-1
The problem
Operators need evidence, processing history, and a safe recovery path.
A priority label is not enough when an operator must decide what to do next or explain what happened later.
- Trust
- The recommendation needs quoted evidence, policy, and a reason that a reviewer can inspect.
- Continuity
- Queue and provider failures need durable processing history, bounded retry behavior, and a safe recovery path.
- Control
- A reviewer must be able to correct the result without erasing the original decision or its trace.
Runtime path
PostgreSQL remains the source of truth.
Pick what happens to the ticket. Each station says what it does, and the ones that sit out fade.
- 01React reviewerisolated synthetic workspace
A reviewer opens a synthetic ticket in their own workspace.
The reviewer changes the urgency, say from MEDIUM to HIGH.
- 02Spring Boot APIvalidation and domain rules
Saves the ticket, its job and an outbox event in one transaction.
- 03Outbox + SQSdurable asynchronous delivery
The relay sends only IDs to SQS. The ticket text stays in PostgreSQL.
SQS delivers the same message twice. Queues can do that.
- 04Triage workerprovider validation or fallback
Reloads the ticket, sends redacted text to the model, and checks every quote and policy ID. App rules set the priority.
The first worker claims the job. The second claim fails its condition, so the job runs once.
The model quotes words that are not in the ticket. Validation rejects it, and deterministic rules triage the ticket.
The demo's synthetic provider timeout. The job is recorded as RETRYABLE_FAILURE.
- 05Review + recoverytrace, feedback, replay
The reviewer sees the quoted evidence, the policy and the reason.
The reviewer still gets a triage result, from the rules instead of the model.
A reviewer inspects the failure record and replays the job.
The original result and its trace stay. The correction is stored next to it.
Every station does its job once. The reviewer sees why the ticket got its urgency.
How it is built
Four choices that keep the result explainable.
Each one answers a way the system could let an operator down.
Durable delivery
PostgreSQL commits the triage request and outbox event in one transaction. A relay publishes the event to SQS, and workers use conditional job claims to handle duplicate delivery.
Constrained AI
The provider receives redacted ticket text and returns structured output. CaseLens validates quoted evidence and policy IDs, calculates priority with application rules, and falls back to deterministic triage after provider or validation failure.
Human correction
Reviewers can correct urgency without rewriting the original result. Evaluation metrics report category and urgency agreement, correction rate, latency, provider, and model version.
Inspectable failure
The demo records a synthetic provider timeout as a retryable failure. A reviewer can replay the work and inspect the failure and recovery records without using real customer data.
Verification
Tests cover normal processing, failure, and recovery.
Tests cover duplicate delivery, invalid model evidence, provider failure, operator replay, workspace isolation, queue integration, infrastructure validation, scans, and deployed browser flows.
Application
Java 21Spring BootReact 19TypeScriptTanStack QueryPostgreSQL 16Flyway
AWS
API GatewayLambda + SnapStartS3CloudFrontSQS + DLQEventBridgeCloudWatchSecrets Manager
Delivery
TerraformGitHub Actions OIDCDockerLocalStackTestcontainersTrivyprotected teardown
Engineering decisions
Decisions and trade-offs
What I chose, and what each choice costs.
- Modular monolith, with one deliberate asynchronous boundary.
I kept the first release in one backend so its domain flow, transaction boundaries, and recovery path stay easy to trace. SQS separates triage processing from the request without adding a fleet of services to deploy and operate.
- The outbox protects the database-to-queue handoff.
CaseLens commits the ticket, job, and outbox event in the same PostgreSQL transaction. The relay can retry publication until SQS accepts it, avoiding a stored ticket whose processing message was lost between two separate writes.
- Queue messages carry identifiers, not ticket content.
Workers receive workspace, case, and job identifiers, then reload the current record from PostgreSQL. This keeps the database authoritative, reduces data copied through SQS, and lets the worker apply current validation and redaction rules.
- Application rules own priority and fallback behavior.
The model proposes structured evidence, category, urgency, and actions. CaseLens validates that output and applies deterministic priority and fallback rules, keeping operational decisions reproducible when provider output is invalid or unavailable.
- The demo favors low idle cost over constant warmth.
Lambda and Neon suit an infrequently reviewed portfolio system, but they introduce cold starts and database connection limits. The deployment bounds connection use and accepts a slower first request rather than paying for always-on capacity.