[NOTICKET] docs: knowledge pipeline test integration — context, audited task checklist, DEV_PLAN §0.8
Browse filesPlanning-only. No code, dependency, table or endpoint added to this repo.
- knowledge_pipeline_context.md: intent, ownership split, the six stages, and the
settled decisions (term-cluster call unit, no relevance gate, mandatory verbatim
provenance span, all content fields Optional, frequency-sorted review queue).
- KNOWLEDGE_PIPELINE_TODO.md: task checklist with owners, for review with Sofhia.
Statuses verified against the existing kex POC and its run artifacts, not estimated.
- DEV_PLAN.md: new §0.8 tracking the work in the usual place, plus two §6 open
decisions (where kex lives; the admin ingest surface's auth posture).
Audit finding that shapes the plan: the extraction half is not greenfield. A working
POC (~2,763 LOC, 21 passing tests, four experiments against real Azure calls) exists
outside version control. Measured on the BUMA standard: GLiNER Indonesian recall 0.854
(PASS, closing the context doc's headline open risk), clustering 2.56x (PASS), nano
schema-fill 0.75 (FAIL against a 0.80 line), span validation 1.00 (PASS).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- DEV_PLAN.md +44 -0
- KNOWLEDGE_PIPELINE_TODO.md +274 -0
- knowledge_pipeline_context.md +299 -0
|
@@ -273,6 +273,45 @@ by design** until #43 (Go-forwarded identity). Not a gap to re-raise — it is a
|
|
| 273 |
CORS was left at `["*"]` on purpose (tightening it needs the FE origin as config, which we chose
|
| 274 |
not to set for now).
|
| 275 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
## 5. Critical path & sequencing
|
| 277 |
|
| 278 |
- **Critical path:** ~~#22 (send Harry the `report_inputs` schema)~~ **✅ resolved 2026-07-22** — now **#32** (`message_traceability` + `message_charts` DDL to Harry) and **#31** (non-convergent migration set). HF deploy (#13) for the playground. (#4 ✅, #21 ✅; Harry's #3 no longer blocks us — Python is getattr-tolerant.)
|
|
@@ -297,3 +336,8 @@ not to set for now).
|
|
| 297 |
- **W3 re-open timing** (deferred `analyze_*` activation) — Rifqi (deferred at the 2026-07-13 approval).
|
| 298 |
- **INV-6 relaxation for S1b targeted repair** — team, only after S1a `repair_candidate` telemetry
|
| 299 |
shows a meaningful hit-rate (SPINE_V2_PLAN §6).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
CORS was left at `["*"]` on purpose (tightening it needs the FE origin as config, which we chose
|
| 274 |
not to set for now).
|
| 275 |
|
| 276 |
+
## 0.8. Knowledge pipeline — test integration (planning, 2026-08-19)
|
| 277 |
+
|
| 278 |
+
**Nothing has landed in this repo.** This section exists so the in-flight work is tracked in the
|
| 279 |
+
usual place; no code, dependency, table or endpoint has been added here yet. Do not read any of it
|
| 280 |
+
into REPO_STATUS.
|
| 281 |
+
|
| 282 |
+
Context and intent: [knowledge_pipeline_context.md](knowledge_pipeline_context.md). Task breakdown,
|
| 283 |
+
owners and verified statuses: [KNOWLEDGE_PIPELINE_TODO.md](KNOWLEDGE_PIPELINE_TODO.md).
|
| 284 |
+
|
| 285 |
+
**Shape.** A six-stage pipeline (parse → free CPU/regex filter → one LLM call per *term cluster* →
|
| 286 |
+
deterministic span validation → diff vs. the active glossary → frequency-sorted expert review) that
|
| 287 |
+
turns client documents into reviewable knowledge entries, ultimately served over MCP. It splits into
|
| 288 |
+
two independently runnable halves — **parsing (Sofhia, MinerU)** and **knowledge extraction
|
| 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).** The extraction half is **not greenfield**: a working POC
|
| 293 |
+
(`kex`, ~2,763 LOC, 21 passing tests, 8-command CLI, four experiments against real Azure calls)
|
| 294 |
+
already exists outside version control at `Documents/Dataeyond_catalog/knowledge-pipeline/`.
|
| 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
|
| 298 |
+
0.00 false rejections (kill 0.90, PASS). Cost **$0.0069** for the document.
|
| 299 |
+
|
| 300 |
+
| # | Task | Owner | Status | Note |
|
| 301 |
+
|---|---|---|---|---|
|
| 302 |
+
| K1 | Put `kex` under version control | Rifqi | ⬜ | No `.git`. ~2,763 LOC + both gold sets + all four experiment results exist on one machine. Highest-priority item; repo home undecided |
|
| 303 |
+
| K2 | Agree the parsed-artifact schema (A1) | Sofhia + Rifqi | 🔄 | Sofhia circulated a draft `contracts.py` 2026-08-19, derived from real MinerU output. Adopted as the base; open items are the document-level envelope (no home for `version`), the section-structure claim, and `source_wording` |
|
| 304 |
+
| K3 | Move the seam off the file path | Sofhia + Rifqi | ⬜ | The POC's boundary is `Parser.parse(path) -> list[Page]` — the one shape the plan forbids. Must land **before** the MinerU parser is built against it |
|
| 305 |
+
| K4 | 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) |
|
| 306 |
+
| K5 | Fix the literal-source-wording violation | Rifqi | ⬜ | A locked decision is currently violated: the BUMA heading reads "Physical **of** Availability (PA)"; the extracted entry carries "Physical Availability". The discrepancy is meant to reach the expert, not be silently corrected |
|
| 307 |
+
| K6 | 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 (a plausible summary cannot be span-checked). Blocks the nano-vs-mini A/B |
|
| 308 |
+
| K7 | Expert review of the gold set | Rifqi → Mas Beta | ⛔ | The 41-term / 15-rule gold set is self-bootstrapped and provisional. Both the recall and precision figures move when it is reviewed |
|
| 309 |
+
| K8 | Port `kex` into this repo | Rifqi | ⬜ | Python 3.13 → 3.12, new deps (GLiNER + torch), gating, and an admin ingest surface whose auth question is a different risk class from the read-only chat surface. **Gated on §6 sign-off — deps (§6.4) and any new endpoint (§6.3).** |
|
| 310 |
+
|
| 311 |
+
**Not decided, do not assume:** whether `kex` becomes a subdirectory here, a separate repo, or a
|
| 312 |
+
branch; whether the admin ingest surface is HTTP at all (the offline script path is the honest
|
| 313 |
+
first milestone); and GLiNER + LangExtract ensembling stays ⏸️ flagged as scope creep.
|
| 314 |
+
|
| 315 |
## 5. Critical path & sequencing
|
| 316 |
|
| 317 |
- **Critical path:** ~~#22 (send Harry the `report_inputs` schema)~~ **✅ resolved 2026-07-22** — now **#32** (`message_traceability` + `message_charts` DDL to Harry) and **#31** (non-convergent migration set). HF deploy (#13) for the playground. (#4 ✅, #21 ✅; Harry's #3 no longer blocks us — Python is getattr-tolerant.)
|
|
|
|
| 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 — where `kex` lives** (§0.8 K1/K8): subdirectory of this repo, separate repo,
|
| 340 |
+
or its own branch. Blocks version control, which blocks everything else in §0.8.
|
| 341 |
+
- **Knowledge pipeline — admin ingest surface** (§0.8 K8): whether it is HTTP at all, and if so its
|
| 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.
|
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Knowledge Pipeline — Task Checklist (draft for review)
|
| 2 |
+
|
| 3 |
+
**Date:** 2026-08-19 · **From:** Rifqi · **For:** Sofhia (review before we agree on next steps)
|
| 4 |
+
**Sources:** `knowledge_pipeline_context.md` (intent) + an audit of the existing `kex` POC
|
| 5 |
+
(`Documents/Dataeyond_catalog/knowledge-pipeline/`, audited 2026-08-19)
|
| 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 |
+
## 0. Audit summary — what already exists
|
| 20 |
+
|
| 21 |
+
`kex` implements the full six-stage flow: `parse → chunk → filters → cluster → rank → extract →
|
| 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 |
+
**Measured on `STD_2026_006_MNO` (the BUMA standard, 9 pages, run `20260813-145132`):**
|
| 26 |
+
|
| 27 |
+
| Funnel stage | Count |
|
| 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 |
+
Other measured figures: cost **$0.0069** for the 9-page document (155,313 prompt tokens, of which
|
| 44 |
+
**125,184 cached**), 242 s wall clock, 3 fields rejected by span check, 0 definition conflicts,
|
| 45 |
+
7 rule entries, 7 formula entries, 1 brief-context summary.
|
| 46 |
+
|
| 47 |
+
**The two things that most change our plan:**
|
| 48 |
+
|
| 49 |
+
1. **E1 answers the standing Indonesian risk.** GLiNER multi-v2.1 scores **0.854 recall** on the
|
| 50 |
+
majority-Indonesian BUMA standard, comfortably above the 0.70 kill line. The context doc lists
|
| 51 |
+
this as the headline open risk — it is now measured, on the exact document named as the test case.
|
| 52 |
+
2. **E3 settles half of the model-tier decision.** nano scores **0.75 precision against a 0.80 kill
|
| 53 |
+
line — a FAIL.** Caveats matter: the scoreable base is 8 entries, and nano is not deterministic at
|
| 54 |
+
`temperature=0` (consecutive identical runs scored 0.75 and 0.625). No mini deployment is
|
| 55 |
+
provisioned, so the spec's nano-vs-mini A/B has never run and **all four branches currently route
|
| 56 |
+
to nano**, including `rule` and `summary`, which the spec explicitly wanted on the larger tier.
|
| 57 |
+
|
| 58 |
+
**Caveat on E2 vs. the context doc.** The context doc claims clustering cuts review burden ~6.2×
|
| 59 |
+
(3,125 → ~500 on a 1,000-page corpus). The **measured** figure on 9 pages is **2.56×**. These are
|
| 60 |
+
not in conflict — compression grows with corpus size as terms repeat — but 6.2× is a projection and
|
| 61 |
+
2.56× is the measurement. Quote the right one to the right audience.
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## A. The seam — shared, and still the first thing to agree
|
| 66 |
+
|
| 67 |
+
The audit changes this section substantially: we now have a concrete starting point instead of a
|
| 68 |
+
blank page, **and** a concrete conflict to resolve.
|
| 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 |
+
| A1 | Agree the parsed-artifact schema | Sofhia + Rifqi | 🔄 | **Sofhia circulated a draft `contracts.py` on 2026-08-19** — derived from real MinerU output, backend-agnostic, with a `kind` discriminator (text/table/chart/equation), `source_items` back-references, `images` as paths, and an explicit verbatim-text warning. **Adopt it as the base.** Open items: merge kex's document-level envelope (§A1a), the `text_level` correction (§A1b), the formatting signal, and 0- vs 1-based pages. **Highest-priority item.** |
|
| 100 |
+
| A1a | Add a document-level envelope | Sofhia + Rifqi | ⬜ | The draft is `list[Chunk]` with no wrapper, so there is nowhere to put `content_hash`, `n_pages`, `source_path`, `parser_name`/`parser_version` — or **`version`**, which A3 requires. kex's `ParseResult` wraps exactly this. Parser identity matters specifically because the seam exists to make parsers swappable: when extraction output changes we must know which parser produced the artifact |
|
| 101 |
+
| A1b | Resolve the section-structure question | 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)`. The draft's sample appears to be the handbook, which genuinely has no numbered headings. Keeping both fields Optional is right; the stated *reason* is not, and acting on it would starve section-aware chunking on exactly the documents that carry numbering |
|
| 102 |
+
| A2 | Move the seam off the file path | Sofhia + Rifqi | ⬜ | Replace `Parser.parse(path) -> list[Page]` as *the contract* with a persisted, versioned `ParseResult`. `list[Page]` may survive as an internal detail of the PyMuPDF fallback; it must stop being the boundary. |
|
| 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 |
+
## B. Parsing pipeline — **Sofhia**
|
| 131 |
+
|
| 132 |
+
Unchanged in scope. One addition from the audit: the MinerU output shape is now known, because a
|
| 133 |
+
real MinerU run of the BUMA standard was inspected (`Downloads/mineru_output_results/…/hybrid_auto/`).
|
| 134 |
+
|
| 135 |
+
**Which files the adapter should read:**
|
| 136 |
+
|
| 137 |
+
| File | Use | Why |
|
| 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 both sample docs | Sofhia | ✅ | **Complete 9-page parse delivered 2026-08-19**: 118 items (90 text, 14 table, 11 equation, 3 chart) across pages 0–8. The rule-of-thumb grounding case is present (Other Activity, Controllable/Uncontrollable, Standby), so the fixture now exercises all four branches. Supersedes the earlier 5-page run. |
|
| 150 |
+
| B3 | Evaluate MinerU quality vs. the current path | Sofhia | ⬜ | Compare specifically on tables, the p.2 legend block, formulas and figures — against the PyMuPDF path the POC uses today, which is a real baseline with real numbers, not a guess. |
|
| 151 |
+
| B4 | Indonesian check on **parsing** | Sofhia | ⬜ | Distinct from E1, which measured GLiNER, not the parser. Confirm MinerU's text/layout quality holds on Bahasa Indonesia. |
|
| 152 |
+
| B5 | Section-aware chunking | Sofhia | 🔎 | **Already built and working** for the PyMuPDF path (`chunk/section_chunker.py`, 244 LOC, tested): numbered-heading regex, breadcrumb-header dedupe, ~1,500-token cap, page-boundary fallback. On the BUMA standard it produced 13 chunks with `used_heading_split=True`. The open question is whether MinerU's typed blocks should feed it or replace it. |
|
| 153 |
+
| B6 | Emit the agreed artifact (A1) | Sofhia | ⬜ | The parser writes our schema, not MinerU's native shape. This is what keeps MinerU swappable. Currently a `NotImplementedError` stub. |
|
| 154 |
+
| B7 | Fallback path behind the same artifact | Sofhia | 🔎 | PyMuPDF already works end-to-end and is the POC default. It needs re-pointing at `ParseResult` as the contract rather than `list[Page]`. |
|
| 155 |
+
| B8 | Parsing subsystem is additive + gated | Sofhia | ⬜ | The existing unstructured path (OCR → chunk → pgvector) in the target repo stays untouched. MinerU is a new flag-gated path beside it. |
|
| 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`. But the heading *text itself* is supplied — see A1b.
|
| 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. kex's chunker already dedupes these
|
| 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` (e.g. *"Gambar 2.1 Analisis Gain/Loss…"*). A chart chunk therefore contributes
|
| 170 |
+
a caption plus an image reference and nothing else to extraction. Expected, but it means chart
|
| 171 |
+
detection buys Scenario 2, not glossary coverage.
|
| 172 |
+
|
| 173 |
+
---
|
| 174 |
+
|
| 175 |
+
## C. Extraction pipeline — **Rifqi**
|
| 176 |
+
|
| 177 |
+
Statuses below are **verified against the code and the run artifacts**, not estimated.
|
| 178 |
+
|
| 179 |
+
### C-1. Built and measured
|
| 180 |
+
|
| 181 |
+
| # | Task | Status | Evidence |
|
| 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 |
+
### C-3. Not built
|
| 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 |
+
| D1 | New dependencies | Rifqi (asks) | ⬜ | GLiNER + torch (~heavy) on extraction, MinerU on parsing. `pyproject.toml` changes in the target repo need sign-off. Note the POC needs **torch ≥ 2.6** and hit a corrupt-wheel failure worth not re-debugging |
|
| 220 |
+
| D2 | New tables (DDL handoff) | Rifqi → Harry | ⬜ | Parsed artifacts, candidate entries, glossary versions + approval audit trail. One consolidated handoff beats three |
|
| 221 |
+
| D3 | Model tier per branch | Rifqi | ⛔ | **Half-answered: nano FAILS at 0.75 vs the 0.80 line.** Blocked on a **mini deployment being provisioned** — until then all four branches run on nano, including `rule` and `summary`, which the spec never intended for it, and the A/B cannot run |
|
| 222 |
+
| D4 | Admin ingest authentication | Rifqi | ⬜ | A different risk class from the read-only chat surface. Raise it rather than assume — and only after the offline path is ported |
|
| 223 |
+
| D5 | GLiNER + LangExtract ensembling | — | ⏸️ | Proposed, not decided; flagged as scope creep. Not in scope for the test integration |
|
| 224 |
+
| 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. E3's scoreable base is 8 entries — each adjudication moves the number ~12 points. Every failure is listed verbatim in `eval/out/report.md` for him |
|
| 225 |
+
|
| 226 |
+
---
|
| 227 |
+
|
| 228 |
+
## E. Outside our two halves
|
| 229 |
+
|
| 230 |
+
| # | Item | Owner | Status | Description |
|
| 231 |
+
|---|---|---|---|---|
|
| 232 |
+
| E1 | Curation UI + expert review flow | Mas Har / Mas Beta | ⬜ | Our pipeline's job ends at the queue. `out/review_queue.json` (66 items, ranked, each with page, section, span and a `review_reason`) is a ready-made input contract |
|
| 233 |
+
| E2 | Versioning + approval audit trail (product side) | Mas Har | ⬜ | The expert chooses which version is active. Pairs with C15/A3 |
|
| 234 |
+
| E3 | MCP server — the consumption surface | — | ⬜ | The POC already emits three of the four target artifacts: `glossary.json`, `interpretation_pack.json`, `formulas.json`, `brief_context.json`. Only the data dictionary (DB profiling) is out of scope for extraction |
|
| 235 |
+
|
| 236 |
+
---
|
| 237 |
+
|
| 238 |
+
## F. Already settled — please do not reopen
|
| 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 |
+
| **G1** | **Literal source wording is being normalised away** | **High** | Decision §4.10 says the pipeline must record what the document actually says and surface the discrepancy. It currently does not. The BUMA standard's heading reads **"2.1.3. Physical of Availability (PA)"**; the extracted entry carries `full_name: "Physical Availability"` — silently corrected. Mechanism: `Chunk.heading` is a separate field from `Chunk.text`, and the chunk text does not contain the phrase at all, so the literal wording never reaches the model. Fix shape: feed the heading verbatim, and/or add a `source_wording` field span-checked against the heading |
|
| 255 |
+
| **G2** | **The POC is not under version control** | **High** | No `.git`. 2,763 LOC, four experiment results, a metrics pack and both gold sets exist only on this machine |
|
| 256 |
+
| **G3** | **85% abstention rate** | Medium | 56 of 66 entries have **no definition**; only 10 carry one. Correct behaviour per the abstention rule, 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 per §5 of the context doc, review experience is where engineering time is worth spending |
|
| 257 |
+
| **G4** | **The seam drifted from the plan** | Medium | `Parser.parse(path) -> list[Page]` is the exact shape the context doc forbids. Cheap to fix now, expensive once MinerU is built against it — hence A2 before B6 |
|
| 258 |
+
| **G5** | **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 |
|
| 259 |
+
| **G6** | **`rule` and `summary` run on the wrong tier** | Medium | Both route to nano because it is the only provisioned deployment. These are the two branches whose failure mode is least detectable — a plausible summary cannot be span-checked. Same blocker as D3 |
|
| 260 |
+
|
| 261 |
+
---
|
| 262 |
+
|
| 263 |
+
## Proposed sequence
|
| 264 |
+
|
| 265 |
+
1. **G2 first, today** — put `kex` under version control. Everything below risks a machine failure until this is done.
|
| 266 |
+
2. **Close A1 on Sofhia's draft** — reply with R1–R9, settle A1a/A1b, and let her push the agreed version to the catalog repo as the module of record. **A2** (move the seam off the file path) rides along with it: doing A2 *before* she builds B6 is the whole point; afterwards it is a rewrite of her work.
|
| 267 |
+
3. **Then split.** Sofhia takes B1–B4 (including a complete 9-page re-parse); Rifqi takes G1 (the literal-wording defect), then A4 (provisional MinerU adapter) and C15/A3 (real versioning behind the diff).
|
| 268 |
+
4. **Reconverge at B6.** Sofhia's parser emits the agreed artifact and replaces the provisional adapter. If A1 was right, nothing in the extraction half changes — that is the test of whether we did A1 properly.
|
| 269 |
+
5. **In parallel, unblock the two ⛔s:** D3 (provision a mini deployment, then run the A/B) and D6 (get the gold set in front of Mas Beta). Both gate quality claims, neither gates engineering.
|
| 270 |
+
6. **Then** C17, C18, C19 and D1/D2/D4 — the port into the target repo.
|
| 271 |
+
|
| 272 |
+
Rationale: this ordering front-loads the two things that are expensive to get wrong later — the seam
|
| 273 |
+
and the version-control gap — and it keeps the four open decisions in section D off both of our
|
| 274 |
+
critical paths.
|
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Knowledge Pipeline — Context for the Data Eyond Python Repo (19 Aug 2026)
|
| 2 |
+
|
| 3 |
+
> **Who this is for:** a Claude Code session working in `Agentic-Service-Data-Eyond-Catalog`
|
| 4 |
+
> (the Python agentic service). This doc carries *context and intent* only — the why, the
|
| 5 |
+
> ownership split, the shape of the pipeline, and the decisions already settled. It does not
|
| 6 |
+
> prescribe folder layout, module names, or endpoint signatures; work those out against
|
| 7 |
+
> `CLAUDE.md`, `REPO_STATUS.md` and the existing subsystem patterns in the repo.
|
| 8 |
+
>
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 1. What we are doing and why now
|
| 12 |
+
|
| 13 |
+
Data Eyond is an AI data-analyst platform. The strategic thesis from the 30 July exec review
|
| 14 |
+
is that **domain knowledge is the differentiator, not architecture** — orchestrators and
|
| 15 |
+
connectors are replicable in a quarter, model capability is rented, only encoded domain
|
| 16 |
+
knowledge compounds. The end goal is an **MCP product** exposing modular domain knowledge
|
| 17 |
+
that EMA and later clients connect to.
|
| 18 |
+
|
| 19 |
+
That knowledge currently comes from experts typing it in. **Mas Beta** (plant & maintenance)
|
| 20 |
+
is the single validation bottleneck — nothing becomes an artifact without him. The knowledge
|
| 21 |
+
pipeline exists to change the expert's job from *authoring* to *reviewing*: the pipeline reads
|
| 22 |
+
the client's own documents (standards, SOPs, handbooks) and proposes candidate knowledge
|
| 23 |
+
entries; the expert approves, edits or rejects them.
|
| 24 |
+
|
| 25 |
+
**Why it lands in this repo now.** We want to integrate the pipeline into Data Eyond to test
|
| 26 |
+
it end-to-end on real documents, in the place where the resulting knowledge will actually be
|
| 27 |
+
consumed. This is a **test integration**, not a productionisation — treat it as a new, gated
|
| 28 |
+
subsystem alongside the existing document/catalog pipelines, not as a rewrite of them.
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## 2. Ownership — two items, two people, one seam
|
| 33 |
+
|
| 34 |
+
The pipeline splits cleanly in two, and the split is deliberate: **they are separate,
|
| 35 |
+
independently runnable, independently swappable pipelines.**
|
| 36 |
+
|
| 37 |
+
| Half | Owner | Scope |
|
| 38 |
+
|---|---|---|
|
| 39 |
+
| **① Parsing (MinerU)** | **Sofhia Az-Zahra** | PDF/DOCX in → structured, typed extracted content out. MinerU replaces the current text+table-only extraction: it adds formula, image, chart and diagram detection, and emits Markdown or JSON with type labels. Presented to the execs 13 Aug as the **prerequisite for Scenario 2**; a written cost justification for MinerU is owed for the budget proposal. |
|
| 40 |
+
| **② Knowledge extraction** | **Rifqi (me)** | Parsed content in → validated candidate knowledge entries out. Term filtering, clustering, evidence ranking, schema-guided LLM attribute fill, span validation, diff against the active glossary version, and the frequency-sorted review queue. |
|
| 41 |
+
|
| 42 |
+
**The seam.** The two halves must meet only at a **persisted, versioned parsed-document
|
| 43 |
+
artifact** — extraction consumes that artifact, never a raw file, never the parser's API.
|
| 44 |
+
This is the single most important structural constraint in the whole plan, because:
|
| 45 |
+
|
| 46 |
+
- MinerU can be swapped (or fall back to the existing Tesseract / Azure Document Intelligence
|
| 47 |
+
paths) without extraction knowing;
|
| 48 |
+
- extraction can be re-run cheaply on an already-parsed corpus when a prompt or schema changes
|
| 49 |
+
— and it will be, often, because prompt iteration is the main development loop here;
|
| 50 |
+
- Sofhia and I can work and test independently, against a fixture artifact, without blocking
|
| 51 |
+
each other;
|
| 52 |
+
- parsing is the slow/expensive-to-rerun half; extraction is the fast-to-iterate half. Keeping
|
| 53 |
+
the boundary hard means we never re-parse to fix an extraction bug.
|
| 54 |
+
|
| 55 |
+
Anything that makes the two halves import each other's internals, or that makes extraction
|
| 56 |
+
take a file path, breaks the point of the split.
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## 3. The pipeline
|
| 61 |
+
|
| 62 |
+
Six stages. The cost story matters as much as the flow: almost everything is free CPU/regex
|
| 63 |
+
work, and the LLM appears in exactly one stage.
|
| 64 |
+
|
| 65 |
+
```mermaid
|
| 66 |
+
flowchart TD
|
| 67 |
+
A["Admin / expert triggers ingest<br/><i>not continuous, not per-user</i>"] --> B
|
| 68 |
+
|
| 69 |
+
subgraph P["① PARSING — Sofhia"]
|
| 70 |
+
B["MinerU<br/>text · tables · formulas · figures · charts"] --> C["Section-aware chunking<br/>split on numbered headings 2.1.1, 2.1.2 …"]
|
| 71 |
+
end
|
| 72 |
+
|
| 73 |
+
C --> SEAM[("Parsed-document artifact<br/>versioned · persisted<br/><b>THE SEAM</b>")]
|
| 74 |
+
SEAM --> D
|
| 75 |
+
|
| 76 |
+
subgraph F["② TERM FILTER — CPU / regex, free"]
|
| 77 |
+
D["GLiNER span filter on ALL chunks → term mentions"] --> H
|
| 78 |
+
E["Discourse-cue regex → rule-of-thumb candidates"] --> H
|
| 79 |
+
G["Legend-block regex → formula vars + abbreviation dictionary"] --> H
|
| 80 |
+
G2["Section pass → summary units"] --> H
|
| 81 |
+
H["Normalise + cluster mentions<br/>PA · P.A. · Physical Availability → 1 cluster"] --> I["Evidence ranking<br/>cue · heading · legend · formula · formatting · first-occurrence"]
|
| 82 |
+
I --> J["Top-3 evidence chunks per cluster"]
|
| 83 |
+
end
|
| 84 |
+
|
| 85 |
+
SEAM --> E
|
| 86 |
+
SEAM --> G
|
| 87 |
+
SEAM --> G2
|
| 88 |
+
|
| 89 |
+
J --> K
|
| 90 |
+
|
| 91 |
+
subgraph X["③ KNOWLEDGE EXTRACTION — the only paid stage"]
|
| 92 |
+
K["1 LLM call per TERM CLUSTER<br/>(not per mention, not per chunk)"]
|
| 93 |
+
L["1 call per cue chunk · 1 per unique formula · 1 per document (summary)"]
|
| 94 |
+
end
|
| 95 |
+
|
| 96 |
+
K --> M
|
| 97 |
+
L --> M
|
| 98 |
+
|
| 99 |
+
subgraph V["④ VALIDATION — deterministic"]
|
| 100 |
+
M["Verbatim-span check → reject field if span not locatable"] --> N["Null definition → escalate to next 3 evidence chunks<br/>max 2 rounds → else flag 'term found, no definition located'"]
|
| 101 |
+
N --> O["Conflicting definitions → definition_conflict + variants[]"]
|
| 102 |
+
end
|
| 103 |
+
|
| 104 |
+
O --> Q["⑤ DIFF vs active glossary version<br/>duplicate (skip) · new · conflicting"]
|
| 105 |
+
Q --> R
|
| 106 |
+
|
| 107 |
+
subgraph RV["⑥ REVIEW — human"]
|
| 108 |
+
R["Queue sorted by mention frequency<br/>PA 250 → UA 180 → … → long tail"] --> S["Expert (Mas Beta) approves / edits / rejects"]
|
| 109 |
+
S --> T["Glossary · Interpretation Pack · Brief Context · Formula"]
|
| 110 |
+
T --> U["Versioning + approval audit trail"]
|
| 111 |
+
end
|
| 112 |
+
|
| 113 |
+
U --> W["MCP server — the consumption surface"]
|
| 114 |
+
|
| 115 |
+
style P fill:#e8f4ff,stroke:#4a90d9
|
| 116 |
+
style F fill:#e6f7e6,stroke:#4caf50
|
| 117 |
+
style X fill:#dbe9ff,stroke:#2f6fd0
|
| 118 |
+
style V fill:#f0e6ff,stroke:#8e5fd0
|
| 119 |
+
style RV fill:#fff0e0,stroke:#e08b3c
|
| 120 |
+
style SEAM fill:#fffbe0,stroke:#c9a227,stroke-width:3px
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
Colour convention, carried from the diagrams shown to the team:
|
| 124 |
+
**green = free** (CPU/regex — most of the pipeline) · **blue = LLM** (the only paid part) ·
|
| 125 |
+
**purple = deterministic code** · **orange = human**.
|
| 126 |
+
|
| 127 |
+
### The five-box version (how this is explained to execs)
|
| 128 |
+
|
| 129 |
+
```mermaid
|
| 130 |
+
flowchart LR
|
| 131 |
+
A["PARSE<br/>MinerU"] --> B["FILTER<br/>free, CPU"] --> C["EXTRACT<br/>LLM, per term cluster"] --> D["VALIDATE + DIFF<br/>deterministic"] --> E["EXPERT REVIEW<br/>frequency-sorted queue"]
|
| 132 |
+
style A fill:#e8f4ff,stroke:#4a90d9
|
| 133 |
+
style B fill:#e6f7e6,stroke:#4caf50
|
| 134 |
+
style C fill:#dbe9ff,stroke:#2f6fd0
|
| 135 |
+
style D fill:#f0e6ff,stroke:#8e5fd0
|
| 136 |
+
style E fill:#fff0e0,stroke:#e08b3c
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
### Ownership view
|
| 140 |
+
|
| 141 |
+
```mermaid
|
| 142 |
+
flowchart LR
|
| 143 |
+
subgraph S["Sofhia"]
|
| 144 |
+
P["Parsing pipeline<br/>MinerU + backends"]
|
| 145 |
+
end
|
| 146 |
+
subgraph R["Rifqi"]
|
| 147 |
+
K["Extraction pipeline<br/>filter → LLM → validate → diff → queue"]
|
| 148 |
+
end
|
| 149 |
+
subgraph H["Mas Har / Mas Beta"]
|
| 150 |
+
C["Curation UI + expert review<br/>versioning, approval"]
|
| 151 |
+
end
|
| 152 |
+
P -->|"versioned parsed artifact<br/>(the only contract)"| K
|
| 153 |
+
K -->|"candidate entries + provenance"| C
|
| 154 |
+
C -->|"active versions"| M["MCP server"]
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
---
|
| 158 |
+
|
| 159 |
+
## 4. Decisions already settled — do not reopen these
|
| 160 |
+
|
| 161 |
+
1. **The LLM call unit is the term cluster** — not the chunk, not the mention. Per-chunk works
|
| 162 |
+
for a 9-page document and breaks at 1,000+ pages: "PA" mentioned 250× would produce 250
|
| 163 |
+
near-identical candidates. Clustering first cuts expert review burden ~6.2× (3,125 raw
|
| 164 |
+
extractions → ~500 term entries on a 1,000-page document). The token saving is trivial
|
| 165 |
+
(~$0.45); **the review-burden reduction is the justification.**
|
| 166 |
+
2. **Conflict detection depends on the clustering.** Two contradictory definitions of the same
|
| 167 |
+
term can only be spotted because all evidence for that term arrives in one call. Two
|
| 168 |
+
separate calls would never meet, and the conflict would surface as two silently-accepted
|
| 169 |
+
entries.
|
| 170 |
+
3. **No standalone relevance gate.** A binary relevance classifier before the term filter is a
|
| 171 |
+
second imperfect classifier whose false negatives drop content *before* extraction sees it —
|
| 172 |
+
and dropped content never reaches expert review to be caught. GLiNER is cheap enough (CPU,
|
| 173 |
+
~400MB) to run unconditionally on every chunk; "zero candidate spans" becomes the relevance
|
| 174 |
+
signal as a byproduct of looking, rather than a judgment made before looking.
|
| 175 |
+
4. **Evidence ranking is allowed even though it is also a filter** — because no term is
|
| 176 |
+
dropped (only which passages feed the call is narrowed), all mentions stay in provenance so
|
| 177 |
+
a bad top-3 is visible, and the escalation loop self-corrects. The rejected relevance gate
|
| 178 |
+
had none of those three properties. This distinction is the reasoning to preserve if the
|
| 179 |
+
ranking is ever revisited.
|
| 180 |
+
5. **`provenance.span` is mandatory and verbatim.** A field whose span cannot be located in the
|
| 181 |
+
source is rejected. This is the primary anti-hallucination control and the thing that makes
|
| 182 |
+
expert review *finishable* — the reviewer checks a quote against a page, not a claim against
|
| 183 |
+
their memory.
|
| 184 |
+
6. **All content fields are Optional in the schema.** The model must be able to answer null; a
|
| 185 |
+
required field is a fabrication invitation for smaller models. `subdomain_tags` is an enum —
|
| 186 |
+
classification, not generation.
|
| 187 |
+
7. **Frequency-sorted review queue.** Directly targets the Mas Beta bottleneck: validate the
|
| 188 |
+
~40 terms carrying the document's meaning first, let the long tail wait.
|
| 189 |
+
8. **Model tier is per branch.** Glossary and Formula are extraction tasks well-guarded by span
|
| 190 |
+
verification → a small/cheap tier is fine. Rule-of-thumb and Summary are reasoning/generation
|
| 191 |
+
tasks where a small model's failure mode is least detectable (a plausible summary cannot be
|
| 192 |
+
span-checked) → larger tier. Test before committing the small tier on the rule-of-thumb
|
| 193 |
+
branch.
|
| 194 |
+
9. **GLiNER-hybrid stays the glossary-branch default.** Pure NER can only fill 2 of 6 schema
|
| 195 |
+
fields (definition, formula and interpretation are not spans — they are assembled from text
|
| 196 |
+
that may be pages apart). Pure small-LLM shows recall 0.93–1.00 at precision 0.15–0.43;
|
| 197 |
+
perfect recall is the failure signature of a model fabricating a value for every field.
|
| 198 |
+
10. **The pipeline records the literal source wording.** It recorded "Physical of Availability"
|
| 199 |
+
from the BUMA standard rather than the textbook's "Physical Availability" — surfacing the
|
| 200 |
+
discrepancy to the expert instead of silently normalising it. Keep that behaviour.
|
| 201 |
+
|
| 202 |
+
---
|
| 203 |
+
|
| 204 |
+
## 5. Deployment reality — this shapes every engineering trade-off
|
| 205 |
+
|
| 206 |
+
The pipeline runs **once at setup (bulk corpus ingest)** and **occasionally when an admin or
|
| 207 |
+
expert adds a file**. It is **not continuous** and **not per-user** — normal engineers cannot
|
| 208 |
+
add data. Consequences, in the order they matter:
|
| 209 |
+
|
| 210 |
+
- **Extraction quality and expert-review efficiency dominate.** A missed term has no
|
| 211 |
+
self-correcting mechanism, because engineers can't add data to fill a gap later.
|
| 212 |
+
- **Maintainability matters more than it looks.** A glue chain invoked every few months is
|
| 213 |
+
exactly the thing nobody remembers how to debug. Prefer boring, inspectable stages over
|
| 214 |
+
clever ones.
|
| 215 |
+
- **API cost is negligible and latency is irrelevant.** ~$8–12 for a 6,000-page corpus;
|
| 216 |
+
an incremental single 9-page file is ~1.5 US cents. Do not spend engineering time optimising
|
| 217 |
+
tokens — engineering time on the review experience is worth more than any token saving
|
| 218 |
+
available here.
|
| 219 |
+
- **No GPU required.** GLiNER is a CPU encoder model (~1.6 seq/s Python, ~6.7 seq/s via the
|
| 220 |
+
Rust port on an i9 8-core). ~15,000 chunks ≈ 2.6h Python / ~40min Rust for a job that runs a
|
| 221 |
+
few times a year. The LLM half is an API call. (Runtime as reported to the team on 13 Aug:
|
| 222 |
+
~11 s/page GLiNER, ~26 s/page LLM — with the caveat that **cost scales on term count, not
|
| 223 |
+
page count.**)
|
| 224 |
+
- **Two ingest modes to build:** *bulk setup ingest* (corpus-scale — the only place
|
| 225 |
+
corpus-frequency statistics are usable as a candidate-term booster) and *incremental
|
| 226 |
+
single-file add* (no corpus context; diff against the active glossary version).
|
| 227 |
+
- **The summary branch is the quiet cost risk** — only ~300 calls but a quarter of all input
|
| 228 |
+
tokens, because summarisation can't be filtered; it needs whole documents.
|
| 229 |
+
|
| 230 |
+
---
|
| 231 |
+
|
| 232 |
+
## 6. Where the output goes
|
| 233 |
+
|
| 234 |
+
Approved candidates become the project's **four artifacts** (see the `context` doc for the
|
| 235 |
+
full architecture):
|
| 236 |
+
|
| 237 |
+
1. **Domain knowledge** — system-prompt material, capped ~1500 tokens: operating and equipment
|
| 238 |
+
hierarchy, time convention (WITA), seasonality, most-important glossary terms, high-level
|
| 239 |
+
business process, and agent limitations.
|
| 240 |
+
2. **Data dictionary** — from DB column profiling; used for query generation. Pak Ricky
|
| 241 |
+
connected the extraction work directly to **automating this**.
|
| 242 |
+
3. **Interpretation pack** — expert-curated interpretation logic, action rules, benchmark
|
| 243 |
+
history, tied to a use case ("PA missed" reads differently from "PA missed but MTBS
|
| 244 |
+
achieved"). Revived on 13 Aug after earlier removal for being too normative — reintroduce
|
| 245 |
+
carefully.
|
| 246 |
+
4. **Skill registry** — query rules and expert-approved formulas. Approval is mandatory because
|
| 247 |
+
the same term computes differently per company (MTTR at BUMA = breakdown duration ÷
|
| 248 |
+
breakdown frequency; MTTR in IT = mean time to resolve).
|
| 249 |
+
|
| 250 |
+
All four carry **versioning plus an approval audit trail**, and the expert chooses which
|
| 251 |
+
version is active. Delivery format is **MCP** — skill registry → MCP skills; data dictionary →
|
| 252 |
+
MCP on demand; interpretation pack → attached to the skill; domain knowledge → system prompt
|
| 253 |
+
but preferably served via MCP so owners can change it without redeploying the engine.
|
| 254 |
+
|
| 255 |
+
---
|
| 256 |
+
|
| 257 |
+
## 7. Grounding material
|
| 258 |
+
|
| 259 |
+
Two real sample documents anchor every design claim above, and should anchor test fixtures too:
|
| 260 |
+
|
| 261 |
+
- **BUMA STD/2026/006/MNO Rev.0.0** — Production Parameter & ECA. 9 pages, majority Bahasa
|
| 262 |
+
Indonesia. Carries: PA (glossary, p.4), Other Activity + the controllability hierarchy
|
| 263 |
+
(rule-of-thumb, pp.8–9), the MOHH/Qty/PA/UA/Pty legend block (formula branch, p.2).
|
| 264 |
+
- **Open Pit Mine Planning & Design** (textbook excerpt) — carries NSR as a **cross-page
|
| 265 |
+
definition**: intro p.92, formula p.93, interpretive remark p.94 (§2.3.5). This is the case
|
| 266 |
+
that justifies section-aware chunking over fixed token windows — the whole definition must
|
| 267 |
+
stay inside one semantic unit.
|
| 268 |
+
|
| 269 |
+
**Known open risk:** GLiNER multi-v2.1's Indonesian performance is unverified, and the BUMA
|
| 270 |
+
standard is majority Bahasa Indonesia. This is the standing "Indonesian degradation" question,
|
| 271 |
+
now with a concrete test case. Verify before trusting recall numbers.
|
| 272 |
+
|
| 273 |
+
**Flagged as scope creep, proposed not decided:** ensembling GLiNER + LangExtract on every
|
| 274 |
+
ingest. Cheap in API terms, but it adds a reconciliation/dedup step between two candidate sets
|
| 275 |
+
that may disagree — new engineering surface for uncertain gain.
|
| 276 |
+
|
| 277 |
+
---
|
| 278 |
+
|
| 279 |
+
## 8. Integration guidance for this repo
|
| 280 |
+
|
| 281 |
+
Deliberately shape-agnostic — decide the specifics against `CLAUDE.md` and the existing
|
| 282 |
+
subsystem patterns, not against this doc.
|
| 283 |
+
|
| 284 |
+
- **Both halves are new subsystems, not modifications** to the existing document/catalog
|
| 285 |
+
pipelines. The current unstructured path (Tesseract OCR → chunk → pgvector) stays as it is;
|
| 286 |
+
the MinerU path is additive and, at least initially, gated.
|
| 287 |
+
- **Follow the repo's existing multi-stage precedent.** The query subsystem (IR → validator →
|
| 288 |
+
compiler → executor, orchestrated from a service facade, triggered by thin entry points) is
|
| 289 |
+
the closest structural analogue to what both halves need.
|
| 290 |
+
- **Respect the hard boundaries.** Go owns the dedorch schema — any new table needs a
|
| 291 |
+
Harry-ready DDL handoff, never DDL executed from Python. Any new endpoint on the live surface
|
| 292 |
+
needs a contract-doc entry, and an admin ingest surface is a different risk class than the
|
| 293 |
+
read-only chat surface — raise the authentication question rather than assuming.
|
| 294 |
+
- **Build the offline path first.** Because this runs a few times a year and is triggered by an
|
| 295 |
+
admin, a script-driven run over a fixture document is the honest first milestone; HTTP
|
| 296 |
+
endpoints are a convenience layer over it, not the other way round.
|
| 297 |
+
- **Fixtures over live parsing in tests.** A committed parsed-artifact fixture from one of the
|
| 298 |
+
two sample documents lets the extraction half be tested without MinerU installed at all —
|
| 299 |
+
which is the seam doing its job.
|