Engineering challenges
Problems, decisions and what each one costs.
Each entry comes from the code. Where the project does not record why something was chosen, the entry says so instead of inventing a reason.
- 01
Multi-case PDFs
- Problem
- Teaching material often bundles several cases in one PDF. A single prompt that splits and extracts at once tends to merge or skip cases.
- Approach
- Detect case boundaries first, then extract each segment in its own call. Non-admin input is capped at 15,000 characters.
- Trade-off
- More model calls per document, in exchange for predictable results and bounded cost.
- 02
Who may touch this case?
- Problem
- Role checks cannot express ownership, shared editing, or soft-deleted records.
- Approach
- A declarative annotation with four access levels, enforced by an aspect that resolves the case before the method runs.
- Trade-off
- Spring AOP only intercepts proxied calls. The limitation is documented on the annotation and covered by aspect tests.
- 03
Hiding the answer
- Problem
- Students must not see the diagnosis before attempting a case, but must see it afterwards.
- Approach
- A reveal policy checks for an assignment submission or graded practice attempt on that case. Embeddings are stripped for everyone who is not staff.
- Trade-off
- The rule lives in one service, so every read path has to go through it. Unit tests cover the policy.
- 04
AI scores teachers can trust
- Problem
- Model output is a draft, not a grade.
- Approach
- A three-state lifecycle (submitted, AI evaluated, teacher evaluated), per-question rubric scores, and teacher overrides that recompute the total.
- Trade-off
- The confidence value is a heuristic over rubric scores and context availability. It is not a calibrated probability.
- 05
One gateway for AI calls
- Problem
- Three services each called the Anthropic API in their own way.
- Approach
- A shared client with prompt caching on the system block, token and cache logging, retry with exponential backoff, and one failure type mapped to 502.
- Trade-off
- Cache hits depend on prefix length and timing. The logs expose them; no measurements are published here.
- 06
Local embeddings
- Problem
- Retrieval should not require a second paid API.
- Approach
- Ollama serves nomic-embed-text locally, with Compose profiles for CPU, NVIDIA and AMD. Ranking is cosine similarity in memory over one case's chunks.
- Trade-off
- Simple and dependency-light, but unsuited to cross-corpus search. The project notes give no formal reason beyond local generation.
- 07
Moving to Keycloak
- Problem
- The project began with its own tokens and later adopted Keycloak.
- Approach
- The decoder tries the backend's own signing key first and falls back to the Keycloak issuer.
- Trade-off
- Two trust paths enlarge the attack surface. Removing the legacy path is a tracked follow-up.
- 08
Notifications off the request path
- Problem
- Sending push messages inline would slow requests and couple domain code to a third party.
- Approach
- Services publish events; async listeners send in batches of 100; a scheduled job reconciles receipts and prunes unregistered device tokens.
- Trade-off
- Events live in process memory, so a crash can lose sends that were not yet accepted by the push service.
- 09
Grades that do not depend on a model
- Problem
- Tumor grades should not rest on a language model's arithmetic.
- Approach
- Gleason/ISUP and WHO grades come from pure, unit-tested calculators. AI only suggests patterns, and each grading records its source.
- Trade-off
- More code than prompting the model, in return for reproducible results.