Files
duocthu/docs/00-project-overview.md
T

117 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 00 — Project overview
## Problem domain
Clinicians in Vietnam consult the **Dược thư Quốc gia Việt Nam 2018** (Vietnamese
National Drug Formulary), a ~1,668-page reference book. Part 2 of that book is
684 drug monographs, each split into up to 19 fixed sections (indications,
contraindications, precautions, dosage, interactions, ADRs, …).
Looking something up in the paper book is slow and the answer is section-shaped:
"what is the paediatric dose of paracetamol" is answered by one specific
subsection of one monograph, not by a summary of the drug. This system makes
that lookup conversational while keeping the answer bound to the book's own
text.
## Who the users are
Doctors and pharmacists. The prompts explicitly instruct the model to keep the
book's professional terminology and *not* simplify for a lay reader
(`apps/ai-service/rag/prompt.py`, rule 6). The UI is Vietnamese-only.
There is no authentication, so in the deployed system "user" means anyone who
can reach the public URL. See [16-security.md](16-security.md).
## What the system does
| Capability | Where |
|---|---|
| Understand a Vietnamese turn (possibly misspelled, abbreviated, multi-turn) into a structured frame | `rag/understanding.py` |
| Resolve drug identity against a 684-drug / 10,164-alias catalog, bounded before the LLM runs | `rag/routing.py` + `rag/understanding.py` |
| Retrieve a whole named monograph section deterministically by payload filter | `adapters/qdrant.py::find_by_section` |
| Reverse lookup: a condition/indication → drugs whose `chi_dinh` names it | `adapters/qdrant.py::find_by_indication` / `search_indication` |
| Two-drug interaction lookup across both monographs | `rag/agent.py::_interaction` |
| Ask a clarifying question instead of dumping every dose band | `rag/agent.py`, `rag/prompt.py` rule 7 |
| Restate retrieved evidence as structured, individually-cited claims | `rag/prompt.py` `ANSWER_SCHEMA` |
| Refuse a generation whose numbers or citations do not trace to the evidence | `rag/grounding.py` |
| Refuse a generation a second LLM pass judges unsupported by its cited block | `rag/answer.py::_verify_entailment` |
| Return printed-page + physical-page + bbox provenance per citation | `rag/answer.py::_indexed_citations` |
| Persist a retrieval trace and per-answer thumbs feedback | `adapters/postgres.py`, `migrations/` |
| As-you-type drug-name autocomplete with no model call | `rag/routing.py::complete` |
## What the system deliberately does not do
- **Does not answer from Part 1 or Part 3 of the book.** Only printed pages
991496 are ingested (`ingestion/segment/detector.py`,
`MONOGRAPH_PRINTED_PAGE_START/END`). Questions about the BSA appendix, IV
preparation tables, ATC index or the general chapters abstain.
- **Does not read numbers out of quarantined tables or 2-D formulas.** A
`VERIFY_PDF` decision returns a notice and the source page instead
(`rag/answer.py`, `rag/service.py::_decide`).
- **Does not rank or recommend.** `prompt.py` rule 10 forbids first-line /
treatment-of-choice framing; a condition→drug answer is a factual list.
- **Does not answer for non-human subjects.** A keyword scope check abstains on
veterinary phrasing (`rag/policy.py`).
- **Does not reverse-look-up "which drug *causes* X" or "which drug is
contraindicated in X".** Both are explicitly routed to an abstain
(`rag/agent.py`, `turn_type == "condition_relation"`).
- **Does not fall back to a raw source dump when a configured generator
fails.** It abstains with the specific failure reason.
- **Does not compute doses.** `rag/calculators.py` implements the book's DuBois
BSA formula but **no runtime code calls it** — see
[27-technical-debt.md](27-technical-debt.md).
## System boundary
```mermaid
flowchart TB
CLIN["Doctor / pharmacist<br/><i>Vietnamese, professional, no account</i>"]
SYS["<b>Dược Thư RAG</b><br/>Grounded Q&A over the 2018 formulary<br/>web + ai-service + ingestion"]
BR["AWS Bedrock<br/><i>Cohere embed-v4 · rerank-v3.5 · Converse</i>"]
LE["Let's Encrypt<br/><i>ACME via Caddy</i>"]
GH["GitHub Actions<br/><i>SSH deploy to EC2</i>"]
PDF[/"duoc-thu-quoc-gia-viet-nam-2018.pdf<br/>37 MB, committed in-repo"/]
CLIN -->|HTTPS chat| SYS
SYS -->|InvokeModel / Converse| BR
SYS <-->|certificate issuance| LE
GH -->|git reset + compose up --build| SYS
PDF -->|offline ingestion, already run| SYS
```
## Runtime components
| Component | State | Notes |
|---|---|---|
| `apps/ai-service` | **Implemented** | The whole RAG engine. ~9.2k lines Python. |
| `apps/web` | **Implemented** | Chat UI + BFF + rate limiting. |
| `ingestion` | **Implemented, already run** | ~8.4k lines. Corpus is loaded. |
| `packages/ui`, `shared-types`, `api-client`, `config` | **Implemented** | Shared React/TS. `api-client` is not imported by `web`'s live path (see [13](13-frontend-architecture.md)). |
| `apps/api-gateway`, `auth-service`, `user-service`, `chat-service` | **Not found** | `README.md` + a 4-line `package.json` each. No source. |
| `apps/mobile` | **Not found** | `README.md` + `.gitkeep`. |
## External dependencies
| Dependency | Required for | Failure behaviour |
|---|---|---|
| Qdrant | Every retrieval | Startup fails if the manifest cannot be read; a query-time failure propagates |
| AWS Bedrock — embed | Dense/indication fallback search only | `QueryEmbeddingUnavailable` → abstain (`rag/ports.py`) |
| AWS Bedrock — Converse | Understanding, generation, entailment | `AnswerGenerationUnavailable` → abstain with a specific reason |
| AWS Bedrock — rerank | Ordering on the similarity fallback | `RerankUnavailable` → original order kept (fail-open) |
| PostgreSQL | Traces, multi-turn history, feedback | Fail-open: answer still returned, trace id becomes an unpersisted UUID |
| Prometheus / Tempo / Grafana | Observability only | Absent = no metrics/traces; service answers unchanged |
Credentials for Bedrock come from the EC2 instance's IAM role — no AWS access
keys appear in any committed file (`infra/docker/docker-compose.prod.yml` header
comment; IAM policy documents in `infra/aws/iam/`).
## Deployment target
**Current:** a single EC2 host running Docker Compose behind Caddy at
`https://realvuxbaro.me`, deployed by `.github/workflows/deploy.yml` over SSH on
push to `master`.
**Target (written, never applied):** Helm chart + ArgoCD `Application` manifests
under `infra/helm/` and `infra/argocd/`, with three placeholder `TODO`s per
environment. See [21-kubernetes-and-argocd.md](21-kubernetes-and-argocd.md).