Commit History
-
[NOTICKET] feat(knowledge-parsing): add 1-based page_no (S6b)
A computed `page_no` (= page_idx + 1) on Chunk, so the human/review-UI page number lives in the artifact instead of each consumer converting it. - Derived, never stored: cannot drift from page_idx, and neither normaliser has to set it. page_idx stays 0-based and raw. - Additive, schema 0.3.2 -> 0.3.3; older artifacts and consumers are unaffected. - Closes S6b (open since 2026-08-19); reverses the earlier "convert in the UI" note now that a real reviewer is about to read these. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>717452f
-
[NOTICKET] feat(knowledge-parsing): carry the document's human-readable title
`doc_id` is on its way to becoming an identifier rather than a label. Every `term_id` / `formula_id` / `rule_id` / `brief_id` downstream is a digest of it, so it must stop depending on what someone typed into a Save dialog — which it does today, being the file stem. A long name makes the cost visible: chunk_id: "STD_2026_006_MNO_ Rev.0.0 - Production Parameter and ECA::0000" That name is embedded in all 31 chunks and then in every entry derived from them. Renaming `Rev.0.0` to `Rev 0.0` re-keys the lot. `source_title` is the field the name moves to. It equals `doc_id` today, and that is the point: the two are now separate, so the identifier can change without the human-readable name going with it. `Provenance` is what a reviewing expert actually reads, and it carries `doc_id` — without this field they would be shown `f10807178b20531e` instead of the standard's name. Added ahead of the switch because it is needed whichever identifier wins (content hash or the Go service's upload…df571be
-
[NOTICKET] feat(knowledge-parsing): commit the reference artifact, and stop the stamp churning
Three changes, all aimed at one problem: the two halves of the pipeline could not reliably agree on which artifact they were measuring. **Metadata now precedes `chunks` in the artifact.** Pydantic serialises in declaration order, so it decides what a person sees on opening the file. With `chunks` first, checking which parser or normaliser produced an artifact meant scrolling past thousands of lines to the end — and that is a routine check now, not a rare one. JSON objects are unordered by specification and every consumer reads by key, so this changes nothing for code and `schema_version` is unchanged: no field, type or meaning moved. **`contracts.py` is out of the normaliser hash.** The artifact's SHAPE is already versioned by `schema_version`, so hashing it again counted the same change twice. The reordering above proved the cost: the stamp moved while the chunks came out byte-identical, which is a false "your artifact is stale". A signal that cries wolf gets ignored, and this one e…a0e59e4 -
[NOTICKET] feat(knowledge-parsing): stamp the artifact with the code that built it
A chunks.json is a frozen snapshot of the normalisation code at the moment it ran. When that code is fixed, existing artifacts do not update themselves — and nothing in their metadata said so. `parser_config` fingerprints MinerU's settings; `schema_version` versions the artifact's SHAPE, which a normalisation bugfix does not change. So a stale artifact and a correct one declared identical metadata, and the only observable difference was the character count. That cost a day. A pre-fix artifact reached the extraction half, term recall came back 0.7073 against a 0.8049 baseline, and it read as a regression in extraction's own work. It was neither side's setup: same filter code, different input. Verified here — scoring that artifact reproduces 169 mentions and 0.7073 exactly, against 186 and 0.8293 for the rebuilt one. `normaliser_version` is a hash of the modules that decide what a chunk CONTAINS: normalize.py, render.py, checks.py, contracts.py. A source hash rather than a hand-mainta…c64a0cb
-
[NOTICKET] fix(knowledge): settle the chunk_id separator on `::`
Rifqi Hafizuddin +1: Claude Opus 5The two halves minted provenance keys differently: the parsing half emits `<doc_id>::<seq>`, while the extraction adapter fell back to `<doc_id>#<seq>` for artifacts arriving without one. Nothing broke in practice — the adapter prefers the supplied id, so real runs always carried `::` — but the two shapes could diverge the moment a fixture or an older artifact took the fallback. `::` wins: it is what the producer actually emits, the calibration doc already uses it in prose, and `#` is URL-fragment syntax that would bite once these ids become MCP resource identifiers. The `KNOWLEDGE_OUTPUT_CONTRACT.md` cited as the source of `#` does not exist in the repo or its history. The separator is now stated in the contract next to the field, because this is the second time an undocumented detail of the seam's shape has cost both halves a round trip. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>e8cc26c -
[NOTICKET] feat(knowledge-parsing): carry image descriptions, apart from the text
MinerU describes images and charts, but only on some backends. Read from its own source (is_image_analysis_option_visible): never on `pipeline`, always on `vlm`, and on `hybrid` only at effort=high — medium disables it outright. Two faults kept that description from ever arriving. `normalize.py` had no branch for `image` items at all. Different backends label the same picture differently: what `pipeline` calls a chart, `vlm` calls an image. Image items fell through to the generic text path, which reads only item["text"] — a field pictures do not have — so caption AND description were dropped in silence. That is most of what looked like "vlm loses chart detection" in yesterday's comparison: vlm found them, we discarded them. The second is worse and was waiting to fire. `_teks_chart` appended `content` straight into Chunk.text. Harmless today because `pipeline` always leaves it empty — but the moment the backend flips to vlm, model-written prose starts flowing into the field the extra…c113bf8
-
[NOTICKET] fix(knowledge-parsing): render markup to prose in text; only numbered headings split sections
Both defects were measured by the extraction side on the same document and gold set, changing only the parse: PyMuPDF (plain text) recall 0.8537 35/41 MinerU, raw markup in text recall 0.7561 31/41 MinerU, formula + table rendered recall 0.8293 34/41 1. Chunk.text now carries readable prose, not markup. The term filter is an NER model reading prose. MinerU writes formulas with every character spaced out — "{ \mathrm { P u r c h a s i n g ~ c o s t s } }" — and tables as HTML; neither yields a single mention for terms the same model finds easily in plain text. render.py rejoins spelled-out words and numbers, folds subscripts, maps operators to non-letter glyphs (a literal "x" for \times would merge into the token beside it), and flattens tables to " | "-separated rows. The markup is kept, not discarded: Chunk.latex and Chunk.table_html hold it verbatim, because the formula branch needs exactly that form. Found while verifying: table cells carr…2cb1336 -
[NOTICKET] fix(knowledge-parsing): keep the heading line out of Chunk.text
The heading was briefly copied into `text` as its first segment, to stop the chunk that defines a term from containing no mention of that term — the BUMA standard names the term in the heading and opens the body with "Adalah ..." without repeating it. The extraction half already solves this, and better, because it measured it on the document: `validate/span_check.py` builds its haystack as heading + text, and `rank/evidence.py` treats a heading that names the term as a mention at offset 0. So copying it into `text` only causes harm: the heading appears twice in that haystack, and its occurrence becomes a real mention, inflating mention_count. That count is what the frozen prototype baseline is compared against (169 mentions -> 66 clusters), so v2 numbers would drift for a reason that has nothing to do with extraction quality. `heading` stays verbatim, which is what both halves actually needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>15028da -
[NOTICKET] feat(knowledge-parsing): document parsing half of the knowledge pipeline
Implements §4 B2/B5/B6 and closes the four open seam items (S2, S3, S4, S6b/c/d) against the contracts.py draft circulated 2026-08-19. Additive and flag-gated: src/knowledge/ (Tesseract -> pgvector) is untouched. Seam — src/knowledge_parsing/contracts.py, pydantic per S6e: - ParsedDocument envelope: content_hash, n_pages, version, parser_name/version/ backend/config, schema_version, raw_output_dir. parser_backend is read from MinerU's _middle.json, so it records what actually ran, not what was configured — a MinerU change and a prompt change stay distinguishable (S2). - Heading structure is READ, not derived. Verified in MinerU's source: pipeline and vlm run identical text_level logic, and bbox is emitted by both. Heading availability is document-dependent, not backend-dependent — the earlier claim came from sampling the McGraw-Hill handbook, which has no numbered headings (S3). - source_wording on TermRecord; heading carried verbatim (S4). - page_idx/page_idxs, 0-based, n…5840d20