[NOTICKET] docs: knowledge pipeline — reframe kex as prototype, not port target
Browse fileskex is a prototype that answered the design questions; extraction v2 gets rebuilt
in this repo. What carries forward is the evidence and the tuned constants, not
the code. Restructures the checklist accordingly:
- New §2 prototype disposition (P1 freeze / P2 harvest constants / P3 migrate the
gold sets + scorer into eval/knowledge/ / P4 record negative findings). P3 lands
before any v2 pipeline code — without a standing baseline "improved" cannot be
shown, and the nano schema-fill experiment already failed.
- §5 splits every stage into DESIGN (validated, with a number) vs BUILD (exists in
this repo). The previous table read 11 done, which was honest about the design
and misleading about a rebuild — none of it exists here.
- Adds the module-name collision: src/knowledge/ is the existing OCR->pgvector
service, so v2 needs its own name.
- Seam section reflects Sofhia's contracts.py draft as the agreed base, with the
envelope / section-structure / source_wording items still open.
DEV_PLAN §0.8 and the §6 open decisions follow the same reframing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- DEV_PLAN.md +24 -18
- KNOWLEDGE_PIPELINE_TODO.md +136 -209
|
@@ -289,9 +289,11 @@ two independently runnable halves — **parsing (Sofhia, MinerU)** and **knowled
|
|
| 289 |
(Rifqi)** — meeting only at a **persisted, versioned parsed-document artifact**. Extraction never
|
| 290 |
takes a file path; that constraint is the whole point of the split.
|
| 291 |
|
| 292 |
-
**Where it stands (audited 2026-08-19).**
|
| 293 |
-
|
| 294 |
-
|
|
|
|
|
|
|
| 295 |
Measured on the BUMA standard (9 pages): GLiNER Indonesian recall **0.854** (kill line 0.70, PASS —
|
| 296 |
this closes the context doc's headline open risk), clustering compression **2.56×** (kill 2.0, PASS),
|
| 297 |
nano schema-fill precision **0.75** (kill 0.80, **FAIL**), span-validation rejection **1.00** with
|
|
@@ -299,18 +301,22 @@ nano schema-fill precision **0.75** (kill 0.80, **FAIL**), span-validation rejec
|
|
| 299 |
|
| 300 |
| # | Task | Owner | Status | Note |
|
| 301 |
|---|---|---|---|---|
|
| 302 |
-
|
|
| 303 |
-
|
|
| 304 |
-
|
|
| 305 |
-
|
|
| 306 |
-
|
|
| 307 |
-
|
|
| 308 |
-
|
|
| 309 |
-
|
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
|
| 315 |
## 5. Critical path & sequencing
|
| 316 |
|
|
@@ -336,8 +342,8 @@ first milestone); and GLiNER + LangExtract ensembling stays ⏸️ flagged as sc
|
|
| 336 |
- **W3 re-open timing** (deferred `analyze_*` activation) — Rifqi (deferred at the 2026-07-13 approval).
|
| 337 |
- **INV-6 relaxation for S1b targeted repair** — team, only after S1a `repair_candidate` telemetry
|
| 338 |
shows a meaningful hit-rate (SPINE_V2_PLAN §6).
|
| 339 |
-
- **Knowledge pipeline —
|
| 340 |
-
|
| 341 |
-
- **Knowledge pipeline — admin ingest surface** (§0.8
|
| 342 |
auth posture. A write surface triggered by an admin is a different risk class from the current
|
| 343 |
unauthenticated read-only chat surface (§0.7) — raise it, do not inherit the posture by default.
|
|
|
|
| 289 |
(Rifqi)** — meeting only at a **persisted, versioned parsed-document artifact**. Extraction never
|
| 290 |
takes a file path; that constraint is the whole point of the split.
|
| 291 |
|
| 292 |
+
**Where it stands (audited 2026-08-19).** A working **prototype** (`kex`, ~2,763 LOC, 21 passing
|
| 293 |
+
tests, 8-command CLI, four experiments against real Azure calls) exists outside version control at
|
| 294 |
+
`Documents/Dataeyond_catalog/knowledge-pipeline/`. It is **not the product and will not be ported** —
|
| 295 |
+
it answered the design questions; extraction v2 gets rebuilt in this repo. What carries forward is
|
| 296 |
+
the *evidence* (gold sets, experiment results) and the *tuned constants*, not the code.
|
| 297 |
Measured on the BUMA standard (9 pages): GLiNER Indonesian recall **0.854** (kill line 0.70, PASS —
|
| 298 |
this closes the context doc's headline open risk), clustering compression **2.56×** (kill 2.0, PASS),
|
| 299 |
nano schema-fill precision **0.75** (kill 0.80, **FAIL**), span-validation rejection **1.00** with
|
|
|
|
| 301 |
|
| 302 |
| # | Task | Owner | Status | Note |
|
| 303 |
|---|---|---|---|---|
|
| 304 |
+
| P1 | Freeze the `kex` prototype (read-only snapshot) | Rifqi | ⬜ | No `.git`. ~2,763 LOC + both gold sets + all four experiment results on one machine. It is an archive, **not** a codebase to develop — v2 is a rebuild here, not a port |
|
| 305 |
+
| P2 | Harvest the prototype's tuned constants | Rifqi | ⬜ | Every number that took runs to find, with its reason, **before** v2 code: rapidfuzz 92, fuzzy off below 5 chars (`PA`/`UA` collide), K=3, the evidence weight table, the 1024-token prefix pad (below it, caching does not engage — worth the measured 54% hit rate), label + cue sets, the 4 prompts |
|
| 306 |
+
| P3 | Migrate the evidence into `eval/knowledge/` | Rifqi | ⬜ | Gold sets (41 terms + 15 rules) + scorer + experiment runners, following the existing `eval/{intent,help,readiness,planner}/` convention. **Before any v2 pipeline code** — without a standing baseline, "improved" is unfalsifiable |
|
| 307 |
+
| S1 | Agree the parsed-artifact schema | Sofhia + Rifqi | 🔄 | Sofhia's draft `contracts.py` (2026-08-19) adopted as the base. Open: document-level envelope (no home for `version`), the section-structure claim, `source_wording` |
|
| 308 |
+
| S5 | Keep the seam off the file path | Sofhia + Rifqi | ⬜ | The prototype's boundary was `Parser.parse(path) -> list[Page]` — the shape the plan forbids. Settle **before** the MinerU parser is built against it |
|
| 309 |
+
| X* | Build extraction v2 in this repo | Rifqi | ⬜ | 17 stages, `src/query/`-shaped. Design is validated by the prototype; **none of it exists here yet** — see the two-column table in the checklist |
|
| 310 |
+
| D2 | Artifact + candidate-entry persistence | Rifqi → Harry | ⬜ | Parsed artifacts, candidate entries, glossary versions + approval audit trail. Go owns dedorch DDL → one consolidated Harry-ready handoff, never DDL from Python (§2.2) |
|
| 311 |
+
| R1 | Fix the literal-source-wording violation | Rifqi | ⬜ | A locked decision is violated: the BUMA heading reads "Physical **of** Availability (PA)"; the extracted entry carries "Physical Availability". The discrepancy is meant to reach the expert |
|
| 312 |
+
| D3 | Provision a mini deployment | Rifqi | ⛔ | Only nano is provisioned, so all four branches route there — including `rule` and `summary`, whose failure mode is least detectable. Blocks the nano-vs-mini A/B |
|
| 313 |
+
| D6 | Expert review of the gold set | Rifqi → Mas Beta | ⛔ | The 41-term / 15-rule gold set is self-bootstrapped and provisional. Now the baseline v2 is measured against |
|
| 314 |
+
| D5 | v2 module name | Rifqi | ⬜ | `src/knowledge/` is taken by the existing OCR→pgvector service. Pick a non-colliding name before the first commit |
|
| 315 |
+
|
| 316 |
+
**Not decided, do not assume:** the v2 module name (D5); whether the admin ingest surface is HTTP
|
| 317 |
+
at all (the offline script path is the honest first milestone); and GLiNER + LangExtract ensembling
|
| 318 |
+
stays ⏸️ flagged as scope creep. **`kex` is not being ported** — it is a prototype that answered the
|
| 319 |
+
design questions; v2 is built here against this repo's patterns.
|
| 320 |
|
| 321 |
## 5. Critical path & sequencing
|
| 322 |
|
|
|
|
| 342 |
- **W3 re-open timing** (deferred `analyze_*` activation) — Rifqi (deferred at the 2026-07-13 approval).
|
| 343 |
- **INV-6 relaxation for S1b targeted repair** — team, only after S1a `repair_candidate` telemetry
|
| 344 |
shows a meaningful hit-rate (SPINE_V2_PLAN §6).
|
| 345 |
+
- **Knowledge pipeline — v2 module name** (§0.8 D5): `src/knowledge/` is already the OCR→pgvector
|
| 346 |
+
service, so extraction v2 needs its own module name before the first commit.
|
| 347 |
+
- **Knowledge pipeline — admin ingest surface** (§0.8 D4): whether it is HTTP at all, and if so its
|
| 348 |
auth posture. A write surface triggered by an admin is a different risk class from the current
|
| 349 |
unauthenticated read-only chat surface (§0.7) — raise it, do not inherit the posture by default.
|
|
@@ -1,274 +1,201 @@
|
|
| 1 |
-
# Knowledge Pipeline — Task Checklist
|
| 2 |
|
| 3 |
-
**Date:** 2026-08-19 · **
|
| 4 |
-
**
|
| 5 |
-
|
| 6 |
**Target repo:** `Agentic-Service-Data-Eyond-Catalog` (Python agentic service)
|
| 7 |
|
| 8 |
**Status legend:** ⬜ not started · 🔄 in progress · ✅ done · ⛔ blocked · 🔎 built but unverified · ⏸️ deferred
|
| 9 |
|
| 10 |
-
> **Headline: the extraction half is not a greenfield build.** A working POC already exists —
|
| 11 |
-
> 2,763 LOC across all nine stages, 21 passing tests, four experiments run against real Azure
|
| 12 |
-
> calls on the BUMA standard, and a metrics pack. Most of section C below is ✅ or 🔎, not ⬜.
|
| 13 |
-
> The remaining work is **porting, hardening and the seam**, not building the pipeline.
|
| 14 |
-
>
|
| 15 |
-
> Nothing here is agreed yet. Please push back on section B (yours) and section A (both of ours).
|
| 16 |
-
|
| 17 |
---
|
| 18 |
|
| 19 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
-
validate → diff → queue`, driven by an 8-command CLI where every stage reads and writes JSON, so
|
| 23 |
-
extraction can be re-run without re-running GLiNER.
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|---|---|
|
| 29 |
-
| Pages | 9 |
|
| 30 |
-
| Chunks (numbered-heading split) | 13 |
|
| 31 |
-
| Mentions (after noise filter; 195 raw) | 169 |
|
| 32 |
-
| Clusters | 66 |
|
| 33 |
-
| Glossary entries | 66 |
|
| 34 |
-
| Review-queue items | 66 |
|
| 35 |
|
| 36 |
| Experiment | Question | Result | Kill line | Verdict |
|
| 37 |
|---|---|---|---|---|
|
| 38 |
| **E1** | GLiNER recall on Bahasa Indonesia technical prose | **0.854** | 0.70 | **PASS** |
|
| 39 |
-
| **E2** | Does clustering cut review burden? | **2.56×** (169 → 66) | 2.0× | **PASS** |
|
| 40 |
| **E3** | Is nano sufficient for schema fill? | **0.75** | 0.80 | **FAIL** |
|
| 41 |
| **E4** | Does verbatim-span validation catch fabrication? | **1.00** (false-rejection 0.00) | 0.90 | **PASS** |
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
|
| 47 |
-
**
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
to nano**, including `rule` and `summary`, which the spec explicitly wanted on the larger tier.
|
| 57 |
|
| 58 |
-
**Caveat
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
2.56× is the measurement. Quote the right one to the right audience.
|
| 62 |
|
| 63 |
---
|
| 64 |
|
| 65 |
-
##
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
`kex/models.py` already defines `ParseResult` / `Chunk`, which is close to the artifact we need —
|
| 71 |
-
persistable, content-hashed, carrying section numbers, page ranges and formatting signals:
|
| 72 |
-
|
| 73 |
-
```python
|
| 74 |
-
class Chunk(BaseModel):
|
| 75 |
-
chunk_id: str # f"{doc_id}#{section_no}#{ordinal}"
|
| 76 |
-
doc_id: str
|
| 77 |
-
section_no: str | None # "2.1.3"
|
| 78 |
-
heading: str | None
|
| 79 |
-
text: str
|
| 80 |
-
page_start: int; page_end: int
|
| 81 |
-
ordinal: int
|
| 82 |
-
has_formula: bool; is_tabular: bool
|
| 83 |
-
bold_spans: list[str] # feeds the formatting evidence signal
|
| 84 |
-
|
| 85 |
-
class ParseResult(BaseModel):
|
| 86 |
-
doc_id: str; source_path: str; content_hash: str
|
| 87 |
-
n_pages: int; chunks: list[Chunk]
|
| 88 |
-
used_heading_split: bool
|
| 89 |
-
```
|
| 90 |
-
|
| 91 |
-
**But the POC's actual seam is the wrong one.** `kex/parse/base.py` defines the boundary as an
|
| 92 |
-
in-process protocol, `Parser.parse(path: Path) -> list[Page]`, where `Page` is `{page_no, text,
|
| 93 |
-
bold_spans}`. That takes a **file path** — the one shape the plan forbids — it is an in-process
|
| 94 |
-
interface rather than a persisted versioned artifact, and its flat `text` field discards exactly
|
| 95 |
-
what MinerU exists to produce.
|
| 96 |
|
| 97 |
| # | Task | Owner | Status | Description |
|
| 98 |
|---|---|---|---|---|
|
| 99 |
-
|
|
| 100 |
-
|
|
| 101 |
-
|
|
| 102 |
-
|
|
| 103 |
-
| A3 | Artifact versioning + storage | Sofhia + Rifqi | ⬜ | Version number, what triggers a re-parse, where it persists. Today `kex` writes `data/interim/*.chunks.json` with a content-hash cache — real but file-based and unversioned. Needs a DB table → Harry-ready DDL handoff (Go owns the schema; Python never runs DDL). |
|
| 104 |
-
| A4 | MinerU → `ParseResult` adapter | Rifqi (provisional) → Sofhia (owns) | ⬜ | Reads a MinerU output folder and emits `ParseResult` **directly, skipping `list[Page]`**. Routing MinerU through `Page.text` flattens typed blocks, LaTeX and table HTML back to a string, then asks the chunker to re-derive by regex what MinerU had already structured. See §B for which files to read. |
|
| 105 |
-
| A5 | Commit fixture artifacts | Rifqi | 🔄 | Both source PDFs are already in `kex/data/raw/` (BUMA standard + Open Pit textbook) and parsed chunk JSON exists in `data/interim/`. Needs promoting to committed fixtures in the target repo. |
|
| 106 |
-
| A6 | Write the seam contract into the repo docs | Rifqi | ⬜ | Extraction never takes a file path; neither half imports the other's internals; parsing stays swappable behind the artifact. So the rule survives us forgetting it — the POC drifted from it already. |
|
| 107 |
-
|
| 108 |
-
### A1 review notes — Sofhia's `contracts.py` draft (2026-08-19)
|
| 109 |
-
|
| 110 |
-
**Adopt as the A1 base.** It is derived from real MinerU output rather than designed on paper, and
|
| 111 |
-
three of its choices are better than what kex has: the `kind` discriminator, `source_items`
|
| 112 |
-
(index back-references into `content_list.json`, which kex has no equivalent of), and the explicit
|
| 113 |
-
**verbatim-text warning** — which independently identifies the same failure mode as our span-check
|
| 114 |
-
control, including that it fails *silently* to null and looks like a bad LLM.
|
| 115 |
-
|
| 116 |
-
| # | Item | Severity | Note |
|
| 117 |
-
|---|---|---|---|
|
| 118 |
-
| R1 | Document-level envelope missing | **High** | See A1a. No home for `version`, and A3 needs one |
|
| 119 |
-
| R2 | Section-structure claim | **High** | See A1b. True for the handbook, not for the BUMA standard |
|
| 120 |
-
| R3 | Formatting signal dropped | Medium | kex's evidence ranking uses six signals — cue · heading · legend · formula · **formatting** · first-occurrence — and reads formatting from `Chunk.bold_spans`. The draft excludes it (rightly, as `text_format` is `vlm`-only). Either derive it from markdown emphasis in `text`, or drop the signal deliberately. It should not disappear by silence |
|
| 121 |
-
| R4 | Page indexing | Medium | Draft is 0-based "as MinerU reports"; kex is 1-based, and page numbers are shown to Mas Beta in the review queue. Off-by-one straight into the expert-facing UI. Pick one and name the field accordingly (`page_idx` if 0-based) |
|
| 122 |
-
| R5 | Chunk granularity unspecified | Medium | The sample chunk `handbook::0000` spans 32 source items across 2 pages. kex caps ~1,500 tokens with paragraph-boundary splitting. Coarse chunks make evidence ranking blunt (top-3 chunks = many tokens per call) and inflate the summary branch's token share, which §5 already names as the quiet cost risk |
|
| 123 |
-
| R6 | `chapter` is lossy across boundaries | Low | `handbook::0000` covers pages [0,1] and is labelled *"THE HORIZONS OF MAINTENANCE MANAGEMENT"*, while `handbook::0001` on page 1 is *"MAINTENANCE STORES AND INVENTORY CONTROL"*. A chunk that crosses a chapter boundary gets one label. Fine if known |
|
| 124 |
-
| R7 | `TermRecord` is thinner than kex's entry | Low | Marked "declared, not yet used", so no conflict — but point it at `kex/models.py` rather than re-deriving. kex additionally carries `interpretation`, `domain`, `company`, `language`, `definition_conflict`, `conflict_variants`, `diff_status`, and a structured `provenance{doc_id, section_no, page, span}` |
|
| 125 |
-
| R8 | dataclass vs pydantic | Low | kex and the target repo are both pydantic v2. A seam without validation is weakly a seam |
|
| 126 |
-
| R9 | **Add `source_wording` now** | Medium | The contract is open, so this is the cheap moment to fix **G1**. With `heading` carried verbatim, a `source_wording` field span-checked against it makes *"Physical of Availability"* survive instead of being silently normalised to *"Physical Availability"* |
|
| 127 |
|
| 128 |
---
|
| 129 |
|
| 130 |
-
##
|
| 131 |
|
| 132 |
-
|
| 133 |
-
real MinerU
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
| `*_content_list_v2.json` | **Primary** | Page-grouped; richest taxonomy: `paragraph` · `title` (with `level`) · `equation_interline` (LaTeX + image ref) · `table` (HTML + image ref) |
|
| 140 |
-
| `*_content_list.json` (v1) | **Secondary** | Flat, but carries explicit `page_idx` and `text_level`. v2 encodes page only as outer list position — cross-check the two |
|
| 141 |
-
| `*_middle.json` | Later, if needed | Line/span-level bbox. Only if the review UI wants to highlight a quote on the page image |
|
| 142 |
-
| `images/` | **Reference only** | Hash-named crops. The LLM stage is text-only — the artifact carries paths, never bytes |
|
| 143 |
-
| `*.md` | **Do not consume** | A rendering. Loses `page_idx` and block-type boundaries; its image paths couple us to the folder layout |
|
| 144 |
-
| `*_model.json`, `*_layout.pdf`, `*_span.pdf`, `*_origin.pdf` | Ignore | Raw model output and debug visualisations |
|
| 145 |
|
| 146 |
| # | Task | Owner | Status | Description |
|
| 147 |
|---|---|---|---|---|
|
| 148 |
-
| B1 | MinerU cost justification (written) | Sofhia | ⬜ | Owed for the budget proposal, from the 13 Aug exec review
|
| 149 |
-
| B2 | MinerU install + run on
|
| 150 |
-
| B3 | Evaluate MinerU quality vs. the current path | Sofhia | ⬜ | Compare
|
| 151 |
-
| B4 | Indonesian check on **parsing** | Sofhia | ⬜ | Distinct from E1, which measured GLiNER, not the parser. Confirm MinerU's text
|
| 152 |
-
| B5 | Section-aware chunking | Sofhia |
|
| 153 |
-
| B6 | Emit the agreed artifact
|
| 154 |
-
| B7 | Fallback path behind the same artifact | Sofhia |
|
| 155 |
-
| B8 |
|
| 156 |
|
| 157 |
**Findings from the real MinerU output that affect B5/B6:**
|
| 158 |
|
| 159 |
- **Heading depth is flattened** — all 29 `title` blocks carry `"level": 2`, including `1. TUJUAN
|
| 160 |
PARAMETER` and `2.1.3. Physical of Availability (PA)`. Hierarchy must come from the numbering
|
| 161 |
-
text, never from `level`.
|
| 162 |
- **Equation LaTeX is character-spaced** — `P r o d u c t i o n = M O H H \times Q t y \times P A
|
| 163 |
\times U A \times P t y`. Naive matching will never find `MOHH`. Needs normalising before the
|
| 164 |
-
formula/legend branch and before span validation can locate anything inside a formula.
|
| 165 |
- **Breadcrumb headings repeat per page** — `2. PENJELASAN PARAMETER` / `2.1. Production Parameter`
|
| 166 |
-
re-appear as `title` blocks on pages 2–8.
|
| 167 |
-
(`_boilerplate_keys`); whatever emits the artifact must not treat them as new sections.
|
| 168 |
- **Charts carry captions only** — all 3 chart items have `content: ""`, with the caption in
|
| 169 |
-
`chart_caption`
|
| 170 |
-
|
| 171 |
-
detection buys Scenario 2, not glossary coverage.
|
| 172 |
|
| 173 |
---
|
| 174 |
|
| 175 |
-
##
|
| 176 |
|
| 177 |
-
|
|
|
|
|
|
|
| 178 |
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
| C1 | Candidate-entry schema | ✅ | `models.py` (222 LOC) encodes all three invariants: content fields Optional, `SubdomainEnum`, `Provenance.span` mandatory |
|
| 184 |
-
| C2 | Offline runner over a fixture | ✅ | 8-command CLI; every stage reads/writes JSON; content-hash cache; `--dry-run` prints exact prompts + token estimate with zero network calls |
|
| 185 |
-
| C3 | GLiNER span filter | ✅ | `filters/gliner_filter.py`, `urchade/gliner_multi-v2.1`, CPU, every chunk, labels in YAML |
|
| 186 |
-
| C4 | **Indonesian performance check** | ✅ | **E1 = 0.854 recall** vs 0.70 kill line, on the BUMA standard, 41-term gold set. The context doc's headline open risk, now measured |
|
| 187 |
-
| C5 | Discourse-cue regex → rule-of-thumb | ✅ | `filters/cue_filter.py`; cues in `config/cues.yaml`; 7 rule entries produced |
|
| 188 |
-
| C6 | Legend-block regex → formula vars | ✅ | `filters/legend_filter.py` + a ≥0.95-precision test; 7 formula entries produced |
|
| 189 |
-
| C7 | Section pass → summary units | ✅ | `extract/summary.py`; produced `brief_context.json` + `.md` |
|
| 190 |
-
| C8 | Normalise + cluster mentions | ✅ | `cluster/` — exact → abbrev → rapidfuzz ≥92; fuzzy off below 5 chars because `PA`/`UA` score highly against each other. **E2 = 2.56×** |
|
| 191 |
-
| C9 | Evidence ranking → top-K | ✅ | `rank/evidence.py`, weight table, K=3, full ranked list retained for escalation |
|
| 192 |
-
| C10 | LLM extraction call | ✅ | `extract/azure_client.py` + 4 branch modules; structured outputs via `json_schema`; prompts as files; prefix padded past 1024 tokens for caching (**54% hit rate measured**) |
|
| 193 |
-
| C11 | Verbatim-span validation | ✅ | `validate/span_check.py`; never repairs a failed span. **E4 = 1.00 rejection, 0.00 false-rejection** |
|
| 194 |
-
| C16 | Frequency-sorted review queue | ✅ | `queue/review_queue.py`; conflicts promoted above frequency. Queue top: Qty(20) · PA(16) · PTY(12) · UA(11) — exactly the terms that carry the document |
|
| 195 |
-
|
| 196 |
-
### C-2. Built but not exercised — needs verification before we trust it
|
| 197 |
-
|
| 198 |
-
| # | Task | Status | What the audit found |
|
| 199 |
-
|---|---|---|---|
|
| 200 |
-
| C12 | Null-definition escalation | 🔎 | Implemented and wired, but **`escalated = 0`** across the whole run. Not a bug: **54 of 66 clusters have exactly 1 evidence chunk** and K=3, so `rounds_available()` correctly returns 0 — the document is too small to reach the path. Unexercised until a larger corpus runs |
|
| 201 |
-
| C13 | Conflict detection | 🔎 | `validate/conflict.py` wired, but **0 conflicts** observed — a single internally-consistent standard gives it nothing to find. Untested against real disagreement |
|
| 202 |
-
| C15 | Diff vs. active glossary version | 🔎 | `diff/glossary_diff.py` is wired, but `write_artifacts` reads `existing` from `out/glossary.json` — **the same file it then overwrites**. All 66 entries came back `new`. Only the empty-baseline path has ever run; there is no version history, no active-version pointer, and no approval state |
|
| 203 |
|
| 204 |
-
#
|
| 205 |
-
|
| 206 |
-
| # | Task | Status | Description |
|
| 207 |
-
|---|---|---|---|
|
| 208 |
-
| C14 | **Literal-source-wording behaviour** | ⛔ | **Currently violated — see §G1.** |
|
| 209 |
-
| C17 | Bulk vs. incremental ingest modes | ⬜ | Single-document CLI only. Neither corpus-frequency boosting (bulk) nor active-version diffing (incremental) exists |
|
| 210 |
-
| C18 | Candidate-entry persistence | ⬜ | Everything is JSON files in `out/`. Needs tables → Harry-ready DDL handoff, same rule as A3 |
|
| 211 |
-
| C19 | Port `kex` into the target repo | ⬜ | 2,763 LOC, Python 3.13 + its own venv vs. the target repo's 3.12; new deps (GLiNER + torch). Sequencing and gating to be decided |
|
| 212 |
-
|
| 213 |
-
---
|
| 214 |
-
|
| 215 |
-
## D. Decisions needed
|
| 216 |
-
|
| 217 |
-
| # | Decision | Owner | Status | Description |
|
| 218 |
|---|---|---|---|---|
|
| 219 |
-
|
|
| 220 |
-
|
|
| 221 |
-
|
|
| 222 |
-
|
|
| 223 |
-
|
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
---
|
| 227 |
|
| 228 |
-
##
|
| 229 |
|
| 230 |
-
| # |
|
| 231 |
|---|---|---|---|---|
|
| 232 |
-
|
|
| 233 |
-
|
|
| 234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
|
| 236 |
---
|
| 237 |
|
| 238 |
-
##
|
| 239 |
-
|
| 240 |
-
1. The LLM call unit is the **term cluster**. Justification is review-burden reduction, **not** tokens.
|
| 241 |
-
2. **No standalone relevance gate.** "Zero candidate spans" is the relevance signal.
|
| 242 |
-
3. **`provenance.span` is mandatory and verbatim.** Never repaired — a repaired span is an unfalsifiable claim.
|
| 243 |
-
4. **All content fields Optional.** Abstention is correct behaviour, not an error.
|
| 244 |
-
5. **API cost is negligible; latency irrelevant.** Measured: $0.0069 for 9 pages. Optimise the review experience, not tokens.
|
| 245 |
-
6. **This is a test integration, not a productionisation.** Both halves are new, gated subsystems.
|
| 246 |
-
7. **Nothing is ever dropped at the term level.** Evidence ranking narrows *evidence*, not terms.
|
| 247 |
-
|
| 248 |
-
---
|
| 249 |
-
|
| 250 |
-
## G. Defects and risks found by the audit
|
| 251 |
|
| 252 |
| # | Finding | Severity | Detail |
|
| 253 |
|---|---|---|---|
|
| 254 |
-
| **
|
| 255 |
-
| **
|
| 256 |
-
| **
|
| 257 |
-
| **
|
| 258 |
-
| **
|
| 259 |
-
| **
|
| 260 |
|
| 261 |
---
|
| 262 |
|
| 263 |
-
##
|
| 264 |
-
|
| 265 |
-
1.
|
| 266 |
-
2. **
|
| 267 |
-
3. **
|
| 268 |
-
4. **
|
| 269 |
-
5. **
|
| 270 |
-
6. **
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
|
|
|
| 1 |
+
# Knowledge Pipeline — Task Checklist
|
| 2 |
|
| 3 |
+
**Date:** 2026-08-19 · **Owner of this doc:** Rifqi · **Shared with:** Sofhia
|
| 4 |
+
**Intent + rationale:** [knowledge_pipeline_context.md](knowledge_pipeline_context.md)
|
| 5 |
+
**Tracked in:** [DEV_PLAN.md](DEV_PLAN.md) §0.8
|
| 6 |
**Target repo:** `Agentic-Service-Data-Eyond-Catalog` (Python agentic service)
|
| 7 |
|
| 8 |
**Status legend:** ⬜ not started · 🔄 in progress · ✅ done · ⛔ blocked · 🔎 built but unverified · ⏸️ deferred
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
+
## How to read this
|
| 13 |
+
|
| 14 |
+
The pipeline splits into two independently runnable halves that meet at **one persisted, versioned
|
| 15 |
+
parsed-document artifact**: **parsing** (Sofhia, MinerU) and **knowledge extraction** (Rifqi).
|
| 16 |
+
Extraction never takes a file path — that constraint is the point of the split, because it keeps the
|
| 17 |
+
parser swappable and lets extraction be re-run cheaply on an already-parsed corpus.
|
| 18 |
+
|
| 19 |
+
**The extraction half has a working prototype (`kex`) that is not the product.** It lives outside
|
| 20 |
+
this repo, is not under version control, and will **not** be ported. It answered the design
|
| 21 |
+
questions; v2 gets rebuilt here against this repo's patterns. That distinction drives the whole
|
| 22 |
+
structure below — §2 disposes of the prototype, §5 builds v2, and §5 deliberately separates *design
|
| 23 |
+
validated* from *implemented here*, because the two are very different amounts of work.
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
|
| 27 |
+
## 1. What the prototype already proved
|
|
|
|
|
|
|
| 28 |
|
| 29 |
+
`kex` — ~2,763 LOC, 21 passing tests, 8-command CLI, four experiments against real Azure calls.
|
| 30 |
+
Measured on the BUMA standard `STD_2026_006_MNO` (9 pages, majority Bahasa Indonesia), run
|
| 31 |
+
`20260813-145132`.
|
| 32 |
|
| 33 |
+
**Funnel:** 9 pages → 13 chunks → 169 mentions (195 raw) → 66 clusters → 66 entries → 66 queue items.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
| Experiment | Question | Result | Kill line | Verdict |
|
| 36 |
|---|---|---|---|---|
|
| 37 |
| **E1** | GLiNER recall on Bahasa Indonesia technical prose | **0.854** | 0.70 | **PASS** |
|
| 38 |
+
| **E2** | Does clustering cut expert review burden? | **2.56×** (169 → 66) | 2.0× | **PASS** |
|
| 39 |
| **E3** | Is nano sufficient for schema fill? | **0.75** | 0.80 | **FAIL** |
|
| 40 |
| **E4** | Does verbatim-span validation catch fabrication? | **1.00** (false-rejection 0.00) | 0.90 | **PASS** |
|
| 41 |
|
| 42 |
+
Also measured: **$0.0069** for the document · 155,313 prompt tokens of which **125,184 cached**
|
| 43 |
+
(54% hit rate) · 242 s wall clock · 3 fields rejected by span check · 56 of 66 entries returned no
|
| 44 |
+
definition · 0 escalations · 0 definition conflicts.
|
| 45 |
|
| 46 |
+
**Two results that settle open questions:**
|
| 47 |
|
| 48 |
+
- **E1 closes the Indonesian risk.** The context doc names GLiNER's unverified Indonesian
|
| 49 |
+
performance as the headline unknown. It is now measured, on the exact document named as the test
|
| 50 |
+
case, comfortably above the kill line.
|
| 51 |
+
- **E3 half-settles the model tier.** nano **fails** at 0.75 against a 0.80 line. Caveats: the
|
| 52 |
+
scoreable base is 8 entries, and nano is not deterministic at `temperature=0` (consecutive
|
| 53 |
+
identical runs gave 0.75 and 0.625). No mini deployment exists, so the intended nano-vs-mini A/B
|
| 54 |
+
has never run and all four branches currently route to nano.
|
|
|
|
| 55 |
|
| 56 |
+
**Caveat when quoting E2.** The context doc's **6.2×** is a 1,000-page projection; **2.56×** is the
|
| 57 |
+
9-page measurement. Compression grows as terms repeat across a corpus. Both are true — quote the
|
| 58 |
+
right one to the right audience.
|
|
|
|
| 59 |
|
| 60 |
---
|
| 61 |
|
| 62 |
+
## 2. Prototype disposition — do this before building v2
|
| 63 |
+
|
| 64 |
+
`kex` is three different assets with three different fates. Treating it as one thing is how you
|
| 65 |
+
either port scaffolding you didn't want or discard measurements you can't recreate.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
| # | Task | Owner | Status | Description |
|
| 68 |
|---|---|---|---|---|
|
| 69 |
+
| **P1** | **Freeze the prototype** | Rifqi | ⬜ | It has no `.git`. ~2,763 LOC, both gold sets and all four experiment results exist on exactly one machine. Snapshot it read-only — zip, orphan branch or private repo, whichever is least ceremony. It is an archive, not a codebase to develop |
|
| 70 |
+
| **P2** | **Harvest the tuned constants** | Rifqi | ⬜ | Every number that took runs to find, written down with its reason, **before** v2 code. Known set: rapidfuzz threshold **92**; fuzzy matching **off below 5 characters** (because `PA` and `UA` score highly against each other); evidence **K=3**; the six-signal evidence weight table; the **1024-token prefix pad** (below that threshold OpenAI-family caching does not engage at all — worth the measured 54% hit rate); `labels.yaml` GLiNER label set; `cues.yaml` discourse cues; the 4 prompt files. None of this is in the spec; it lives only as constants and comments, and a clean rewrite will rediscover them as production defects |
|
| 71 |
+
| **P3** | **Migrate the evidence into `eval/knowledge/`** | Rifqi | ⬜ | Gold sets (41 terms + 15 rules) + scorer + the four experiment runners. Follows the existing convention exactly — `eval/{intent,help,readiness,planner}/` each carry `README.md`, `<area>_dataset.json`, `run_eval.py`, and `results/<name>_result_YYYY-MM-DD_HHMMSS.json` files that are never overwritten. **Do this before any v2 pipeline code**: without a standing baseline, "new and improved" is unfalsifiable, and given E3 already failed, quality is exactly what this work will be judged on |
|
| 72 |
+
| **P4** | **Record the negative findings** | Rifqi | ⬜ | Things v2 must not re-derive: nano fails schema fill; escalation is unreachable on small documents (54 of 66 clusters had exactly 1 evidence chunk against K=3, so the loop correctly never fired); 85% of entries returned no definition; conflict detection had nothing to find in a single internally-consistent standard |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
---
|
| 75 |
|
| 76 |
+
## 3. The seam — shared with Sofhia
|
| 77 |
|
| 78 |
+
**Status: under active discussion.** Sofhia circulated a draft `contracts.py` on 2026-08-19,
|
| 79 |
+
derived from real MinerU output rather than designed on paper, and it is adopted as the base. Three
|
| 80 |
+
of its choices improve on the prototype: a `kind` discriminator (text/table/chart/equation),
|
| 81 |
+
`source_items` index back-references into MinerU's `content_list.json`, and an explicit
|
| 82 |
+
verbatim-text warning that independently identifies the same failure mode as the span-check control
|
| 83 |
+
— including that it fails *silently to null* and looks like a bad LLM.
|
| 84 |
|
| 85 |
+
| # | Task | Owner | Status | Description |
|
| 86 |
+
|---|---|---|---|---|
|
| 87 |
+
| **S1** | Agree the parsed-artifact schema | Sofhia + Rifqi | 🔄 | Under discussion. Everything else in this section is an open item against her draft |
|
| 88 |
+
| **S2** | Add a document-level envelope | Sofhia + Rifqi | 🔄 | The draft is `list[Chunk]` with no wrapper, so there is nowhere for `content_hash`, `n_pages`, `source_path`, or **`version`** — and a versioned artifact is a plan requirement. Proposed: `ParsedDocument{doc_id, source_path, content_hash, version, n_pages, parser_name, parser_version, chunks}`. Parser identity matters *because* the seam exists to make parsers swappable: when extraction output shifts, we must be able to tell a parser change from a prompt change |
|
| 89 |
+
| **S3** | Correct the section-structure claim | Rifqi → Sofhia | 🔄 | The draft states MinerU does not supply section structure (`text_level` on 1 of 84 items). **On the BUMA standard it does:** the full 9-page parse carries `text_level` on **29 of 118 items**, cleanly numbered `1.` → `2.2.5`, including `2.1.3. Physical of Availability (PA)`. Her sample appears to be the handbook (its `doc_id` is `handbook`, and it has item types `page_number`/`header` absent from the BUMA parse), which genuinely has no numbered headings. Keeping both fields Optional is correct; the stated *reason* is not, and acting on it would starve section-aware chunking on exactly the documents that carry numbering — i.e. every client standard |
|
| 90 |
+
| **S4** | Add `source_wording` while the contract is open | Rifqi → Sofhia | 🔄 | Cheap moment to fix the defect in §7. With `heading` carried verbatim, a `source_wording` field span-checked against it makes *"Physical **of** Availability"* survive instead of being silently normalised |
|
| 91 |
+
| **S5** | Keep the seam off the file path | Sofhia + Rifqi | ⬜ | The prototype's boundary was `Parser.parse(path) -> list[Page]` — the one shape the plan forbids. v2 must not reproduce it. Must be settled **before** the MinerU parser is built against it, or it becomes a rewrite of Sofhia's work |
|
| 92 |
+
| **S6** | Minor contract items (deferred to review) | Rifqi → Sofhia | ⏸️ | Raised but not blocking: **(a)** the formatting evidence signal — ranking uses six signals including formatting, read from `bold_spans`; the draft rightly excludes it as backend-specific, so either derive it from markdown emphasis or drop it deliberately, not by silence. **(b)** page indexing — draft is 0-based "as MinerU reports", prototype is 1-based, and page numbers are shown to the expert; name it `page_idx` if 0-based. **(c)** chunk-size policy — the sample chunk spans 32 source items across 2 pages; the prototype capped ~1,500 tokens. **(d)** `chapter` is lossy when a chunk crosses a chapter boundary. **(e)** pydantic over dataclass, matching this repo. **(f)** point `TermRecord` at the prototype's richer entry rather than re-deriving it |
|
| 93 |
+
| **S7** | Commit fixture artifacts | Rifqi | ⬜ | Both source PDFs exist (BUMA standard + Open Pit textbook). Promote a parsed artifact of each into the target repo as committed fixtures, so the extraction half is testable **with MinerU not installed at all** — the seam doing its job |
|
| 94 |
+
| **S8** | Write the seam rule into the repo docs | Rifqi | ⬜ | Extraction never takes a file path; neither half imports the other's internals; parsing stays swappable behind the artifact. The prototype drifted from this, so the rule needs to outlive our memory of it |
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
## 4. Parsing pipeline — Sofhia
|
| 99 |
|
| 100 |
+
MinerU replaces the current text+table-only extraction: it adds formula, image, chart and diagram
|
| 101 |
+
detection with type labels. Presented to the execs 13 Aug as the prerequisite for Scenario 2.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
| # | Task | Owner | Status | Description |
|
| 104 |
|---|---|---|---|---|
|
| 105 |
+
| **B1** | MinerU cost justification (written) | Sofhia | ⬜ | Owed for the budget proposal, from the 13 Aug exec review |
|
| 106 |
+
| **B2** | MinerU install + run on the sample docs | Sofhia | ✅ | Complete 9-page parse of the BUMA standard delivered 2026-08-19: **118 items** (90 text, 14 table, 11 equation, 3 chart), pages 0–8. Includes the rule-of-thumb grounding content (Other Activity, Controllable/Uncontrollable, Standby), so the fixture exercises all four extraction branches. Supersedes an earlier 5-page run |
|
| 107 |
+
| **B3** | Evaluate MinerU quality vs. the current path | Sofhia | ⬜ | Compare on tables, the p.2 legend block, formulas and figures — against the PyMuPDF baseline the prototype used, which has real numbers rather than a guess |
|
| 108 |
+
| **B4** | Indonesian check on **parsing** | Sofhia | ⬜ | Distinct from E1, which measured GLiNER, not the parser. Confirm MinerU's text and layout quality hold on Bahasa Indonesia |
|
| 109 |
+
| **B5** | Section-aware chunking | Sofhia | ⬜ | Split on numbered headings rather than fixed token windows. Acceptance test: the NSR definition in the Open Pit textbook (intro p.92, formula p.93, remark p.94) stays inside one semantic unit. The prototype's chunker solved this for the PyMuPDF path and is worth reading before rebuilding — it already handles the breadcrumb problem below |
|
| 110 |
+
| **B6** | Emit the agreed artifact | Sofhia | ⬜ | The parser writes our schema, not MinerU's native shape. This is what keeps MinerU swappable. Gated on §3 |
|
| 111 |
+
| **B7** | Fallback path behind the same artifact | Sofhia | ⬜ | Tesseract / Azure Document Intelligence emits the *same* artifact, so extraction never knows which parser ran |
|
| 112 |
+
| **B8** | Keep the parsing subsystem additive + gated | Sofhia | ⬜ | The existing unstructured path (`src/knowledge/`, Tesseract OCR → chunk → pgvector) stays untouched. MinerU is a new flag-gated path beside it |
|
| 113 |
|
| 114 |
**Findings from the real MinerU output that affect B5/B6:**
|
| 115 |
|
| 116 |
- **Heading depth is flattened** — all 29 `title` blocks carry `"level": 2`, including `1. TUJUAN
|
| 117 |
PARAMETER` and `2.1.3. Physical of Availability (PA)`. Hierarchy must come from the numbering
|
| 118 |
+
text, never from `level`. The heading *text* is supplied (see S3).
|
| 119 |
- **Equation LaTeX is character-spaced** — `P r o d u c t i o n = M O H H \times Q t y \times P A
|
| 120 |
\times U A \times P t y`. Naive matching will never find `MOHH`. Needs normalising before the
|
| 121 |
+
formula/legend branch, and before span validation can locate anything inside a formula.
|
| 122 |
- **Breadcrumb headings repeat per page** — `2. PENJELASAN PARAMETER` / `2.1. Production Parameter`
|
| 123 |
+
re-appear as `title` blocks on pages 2–8. They must not be treated as new sections.
|
|
|
|
| 124 |
- **Charts carry captions only** — all 3 chart items have `content: ""`, with the caption in
|
| 125 |
+
`chart_caption`. A chart chunk contributes a caption plus an image reference and nothing else.
|
| 126 |
+
Chart detection buys Scenario 2, not glossary coverage.
|
|
|
|
| 127 |
|
| 128 |
---
|
| 129 |
|
| 130 |
+
## 5. Extraction pipeline v2 — Rifqi, built in this repo
|
| 131 |
|
| 132 |
+
**Two separate columns, deliberately.** *Design* = validated by the prototype, with a number behind
|
| 133 |
+
it. *Build* = exists in this repo. The gap between them is the remaining engineering effort, and
|
| 134 |
+
collapsing the two is how a rebuild gets under-estimated.
|
| 135 |
|
| 136 |
+
**Structural target:** follow `src/query/` (typed models → validator → deterministic stages →
|
| 137 |
+
service facade → thin entry points), with the offline runner in `scripts/`. **Naming collision:**
|
| 138 |
+
`src/knowledge/` is already taken by the existing OCR→pgvector service — v2 needs its own module
|
| 139 |
+
name (`src/kex/`, `src/knowledge_extraction/`, TBD in §6).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
+
| # | Stage | Design | Build | Note |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|---|---|---|---|---|
|
| 143 |
+
| **X1** | Candidate-entry schema | ✅ | ⬜ | Three invariants to re-encode: all content fields Optional (a required field is a fabrication invitation), `subdomain_tags` as an enum, `provenance.span` mandatory. Pydantic v2 |
|
| 144 |
+
| **X2** | Offline runner over a fixture | ✅ | ⬜ | Script-driven run is the honest first milestone; HTTP is a convenience layer over it, never the reverse. Every stage reads/writes its own artifact so extraction re-runs without re-running GLiNER |
|
| 145 |
+
| **X3** | GLiNER span filter | ✅ | ⬜ | Runs on **all** chunks unconditionally. No standalone relevance gate — "zero candidate spans" is the relevance signal, produced as a byproduct of looking rather than a judgment made before looking |
|
| 146 |
+
| **X4** | Indonesian performance | ✅ **0.854** | n/a | Answered by E1. Re-measure once on v2 to confirm no regression |
|
| 147 |
+
| **X5** | Discourse-cue filter → rule-of-thumb | ✅ | ⬜ | Free CPU pass. Grounding case: Other Activity + the controllability hierarchy |
|
| 148 |
+
| **X6** | Legend-block filter → formula vars | ✅ | ⬜ | Grounding case: the MOHH/Qty/PA/UA/Pty legend block. Needs the LaTeX normalisation noted in §4 |
|
| 149 |
+
| **X7** | Section pass → summary units | ✅ | ⬜ | The quiet cost risk: few calls but ~¼ of all input tokens, because summarisation cannot be filtered — it needs whole documents |
|
| 150 |
+
| **X8** | Normalise + cluster mentions | ✅ **2.56×** | ⬜ | The LLM call unit is the **term cluster**. Carry the tuned constants from P2 |
|
| 151 |
+
| **X9** | Evidence ranking → top-K | ✅ | ⬜ | Six signals: cue · heading · legend · formula · formatting · first-occurrence. No term is ever dropped — ranking narrows *evidence*, not terms, and the full ranked list is retained for escalation |
|
| 152 |
+
| **X10** | LLM extraction call | ✅ | ⬜ | One call per term cluster, plus one per cue chunk, per unique formula, per document. Keep prompts as files with a byte-identical cached prefix |
|
| 153 |
+
| **X11** | Verbatim-span validation | ✅ **1.00** | ⬜ | A field whose span cannot be located is rejected, **never repaired** — a repaired span is an unfalsifiable claim, which is what the control exists to prevent |
|
| 154 |
+
| **X12** | Null-definition escalation | 🔎 | ⬜ | Built in the prototype but **never fired**: 54 of 66 clusters had exactly 1 evidence chunk against K=3, so the loop correctly had nowhere to escalate. Unverified until a larger corpus runs |
|
| 155 |
+
| **X13** | Conflict detection | 🔎 | ⬜ | Built but **0 conflicts found** — one internally-consistent standard gives it nothing. Untested against real disagreement. Note it only works *because* all evidence for a term arrives in one call |
|
| 156 |
+
| **X14** | Diff vs. active glossary version | 🔎 | ⬜ | The prototype diffed against the file it then overwrote, so only the empty-baseline path ever ran and every entry came back `new`. v2 needs a real active-version pointer |
|
| 157 |
+
| **X15** | Frequency-sorted review queue | ✅ | ⬜ | Sorted by mention count, conflicts promoted above frequency. Prototype queue topped out Qty(20) · PA(16) · PTY(12) · UA(11) — exactly the terms carrying the document |
|
| 158 |
+
| **X16** | Bulk vs. incremental ingest | ⬜ | ⬜ | Bulk setup ingest (corpus-frequency statistics as a candidate booster) and incremental single-file add (no corpus context; diff against the active version). Neither exists |
|
| 159 |
+
| **X17** | Persistence | ⬜ | ⬜ | Prototype wrote JSON files. v2 needs tables → see D2 |
|
| 160 |
|
| 161 |
---
|
| 162 |
|
| 163 |
+
## 6. Decisions needed
|
| 164 |
|
| 165 |
+
| # | Decision | Owner | Status | Description |
|
| 166 |
|---|---|---|---|---|
|
| 167 |
+
| **D1** | New dependencies | Rifqi (asks) | ⬜ | GLiNER + torch (heavy) for extraction; MinerU for parsing. `pyproject.toml` changes need sign-off. Prototype needed **torch ≥ 2.6** and hit a corrupt-wheel failure worth not re-debugging |
|
| 168 |
+
| **D2** | New tables (DDL handoff) | Rifqi → Harry | ⬜ | Parsed artifacts, candidate entries, glossary versions + approval audit trail. Go owns the dedorch schema — Python never executes DDL. One consolidated Harry-ready handoff beats three |
|
| 169 |
+
| **D3** | Model tier per branch | Rifqi | ⛔ | **Half-answered: nano FAILS at 0.75 vs 0.80.** Blocked on a **mini deployment being provisioned**. Until then all four branches run on nano — including rule and summary, whose failure mode is least detectable, since a plausible summary cannot be span-checked |
|
| 170 |
+
| **D4** | Admin ingest surface | Rifqi | ⬜ | Whether it is HTTP at all, and if so its auth posture. A write surface triggered by an admin is a different risk class from the current unauthenticated read-only chat surface — raise it, don't inherit the posture by default. Only after the offline path works |
|
| 171 |
+
| **D5** | v2 module name | Rifqi | ⬜ | `src/knowledge/` is taken by the existing OCR→pgvector service. Pick a non-colliding name before the first commit |
|
| 172 |
+
| **D6** | Expert review of the gold set | Rifqi → Mas Beta | ⛔ | The 41-term / 15-rule gold set is self-bootstrapped and provisional. E1 and E3 both move when it is reviewed, and E3's scoreable base is 8 entries — each adjudication shifts it ~12 points. Now *more* important than before: it is the baseline v2 gets measured against |
|
| 173 |
+
| **D7** | GLiNER + LangExtract ensembling | — | ⏸️ | Proposed, not decided; flagged as scope creep. Cheap in API terms but adds a reconciliation step between two candidate sets that may disagree. Not in scope for the test integration |
|
| 174 |
|
| 175 |
---
|
| 176 |
|
| 177 |
+
## 7. Defects and risks to carry into v2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
| # | Finding | Severity | Detail |
|
| 180 |
|---|---|---|---|
|
| 181 |
+
| **R1** | **Literal source wording is normalised away** | **High** | A locked decision says the pipeline records what the document actually says and surfaces the discrepancy to the expert. It does not. The BUMA heading reads **"2.1.3. Physical of Availability (PA)"**; the extracted entry carries `full_name: "Physical Availability"` — silently corrected. Mechanism: the heading is a separate field from the chunk text, and the chunk text does not contain the phrase, so the literal wording never reaches the model. Fix shape: feed the heading verbatim + add the span-checked `source_wording` field (S4) |
|
| 182 |
+
| **R2** | **Prototype is not under version control** | **High** | Addressed by P1 |
|
| 183 |
+
| **R3** | **85% abstention** | Medium | 56 of 66 entries carry no definition. Correct behaviour — for a term the document never defines, null *is* the right answer — but it means the review queue is mostly "term found, no definition in document". Whether that is useful to Mas Beta or noise is a **review-experience question to put to him**, and review experience is explicitly where engineering time is worth spending |
|
| 184 |
+
| **R4** | **Everything generalises from one 9-page document** | Medium | Single document, single language, single company. E2's compression, E3's scoreable base and the escalation path are all constrained by it |
|
| 185 |
+
| **R5** | **Rule and summary run on the wrong tier** | Medium | Both route to nano because it is the only provisioned deployment. Same blocker as D3 |
|
| 186 |
+
| **R6** | **Rewrites drop accidental knowledge** | Medium | Addressed by P2. The `PA`/`UA` fuzzy-collision guard and the 1024-token caching threshold are the known examples; there will be others |
|
| 187 |
|
| 188 |
---
|
| 189 |
|
| 190 |
+
## 8. Settled — do not reopen
|
| 191 |
+
|
| 192 |
+
1. The LLM call unit is the **term cluster**. Justification is review-burden reduction, **not** tokens (~$0.45 saved is trivial).
|
| 193 |
+
2. **Conflict detection depends on the clustering** — two contradictory definitions can only be spotted because all evidence for a term arrives in one call.
|
| 194 |
+
3. **No standalone relevance gate.** A pre-filter's false negatives drop content before extraction sees it, and dropped content never reaches review to be caught.
|
| 195 |
+
4. **Evidence ranking is allowed** even though it is also a filter — no term is dropped, all mentions stay in provenance, and the escalation loop self-corrects. Preserve those three properties if it is ever revisited.
|
| 196 |
+
5. **`provenance.span` is mandatory and verbatim**, never repaired. The primary anti-hallucination control, and what makes expert review finishable: the reviewer checks a quote against a page, not a claim against their memory.
|
| 197 |
+
6. **All content fields Optional.** Abstention is correct behaviour, not an error.
|
| 198 |
+
7. **Frequency-sorted review queue** — validate the ~40 terms carrying the document first, let the long tail wait.
|
| 199 |
+
8. **API cost is negligible, latency irrelevant** (measured $0.0069 for 9 pages). This runs a few times a year, admin-triggered, not continuous and not per-user. Optimise the review experience, never tokens. Prefer boring, inspectable stages.
|
| 200 |
+
9. **The pipeline records the literal source wording** and surfaces discrepancies rather than normalising them (currently violated — R1).
|
| 201 |
+
10. **This is a test integration, not a productionisation.** Both halves are new, gated subsystems alongside the existing document/catalog pipelines — not a rewrite of them.
|