File size: 22,630 Bytes
b3f5a94
 
72fa63d
b3f5a94
72fa63d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3f5a94
 
 
72fa63d
b3f5a94
72fa63d
 
 
b3f5a94
72fa63d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3f5a94
 
72fa63d
b3f5a94
72fa63d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3f5a94
 
 
72fa63d
b3f5a94
 
 
 
72fa63d
 
b3f5a94
72fa63d
b3f5a94
 
72fa63d
 
 
 
 
b3f5a94
 
 
72fa63d
b3f5a94
 
 
 
 
 
 
 
72fa63d
 
 
b3f5a94
 
 
72fa63d
b3f5a94
 
 
 
 
 
 
72fa63d
 
 
 
 
 
b3f5a94
72fa63d
b3f5a94
 
 
 
 
72fa63d
 
 
 
b3f5a94
 
72fa63d
 
b3f5a94
 
72fa63d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3f5a94
 
 
72fa63d
b3f5a94
72fa63d
 
 
 
 
 
 
 
 
 
 
b3f5a94
72fa63d
b3f5a94
72fa63d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3f5a94
72fa63d
b3f5a94
72fa63d
b3f5a94
72fa63d
 
 
 
 
 
 
 
 
b3f5a94
72fa63d
b3f5a94
72fa63d
b3f5a94
72fa63d
 
 
 
 
 
 
 
b3f5a94
 
 
72fa63d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# Vantage β€” Master Plan

> **Agentic Market Intelligence.** The single source of truth (SSOT) for this project.
> *"What is the market asking for that our research library hasn't covered yet?"*
>
> This document is the master reference. Every architectural, scoping, and sequencing
> decision lives here. Update it as the project evolves; do not let code drift from it.

