Repository navigation
v0.1.0
Walking Skeleton Contract Clause Risk Reviewer
1. Architectural Summary
Contract Clause Risk Reviewer คือระบบ AI-Grounded RAG สำหรับวิเคราะห์ความเสี่ยงของข้อสัญญา (clause-level risk review) ใน pattern Intake → Review → Analysis → Generation ผู้ใช้อัปโหลดสัญญา (.pdf / .docx / .txt) → ระบบ parse → segment เป็น clause → classify ประเภท → match กับ playbook ผ่าน hybrid retrieval (pgvector + BM25) → score ความเสี่ยง → grounding judge → สรุปเป็น ContractReport พร้อม citation
ใน v0.0.1 นี้ walking skeleton ทำงานได้จริงแบบ end-to-end รวมถึง:
- Pydantic schemas ครบ (clause, report, taxonomy, playbook, eval)
- FastAPI pipeline
POST /contracts/reviewต่อ Gemini API + Postgres/pgvector จริง - Auth (Google OAuth + JWT), DB (Alembic migrations), Redis-backed repos
- Frontend React 19 ต่อ backend จริง (ไม่มี mock data เหลือ)
- 268 unit/integration tests ผ่านหมด + 11 live LLM tests
สิ่งที่จะพัฒนาต่อใน v0.2.0: Router agent แบบ MoE, ระบบ eval อัตโนมัติกับ gold set เต็ม, guardrail เพิ่มเติม, และ polished UI/UX
| Count (self-audit) | Number | Notes |
|---|---|---|
| Blocking gaps vs I1 pass bar | 0 | Pipeline ใช้งานได้จริงแล้ว |
| Design foresight risks for I2 | 2 | Router MoE ยังไม่แยก specialist, classification score ต่ำ (45%) |
| Schema / contract anti-patterns spotted | 1 | list[dict] ใน history bag ควรเป็น typed model |
2. Problem → Solution Shape
2.1 Users & Primary Use Case
ผู้ใช้: นักกฎหมายภายในองค์กร (in-house counsel) และทีม legal ops ที่ต้องตรวจสอบสัญญาในปริมาณมาก
Primary flow:
อัปโหลด .pdf/.docx/.txt → POST /contracts/review
→ ได้ ContractReport (clause list + risk level + citation + grounding verdict)
→ override risk level ผ่าน sidebar
→ accept clause ที่ตรวจผ่านแล้ว
2.2 In Scope / Out of Scope
In Scope (v0.0.1–v1.0.0):
- Parse PDF, DOCX, TXT (รองรับสัญญาไทย + อังกฤษ)
- Clause segmentation (regex + heading detection)
- 12 clause types (จาก CUAD taxonomy)
- Hybrid RAG retrieval (pgvector cosine + BM25)
- Risk scoring (HIGH / MEDIUM / LOW / ACCEPTABLE)
- Grounding judge (citation verbatim check >= 4 words)
- Override + Accept flow พร้อม audit log
- Contract metadata extraction (parties, dates, value, governing law)
- Report history (Postgres permanent storage)
- Auth: Google OAuth + JWT
Out of Scope (Demo Day):
- Multi-document cross-reference analysis
- Negotiation suggestion generation
- Real-time collaboration
- Export (JSON/CSV/PDF) — ตัดออกเพื่อ scope ชัด
2.3 Demo Script Awareness
| Step | Status in v0.0.1 |
|---|---|
| 1. Login ด้วย Google account | Real (Google OAuth ทดสอบกับ account จริงแล้ว) |
| 2. อัปโหลด .docx สัญญาตัวอย่าง | Real (ยิง POST /contracts/review จริง) |
| 3. ดู clause list พร้อม risk badge + citation | Real (แสดงจาก ContractReport จริง) |
| 4. Override risk level ของ clause | Real (POST /contracts/{id}/override) |
| 5. Accept clause ที่ตรวจผ่าน | Real (POST /contracts/{id}/accept) |
| 6. ดูประวัติรายงานเดิม | Real (GET /contracts + deep link /contract?report=<id>) |
3. System / Agent Topology
3.1 Architecture Diagram
flowchart TD
User["User (Browser)"]
FE["React 19 Frontend\n/login · /manual · /contract"]
API["FastAPI API\nPOST /contracts/review\nGET /contracts\nPOST /contracts/{id}/override\nPOST /contracts/{id}/accept"]
Auth["Auth Layer\nGoogle OAuth + JWT\n/auth/*"]
subgraph Pipeline ["Review Pipeline"]
Parser["Parser Agent\nPDF/DOCX/TXT to ParsedDocument"]
Segmenter["Segmenter Agent\nText to Clause list\n(regex + Thai heading)"]
Classifier["Classifier Agent\nClause to 12 types\n(LLM structured output)"]
RAG["RAG Retriever\nHybrid: pgvector cosine + BM25\nto Playbook positions"]
Scorer["Risk Scorer\nHIGH/MEDIUM/LOW/ACCEPTABLE"]
Judge["Grounding Judge\nCitation verbatim >= 4 words\n+ hallucination check"]
Metadata["MetadataExtractor\nParties / Dates / Value\n/ Governing Law"]
end
DB["Postgres + pgvector\nusers · contract_reports\nplaybook_embeddings\naudit_overrides"]
Redis["Redis\ncontract repo TTL"]
LLM["LLM Provider\nGemini / Claude / GLM\nLLM_PROVIDER env"]
User --> FE --> API --> Auth
API --> Pipeline
Parser --> Segmenter --> Classifier --> RAG --> Scorer --> Judge
Metadata -.->|"1 call/doc"| LLM
Classifier --> LLM
Judge --> LLM
RAG --> DB
Scorer --> DB
API --> DB
API --> Redis3.2 Agent Responsibility Table
| Agent / Component | Responsibility | I1 Status |
|---|---|---|
Parser |
PDF/DOCX/TXT → ParsedDocument (PyMuPDF, python-docx) |
Real |
Segmenter |
Split → ClauseList (regex, Thai/English heading detection) |
Real |
Classifier |
Clause → 12 CUAD types (LLM structured output) | Real |
RAG Retriever |
Hybrid pgvector cosine + BM25 → playbook positions | Real |
Risk Scorer |
Aggregate risk from classifier + RAG match | Real |
Grounding Judge |
Verify citation verbatim >= 4 words, hallucination check | Real |
MetadataExtractor |
Extract parties/dates/value/law from doc header+footer | Real (1 LLM call/doc) |
LLM Provider Adapter |
Gemini / Claude / GLM via LLM_PROVIDER env |
Real |
Auth (Google OAuth + JWT) |
Login flow, token management, route guard | Real |
Alembic Migrations |
DB schema versioning | Real |
Data Retention Job |
scripts/purge_reports.py |
Real (manual cron) |
3.3 Sync vs Async Boundary (Design Foresight)
Today (v0.0.1):
POST /contracts/review— synchronous request/response (blocking I/O ย้ายrun_in_threadpoolแล้ว)/healthตอบ 1.2–2.0 ms ระหว่าง review 18.9 วิ
Planned (v0.2.0/v1.0.0):
- สัญญาใหญ่ (>300 clauses) ควรเป็น
202 + job_id+ polling / WebSocket MAX_CLAUSES=300guard ไว้แล้ว
4. API Contracts & Schemas (I1 required)
4.1 Endpoint Table
| Method | Path | Request | Response Model | I1 Behavior |
|---|---|---|---|---|
| POST | /contracts/review |
multipart/form-data (file) |
ContractReport |
Real — full pipeline |
| GET | /contracts |
bearer auth | List[ContractSummary] |
Real — Postgres |
| GET | /contracts/{report_id} |
bearer auth | ContractReport |
Real — Postgres |
| DELETE | /contracts/{report_id} |
bearer auth | 204 |
Real |
| POST | /contracts/{id}/override |
OverrideRequest |
ContractReport |
Real + audit log |
| POST | /contracts/{id}/accept |
AcceptRequest |
ContractReport |
Real + audit log |
| GET | /playbook/search |
query params | List[PlaybookPosition] |
Real — pgvector |
| POST | /evaluate |
EvalRequest |
EvalReport |
Real |
| GET | /health/db |
— | {"status": "connected"} |
Real |
| GET + POST | /auth/* |
— | JWT token / redirect | Real (Google OAuth) |
4.2 Schema Fidelity Checklist
| ✓ | Check | Notes |
|---|---|---|
| ☑ | Field names match PRD Pydantic shapes | ClauseResult, ContractReport, PlaybookPosition, EvalReport |
| ☑ | Types / Literals / required vs optional match PRD intent | risk_level: Literal["HIGH","MEDIUM","LOW","ACCEPTABLE"] |
| ☑ | Confidence / review flags present | grounding_verdict, needs_review implicit via override flow |
| ☑ | Citations / source_span / evidence fields present | citation.playbook_position, citation.excerpt (verbatim) |
| ☑ | FastAPI response_model= declared (OpenAPI shows schemas) |
ทุก endpoint ประกาศ response_model |
| ☑ | Avoid dict / Any dumping for the main AI response |
Pydantic models ทุกชั้น |
4.3 Known Schema Risks
history/ audit log — ปัจจุบันใช้list[dict]บางส่วน; ควร migrate ไป typedAuditEntrymodel ใน v0.2.0- Field-level confidence —
risk_levelเป็น Literal แต่ยังไม่มีconfidence_score: floatแยกต่างหาก; judge ใช้ grounding verdict แทน — พิจารณาเพิ่มใน v0.2.0 - Schema versioning —
ContractReportยังไม่มีschema_versionfield; จำเป็นถ้ารายงานเก่าใน Postgres ต้อง migrate
5. Sample I/O Evidence
5.1 Sample Request
curl -X POST http://localhost:8000/contracts/review \
-H "Authorization: Bearer <JWT_TOKEN>" \
-F "file=@sample_contract.docx"5.2 Sample Response (Validated JSON)
{
"report_id": "rpt_01j9x3k2m4n5p6q7r8s9t0u1v2",
"contract_metadata": {
"parties": ["บริษัท ABC จำกัด", "บริษัท XYZ จำกัด"],
"effective_date": "2025-01-01",
"governing_law": "กฎหมายไทย"
},
"summary": {
"total_clauses": 12,
"risk_distribution": { "HIGH": 2, "MEDIUM": 5, "LOW": 4, "ACCEPTABLE": 1 },
"overall_risk": "MEDIUM"
},
"clauses": [
{
"clause_id": "cl_001",
"clause_type": "termination",
"text": "คู่สัญญาฝ่ายใดฝ่ายหนึ่งอาจบอกเลิกสัญญาได้โดยไม่ต้องแสดงเหตุผล...",
"risk_level": "HIGH",
"ai_rationale": "ข้อสัญญานี้ให้สิทธิ์บอกเลิกสัญญาได้โดยไม่มีเหตุผล ซึ่งขัดกับ playbook ของบริษัท",
"suggested_fallback": "การบอกเลิกต้องแจ้งล่วงหน้า 30 วัน และมีเหตุผลอันสมควร",
"citation": {
"playbook_position": "termination.unilateral_termination",
"excerpt": "การบอกเลิกสัญญาต้องมีเหตุผลอันสมควรและแจ้งล่วงหน้า"
},
"grounding_verdict": {
"is_grounded": true,
"verdict_reason": "excerpt verified verbatim in playbook (6 words)"
},
"accepted": false
}
],
"disclaimer": "รายงานนี้สร้างโดย AI — กรุณาตรวจสอบโดยผู้เชี่ยวชาญด้านกฎหมายก่อนนำไปใช้"
}Response validates against ContractReport Pydantic model — tested via pytest -m live_llm with real Gemini + pgvector.
5.3 How to Reproduce
# 1. Clone repo
git clone https://github.com/tsuna-n/Contract-Clause-Risk-Reviewer.git
cd Contract-Clause-Risk-Reviewer
# 2. Copy env and fill in secrets
cp apps/backend-fastapi/.env.example apps/backend-fastapi/.env
# Set GEMINI_API_KEY, DATABASE_URL, REDIS_URL, GOOGLE_CLIENT_ID/SECRET
# 3. Start infra
docker compose -f infrastructure/docker-compose.yml up -d
# 4. Run migrations
cd apps/backend-fastapi
alembic upgrade head
# 5. Start API
uvicorn app.main:app --reload
# OpenAPI docs: http://localhost:8000/docs
# 6. Run tests (offline, free)
pytest # 268 tests
# 7. Run live LLM tests (costs API credits)
pytest -m live_llm # 11 tests, ~3 min6. Repo Hygiene & Layout
| ✓ | Item | Path / Note |
|---|---|---|
| ☑ | README with run instructions | README.md (root) + apps/backend-fastapi/README.md |
| ☑ | .env.example (no secrets committed) |
apps/backend-fastapi/.env.example |
| ☑ | Schemas package / module | apps/backend-fastapi/app/schemas/ |
| ☑ | API routes package | apps/backend-fastapi/app/routers/ |
| ☑ | Docs folder with architecture diagram | Section 3 Mermaid + README embedded |
| ☑ | Data pack folder | apps/backend-fastapi/data/ (CUAD fixtures, gold labels) |
Repo Tree (top-level):
Contract-Clause-Risk-Reviewer/
├── README.md
├── .gitignore
├── apps/
│ ├── backend-fastapi/
│ │ ├── README.md
│ │ ├── .env.example
│ │ ├── app/
│ │ │ ├── main.py
│ │ │ ├── schemas/ # Pydantic models
│ │ │ ├── routers/ # FastAPI routes
│ │ │ ├── ai/ # LLM provider adapter, agents
│ │ │ ├── db/ # SQLAlchemy models, Alembic
│ │ │ └── services/ # Review pipeline, RAG
│ │ ├── data/
│ │ │ └── gold/ # Gold label sets (CUAD)
│ │ ├── scripts/
│ │ │ ├── build_cuad_fixtures.py
│ │ │ └── purge_reports.py
│ │ ├── tests/
│ │ │ ├── unit/
│ │ │ ├── integration/
│ │ │ └── live/ # pytest -m live_llm
│ │ └── alembic/
│ └── web/ # React 19 + Vite + Tailwind
│ ├── src/
│ │ ├── lib/api.ts
│ │ ├── lib/contracts.ts
│ │ └── pages/ # /login, /manual, /contract
│ └── ...
└── infrastructure/
└── docker-compose.yml # Postgres (pgvector) + Redis + api
7. Design Foresight for I2 / I3
| Check Area | I1 Expectation | Planned Approach |
|---|---|---|
| LLM Orchestration | Name provider(s); structured output plan | Gemini (primary) / Claude / GLM via LLM_PROVIDER; structured JSON per adapter; LLM_THINKING=disabled default |
| RAG / KB | What ingested? Chunking unit? | CUAD v1 (510 contracts) → playbook positions; chunk = clause position; hybrid pgvector cosine + BM25 already live |
| Router + Specialists | When does routing become real (I2)? | v0.2.0: Router agent → specialist per clause type (termination, payment, IP, …); ปัจจุบัน classifier → single pipeline |
| Judge / Grounding | What will judge verify? | Verbatim citation check (>= 4 words), hallucination detection, verdict.reason fed back into retry prompt |
| HITL / Confidence | How will needs_review route to humans? |
Override sidebar + Accept flow live; v0.2.0: confidence_score + auto-flag queue |
| Eval Pack | Gold set size; metrics & thresholds | 3 contracts / 90 clauses / 18 min; segmentation_f1 100%, citation_validity 100%, classification 45.45%*, risk 50% |
| Guardrails (I3) | Disclaimer, injection, PII / retention | Disclaimer in report; MAX_UPLOAD_BYTES=10MB, MAX_CLAUSES=300; PLAYBOOK_ADMIN_EMAILS; REPORT_RETENTION_DAYS + purge job |
| Reliability | Retries, timeouts, async jobs | SDK-level retry (timeout/429/5xx/empty/JSON broken); run_in_threadpool; v0.2.0: 202+job_id for large contracts |
classification 45.45% ต่ำเพราะ label set เปลี่ยน ไม่ใช่ pipeline เสื่อม (อธิบายใน Section 8)
8. Findings & Risks (I1 required)
[LOW] Finding 1 — Classification Accuracy Drop After Label Set Change
Component: app/ai/agents/classifier.py · data/gold/
Issue: classification metric ลดจาก 57.69% → 45.45% หลังเปลี่ยน gold label set ใหม่ (label ที่ละเอียดกว่า ไม่ใช่ pipeline เสื่อม)
Impact: Metric ดูต่ำกว่าจริง; อาจสร้างความเข้าใจผิดเวลา report ต่อ stakeholder
Remediation: เพิ่ม note ใน eval report ว่า label set เปลี่ยน + สร้าง baseline comparison — target v0.2.0
[MEDIUM] Finding 2 — No confidence_score Field on ClauseResult
Component: app/schemas/clause.py
Issue: Judge ให้ grounding verdict แต่ไม่มี numeric confidence; HITL routing ต้องอาศัย override manual
Impact: Auto-flagging clauses สำหรับ reviewer queue ใน v0.2.0 จะซับซ้อนขึ้น
Remediation: เพิ่ม confidence_score: float และ needs_review: bool ใน schema — target v0.2.0
[LOW] Finding 3 — schema_version Missing from ContractReport
Component: app/schemas/report.py
Issue: รายงานใน Postgres ไม่มี version tag; schema เปลี่ยนใน v0.2.0 อาจทำให้ GET /contracts/{id} deserialize รายงานเก่าพัง
Impact: Breaking change เมื่อ schema evolve ใน future iterations
Remediation: เพิ่ม schema_version: str = "0.0.1" — v0.1.0 hotfix ก่อน schema เปลี่ยน
9. Quick Fixes & Next Iteration Plan
Before tagging v0.0.1 (must fix)
- ไม่มี blocking gap — pipeline ทำงานได้จริง end-to-end
- เพิ่ม
schema_version: str = "0.0.1"ในContractReport(hotfix) - ตรวจสอบ
.env.exampleครบทุก env var ที่ใช้จริง
Iteration 2 — AI Core (v0.2.0)
I2 จะต้องมี: RAG/KB ingestion · Router + specialist agents · Real LLM structured output · Judge/grounding · Eval on gold subset ·
eval_report.md
(v0.0.1 ทำเกินหมดแล้ว — I2 focus ที่ MoE routing + eval automation)
- Router Agent → dispatch per clause type → Specialist agents (termination, payment, IP, indemnity)
- เพิ่ม
confidence_score+needs_reviewใน schema - Eval automation pipeline (CI-triggered gold set eval)
202 + job_idasync pattern สำหรับสัญญา >300 clausesschema_versionmigration utility สำหรับรายงานเก่า
Iteration 3 — Demo Day (v1.0.0)
- UI polish: report export (backend endpoint), comparison view
- Full gold eval vs thresholds (target: classification >= 60%, risk >= 65%)
- Cron schedule สำหรับ retention job (เมื่อมีนโยบายเก็บข้อมูลจริง)
- Demo video recording
10. Release Notes Draft (I1 required)
v0.0.1 — Walking Skeleton (End-to-End Working)
Project: Contract Clause Risk Reviewer
Pattern: Intake → Review → Analysis → Generation (Grounded RAG)
What's Included
Backend (FastAPI + Postgres/pgvector + Redis)
- Pydantic schemas:
ClauseResult,ContractReport,PlaybookPosition,EvalReport,AuditEntry - FastAPI review pipeline:
POST /contracts/review— Parse → Segment → Classify → RAG match → Risk score → Grounding judge → Report (tested with Gemini + pgvector live) - Architecture diagram: Section 3 Mermaid in this document
- Auth: Google OAuth + JWT (tested with real Google account)
- DB: Alembic migrations (users, contract_reports, playbook_embeddings, audit_overrides)
- LLM Provider Adapter: Gemini / Claude / GLM switchable via
LLM_PROVIDERenv - Hybrid RAG: pgvector cosine + BM25 on 36 playbook positions (12 clause types)
- Override + Accept flow:
POST /contracts/{id}/override+POST /contracts/{id}/acceptwith audit log - Contract metadata extraction: parties / dates / value / governing law (verbatim check)
- Report history: Postgres permanent storage + deep link + delete
- Data fixtures: CUAD v1 → 12 sample contracts + 327 gold clauses
- Guardrails:
MAX_UPLOAD_BYTES=10MB,MAX_CLAUSES=300, grounding badge, disclaimer - Tests: 268 unit/integration tests (offline, free) + 11 live LLM tests
Frontend (React 19 + Vite + Tailwind)
/login— Google login/manual— report history + upload/contract— clause list + risk badge + citation + override sidebar + accept button- No mock data — all connected to real backend
What's Mocked / Deferred
- Router agent (MoE pattern) — currently: classifier → single pipeline; v0.2.0: Router → Specialists
confidence_scorefield (using grounding verdict instead)schema_versionfield inContractReport- Async job pattern (202 + job_id) for large contracts
- Export endpoint (server-side)
- Cron schedule for retention job
How to Run
git clone https://github.com/tsuna-n/Contract-Clause-Risk-Reviewer.git
cd Contract-Clause-Risk-Reviewer
cp apps/backend-fastapi/.env.example apps/backend-fastapi/.env
# Set GEMINI_API_KEY, DATABASE_URL, REDIS_URL, GOOGLE_CLIENT_ID/SECRET
docker compose -f infrastructure/docker-compose.yml up -d
cd apps/backend-fastapi && alembic upgrade head
uvicorn app.main:app --reload
# OpenAPI: http://localhost:8000/docsPass Bar Evidence
POST /contracts/review → ContractReport (validated Pydantic model)
Tested: Gemini API + Postgres/pgvector live
segmentation_f1: 100% · citation_validity: 100%
268 tests passing (0 skip) + 11 live LLM tests