Releases: KingsleyOWO/Semark
Release list
v0.3.1 — Delivery correctness: page furniture, table repair, zh-TW conversion
Release Notes 2026-08-12 (v0.3.1)
English | 繁體中文
This is a delivery-correctness release. v0.3.0 shipped the screenshot pipeline; running a
corpus of research reports through it surfaced a class of defect the quality gate could not
see — output that is well-formed, passes every check, and is missing or garbling exactly the
text a retrieval query lands on. Every fix below was measured against that corpus rather than
against the few documents that first showed the symptom. (The corpus grew from 100 to 167
documents during the round, which is why the denominators below differ.)
Previous release: Release Notes 2026-07-17 (v0.3.0).
Highlights
Body text, not the page it was printed on
- Page furniture (running heads, folios, volume lines) had a detector and no production
caller: 1,826 lines were delivered as body paragraphs, inside 851 of 1,843 chunks. All three
delivery surfaces now skip them, and short text that recurs in the same margin band on two
or more pages is additionally tagged as furniture. - The supplement pass — which re-reads each page and fills in what no block covers — was
duplicating and reordering the prose it was meant to complete. Coverage is now directional,
spans one page either side, ignores markdown escaping and whitespace, and falls back to bbox
containment for text no comparison can match. Supplements are spliced into the parser's
reading order per page instead of the whole document being re-sorted by vertical position;
one supplement had been enough to interleave the columns of a two-column layout. - Documents whose cover title is set vertically delivered a garbled
H1— OCR of vertical
type drops and swaps characters — while a clean copy of the same title sat unused in the IR
as a horizontal running head. 49 of 167 documents were affected. The running head is now
authoritative for characters but not for which segments a title has, so a cover kicker the
running head omits is kept, and a series label it adds is refused.
Tables
- Multi-row headers. A one-row-header assumption sent a table's sub-column names to a
fallback (欄位4: 22.40, with nothing to say it is a share of imports) and shipped the
sub-column row as the first data record. Header rows are now folded behind four guards, each
with a live counter-example. Placeholder column names: 926 → 8. - Table titles. Where the parser gave no caption (52 of 128 tables), the title came from a
document-level string picked by substring match, truncated to 100 characters. The nearest
caption printed above the table is now used; 46 of the 52 recover their printed name. - Numeric tables were being discarded as OCR noise. Any purely numeric cell counted as
"weak", so a wide statistics table was structurally guaranteed to read as garbage and have
every data row dropped — 7 tables in 4 reports, whose absence then tripped the
empty-output check, the VLM audit, and a reviewer pass of roughly 100k tokens. Well-formed
figures are now read as data; lone digits and mash such as4.64.74.7still count as noise.
11 of 11 tables recovered. - Attribution rows. The 注/資料來源 line printed inside a table's own border looks exactly
like a data row with only its first column filled, so it was emitted as a record whose
heading is a sentence and whose one field states something the table never said. It is now
recognised by its wording — narrowly, against the openings papers actually use — and
rendered as a trailing line after the records. A row that does not announce itself stays
data, because a row whose cell boundaries the parser lost is full-width too and the two are
indistinguishable by shape. - Collapsed cell boundaries are now repaired. New capability; see Compatibility Notes.
- Table titles in
chunks.jsonl. The parser hands captions back as lists of strings; 76
table titles shipped as the Python list repr, andchunks.jsonlis what retrieval reads.
Captions are now flattened once, where the payload is built. The same shape had been making
the figure caption-length gate fire on every properly captioned figure, so the model was
re-captioning figures that already had a caption printed beside them.
Traditional Chinese output
- The simplified-content detector converted with
s2tand compared against the source, but
that mapping treats 台/布/占/干/了 — every one of them standard zh-TW — as simplified. Ordinary
Taiwanese prose therefore read as simplified, and was then rewritten bys2twp's phrase
table (公布→公佈, 關鍵零組件→關鍵零元件, 數據→資料, institution names 台→臺). Ambiguous variant
pairs are folded before detection, conversion usess2tw, and the author's own variant
choice is restored positionally afterwards. - Conversion never saw the parser's own text: its single call site was the VLM render path, so
849 unambiguously simplified characters reached the output across 88 of 100 documents. It
now runs as a normalize post-pass over every text-bearing payload field, whatever produced
the block, with a kana guard applied line by line so a bibliography's Japanese entries are
not "repaired". - Two characters the detector could not see at all (凈, 説 —
s2tmaps them to themselves)
passed byte-identical and never reached conversion: 51 and 25 occurrences.
VLM robustness
- A model the endpoint does not serve now reports unavailable. The availability check
returned true whenever the HTTP endpoint answered — including the branch that had just
reported the configured model missing — so a model that was never pulled, or a typo in the
name, did not stop the stage once with something a human could act on; it let every
enrichment request go out and fail one at a time. An endpoint that enumerates no models
stays permissive (some gateways return an empty list), and Ollama's implicit:latesttag
is matched so a working bare-name configuration is not called broken. - Crops below the vision model's 32px patch factor are never sent. Four crops of 32x31,
34x31 and 37x31px caused 8 model-runner panics and 8 lost enrichments; the server reports
this to the client as a generic resource-limit error that reads like an out-of-memory
condition and is not one.
API, operations, security
- Named-host access is now opt-in. The frontend dev server ran with all hosts allowed on
0.0.0.0, so any website could DNS-rebind to it and reach the unauthenticated backend
through the/apiproxy. SeeSEMARK_FRONTEND_ALLOWED_HOSTSbelow;localhostand direct
IP access are unchanged. - The privacy scrub now runs at the write boundary. It had been applied at scattered call
sites, leaving four delivered surfaces unmasked:dataset.md, the structured-repair
main-document re-export,chunks.jsonlmetadata, and the VLM-audit excerpts in
quality_gate.json/llm_vlm_outputs.md. - Delivered writes are atomic. No delivered artifact was written atomically, and split
documents were deleted before their replacements were written, so a crash mid-export left
deleted files still referenced by the old index. Stale split documents are now removed only
after the new set lands. - Download lists past 500 runs. With the default filter, the outputs-summary endpoint only
ever inspected the newest 500 runs and reported the count within that window as the total,
so installs past that point silently lost older documents from download lists. List queries
also gained a stable tiebreaker —created_athas second resolution, so batch-created rows
tied and could skip or duplicate rows across pages. - Enrich cache identity is parse-aware. Keys folded in neither the parse configuration nor
the parser version, so re-parsing under different settings could serve a caption belonging
to a different figure, and switching the output language served captions in the old
language. Previously cached rows re-enrich once. - Pipeline concurrency. One module-level semaphore is now shared by the queue workers and
the directbackground=falsepath, which previously ran unbounded concurrent parse+VLM
pipelines. The startup sweep also cancels PENDING runs orphaned by a restart (the queue is
in-memory), and the task queue is stopped before the database disconnects so cancellation
handlers can still write run status during shutdown. - Viewer. The bbox overlay was vertically compressed on every page — on A4 portrait a
page-bottom block drew at 71% height, so clicking a region selected the wrong block. Tables
that the package stage deliberately keeps as raw HTML now render in the main-text pane
instead of being silently dropped, throughrehype-rawpaired withrehype-sanitize
(scripts, event handlers and inline styles stay stripped). Relative image paths resolve
through the asset endpoint instead of 404ing against the SPA route. - The health endpoint reports the real application version instead of a hardcoded
0.1.0.
Content that is not content
- A promotional insert — advertisements with prices, order lines and QR codes — occupies the
right column of the last page in 32 documents while the left column still carries the
reference list, so this had to be a column-level judgement: dropping the page would have
deleted the references. The column boundary is derived from the page's own gutter rather
than a hard-coded coordinate. It also keeps 105 covers and QR codes out of the model. - A twenty-page report's bibliography was being scored with thresholds calibrated for a
one-page form:DOI:andhttps:entries plus colon-terminated citation titles read as
field labels, and 申請單 matched inside 申請單位 ("applicant unit"). 5 of 100 research reports
were flagged as fillable forms, each raising an empty-structured-output issue and its
...
v0.3.0 — Screenshot understanding, privacy masking, download redesign
Release Notes 2026-07-17 (v0.3.0)
This release focuses on screenshot-based how-to documents: understanding UI screenshots, keeping personal information out of the delivered output, and making downloads predictable.
Highlights
New demos: screenshots vs parse-only baseline
Two public screenshot how-to guides were added under examples/demos/ — a Traditional Chinese treasury payment slip guide and pages from the VA Customer Engagement Portal vendor guide. Each demo ships the raw parse-only baseline (raw-parse.md, where entire screens are dead image links) next to the semantic output, and the README Demo Preview was restructured around the pain points each demo solves. Reviewing these demos also drove five export-quality fixes: guide-style title inference (操作說明/使用說明), bracket section labels (「【說明】」) no longer become document titles, repeated screenshot OCR lines are deduplicated in the main document, an applied semantic repair backfills the document title from the repaired heading, and the form-template gate respects the repair verdict instead of re-flagging reviewer-authored guides.
Screenshot understanding
- Figure captions are grounded against text that actually appears in the document, suppressing hallucinated product or vendor names in captions and keywords.
- Screenshots are no longer mislabeled as flowcharts: menu-path evidence (
A > B > C) still becomes structured content, but the stored image type stays truthful. - Decorative icons and unreadable crops (no legible OCR text, self-declared blurry captions) are filtered out of the RAG weave and the chunk stream.
- OCR noise (page-number groups, cropped account fragments) is no longer promoted to headings, keeping chunk heading paths meaningful.
- Traditional Chinese output cleanup: OpenCC 了/瞭 round-trip damage is repaired on both the detection and conversion side, and mainland vocabulary is mapped to Taiwan usage (界面→介面, 圖標→圖示, 分辨率→解析度, …) with guards for terms like 界面活性劑.
Personal information masking
- New deterministic privacy scrub for content transcribed from screenshots: mailbox subject/sender lines, domain account IDs (for example
CORP\x12345), and personal numeric email account IDs (d*****@…) are masked on every delivered surface — Markdown, split documents, and chunks. - Instructional content survives: account-format explanations and UI menu labels are explicitly preserved.
- Enabled by default; toggle under Settings → Output Package as Mask Private Info (遮蔽個人資訊).
- Masking is a best-effort safety net for common patterns. If personal or sensitive content must never appear in the output, blur or cover it in the source screenshots before processing.
Downloads redesigned
- 主文 (main text) downloads now serve exactly the document the viewer renders — the previous separate render path could differ from the on-screen content and has been removed. Existing runs benefit immediately, no re-processing needed.
- The Documents page download flow is a per-action menu: choose content (main text only / all documents), choose format (MD/DOCX/TXT), download. No more global mode toggles that silently override the checked documents; the last choice is remembered.
- Batch downloads export each document once, from its newest run (
dedupe_by_docin the batch API, default off for compatibility), and the menu states the effective scope: runs selected → documents exported. - Single-file cases download the file directly instead of a one-file ZIP; flat ZIP entries no longer overwrite each other when several runs share a source name.
- The run list loads completely (paged) instead of stopping at the first 100 runs.
Quality gate alignment
- Gate verdicts now track content quality for how-to guides: table-of-contents dot leaders and quote-enclosed cropped UI labels no longer trigger the truncated-output warning, while genuinely truncated lines still do.
- Verdict cascade repair and table fidelity preservation in the packaging stage.
Compatibility Notes
outputs/main_text.mdis no longer written for new runs; the download API resolves the main document from the split-document index. Files from older runs remain readable through the same endpoint (legacy fallback).- The batch download API gained the optional
dedupe_by_docflag (defaultfalse), so existing API clients keep their exact-runs semantics. - Backend test suite: 437 tests green.
v0.2.0 — Output Quality Overhaul
Semark v0.2.0 — Output Quality Overhaul
A ground-up pass on RAG output quality, backed by a new golden-eval harness that
scores fact recall, chunk shape, and the quality gate on every run.
Reviewer (semantic-repair) — the quality lever
- Per-role
use_json_schematoggle (settings API). Turning strict
constrained decoding off for the reviewer role lets it emit applicable
rewrites. With strict schema on, the reviewer drops facts and its repair is
rejected, leaving docs atneeds_review; off, a local model matches a
commercial reviewer on text-layer documents. - Long-output budget floored at 24000 (was 8192). Whole-document rewrites of
multi-page docs are no longer truncated — an 8192 cap dropped ~20% of facts on
an 8-page document. Operators can still raise it to the 32768 ceiling. - Text-only reviewer support (
SEMARK_REVIEW_SEND_PAGE_IMAGE) for models
that reject image input; the page-image audit is skipped accordingly. - Fact-preservation guard: reviewer rewrites that drop numbers, dates, legal
references, or field labels are rejected, and the gate is re-validated after a
repair instead of blindly clearing. - Reviewer client runs with no auto-retry to bound latency.
Parse & enrichment
- Data-URI image transport by default (Ollama-compatible). The old
static-URL default silently starved the VLM of images. - Per-task token caps — no more collapse-to-1024 truncation of dense form JSON.
- ACCURATE profile uses
method=AUTO— born-digital text is no longer
needlessly re-OCR'd. - Scanned visual pages routed through full-page VLM enrichment.
- MinerU hybrid backend
--effortpassthrough for parser A/B. - Higher page-render DPI, block-type coverage, and page-furniture detection.
Chunking & structure
heading_pathmetadata on structured / repair / fallback chunks.- CJK-aware splitting; oversized chunks split to embedder-sized (e.g. a doc that
produced 6 chunks up to 1504 tokens now yields 19 chunks ≤ 912 tokens).
Evaluation
- Golden-eval harness (
semark-eval golden): fact-recall scoring,
must_include / must_not_include, chunk stats,heading_pathcoverage, gate
passthrough, and run-vs-run comparison.
Maintainability
- Corpus-specific heuristics externalized to a data-driven ruleset
(SEMARK_CORPUS_RULES_PATH). - Policy-title heuristic generalized — no institution hardcoding.
- Fixtures/repo de-identified; duplicate deployment file removed.
342 tests green.