Fix migration workflow: upload as artifact instead of scp to practice EC2
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# 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
|
||||
99–1496 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).
|
||||
Reference in New Issue
Block a user