Skip to content

Architecture review: fix critical bugs, add observability, modernize dedup#12

Merged
strickvl merged 10 commits into
mainfrom
fix/architecture-review-phases-1-2-4
Feb 14, 2026
Merged

Architecture review: fix critical bugs, add observability, modernize dedup#12
strickvl merged 10 commits into
mainfrom
fix/architecture-review-phases-1-2-4

Conversation

@strickvl

@strickvl strickvl commented Feb 14, 2026

Copy link
Copy Markdown
Owner

Summary

Implements phases 1, 2, and 4 from the architecture review, plus cross-platform compatibility, CI/lint infrastructure, and code review fixes.

Phase 1: Fix critical bugs (8af0664)

  • Reflection loop JSON preservationiterative_improve() now preserves the full JSON envelope instead of losing it after the first iteration
  • Event prompt/schema drift — updated events.md prompt to match actual Pydantic schema fields
  • Local mode extraction — fixed cloud/local response model mismatch so Ollama extraction actually returns entities
  • OLLAMA_API_URL — now reads from environment variable instead of hardcoded value

Phase 2: Add observability (acdebd2)

  • PhaseOutcome — structured result objects replace silent [] returns, carrying error context through the pipeline
  • Extraction QC — validates required fields, normalizes names, deduplicates within-article entities
  • Profile QC — minimum length, citation checks, tag count, confidence range validation

Cross-platform compatibility (68de0d0)

  • Made PyTorch/sentence-transformers optional imports (lazy loading)
  • EmbeddingManager stores model name + dimension on entities for tracking
  • _embeddings_compatible() skips incompatible embeddings during similarity search

Phase 4: Dedup modernization (c6d528a)

  • Lexical blocking — RapidFuzz WRatio pre-filters candidates before cosine similarity (O(n) → O(k))
  • Per-type similarity thresholds — configurable per entity type from domain config (dedup.similarity_thresholds)
  • Embedding fingerprints"{model}:{dim}" fingerprint stored on all entities for model change detection

CI & Lint (1489be2, ad9e2b0, 8c4f44b)

  • Added GitHub Actions workflow for lint (ruff check + ruff format --check) and tests
  • Aligned local lint.sh with CI — both now run full ruff ruleset
  • Fixed 14 pre-existing lint issues (bare excepts, ambiguous vars, type comparisons, etc.)
  • Added ruff ignores: E501 (formatter handles line length), F403/F405 (FastHTML star imports), B019 (intentional lru_cache on methods)

Code review fixes (b2ef469)

  • Changed embedding default from hybrid to cloud (prevents crash when local-embeddings not installed)
  • Made QC required fields schema-driven from Pydantic models (supports multi-domain)
  • Fixed normalize_name(): strip/collapse whitespace + Unicode NFC only (no more .title() corrupting acronyms like FBI→Fbi)
  • Fixed CI test filtering: -m "not asyncio" (marker) instead of -k (substring)
  • Guard against KeyError on older entities missing articles key
  • asyncio.get_event_loop()asyncio.get_running_loop() (deprecation fix)
  • Shallow copy → deepcopy for nested config defaults
  • Fixed misleading merge log: shows actual embedding mode/model, not LLM mode
  • PhaseOutcome.to_metadata_dict() uses exclude_none=True to reduce storage noise

Test plan

  • All 80 non-async tests pass (pytest -m "not asyncio")
  • 15 new tests: lexical blocking (4), config threshold/blocking resolution (7), fingerprint helpers (4)
  • Existing merger smoke tests updated with fingerprint assertions and stub improvements
  • Format (ruff format) and lint (ruff check) clean
  • CI workflow runs both lint and test jobs

1. Fix reflection loop JSON preservation: iterative_improve() now uses
   coerce_to_json_text() to serialize the full Pydantic model (tags,
   confidence, sources) instead of extracting only the .text field,
   which broke the reflection loop by losing the JSON envelope.

2. Fix event prompt/schema drift: Guantanamo events prompt now matches
   the actual Event model fields (event_type, start_date, description,
   end_date, is_fuzzy_date, tags) instead of outdated field names
   (type, date) that caused parse failures.

3. Fix local mode extraction: extract_local() now uses List[Entity]
   (same as cloud) instead of container models (ArticlePeople, etc.)
   that expected {"people": [...]} while prompts output bare arrays.
   This was causing near-zero recall for all entity types in local mode.

4. Fix hardcoded OLLAMA_API_URL: Now reads from environment variable
   with localhost default, instead of hardcoded LAN IP.

Bonus: extraction exceptions are now logged instead of silently swallowed.
…se 2)

New modules:
- src/utils/outcomes.py: PhaseOutcome datatype that carries success/failure,
  error payload, QC flags, and counts alongside a usable fallback value.
  Replaces silent return-empty-list patterns with structured outcomes.