## Table of Contents
1. [Product & Problem](#1-product--problem)
2. [The Five Capabilities](#2-the-five-capabilities)
3. [GapScore Methodology](#3-gapscore-methodology)
4. [Scope](#4-scope)
5. [Key Trade-offs](#5-key-trade-offs)
6. [Tools & Stack](#6-tools--stack)
7. [Architecture & Patterns](#7-architecture--patterns)
8. [Folder Structure](#8-folder-structure)
9. [Data Model](#9-data-model)
10. [The Agent](#10-the-agent)
11. [Edge Cases](#11-edge-cases)
12. [Build Phases (organic, local-first)](#12-build-phases-organic-local-first)
13. [Guiding Principles](#13-guiding-principles)
14. [Local Dev Workflow](#14-local-dev-workflow)
15. [Reproducibility & Delivery](#15-reproducibility--delivery)
16. [Evaluation Methodology](#16-evaluation-methodology)
17. [Observability](#17-observability)
18. [Demo Script](#18-demo-script)
19. [Effort Summary](#19-effort-summary)

---

## 1. Product & Problem

Research organizations (the case study is framed around a Gartner-like firm) maintain a large
internal library of analyst content. The market, meanwhile, moves constantly β€” new topics surge
in search and news every week. The strategic question for research leadership is:

> **Where is market demand outrunning our coverage?**

**Vantage** answers this by connecting **external market demand** to **internal library
coverage** through a shared topic vocabulary. For any topic it knows two things at once:
- **Momentum** β€” how hot the topic is in the outside world (search/news signal).
- **Coverage** β€” how well the internal library addresses it (retrieval density).

The gap between them β€” the **GapScore** β€” drives a ranked commissioning list and a
conversational research assistant. Every answer is grounded in cited sources, and every agent
decision is fully auditable.

**Simulated internal library:** we use the public **HuffPost News Category Dataset**
(~200k pre-labeled articles) as a stand-in for the proprietary research corpus. Its built-in
category labels give us topic ground truth for free.

---

## 2. The Five Capabilities

Mapped directly to the case-study brief:

1. **Detect emerging topics** β€” from public search/news signals (Google Trends via pytrends).
2. **Answer client questions** β€” RAG over the library returning **cited** sources.
3. **Identify gaps** β€” compare topic momentum vs. internal coverage (the core metric).
4. **Knowledge Graph** β€” model documents, topics, entities, and their relationships.
5. **Agentic orchestration** β€” an agent that plans, retrieves, analyzes, and synthesizes
   across all of the above.

---

## 3. GapScore Methodology

The core metric. **Borrowed, not invented** β€” adapted from SEO keyword opportunity scoring
(Ahrefs/SEMrush content-gap methodology), generalized to research topics.

```
GapScore(topic) = Momentum_percentile(topic) Γ— (1 βˆ’ Coverage_percentile(topic))
```

**Why this formula is defensible:**
- Both inputs are **percentile ranks** across all tracked topics β†’ same ordinal scale β†’
  multiplication/comparison is dimensionally valid (avoids the trap of subtracting two
  differently-scaled raw numbers).
- Output is bounded **[0, 1]**, never negative.
- The **product** (not difference) means *both* conditions must hold to flag a gap: a topic
  nobody wants AND that we cover well is correctly scored near 0.

**Inputs defined:**
- **Momentum_percentile** β€” percentile rank of the topic's search-interest z-score
  (rolling baseline) across all tracked topics. Source: pytrends, cached.
- **Coverage_percentile** β€” percentile rank of the topic's *retrieval density* (count of
  library chunks above a similarity threshold, normalized) across all tracked topics.

**The 2Γ—2 decision matrix** (how results are presented to a business audience):

```
           HIGH COVERAGE
                β”‚
   Over-Served  β”‚  Well-Covered βœ…
   (deprioritize)β”‚  (maintain)
────────────────┼──────────────── MOMENTUM β†’
   Ignore       β”‚  πŸ”΄ PRIORITY GAP
   (low demand) β”‚  (commission now)
                β”‚
           LOW COVERAGE
```

**Worked example:** topic "Generative AI Enterprise" β€” momentum_pctile 0.96, coverage_pctile
0.12 β†’ GapScore = 0.96 Γ— (1 βˆ’ 0.12) = **0.84** β†’ πŸ”΄ Priority Gap.

**Union seeding (solves cold start + blind spot):** topics are seeded from the **union** of
(a) internal topics (HuffPost categories / extracted keywords) and (b) external trending terms
(pytrends top-N). Internal seeding solves the cold-start problem (no external taxonomy needed);
external seeding patches the blind spot where we have **zero** coverage of a surging topic (such
topics would never appear if we only looked inward).

---

## 4. Scope

**In scope**
- HuffPost dataset as the *simulated internal library* (~200k pre-labeled articles).
- Hybrid RAG (dense + sparse) β†’ cross-encoder rerank β†’ cited answers.
- **Union-seeded** gap analysis (internal βˆͺ external) β€” cold start + zero-coverage blind spot.
- Knowledge Graph in managed Neo4j; entities via spaCy NER; IPTC-style topic hierarchy.
- LangGraph agent (4 tools) with multi-turn memory + multi-hop reasoning.
- React/TS dashboard; evaluation harness; Langfuse observability; one-command Docker delivery.

**Out of scope (with reason)**
- **BERTopic** β€” HuffPost categories already label topics; nothing to discover.
- **GDELT / Guardian / NYT / SEC / SPARQL** β€” size/complexity/auth; documented as production paths.
- **Self-hosted DBs** β€” managed free tiers (Qdrant Cloud, Neo4j AuraDB) instead.
- **XGBoost/learning-to-rank reranker** β€” needs labeled data; pre-trained cross-encoder wins zero-shot.
- **Auth, multi-tenancy, streaming ingestion** β€” V2 items, mentioned in write-up.

---

## 5. Key Trade-offs

| Decision | Chosen | Why | Trade-off accepted |
|---|---|---|---|
| External signal | pytrends (cached) | Free, fast, offline-safe | Lower fidelity than GDELT |
| Reranker | Pre-trained cross-encoder | No training data needed | Not domain-tuned |
| Topic seeding | Union (internal βˆͺ external) | Catches thin *and* zero coverage | Slightly more compute |
| Architecture | 3-service modular monorepo | CQRS + SSOT, KISS | Not "pure" microservices |
| Databases | Managed free tiers | Zero ops, zero cost | Free-tier capacity limits |
| Gap compute | Scheduled/offline, API reads | Never blocks user queries | Gaps eventually-consistent |
| Topic modeling | HuffPost category labels | Labels already exist | Coarser than learned topics |
| Delivery | Pre-built GHCR images | One-command boot for reviewer | CI build pipeline upfront cost |

---

## 6. Tools & Stack

| Layer | Tool | Role |
|---|---|---|
| Frontend | React + Vite + TS + Tailwind + shadcn/ui + react-force-graph | Dashboard UI |
| API | FastAPI | Read side / BFF + agent host |
| Agent | LangGraph | Router, 4 tools, ReAct, memory |
| Domain | `vantage_core` (shared pkg) | SSOT logic: gapscore, retrieval, topics |
| Vectors | **Qdrant Cloud (free)** | Hybrid dense + sparse search (~1M vectors) |
| Embeddings | all-MiniLM-L6-v2 | Chunk + query vectors (384-dim) |
| Rerank | cross-encoder/ms-marco-MiniLM-L-6-v2 | Precision boost (top-50 β†’ top-5) |
| Graph | **Neo4j AuraDB (free)** | Topics/entities/gap scores (~200k nodes) |
| NER | spaCy `en_core_web_sm` | Entity extraction (ORG/PERSON/GPE) |
| Signal | pytrends | Momentum (cached, TTL 6h) |
| LLM | OpenAI `gpt-4o-mini` | Routing + cited generation |
| Eval | RAGAS + NDCG@5 | Faithfulness, context recall, relevance |
| Observability | Langfuse Cloud | Trace every call/decision |
| Delivery | Docker + GHCR + one compose | `docker compose up` |

---

## 7. Architecture & Patterns

**Three services + two managed databases.** Local-first during development (plain scripts +
`start.sh`/`stop.sh`); containerized only at the final phase.

- **`apps/web`** β€” React/TS UI (chat, gap radar, graph). Thin; types generated from OpenAPI.
- **`apps/api`** β€” FastAPI **read side** (BFF). Hosts the LangGraph agent + 4 tools. Reads only.
- **`apps/worker`** β€” **write side**. Ingestion, KG build, signal refresh, gap computation.
- **`packages/vantage_core`** β€” **SSOT domain**: ports & adapters, models, gapscore, retrieval.
- **`contracts/openapi.yaml`** β€” API-first contract β†’ generates the TS client (type SSOT).
- **Managed:** Qdrant Cloud (vectors), Neo4j AuraDB (graph).

**Patterns in play (and what each means here):**
- **SSOT** β€” Qdrant owns chunks/vectors; Neo4j owns topics/entities/gap scores; `signals.json`
  owns external momentum; `vantage_core.models` owns schemas; `openapi.yaml` owns API types.
- **CQRS-lite** β€” `worker` writes (ingest, compute gaps); `api` reads. Gap math never blocks queries.
- **Hexagonal (ports & adapters)** β€” domain depends on interfaces; Qdrant/Neo4j/OpenAI/pytrends
  are swappable adapters (e.g. pytrends β†’ GDELT later).
- **Repository** β€” `VectorRepo`, `GraphRepo`, `SignalSource` abstract all I/O; domain is unit-testable.
- **Twelve-Factor** β€” config in `.env`, stateless services, disposable containers, dev/prod parity.
- **Cache-aside + TTL** β€” `signals.json` cached; recompute on miss/expiry.
- **Idempotent batch** β€” worker uses upserts; re-running ingestion is safe.
- **Graceful degradation** β€” every external dependency has a fallback/health-check.

---

## 8. Folder Structure

```
vantage/
β”œβ”€β”€ docs/MASTER_PLAN.md          # this file β€” the master reference
β”œβ”€β”€ README.md                    # quickstart + architecture diagram (P13)
β”œβ”€β”€ .env.example                 # canonical list of every env var
β”œβ”€β”€ start.sh / stop.sh           # local run (P9–P10): boot/stop api + web
β”œβ”€β”€ Makefile                     # convenience targets (ingest/seed/eval/...)
β”œβ”€β”€ docker-compose.yml           # references GHCR images (P13)
β”œβ”€β”€ .github/workflows/ci.yml     # build+push images to GHCR (P13)
β”œβ”€β”€ contracts/
β”‚   └── openapi.yaml             # API-first contract β†’ TS client (SSOT for types)
β”œβ”€β”€ packages/
β”‚   └── vantage_core/            # SSOT domain logic (imported by api + worker)
β”‚       β”œβ”€β”€ domain/{gapscore,retrieval,topics}.py
β”‚       β”œβ”€β”€ adapters/{vector_qdrant,graph_neo4j,signals_pytrends,llm_openai}.py
β”‚       β”œβ”€β”€ ports.py             # VectorRepo, GraphRepo, SignalSource interfaces
β”‚       β”œβ”€β”€ models.py            # pydantic domain models (SSOT schemas)
β”‚       └── config.py            # settings from .env
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ api/                     # FastAPI read side + LangGraph agent
β”‚   β”‚   └── vantage_api/{main.py,routers/,agent/{graph,state,tools/}}
β”‚   β”œβ”€β”€ worker/                  # write side jobs
β”‚   β”‚   └── vantage_worker/{ingest_documents,build_graph,compute_signals,compute_gaps}.py
β”‚   └── web/                     # React + TS dashboard
β”‚       └── src/{features/{chat,gap-radar,graph}/,lib/api/,App.tsx}
β”œβ”€β”€ scripts/                     # one-shot dev scripts (local-first)
β”‚   β”œβ”€β”€ healthcheck.py  download_data.sh  ingest.py  search.py
β”‚   β”œβ”€β”€ ask.py  refresh_signals.py  compute_gaps.py  build_graph.py  agent_cli.py
└── eval/{golden_dataset.json,run_eval.py,report.md}
```

---

## 9. Data Model

**Qdrant (vectors)** β€” one point per chunk:
- `id`, `dense_vector` (384-dim), `sparse_vector` (BM25)
- payload: `doc_id`, `title`, `category`, `date`, `source`, `chunk_text`

**Neo4j (graph)** β€” nodes and edges:
- Nodes: `(:Document {id,title,category,date})`, `(:Topic {name,iptc_code,coverage_pctile,
  momentum_pctile,gap_score,quadrant})`, `(:Entity {name,type})`
- Edges: `(Document)-[:MENTIONS]->(Topic)`, `(Document)-[:CONTAINS]->(Entity)`,
  `(Entity)-[:RELATED_TO]->(Topic)`, `(Topic)-[:BROADER]->(Topic)`

**`signals.json`** β€” `{fetched_at, topics:[{keyword,z_score,momentum_pctile}]}`.

**`gap_log.jsonl`** β€” async query-level log: `{query,topic,retrieval_score,momentum_pctile,gap_score,ts}`.

---

## 10. The Agent

**LangGraph, ReAct pattern.** State carries conversation memory and gap log:
```python
class AgentState(TypedDict):
    messages: Annotated[list, add_messages]   # multi-turn memory
    current_context: str                       # e.g. "APAC", "healthcare"
    last_topics: list[str]                     # topics from previous turn
    gap_log: list[dict]                        # running query-level gap signals
```

**Four tools:**
- **RAG Tool** β€” Qdrant hybrid β†’ cross-encoder β†’ cited answer.
- **Signal Tool** β€” reads `signals.json` β†’ ranked momentum.
- **Gap Tool** β€” Neo4j Cypher β†’ GapScore table + quadrant.
- **Graph Tool** β€” Neo4j Cypher β†’ entity/topic/relationship traversal.

**Router** classifies intent and selects tool(s). **Multi-hop** chains tools within one query
(e.g. Gap β†’ Graph β†’ RAG). **Multi-turn memory** resolves references like "which of those…".
**Async side-effect:** after each RAG answer with low retrieval score, write to `gap_log.jsonl`.

---

## 11. Edge Cases

Representative set (full list maintained alongside tests):
- **Zero relevant docs** β†’ answer "no internal coverage" + log gap; never hallucinate.
- **LLM omits/forges citations** β†’ post-validate against retrieved chunk IDs; retry/drop.
- **Topic trending, zero coverage** β†’ union seeding catches it; coverage_pctile 0 β†’ max gap.
- **pytrends 429 / flat series** β†’ fall back to cache; guard z-score divide-by-zero.
- **Garbage / variant entities** β†’ type whitelist + min-frequency + alias normalization.
- **AuraDB node limit** β†’ cap ingested docs; documented.
- **Wrong/missing env var** β†’ fail fast with explicit message (health-check).
- **Worker hasn't run** β†’ API returns "gap data not yet computed" empty-state.
- **OpenAI/Langfuse down** β†’ backoff / fire-and-forget; never crash the demo.
- **Multi-hop latency (10–20s)** β†’ FE progress indicator / per-tool status.

---

## 12. Build Phases (organic, local-first)

**Principle:** vertical slices β€” each phase delivers *one* runnable, testable component. Infra
(Docker/GHCR) comes last. Managed DBs are remote endpoints we connect to from local scripts.

### Phase 1 β€” πŸ”Œ Bootstrap & Connectivity *(🟒 2h)*
- **Contains:** repo folders, `requirements`, venv, `config.py`, `.env.example`; provision
  Qdrant Cloud + Neo4j AuraDB; `scripts/healthcheck.py`.
- **Test:** `python scripts/healthcheck.py` β†’ `Qdrant OK Β· Neo4j OK Β· OpenAI OK`.
- **Deliverable:** green health check. *(No Docker, no GHCR.)*

### Phase 2 β€” πŸ“š Data Ingestion *(🟑 3–4h)*
- **Builds on:** P1. **Contains:** `download_data.sh`, `models` (Document/Chunk), loader,
  chunker, embedder, Qdrant adapter (upsert), `scripts/ingest.py`.
- **Test:** ingest 2k sample docs β†’ `scripts/peek.py` shows counts + a sample chunk.
- **Deliverable:** populated Qdrant collection.

### Phase 3 β€” πŸ”Ž Retrieval (hybrid + rerank) *(🟑 3–4h)*
- **Builds on:** P2. **Contains:** hybrid dense+sparse search, cross-encoder rerank,
  `scripts/search.py` (CLI).
- **Test:** 5 manual queries return on-topic chunks; rerank order sensible.
- **Deliverable:** `retrieval` module + search CLI.

### Phase 4 β€” πŸ’¬ RAG Answers (cited) *(🟑 3–4h)*
- **Builds on:** P3. **Contains:** prompt template, OpenAI adapter, citation formatter +
  validation, `scripts/ask.py`.
- **Test:** ask 5 questions β†’ real citations; "no coverage" path triggers correctly.
- **Deliverable:** working RAG via CLI.

### Phase 5 β€” πŸ“ˆ External Signals *(🟒 2–3h)*
- **Builds on:** P1. **Contains:** pytrends adapter, z-score→percentile, cache-aside TTL →
  `signals.json`, `scripts/refresh_signals.py`.
- **Test:** run β†’ `signals.json` populated; rate-limit falls back to cache.
- **Deliverable:** momentum data.

### Phase 6 β€” 🎯 Coverage + GapScore *(🟑 3–4h)*
- **Builds on:** P2, P5. **Contains:** coverage via Qdrant density, **union seeding** +
  normalization, `gapscore.py`, `scripts/compute_gaps.py`.
- **Test:** gap table prints; zero-coverage topic β†’ max gap; **unit tests** on `gapscore`.
- **Deliverable:** `gap_scores.csv`.

### Phase 7 β€” πŸ•ΈοΈ Knowledge Graph *(🟑 3–4h)*
- **Builds on:** P2, P6. **Contains:** spaCy NER + entity filtering/normalization, Neo4j
  adapter (Doc/Topic/Entity upserts + edges), write gap scores onto Topic nodes,
  `scripts/build_graph.py`.
- **Test:** Cypher in Neo4j Browser returns top gap topics + linked entities.
- **Deliverable:** populated graph.

### Phase 8 β€” πŸ€– Agent (CLI) *(πŸ”΄ 4–5h)*
- **Builds on:** P4, P5, P6, P7. **Contains:** LangGraph state (memory), router, 4 tools,
  ReAct multi-hop, `scripts/agent_cli.py`.
- **Test:** all 5 demo queries β†’ correct routing + multi-hop + memory.
- **Deliverable:** working agent, terminal-only.

### Phase 9 β€” 🌐 API *(🟑 3–4h)*
- **Builds on:** P8. **Contains:** FastAPI routers (chat/gaps/graph), session-keyed memory,
  emit `openapi.yaml`, `start.sh`/`stop.sh` (local uvicorn).
- **Test:** `curl` each endpoint; `openapi.yaml` generated.
- **Deliverable:** API running locally via `start.sh`.

### Phase 10 β€” 🎨 Frontend *(πŸ”΄ 6–8h)*
- **Builds on:** P9. **Contains:** React/Vite/TS + Tailwind + shadcn, 3 features
  (chat/gap-radar/graph), generated TS client; `start.sh` boots **api + web**, `stop.sh` stops both.
- **Test:** click through 3 tabs; all 5 queries work in the UI.
- **Deliverable:** full local app via `start.sh`.

### Phase 11 β€” πŸ“Š Evaluation *(🟒 2–3h)*
- **Builds on:** P4. **Contains:** golden set (10 hand + 40 Opus), RAGAS + NDCG@5 β†’ `eval/report.md`.
- **Test:** `python eval/run_eval.py` prints metrics.
- **Deliverable:** metrics report for README.

### Phase 12 β€” πŸ”­ Observability *(🟒 2h)*
- **Builds on:** P8. **Contains:** Langfuse wiring across LLM/tool/agent calls (fire-and-forget).
- **Test:** traces appear; app works if Langfuse is down.
- **Deliverable:** traced runs.

### Phase 13 β€” πŸ“¦ Containerize + GHCR + One-Command *(🟑 3–4h, the very end)*
- **Builds on:** all. **Contains:** 3 Dockerfiles, `docker-compose.yml` (GHCR images),
  healthchecks + `depends_on`, GitHub Action build/push, README + architecture diagram.
- **Test:** clean machine β†’ `docker compose up` β†’ everything boots.
- **Deliverable:** submission-ready reproducible repo.

---

## 13. Guiding Principles

| Principle | How it's honored |
|---|---|
| One thing at a time | Each phase = a single capability, nothing bundled |
| Always runnable | Every phase ends with a script/CLI you can execute |
| Local-first | Plain scripts; `start.sh`/`stop.sh` at P9–P10; Docker/GHCR last (P13) |
| Builds upward | Search β†’ RAG β†’ Signals β†’ Gap β†’ Graph β†’ Agent β†’ API β†’ UI |
| Testable atoms | Each phase has a concrete pass/fail test |
| De-risked | Hardest integration (agent, P8) sits on already-proven tools |
| SSOT everywhere | Domain in `vantage_core`; types in `openapi.yaml`; no duplicated logic |

---

## 14. Local Dev Workflow

- **Dependencies:** Python 3.11 venv per service; `pip install -e packages/vantage_core`.
- **Config:** copy `.env.example` β†’ `.env`; fill Qdrant/Neo4j/OpenAI/Langfuse creds.
- **One-shot scripts (P2–P8):** `ingest.py`, `search.py`, `ask.py`, `refresh_signals.py`,
  `compute_gaps.py`, `build_graph.py`, `agent_cli.py` β€” run individual capabilities.
- **Long-running services (P9–P10):** `./start.sh` boots api (uvicorn) + web (vite);
  `./stop.sh` stops both. No Docker required during development.
- **Seeding:** `make seed` (or `scripts/seed_all.sh`) populates the managed DBs end-to-end,
  idempotently β€” runnable by reviewer if they bring their own free-tier creds.

---

## 15. Reproducibility & Delivery

- **Images:** built and pushed to **GHCR** (`ghcr.io/<user>/vantage-{api,worker,web}`) by a
  GitHub Action on merge to main.
- **One command:** reviewer clones, drops in provided `.env` (demo creds), runs
  `docker compose up` β†’ Docker pulls pre-built images β†’ full stack boots. DBs are pre-seeded.
- **Rule:** always read `docker-compose.yml` and `.env.example` before answering infra
  questions; never guess env var names or ports.

---

## 16. Evaluation Methodology

- **Golden set:** 50 Q&A β€” **10 hand-labeled** (human ground truth, breaks LLM-circularity)
  + 40 generated by Claude Opus over sampled articles.
- **Metrics:** RAGAS (faithfulness, context recall, answer relevance) + **NDCG@5** for retrieval.
- **Output:** `eval/report.md` table, surfaced in the README.
- **Honesty:** LLM-as-judge limitations stated explicitly; the 10 human-labeled anchor credibility.

---

## 17. Observability

- **Langfuse Cloud** traces every LLM call, tool call, and agent handoff.
- Logged per query: query β†’ extracted topic β†’ momentum β†’ retrieval score β†’ answer β†’ gap flag.
- **Fire-and-forget:** tracing never blocks or breaks a user response.

---

## 18. Demo Script

Five rehearsed queries, in order β€” each builds on the last and together they exercise every
capability in the brief:
1. **RAG only** β€” *"What are the challenges of AI in healthcare?"* β†’ cited answer.
2. **Signal only** β€” *"What technology topics are surging right now?"* β†’ ranked momentum.
3. **Gap only** β€” *"What should we commission next quarter?"* β†’ GapScore table + 2Γ—2.
4. **Multi-hop** (Gap→Graph→RAG) — *"Which companies sit in our biggest AI gaps?"* → synthesis.
5. **Multi-turn memory** β€” follow-up *"Which of those are we thin on?"* β†’ memory-scoped gaps.

---

## 19. Effort Summary

| Phase | Theme | Effort |
|---|---|---|
| 1 | Bootstrap & Connectivity | 🟒 2h |
| 2 | Data Ingestion | 🟑 3–4h |
| 3 | Retrieval (hybrid + rerank) | 🟑 3–4h |
| 4 | RAG Answers (cited) | 🟑 3–4h |
| 5 | External Signals | 🟒 2–3h |
| 6 | Coverage + GapScore | 🟑 3–4h |
| 7 | Knowledge Graph | 🟑 3–4h |
| 8 | Agent (CLI) | πŸ”΄ 4–5h |
| 9 | API | 🟑 3–4h |
| 10 | Frontend | πŸ”΄ 6–8h |
| 11 | Evaluation | 🟒 2–3h |
| 12 | Observability | 🟒 2h |
| 13 | Containerize + GHCR | 🟑 3–4h |
| **Total** | | **β‰ˆ 42–53h** |

Build order is strictly sequential P1 β†’ P13. Fits comfortably within the 2–3 day budget.