Commit graph

599 commits

Author SHA1 Message Date
LLLin000
b4c0404542 feat: deliver actionable Maintenance inbox and privacy-safe issue drafts (#80)
Maintenance derives worst-constituent severity from Installation/
Library/OCR/Memory/Help with no independent probe/action. Items
route to constituent module actions; re-probe removes resolved
items. Accessible destructive confirmation (Tab trap, Escape,
inert bg, focus restore) plus editable locally redacted OCR
issue drafts with explicit GitHub open and no token storage.

Backend: probe_maintenance aggregation, quality_unacceptable
detection with scope_count, 78/78 gate.

Frontend: Maintenance tab, inbox rendering, PaperForgeConfirmModal,
PaperForgeIssueDraftModal with double-redaction, excluded-category
preview, _REAL_PROBE + strict envelope validation, container-query
responsive at 730/768px.

Verified: 78/78 backend, 384/385 plugin (1 known baseline),
typecheck clean, 264.9KB build, Obsidian 1.12.7 smoke.
2026-07-19 14:09:54 +08:00
LLLin000
69a6223910 feat: expose library OCR and memory capability tracers (#78) 2026-07-18 18:13:08 +08:00
LLLin000
85a7ae30a2 feat: add OCR rebuild UX and capability tracer 2026-07-15 20:36:15 +08:00
LLLin000
906b3cafc0 fixup: headless via SetupPlan only, ConfigWriter always schema_version 2
- Removed headless_setup call from CLI dispatch entirely. --headless,
  --modular, and bare all construct the same SetupPlan with identical
  _cfg/zotero_path/agent_type/skip_checks. Only the deprecation notice
  differs per entry point.
- Added skip_checks parameter to SetupPlan (forwarded to CLI arg).
- ConfigWriter always writes schema_version '2' unconditionally (never
  preserves a v1 value from existing config).

RED commands:
  python -m pytest tests/test_setup_plan_v2.py::TestConfigWriterSchemaVersion -v
  python -m pytest tests/test_setup_plan_v2.py::TestCliHeadlessViaSetupPlan -v

GREEN commands (same, after fixes): 61/61 passed.
2026-07-15 02:23:46 +08:00
LLLin000
7b7474237a fixup: address #75 review — headless canonical, zotero forwarding, visible deprecation, all legacy path keys
- --headless now delegates to SetupPlan for canonical config, then runs
  headless_setup for full deployment; canonical ConfigWriter pass ensures
  v2 vault_config format.
- --zotero-data is forwarded through SetupPlan.zotero_path for both
  --modular and bare paths.
- Deprecation warnings use print(stderr) instead of filtered
  DeprecationWarning so users always see them.
- ConfigWriter.PATH_KEYS extended to converge all seven legacy path keys
  (system_dir, resources_dir, literature_dir, control_dir, base_dir,
   skill_dir, command_dir) into vault_config, preserving non-path metadata.
- Added CLI-level tests: deprecation on stderr, config canonicalization,
  --literature-dir forwarding, --zotero-data plumbing.
2026-07-15 02:17:04 +08:00
LLLin000
af84969972 feat: canonicalize setup and configuration migration (closes #75)
ConfigWriter now writes v2 canonical format (nested vault_config with
schema_version). On rerun it merges with existing config rather than
overwriting, making repeated runs idempotent.

load_vault_config reverses the v1 precedence: vault_config block now
wins over legacy top-level path keys, which are treated as a read-only
fallback with a UserWarning. This is the N+1 transition — old v1
configs remain readable with a warning; all writes produce v2 only.

Bare and --headless setup now delegate to the canonical SetupPlan engine
(the --modular path) with a DeprecationWarning.

Closes #75.
2026-07-15 02:07:13 +08:00
LLLin000
e556c8bab0 fix: complete final OCR batch item normally 2026-07-14 18:32:48 +08:00
LLLin000
3a516add86 fix: align OCR maintenance selection semantics 2026-07-14 18:27:53 +08:00
LLLin000
349d4f6dcb feat: add streaming OCR rebuild progress 2026-07-14 18:05:49 +08:00
LLLin000
b4ac277438 Merge branch 'feat/retrieval-recovery'
# Conflicts:
#	paperforge/commands/embed.py
2026-07-11 01:26:37 +08:00
LLLin000
0bc054e7b0 fix: cooperative stop with state settlement and build-loop cancellation
embed stop now sets status=stopping, waits for the build PID to exit
(8s timeout), force-kills as last resort, then settles to idle.

Build loop checks the cancellation flag (read_vector_build_state)
between papers in the main loop and drains in-flight work before
cleanly exiting without marking 'completed'.

Fixes pre-existing UnboundLocalError from a local import shadowing
the global mark_vector_build_state symbol in the run() function.
2026-07-10 23:55:21 +08:00
LLLin000
40326d6759 fix: add vec0 k-NN health probe to embed status
embed status now runs a zero-vector k-NN query on each vec0 table
that has companion meta rows. If the probe fails (table dropped,
extension unavailable, index corrupted), healthy=false and
corrupted=true are reported even when meta row counts > 0.

Includes integration test that drops vec_fulltext and verifies
healthy=false.
2026-07-10 23:55:16 +08:00
LLLin000
66c2db427e fix: add missing vec_fulltext_meta DDL and embed lifecycle tests
schema.py: CREATE_VEC_FULLTEXT_META was referenced at line 328 but
never defined. Adds the missing DDL constant so the v5 schema migration
does not raise NameError on fresh databases.

tests: add embedding test suite with four tests covering vec0 resume
detection, empty-table fallback, force drop, and delete-then-write
ordering verification.
2026-07-10 23:39:10 +08:00
LLLin000
fc1dca429c Unify PFResult envelope: data.matches with consistent field names, --deep flag
- search.py: normalize FTS results to unified field set (score from rank,
  add text/heading/source keys). Result key unchanged (already 'matches').
- retrieve.py: rename data.chunks to data.matches in standard and deep paths.
  Map field names: paper_id->zotero_key, chunk_text->text, section_path->heading.
  Expand enrichment SQL to fetch journal and domain.
  --deep flag already wired in CLI parser.
- tests/cli/test_json_contracts.py: add contract tests verifying PFResult
  envelope shape, data.matches key presence, unified field names,
  --deep flag acceptance, and field type correctness.
2026-07-10 23:15:16 +08:00
LLLin000
984dfc6114 Unify PFResult envelope: data.matches with consistent field names, --deep flag
- search.py: normalize FTS results to unified field set (score from rank,
  add text/heading/source keys). Result key unchanged (already 'matches').
- retrieve.py: rename data.chunks to data.matches in standard and deep paths.
  Map field names: paper_id->zotero_key, chunk_text->text, section_path->heading.
  Expand enrichment SQL to fetch journal and domain.
  --deep flag already wired in CLI parser.
- tests/cli/test_json_contracts.py: add contract tests verifying PFResult
  envelope shape, data.matches key presence, unified field names,
  --deep flag acceptance, and field type correctness.
2026-07-10 23:10:36 +08:00
LLLin000
4ff165ae49 feat(#38, #40): version history panel + @ Deep Search
#38: File Recovery-style version history panel with i18n (en/zh).
- _renderVersionMode() with paper list + timeline + restore/compare
- version-history.ts service for scan/restore/compare
- Paper mode [版本历史] button
- i18n strings for all panel text

#40: @ Deep Search with query rewrite + hybrid retrieval.
- query_rewrite.py: abbreviation expansion (28 terms) + synonyms (12)
- hybrid_search(): BM25 FTS5 + vec0 k-NN + score fusion (0.3/0.7)
- retrieve --deep flag routes to hybrid_search
- Graceful vec0 fallback (FTS5-only)
- 17 new tests

Co-authored-by: VersionPanel <agent>
Co-authored-by: DeepSearch <agent>
2026-07-10 01:01:48 +08:00
LLLin000
27e45f9419 feat(#37): auto-backup fulltext before rebuild
backup_render_before_rebuild() copies render/fulltext.md (and
render-map.json, heading-events.json) to versions/v{N}/ before
rebuild overwrites them. Creates/updates versions/manifest.json
with version metadata. Idempotent: skips when no render exists.

Hook placed between phase 3 and phase 4 in _rebuild_one_paper.

6 new tests.
2026-07-10 00:34:44 +08:00
LLLin000
06fec04651 feat: Phase 0, 1, 3a — legacy OCR fix, ChromaDB migration, search bar UI
Phase 0 (#35): _needs_derived_rebuild returns (False, 'legacy_ocr') for
papers without structured blocks and derived_version. _can_rebuild adds
has_structured check. No misleading rebuild suggestions for legacy users.

Phase 1 (#36): migrate_chroma_to_vec0() copies existing ChromaDB vectors
to vec0 tables with zero API calls. Idempotent (skips by paper_id).
delete_paper_vectors() cleans both ChromaDB and vec0. CLI: embed migrate.
5 new tests.

Phase 3a (#39): Search bar in collection panel — two-mode input
(M metadata / @ deep) with card results display. JSON output parsed
from search/retrieve CLI. 289 lines TypeScript, 175 lines CSS.
Build passes clean.

Co-authored-by: Phase0LegacyOcr <agent>
Co-authored-by: ChromaMigration <agent>
Co-authored-by: SearchBarUI <agent>
2026-07-10 00:02:02 +08:00
LLLin000
d24c78c5dc feat(#27 #28 #29 #30 #32): migrate write/read/status/build_state from ChromaDB to sqlite-vec
#27: write_encoded_payload and delete_paper_vectors use sqlite-vec
  - builder.py: writes to vec0 tables + companion meta tables
  - _chroma.py: delete_paper_vectors deletes from vec0 + meta by paper_id
  - Stores body_units_hash, object_units_hash, retrieval_policy_version in meta tables

#28: merge_retrieve and retrieve_chunks use vec0 k-NN search
  - search.py: vec0 k-NN queries with companion meta JOINs
  - Same dedup/per-paper-cap logic, same result shapes

#29: build_state migrated from JSON file to SQLite build_state table

#30: E2E embed+retrieve test with sqlite-vec + FixedProvider
  - 3 tests: body roundtrip, source correctness, per-paper cap

#32: E2E OCR pipeline test with fixture PDFs

Additional changes:
- schema.py: bump to v6, add hash/policy columns to vec companion meta tables
- embed.py: _assert_collections_healthy uses sqlite-vec; resume hash checks read from meta tables
- status.py: chunk counts from SQL COUNT on companion tables
- Removed unused get_collection/embed_paper imports from embed.py
- All tests updated: 76 pass (was 72, +4 new E2E tests, +4 for migration)
- FixedFixedProvider to generate 1536-dim vectors (matching vec0 schema)
- Fixed health check tests for sqlite-vec path
2026-07-09 17:52:45 +08:00
LLLin000
49f84a1aad feat(#31): OpenAICompatibleProvider uses openai SDK with requests fallback
Rewrite embedding provider to use openai.OpenAI client instead of
raw requests.post. Old code preserved in requests_fallback.py,
selectable via VECTOR_DB_PROVIDER_TYPE env/setting.

Changes:
- paperforge/embedding/providers/openai_compatible.py: rewrite to SDK
- paperforge/embedding/providers/requests_fallback.py: NEW old impl
- paperforge/embedding/_config.py: add get_provider_type()
- tests/test_openai_compatible.py: 6 new tests (encode, timeout,
  provider_type switch, no-key error, custom base url)

feat(#33): E2E test fixtures — 3 synthetic PDFs + expected outputs

3 synthetic papers generated with PyMuPDF, each 3 pages:
- paper_a: The Effect of Machine Learning on Clinical Outcomes
- paper_b: A Randomized Trial of Remimazolam vs Propofol (with tables)
- paper_c: Deep Learning for Medical Image Segmentation (with figures)

Changes:
- tests/fixtures/papers/: 3 PDFs
- tests/fixtures/expected_outputs/: expected blocks + body units
- tests/conftest.py: e2e_fixture_dir + synthetic_paper_paths fixtures

feat(#26): sqlite-vec schema — vec0 virtual tables + build_state

Schema version bumped 4→5. Adds:
- 3 vec0 virtual tables: vec_fulltext, vec_body, vec_objects (1536-dim)
- 3 companion metadata tables: vec_*_meta (paper_id, chunk_index, text)
- build_state table (key/value/updated_at)
- ensure_vec_extension() in db.py for extension loading

Changes:
- paperforge/memory/schema.py: schema v5, new tables in ensure_schema()
- paperforge/memory/db.py: ensure_vec_extension() helper
- tests/test_vector_schema.py: 5 new tests (tables, virtual, idempotent, v5)
2026-07-09 17:15:19 +08:00
LLLin000
2b23ad716f feat(#20): hash-based OCR change detection — two-tier mtime+xxhash staleness
Replace version-constant-only detection with two-tier content-hash
detection against blocks.structured.jsonl.

Tier 1 (stat): compare st_mtime + st_size of blocks.structured.jsonl
against stored values in meta.json. Microsecond skip for unchanged files.

Tier 2 (hash): compute xxhash of the file when stat changes. If the hash
matches stored structured_content_hash, update mtime/size only (false
alarm recovery). If it differs, mark paper for rebuild.

Changes:
- pyproject.toml: add xxhash>=3.4.0 dependency
- paperforge/worker/ocr_versions.py: add compute_structured_hash()
- paperforge/worker/ocr_rebuild.py: _apply_post_rebuild_version_flags()
  writes hash+mtime+size to meta.json when vault+key provided
- paperforge/commands/ocr.py: _needs_derived_rebuild() two-tier check
  before version-constant fallback
- New tests: hash compute (hex, determinism, missing file),
  stat skip, hash match (false alarm recovery), hash mismatch trigger

61 tests pass (14 existing + 3 hash rebuild + 4 hash compute + 40 other).
2026-07-09 17:09:28 +08:00
LLLin000
9d150d1d95 test: add regression assertion for health-probe get(limit=1) and guard against query() 2026-07-09 02:17:49 +08:00
LLLin000
a588bd884a fix(#18): atomic build_state writes with .tmp fallback recovery
test(#17): integration tests for embed pipeline against EphemeralChromaDB

- build_state: write to .tmp first, leave as backup; read falls back to
  .tmp if main file corrupt. Extracted default/fallback state helpers.
- integration: 10 tests covering payload prep, encode→write→retrieve
  round-trip for all 3 collections, per-paper cap, delete, and
  encode_paper_job — all against real ChromaDB EphemeralClient with
  mocked provider
2026-07-09 02:03:20 +08:00
LLLin000
db2988d8f0 PR9C: Streaming embed pipeline with continuous progress
Replace two-phase (prepare all, then encode+write) with sliding-window
pipeline. Maintains bounded in-flight window (max_workers * 4 = 16),
emits EMBED_PROGRESS from the first paper completion (~2s vs ~30s).

Key changes:
- producer/consumer loop: prepare + submit one paper at a time
- wait(FIRST_COMPLETED) in main thread for encode results
- processed_count = skip + embedded, monotonic EMBED_PROGRESS
- resume skip and no-payload paths also advance processed_count
- encode failure fails closed (return 1, no silent skip)
- removed PR9B_BATCH_SIZE and _batched() (replaced by sliding window)
- 4 integration tests for progress counting, failure, and write cycle
2026-07-07 02:04:12 +08:00
LLLin000
6ff0f6d38b PR9B: Embed Parallel Encode
- Four new dataclasses: EmbeddingPayload, EncodedPayload, PaperEmbeddingJob, PaperEncodedBundle
- prepare_legacy/body/object_payload: extract prepare phase from legacy embed functions
- encode_payload / encode_paper_job: worker-thread-safe encode functions
- write_encoded_payload: ChromaDB serial write
- prepare_payloads_for_entry: routing to correct prepare function
- Existing embed_body_units/embed_paper/embed_object_units refactored as wrappers
- Embed main loop: three-phase pipeline (PREPARE -> ENCODE -> WRITE) with ThreadPoolExecutor
- Bounded batch processing (BATCH_SIZE=8, MAX_WORKERS=4)
- Encode-success-gated delete: old vectors only removed after encode succeeds
- 23 regression tests for new functions
2026-07-06 22:41:25 +08:00
LLLin000
89f5a3fce6 PR9A: Resume & Rebuild Correctness
- OCR rebuild --all: version/artifact-based selection via _needs_derived_rebuild()
- --status and explicit keys: manual override, no version filter
- Removed .done.{key} checkpoint markers from selection logic
- _apply_post_rebuild_version_flags now writes derived_version
- Embed resume entry: three-gate protection (stale state / missing DB / corrupt DB)
- _pid_alive() cross-platform PID health check
- _assert_collections_healthy() explicit ChromaDB probe
- 14 regression tests for selection logic, version flags, and resume guards
2026-07-06 22:33:08 +08:00
LLLin000
0dfbf2a68d feat: PR 8 — Object Vector Retrieval (paperforge_objects collection)
New paperforge_objects collection in ChromaDB for figure/table caption
vector search, completing the memory layer retrieval triad:

  paperforge_fulltext → legacy_chunk
  paperforge_body     → body_unit
  paperforge_objects  → object_unit (NEW)

Changes:
- manifest.py: compute_object_units_hash(), manifest records both hashes
- _chroma.py: _COLLECTION_NAMES includes paperforge_objects
- builder.py: get_object_units_for_embedding() + embed_object_units()
- status.py: object_chunk_count, total_chunks
- state_snapshot.py: write_vector_runtime() extended with new fields
- search.py: 3rd collection, dict-based source mapping, object_kind/label
- __init__.py: exports for new functions
- commands/embed.py: _has_object_units_in_db, body+object structured
  path routing with dual-hash resume, extended counters
- Test fix: test_build_paper_manifest data complete for hash functions

Verification: 20 object vectors embedded, all per-paper counts match,
status total consistent (58266 = 57435 + 811 + 20),
object caption queries return object_unit results via merge_retrieve.
Unit tests: 153 pass, 1 skip.
2026-07-06 19:58:46 +08:00
LLLin000
e551f79a77 fix: PR 7 post-test hardening — object units, node_id, expanded tests
P0 fixes from test report review:
- object unit_id: fallback to f"{obj_type}:p{page}:{block_id}" when
  figure_id/table_id missing (prevents DB overwrite on INSERT OR REPLACE)
- object caption_key: page-qualified f"p{page}:{caption_bid}" to match
  page-qualified subtree_block_ids in find_owning_node
- object block_map: page-qualified keys for consistency with body_units
- structure node_id: changed from sec:{block_id} to sec:p{page}:{block_id}
  for stability (reduces vector ID churn from emitted_order changes)

New tests:
- Section 4b: Object units DB persistence (unit_id unique, non-empty labels,
  DB count == list count after upsert)
- Section 5b: Coverage gate (v2_tree / render_map / body_papers counts)

Layer A: 1669/1669 pass (+60 checks from 4b + 5b)
Unit tests: 153 pass, 1 skip
2026-07-06 19:34:05 +08:00
LLLin000
01461330bb fix: final hardening — policy revision + Layer A all green
- _body_unit_role_kind now excludes only reference_item/reference_heading
  (per policy: everything in fulltext except ref zone)
- backmatter_body unit_kind removed; all units are 'body'
- bounds_map in structure_tree uses page-qualified key (fixes collision
  when same block_id appears on different pages)
- Layer A test plan: emitted_ids uses page-qualified keys + handles
  block_id=0 (falsy value)
- Layer A: 1609 passed, 0 failed on real vault (22 papers)
- Unit tests: 152 passed, 1 pre-existing skip
2026-07-06 19:08:54 +08:00
LLLin000
77a6e0cfec fix: heading→emitted events, backmatter→heading events, page-qualified keys
- Section/subsection headings now appear in BOTH heading_events and
  emitted_block_events (emitted_as='heading') for complete event stream
- Backmatter headings (Funding/Acknowledgments/Data Availability/Conflicts)
  now generate heading_events → appear as structure tree sections with
  their own body_units
- Page-qualified block IDs (p{page}:{block_id}) in tree own/subtree_block_ids
  and body_units block_map: eliminates ambiguous block_id collisions when
  same block_id appears on multiple pages in real OCR data
- TC-9.2 wording fix, KEYS empty guard, global unit_id uniqueness check
2026-07-06 18:19:17 +08:00
LLLin000
9f79ac3482 fix: hardening — FTS per-paper, object_units keys, unit_id collision, resume hash
P0-1: FTS per-paper insert — move SELECT INTO body_units_fts inside the
per-paper loop with WHERE paper_id = ? to avoid re-inserting other papers'
rows during full rebuild. Fix sqlite3.DatabaseError on empty FTS table.

P0-2: object_units real role_index keys — read from 'captions'/'tables'
(what build_role_indexes actually outputs) with fallback to old key names.

P1-1: unit_id collision for mixed body/backmatter — include unit_kind
suffix (:backmatter_body) in unit_id. Also fix duplicate tree node_ids
(block_id reused on different pages) by appending order{emitted_order}.

P1-2: Embed resume body_units_hash — add compute_body_units_hash() to
manifest.py, write hash + retrieval_policy_version to Chroma metadata,
resume compares both before skipping.
2026-07-06 16:56:33 +08:00
LLLin000
288829bb5b feat: PR 1 — nested structure tree + body_units correctness
- RenderOutput dataclass with heading_events/emitted_block_events tracking
- Stack-algorithm structure tree with own/subtree block IDs
- Recursive body_units builder with role helper + token cap splitting
- Schema v4: section_path_json, section_level, section_title, part_ordinal
- Phase 4/5 reorder in ocr_rebuild.py and ocr.py
- 12 new test cases for tree nesting, own_block_ids, rendered-order intervals
2026-07-06 16:42:08 +08:00
LLLin000
36d7f79d50 fix: rescue single narrow side-caption figure layouts
Add a narrow sidecar rescue for the 37-like pattern:
- one formal narrow figure caption on a page
- same-row right-side image
- no x-overlap required
- bounded x-gap and strong y-overlap
- only when primary same-page matching missed the caption

This preserves the existing >=2 narrow-caption sidecar behavior while
rescuing left-caption/right-image layouts that previously fell to
cross_page_reservation.

Verified on real vault:
- 37LK5T97 page 2: FIG. 1 now matches as figure_001 via sidecar
- QGCAFI3P page 4: narrow side-caption rescued
- QZXT9L27 page 11: narrow side-caption rescued

Targeted tests: 296 passed
2026-07-06 02:38:59 +08:00
LLLin000
7b29ab68ca feat: improve vnext figure matching and split OCR status
This batch implements the validated PR-2 / PR-3 workstreams.

PR-2b: continued captions in vnext
- add figure continuation helpers
- mark continuation legends in candidate indexing
- run ContinuationCaptionPass before PrimarySamePagePass
- emit unique continuation records with trace metadata
- skip continuation legends in ordinary same-page proposals

PR-2a: short-caption geometry
- reuse _TRUNCATED_LEGEND_ONLY_PATTERN
- add narrow short-caption branch at the vnext group/page seam
- require same page, same column/x-overlap, tight gap, and local uniqueness
- avoid global threshold changes or score_figure_match edits

PR-3: completeness normalization and additive status split
- normalize legacy/vnext figure completeness shapes
- keep overall unchanged
- add structure_status, figure_status, table_status,
  dimension_statuses, status_reasons

Verification:
- 443 targeted tests passing
- real-vault rebuilds:
  * SKXTCE6M: unmatched_legends 6->0, overall green
  * SRNJDAA2: unmatched_legends 9->0, figure green
  * 6QNRHRKX: matched 0->4, figure yellow (improved, not solved)
  * 9DM6MCIF: unmatched_legends 3->0, figure green
  * XGT9Z257: unmatched_legends 4->0, figure green
  * V4UTP5X7: figure green while structure remains red
2026-07-06 00:55:40 +08:00
LLLin000
7c702b1bcf fix: exclude table-owned media and inline figure body refs from figure matching
PR-1 of the architecture review fixes.

Two entry-level filters before figure matching:

1. _is_table_owned_media(): excludes media_asset blocks with table
   roles/raw_labels/hints from the figure asset pool. Previously these
   entered as figure candidates and ended up as unmatched legends/orphans.

2. _looks_like_inline_figure_body_reference(): detects body text like
   'Figure 11-10 shows...' (hyphenated ranges, list ranges) and flags
   it as an inline reference, not a figure legend. Applied in both
   ocr_figures.py and ocr_roles.py for consistent early rejection.

Verified on real vault:
  SKXTCE6M: unmatched_legends 6→0, overall yellow→green
  SRNJDAA2: unmatched_legends 9→0

Tests: 384 pass (+9 new), 0 fail
2026-07-05 22:36:17 +08:00
LLLin000
d50ea4631b fix: final cleanup before merge — stale test, helper, RGB safety
1. Stale test: test_extract_objects_renders_same_page_once_for_multiple_crops
   now asserts render_pdf_page_cached is NOT called (new PageRenderContext path)
   and pages/page_001.jpg is NOT created.

2. Extract _resolve_object_crop_pdf_path() helper with 4-case test.
   Phase 4 now uses it instead of inline fallback logic.
   Test locks in: Phase 1 resolved path beats stale meta['source_pdf'].

3. PageRenderContext: use fitz.csRGB in get_pixmap() for safe CMYK/n>3 handling.
   Removed fragile pix.n mode detection.

4. Added test_extract_and_write_objects_with_use_disk_page_cache_false_and_valid_pdf
2026-07-05 21:36:17 +08:00
LLLin000
643052489a fix: determinism + correctness fixes before further optimization
6 fixes bundled:

1. Cleanup: remove accidental empty file '6s}'

2. Rebuild determinism: add 'use_disk_page_cache' gate to _crop_asset_from_pdf.
   Rebuild (use_disk_page_cache=False) never reads or writes pages/page_XXX.jpg.
   All rendering goes through PageRenderContext in-memory.
   Legacy callers keep backward-compat behavior (default True).

3. PageRenderContext safety:
   - Only use when page_width>0 and page_height>0 and not rotation_deg
   - Fix Pixmap.n mode detection (L/RGB/RGBA -> convert to RGB)
   - Fix Image.Resampling name bug (Image -> PILImage)
   - Rotated crops always fall back to direct PDF clip path

4. Phase 1+2a merged traversal: always extract PDF lines on every page,
   regardless of whether span_metadata already exists. Fixes figure inventory
   data gaps when pages have pre-existing span coverage.

5. Phase 4: use resolved source_pdf_path from Phase 1, not ocr_meta['source_pdf']
   fallback (which may be stale or missing).

6. Added 5 determinism tests
2026-07-05 21:32:05 +08:00
LLLin000
83b516aac0 fix: optimize span backfill with per-page rawdict batching and incremental re-extraction
- Group raw_blocks by page, call get_text('rawdict') once per page instead of once per block
  (1433 calls → 60 calls, 68s → 16.6s for a 60-page paper)
- Add _spans_from_rawdict() helper to filter cached rawdict by char bbox center overlap
- Keep extract_pdf_spans_for_block() as single-block compatibility API unchanged
- Add incremental backfill: skip pages where all blocks already have span_metadata
- Remove figure_title from _is_text_like_raw_block() coverage count (figure titles are
  typically image-rendered, not selectable PDF text)
- Add regression test: fast path output matches single-block API
2026-07-05 20:39:52 +08:00
LLLin000
c34ebbce81 feat: rebuild speed-up with parallel rebuild, threaded asset cropping, maintenance refactor, embed progress, CLI docs 2026-07-05 19:43:07 +08:00
LLLin000
7931f658bd feat: add rebuild-time fulltext backups and provenance 2026-07-05 17:00:41 +08:00
LLLin000
cdf969d85e feat: persist machine fulltext hash for initial OCR 2026-07-05 16:58:23 +08:00
LLLin000
1474c061a8 feat: surface drift state and invalidate maintenance cache 2026-07-05 16:52:17 +08:00
LLLin000
d8451947ad refactor: separate render artifact writes from user fulltext commit 2026-07-05 16:50:58 +08:00
LLLin000
0a3e40fd92 feat: add fulltext writeback state helpers 2026-07-05 16:45:31 +08:00
LLLin000
c534e481c7 fix: close 4 review gaps — registry, body/object-unit schemas, compat vector arm
Changes:
- Add paper-lookup, content-discovery, scoped-fetch to command registry
- Add unit_kind column to body_units schema (DEFAULT 'body')
- Replace object_units schema: object_role -> object_kind, object_label,
  caption_text, nearby_body_text; drop unit_text, drop object_role
- Bump schema version to 3 with deterministic drop/recreate migration for
  derived tables (body_units, body_units_fts, object_units)
- Update _upsert_body_units / _upsert_object_units for new columns
- Update build_object_units to emit object_kind, object_label, caption_text,
  nearby_body_text fields
- Add vector secondary arm to _run_compat_content_discovery: runs paper_fts
  primary, conditionally adds vector_retrieve secondary when vector is healthy
- Add/fix tests for schema columns, builder output, registry, and compat
  content-discovery vector-secondary path
2026-07-05 15:13:43 +08:00
LLLin000
1e6edad7ba feat: route Layer 4 gateway through body units and structure tree 2026-07-05 14:39:19 +08:00
LLLin000
9d332980f0 feat: add body units, manifest, and body-unit FTS 2026-07-05 14:21:27 +08:00
LLLin000
e23f35114c feat: add LanceDB evaluation backend scaffold 2026-07-05 14:19:37 +08:00
LLLin000
9d5a008df1 feat: make paper lookup decomposed and coverage-scored 2026-07-05 14:14:03 +08:00
LLLin000
b9f4d6c4f0 refactor: add vector backend adapter with Chroma compatibility 2026-07-05 14:13:59 +08:00