Spaces:
Sleeping
Sleeping
Commit History
-
rename package doc_agent -> docfield, distribution -> docfield-extract
The last identifier still carrying the old framing. The project is not an agent and the README says so; the import path said otherwise on every line of every module. Moved with git mv so history follows all 26 files, and kept as a package directory rather than flattened -- removing it would break the src layout the Space entry point depends on. pyproject needs [tool.uv.build-backend] module-name = "docfield". The uv build backend derives the module name from the project name, so a distribution called docfield-extract would otherwise look for src/docfield_extract/ and fail to find the package. Distribution name and import name differ on purpose: docfield-extract matches the repo, docfield is what you type. DB_PATH default moves from ./data/agent.db to ./data/docfield.db. This orphans any existing local database -- a watcher run against the new default starts an empty store and the old file is simply ignored, not migrated. There was no .db file in this working tree, so nothing was act…b6beb2d
-
logging: keep extracted field values out of log records
The Space log pane is readable by visitors. The privacy notice asks people not to upload real documents but cannot enforce it, so no log record should carry document content if someone ignores it. A decision plus a rule code is enough to debug from. Two sites logged field values and no longer do: - gemini.py logged total and vendor_name on every successful extraction (at DEBUG, so currently filtered, but a level change would have exposed it). Now logs a populated-field count and a line-item count instead. - store/db.py logged the total of every persisted record at INFO. This is the watcher path, not the demo, but the same reasoning applies to a local log file. Now logs the decision. core.py's per-document record needed no change: it already carried only path, modality, backend, decision, confidence, model_signal, and the codes of failed rules -- never a field value. An earlier commit message and the app.py docstring both claimed otherwise; the docstring is corrected here. L…bd211ba -
web: trim docstrings and drop the copy-preference test
The previous commit over-documented. Full Args/Returns blocks on one-line renderers restate the signature in prose; they were noise on functions whose inputs and output are evident from the type hints. Kept the docstrings that carry reasoning a reader cannot recover from the code: _review_reason's precedence order, _render_startup_error's explanation of why a pre-result failure needs its own wording, the note on why _RuleCopy holds three strings, the evidence-on-failure-not-success choice, and the inline comment on why H3's copy avoids the word "subtotal". Removed all seventeen Args/Returns blocks. Also dropped the test asserting no digits or benchmark words appear in the intro copy. That pinned a wording preference, not a contract, and would fail on an innocuous edit. The privacy-notice test stays -- NFR-2 is a real requirement, not a preference -- renamed to say what it guards. The drift guards on rule copy and the code-leak assertions stay untouched; those catch regressions that w…9b2aa9f -
web: explain the decision in the output, in plain language
The demo described the project in its intro and then handed the user a raw rule table -- "H1 | Hard rule | OK". Someone looking at a verdict and asking why had to learn the rule codes to find out. The explanation belongs where the verdict is, phrased for a reader who has never seen this codebase. - Verdict renders above the tabs, always visible, naming the deciding reason. Precedence: a processing failure outranks a hard-rule failure, which outranks falling short of the threshold. The renderers read result.decision and never recompute it (architecture rule 1). - Checks tab states each rule as a sentence, grouped into what must pass and what only lowers the score. _RULE_COPY carries three strings per rule -- a title for skips, and separate pass/fail sentences, because one assertion plus a status column reads as a contradiction on failure. - Rule codes survive only in the Details tab, which also keeps the raw rule messages, confidence against the threshold, backend and mod…5531435 -
docs: drop remaining "agent" framing from shipped docstrings and backlog
The rename pass touched six files and missed three places that still call the system an agent. README.md states plainly that this is a pipeline, not an agent, with no tool loop and no autonomous decision-making; these contradicted it. config.py is the sharpest of the three: a public class docstring on Settings describing "the extraction agent". PROGRESS_FUTURE.md is linked from the README as the public backlog, so "autonomous watcher" was user-visible. "unattended" is the accurate word for the watcher -- it runs without a human present, which is not the same as deciding anything on its own. Left alone deliberately: the frozen specs in docs/, the doc_agent package name and its imports (a separate open decision), the Gemini prompt string (changing it would alter model behaviour and invalidate the eval cache), and every reference to the agentic loop that BUILT the project -- that one is a real agent loop and the wording is correct.c65024b -
validation: monetary tolerance scales with rounded terms, not with value
H2 and H3 gate auto-acceptance, and their tolerance was max(0.02, 0.005 * amount) -- 0.5% of the document value. That is the same defect the README records fixing on the measurement side, still live on the decision side, where the consequence is a wrong total being written rather than a metric being off. At 500 it permitted a 2.50 discrepancy; at 100,000 it permitted 500. It is also mis-specified against its own stated intent. The old comment said large invoices should "tolerate the accumulated rounding of many line items", which is a fair thing to want, but rounding accumulates with the *number* of figures summed, not their magnitude. A 100,000 invoice with two lines has no more rounding in it than a 100 one. Replace the relative term with a count-based one: 0.02 plus half a cent per additional independently-rounded quantity. H2 compares three stated figures and takes the floor alone. H3 allows per line item summed. S4 allows per unit, since a unit price rounded to the cent carries …6eb59bb -
validation: S2 skips an absent currency instead of failing it
S2 failed a document that stated no currency. That is a domain mismatch, not a correctness signal. Whether an issuer prints a currency code is a property of the issuer; for a receipt that omits one, None is the *correct* extraction. Penalising it confuses "the model told us nothing" with "the model looks wrong", and costs recall for no precision gain. The module docstring already stated this principle -- absent inputs are skipped, not failed, with H4 the one declared exception. S2 was a second, undeclared exception. S1 and S3 do check presence, but for fields every document in scope carries; a currency code is not one of those. The docstring now says so rather than leaving the asymmetry unexplained. A currency that IS present but resolves to no known code still fails. That is a real anomaly: the field was read and what came back is not a currency, which casts doubt on the rest of the read. Removing S2 outright would have discarded that signal too. Measured by holding the cached mode…a0889d7 -
eval: score under current rules with --revalidate, and detect cache drift
The predict phase freezes both the confidence scalar and the validation report into every cache entry. The score phase replayed only route() over those frozen values, so any change to a soft rule in validation/rules.py or to the weights in routing/score.py was invisible to a re-scored report: the cache silently went stale against the codebase and still printed confident-looking numbers describing a rule set no longer in the code. Close it by recomputing from the cached predicted document, which predict already stores in full. Validation and scoring are pure functions of that document, so both recompute exactly, offline, for zero API calls. - eval/revalidate.py: reconstitute Document -> validate -> score, plus drift detection comparing recomputed against frozen values. - score --revalidate scores under current rules. Every report header now states which rule set produced its numbers, so cached-scalar and current-rule figures can never be confused six months from now. - Drift is …f1a9398 -
docs: rename to DocField Extract, reframe as key-value extraction pipeline
Repo renamed to docfield-extract. Three framing corrections, because the old README described a system this is not: - Drop "agent" language. There is no tool loop and no autonomous decision-making; the LLM output is data, never control flow. It is a modality router, one schema-constrained extraction call, and rule-based validation. The "Where's the agent?" section is gone. - Not a receipt parser. SROIE is the benchmark, not the scope; the schema is general business-document fields. - Position in the IDP taxonomy as key-value extraction, distinct from table extraction. line_items is populated opportunistically and carries no ground truth, so it is not a table capability. Lead with the evaluation harness rather than burying it, and state its limitations where a reader will hit them: only 4 fields labeled, tax and invoice_number unscored, line items unscored, 100% precision is over 18 values, the operating point was chosen on the slice it is reported on, confidence scoring i…14990f1
-
phase 5: set CONFIDENCE_THRESHOLD=0.50, record SROIE results (T10)
Set the threshold to 0.50 from the SROIE n=100 eval. Gemini exposes no per-field confidence, so every document scores 0.50 and the sweep is binary -- the H2/H3 arithmetic checks, not this score, keep auto-accepted totals correct. Note that caveat in the config, .env, and README. Record the SROIE numbers in the README: total 99% P/R/F1, 18% auto-accepted at 100% precision on total, the one wrong total caught by validation. Sync routing's DEFAULT_CONFIDENCE_THRESHOLD to 0.50; update the three tests that pinned 0.85. Leave docs/ untouched. 218 tests pass, ruff clean.51f6fc8
-
feat(web): Gradio stateless web demo (T9)
Single-upload Blocks UI: accepts PDF/JPG/PNG/WEBP, calls process_document from core (no pipeline logic in the web layer), renders extracted fields with per-field confidence in a table, full H1-H4/S1-S4 validation report, and the accept/review decision with confidence score. Synthetic/public documents only notice shown on every page (NFR-2). Stateless -- no SQLite writes, no file moves. Launch: uv run python -m doc_agent.web.app.8089454 -
fix(lint): clear 5 ruff F401/F841 errors found by verifier
Remove unused dest variable in watcher._process_one (F841), drop unused sqlite3 import in export.py (F401), remove json and call imports in test_gemini.py (F401), remove unused pytest import in test_watcher.py (F401). All 201 tests still pass.d4cf766 -
feat(watcher): folder watcher + batch runner (T8)
watcher.py: _process_one (hash -> process_document -> persist -> move), process_inbox for one-shot batch mode, run_watcher for continuous watchdog mode. Corrupt/unreadable files route to review and the loop continues. __main__.py wires up continuous mode. 9 unit tests cover accept/review routing, corrupt-file resilience, duplicate-hash idempotency, unsupported file skipping, and directory creation. Verified on a mixed batch (PDF + photo + corrupt): all processed, corrupt routed to review with logged reason, loop never stopped.32d2d9e -
feat(store): SQLite persistence + CSV export (T7)
db.py: append_record with UNIQUE(content_hash) idempotency, _connect singleton, flat row serialisation (line_items as JSON, dates as ISO strings). export.py: export_csv reads all rows and writes a flat CSV with line_item_count instead of expanding items. 12 unit tests covering insert, duplicate skip, field round-trip, CSV header+row, empty-DB, multi-record, and parent-dir creation. 192 tests green.8e1d97f -
feat(docling): Docling parser + native-PDF acquire path (T3)
docling_parser.py: DocumentConverter singleton with OCR disabled (native PDFs have embedded text; disabling OCR avoids the RapidOCR torch model that fails on Windows). core.py _make_acquire now handles native_pdf by calling parse_pdf and returning a text payload. Verified on a Foodpanda PDF receipt: 17 line items, vendor, date, currency all correct; H2/H3 hard failures correctly caught a total mismatch and routed to review.e2eec89
-
feat(gemini): Gemini backend + vision-direct image acquire (T2)
GeminiBackend: multimodal call, _ExtractionSchema with string dates for clean JSON schema, schema-constrained output via response_schema, 3-attempt exponential backoff. Factory registers the gemini builder. core.py replaces the placeholder _default_acquire with _make_acquire(settings), handling image+vision_direct by loading raw bytes; native_pdf/OCR still deferred. 11 mocked unit tests cover parsing, null fields, retry, backoff, exhaustion, and factory wiring. Verified on a real SGD receipt: vendor, date, line items, and subtotal correct; H2 correctly caught total!=subtotal+tax and routed to review. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>7a6af4c -
phase 3.2: content-hash idempotency helper
54cf0e1
-
phase 3.1: core pipeline orchestration (stub-backed smoke test)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>90d0304 -
phase 2.4: backend interface + factory + offline stub backend
d17ca40 -
phase 2.1: modality detection
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>e9e88a5 -
phase 1.3: confidence scoring + routing decision
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>5ab3dcf -
phase 1.2: validation rules (hard/soft + arithmetic checks)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>fd5e760 -
phase 1.1: pydantic document schema + normalizers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>a003369 -
phase 0.2: config loader with validation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>d17fef4 -
phase 0.1: project scaffold (uv, py3.11, package layout)
001b1d5