Skip to content

Releases: KingsleyOWO/Semark

v0.3.1 — Delivery correctness: page furniture, table repair, zh-TW conversion

Choose a tag to compare

@KingsleyOWO KingsleyOWO released this 12 Aug 07:46

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 as 4.64.74.7 still 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, and chunks.jsonl is 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 s2t and 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 by s2twp's phrase
    table (公布→公佈, 關鍵零組件→關鍵零元件, 數據→資料, institution names 台→臺). Ambiguous variant
    pairs are folded before detection, conversion uses s2tw, 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 (凈, 説 — s2t maps 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 :latest tag
    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 /api proxy. See SEMARK_FRONTEND_ALLOWED_HOSTS below; localhost and 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.jsonl metadata, 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_at has 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 direct background=false path, 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, through rehype-raw paired with rehype-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: and https: 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
    ...
Read more

v0.3.0 — Screenshot understanding, privacy masking, download redesign

Choose a tag to compare

@KingsleyOWO KingsleyOWO released this 17 Jul 08:45

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_doc in 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.md is 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_doc flag (default false), so existing API clients keep their exact-runs semantics.
  • Backend test suite: 437 tests green.

v0.2.0 — Output Quality Overhaul

Choose a tag to compare

@KingsleyOWO KingsleyOWO released this 09 Jul 07:07

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_schema toggle (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 at needs_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 --effort passthrough for parser A/B.
  • Higher page-render DPI, block-type coverage, and page-furniture detection.

Chunking & structure

  • heading_path metadata 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_path coverage, 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.