feat: Intelligence Pipeline v3 — full implementation
Multi-stage evidence-grounded inference architecture replacing the monolithic 9B model extraction pipeline. CPU-first specialist services handle routine extraction while the 9B vLLM model is preserved for semantic adjudication of ambiguous cases. Key components: - Capability-aware inference gateway (OpenAI-compatible + Ollama) - Endpoint registry with DB migrations and REST API - Sentence-aware document segmenter (property tests) - Deterministic financial parsing with offset integrity - Symbol resolution with ambiguity detection - Specialist service (GLiNER2, dynamic batching, K8s deployment) - Company-specific sentiment (FinBERT, calibration) - Retrieval-based novelty and duplicate detection - Confidence calibration pipeline - Deterministic routing engine (property tests) - 9B adjudication layer with VRAM gating - Stock-specific impact model (features, labels, baseline, trained) - Pipeline orchestrator (state machine, queues, leases, feature flags) - Bounded parallelism (async workers, semaphore, load shedding) - Observability (tracing, metrics, alerts) - Compatibility adapter (v3→v2 golden mapping tests) - Shadow/canary promotion framework - Active learning and fine-tuning pipeline Test results: 1,161 tests pass, ruff lint clean. All 282 spec tasks completed.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
"""Exact and near-duplicate fingerprinting for document deduplication.
|
||||
|
||||
Provides:
|
||||
- SHA-256 exact fingerprint on normalized text
|
||||
- SimHash for near-duplicate detection with configurable threshold
|
||||
- Hamming distance comparison between SimHash values
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import re
|
||||
import struct
|
||||
|
||||
|
||||
def _normalize_text(text: str) -> str:
|
||||
"""Normalize text for fingerprinting.
|
||||
|
||||
Lowercases, collapses whitespace, strips leading/trailing space.
|
||||
This ensures minor formatting differences don't defeat deduplication.
|
||||
"""
|
||||
text = text.lower()
|
||||
text = re.sub(r"\s+", " ", text)
|
||||
return text.strip()
|
||||
|
||||
|
||||
def compute_exact_fingerprint(text: str) -> str:
|
||||
"""Compute SHA-256 fingerprint of normalized text.
|
||||
|
||||
Args:
|
||||
text: Raw document text.
|
||||
|
||||
Returns:
|
||||
Hex-encoded SHA-256 digest of normalized text.
|
||||
"""
|
||||
normalized = _normalize_text(text)
|
||||
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _tokenize(text: str) -> list[str]:
|
||||
"""Split normalized text into tokens for SimHash computation."""
|
||||
normalized = _normalize_text(text)
|
||||
return normalized.split()
|
||||
|
||||
|
||||
def _hash_token(token: str) -> int:
|
||||
"""Hash a single token to a 64-bit integer using MD5 truncation."""
|
||||
digest = hashlib.md5(token.encode("utf-8")).digest() # noqa: S324
|
||||
return struct.unpack("<Q", digest[:8])[0]
|
||||
|
||||
|
||||
def compute_simhash(text: str) -> int:
|
||||
"""Compute 64-bit SimHash of text for near-duplicate detection.
|
||||
|
||||
SimHash produces locality-sensitive fingerprints: similar documents
|
||||
will have SimHash values with low Hamming distance.
|
||||
|
||||
Args:
|
||||
text: Raw document text.
|
||||
|
||||
Returns:
|
||||
64-bit SimHash integer value.
|
||||
"""
|
||||
tokens = _tokenize(text)
|
||||
if not tokens:
|
||||
return 0
|
||||
|
||||
# Accumulator for each bit position
|
||||
v = [0] * 64
|
||||
|
||||
for token in tokens:
|
||||
token_hash = _hash_token(token)
|
||||
for i in range(64):
|
||||
if token_hash & (1 << i):
|
||||
v[i] += 1
|
||||
else:
|
||||
v[i] -= 1
|
||||
|
||||
# Build final hash from accumulator signs
|
||||
fingerprint = 0
|
||||
for i in range(64):
|
||||
if v[i] > 0:
|
||||
fingerprint |= 1 << i
|
||||
|
||||
return fingerprint
|
||||
|
||||
|
||||
def hamming_distance(a: int, b: int) -> int:
|
||||
"""Compute Hamming distance between two 64-bit SimHash values.
|
||||
|
||||
Args:
|
||||
a: First SimHash value.
|
||||
b: Second SimHash value.
|
||||
|
||||
Returns:
|
||||
Number of differing bits (0-64).
|
||||
"""
|
||||
xor = a ^ b
|
||||
# Count set bits (Brian Kernighan's algorithm)
|
||||
distance = 0
|
||||
while xor:
|
||||
xor &= xor - 1
|
||||
distance += 1
|
||||
return distance
|
||||
|
||||
|
||||
def is_near_duplicate(fp1: int, fp2: int, threshold: int = 3) -> bool:
|
||||
"""Determine if two SimHash fingerprints indicate near-duplicate content.
|
||||
|
||||
Args:
|
||||
fp1: First SimHash value.
|
||||
fp2: Second SimHash value.
|
||||
threshold: Maximum Hamming distance to consider near-duplicate.
|
||||
Default of 3 is conservative for 64-bit SimHash.
|
||||
|
||||
Returns:
|
||||
True if the documents are near-duplicates.
|
||||
"""
|
||||
return hamming_distance(fp1, fp2) <= threshold
|
||||
Reference in New Issue
Block a user