- src/utils/quality_controls.py: Deterministic QC for extraction (required-
  field validation, name normalization, within-article dedup, suspicious-
  result flagging) and profiles (min text length, citation checks, tag
  count, confidence range). QC never raises — it drops/fixes and reports.

Integration points:
- article_processor.py: check_relevance() and extract_single_entity_type()
  now return PhaseOutcome objects. Extraction QC runs automatically after
  each entity type extraction. Outcomes stored in processing_metadata.

- profiles.py: Added create_profile_with_outcome() and
  update_profile_with_outcome() wrappers that never raise, run profile QC,
  and return PhaseOutcome alongside the existing return tuple.

- process_and_extract.py: Relevance outcomes surfaced in phase_outcomes.
  Processing metadata persisted back to article rows.

- constants.py: Added QC threshold constants (min name length, min text
  length, min tag count).

All existing APIs preserved — new outcome functions are additive wrappers.
…rs optional

Make the project installable and usable on Intel Mac (where PyTorch has
no wheels) by making sentence-transformers an optional dependency and
adding robust embedding backend selection.

Tier 1 — Unblock Intel Mac:
- Move sentence-transformers from base deps to [local-embeddings] extra
- Add graceful ImportError in LocalEmbeddingProvider with actionable guidance
- Change template default embedding mode from 'local' to 'cloud'

Tier 2 — Robust portability:
- Add EmbeddingMode.AUTO that auto-detects available backends via find_spec
- Add explicit device config (auto/cpu/cuda/mps) for local embeddings
- Persist embedding model name + dimension alongside profile_embedding
- Add compatibility checking in find_similar_entity to prevent silent
  dedup failures from dimension/model mismatches across environments

Also fixes:
- Embedding manager cache key now includes mode (was domain-only)
- Mutable default dict in EmbeddingConfig/Result replaced with Field(default_factory)
- Cloud provider returns stable configured model name (not response model)
- DomainConfig defaults aligned to 'cloud' for platform safety
… fingerprints (Phase 4)

- Add RapidFuzz lexical blocking in find_similar_entity() to pre-filter
  candidates before cosine similarity (O(n) → O(k))
- Replace single SIMILARITY_THRESHOLD with per-entity-type thresholds
  loaded from domain config (dedup.similarity_thresholds section)
- Store profile_embedding_fingerprint on all entities for model change detection
- Add DomainConfig.get_similarity_threshold(entity_type) with 4-level fallback
- Add DomainConfig.get_lexical_blocking_config() accessor
- Add EmbeddingManager.fingerprint_from_result() and make_fingerprint() helpers
- 15 new tests covering lexical blocking, threshold resolution, and fingerprints
Runs on push to main and on PRs. Two jobs:
- lint: ruff check + format verification
- test: pytest with async tests excluded (pre-existing config issue)
- Document quality controls (PhaseOutcome, extraction QC, profile QC)
- Document dedup modernization (lexical blocking, per-type thresholds, fingerprints)
- Add CI workflow mention to testing sections
- Update project structure with new utility modules
- Update "Built with" to include RapidFuzz
ruff is in [project.optional-dependencies] dev (--extra dev),
while pytest-asyncio etc. are in [dependency-groups] dev (--group dev).
CI needs both.
- Add E501 ignore (ruff format handles line length)
- Add per-file-ignores for FastHTML star imports (F403/F405) and
  config_loader lru_cache on methods (B019)
- Fix bare excepts → except Exception (E722)
- Fix ambiguous variable name l → loc (E741)
- Fix type comparison == → is (E721)
- Fix lambda loop variable binding (B023)
- Remove unused test variables (F841)
- Remove f-strings without placeholders (F541)
- Update lint.sh to run full ruff check (matching CI)
…d quality

Must-fix items:
- Change embedding default from hybrid to cloud (prevents crash when
  local-embeddings extra not installed)
- Make QC required fields schema-driven from Pydantic models instead of
  hardcoded (supports multi-domain without breakage)
- Fix normalize_name: strip/collapse whitespace + Unicode NFC only,
  drop .title() which corrupted acronyms (FBI→Fbi, McDonald→Mcdonald)
- Fix CI test filtering: -m "not asyncio" (marker) instead of -k (substring)
- Guard against KeyError on older entities missing "articles" key

Suggestions:
- asyncio.get_event_loop() → asyncio.get_running_loop() (deprecation)
- Shallow copy → deepcopy for nested config defaults
- Fix misleading log: show actual embedding mode/model, not LLM mode
- PhaseOutcome.to_metadata_dict() uses exclude_none=True
- Update smoke test stub with mode/get_active_model_name attrs
@strickvl strickvl merged commit f426f5e into main Feb 14, 2026
2 checks passed
@strickvl strickvl deleted the fix/architecture-review-phases-1-2-4 branch February 14, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant