This commit is contained in:
LASTA_DEV01\lasta
2026-05-22 19:07:07 +09:00
parent c89edecf8c
commit 93980da14d
23 changed files with 1211 additions and 648 deletions

View File

@@ -44,6 +44,100 @@ from crawler_platform.app.core.research.memory_store import ResearchMemoryStore,
DOMAIN_NAME_PATTERN = re.compile(r"^[a-z0-9][a-z0-9_-]{1,79}$")
SITE_CRAWL_CANCEL_REQUESTS: set[int] = set()
COMPARISON_KEYS = ("both_agree", "rule_only", "llm_only", "conflict", "rejected_by_validation")
def extraction_log_summary(raw_output: dict[str, Any]) -> dict[str, Any]:
candidate_claims = raw_output.get("candidate_claims")
if not isinstance(candidate_claims, list):
candidate_claims = []
raw_comparison = raw_output.get("comparison")
if not isinstance(raw_comparison, dict):
raw_comparison = {}
comparison = {key: int(raw_comparison.get(key) or 0) for key in COMPARISON_KEYS}
if not any(comparison.values()):
comparison.update(comparison_from_candidate_claims(candidate_claims))
validation = raw_output.get("validation")
if isinstance(validation, dict) and comparison["rejected_by_validation"] == 0:
comparison["rejected_by_validation"] = number_or_default(validation.get("rejected_claim_count"), 0)
return {
"candidate_count": len(candidate_claims),
"comparison": comparison,
"rule_entity_count": number_or_none(raw_output.get("rule_entity_count")),
"rule_claim_count": number_or_derived(
raw_output.get("rule_claim_count"),
candidate_claims,
source="rule",
),
"llm_entity_count": number_or_none(raw_output.get("llm_entity_count")),
"llm_claim_count": number_or_derived(
raw_output.get("llm_claim_count"),
candidate_claims,
source="llm",
),
"agreement_claim_count": number_or_default(raw_output.get("agreement_claim_count"), comparison["both_agree"]),
"rule_only_claim_count": number_or_default(raw_output.get("rule_only_claim_count"), comparison["rule_only"]),
"llm_only_claim_count": number_or_default(raw_output.get("llm_only_claim_count"), comparison["llm_only"]),
"conflict_claim_count": number_or_default(raw_output.get("conflict_claim_count"), comparison["conflict"]),
}
def comparison_from_candidate_claims(candidate_claims: list[Any]) -> dict[str, int]:
comparison = {key: 0 for key in COMPARISON_KEYS}
for claim in candidate_claims:
metadata = claim_metadata(claim)
agreement = str(metadata.get("agreement") or metadata.get("claim_kind") or "").lower()
if agreement == "rule_and_llm":
comparison["both_agree"] += 1
elif agreement == "rule_only":
comparison["rule_only"] += 1
elif agreement == "llm_only":
comparison["llm_only"] += 1
elif agreement == "conflict":
comparison["conflict"] += 1
return comparison
def claim_metadata(claim: Any) -> dict[str, Any]:
if not isinstance(claim, dict):
return {}
metadata = claim.get("metadata")
return metadata if isinstance(metadata, dict) else {}
def number_or_none(value: Any) -> int | None:
if isinstance(value, bool):
return None
if isinstance(value, (int, float)):
return int(value)
return None
def number_or_default(value: Any, default: int) -> int:
parsed = number_or_none(value)
return default if parsed is None else parsed
def number_or_derived(value: Any, candidate_claims: list[Any], *, source: str) -> int:
parsed = number_or_none(value)
if parsed is not None:
return parsed
return sum(1 for claim in candidate_claims if claim_matches_source(claim, source))
def claim_matches_source(claim: Any, source: str) -> bool:
metadata = claim_metadata(claim)
extraction_source = str(metadata.get("extraction_source") or "").lower()
agreement = str(metadata.get("agreement") or "").lower()
if extraction_source == source:
return True
if source == "rule":
return agreement in {"rule_only", "rule_and_llm"}
if source == "llm":
return agreement in {"llm_only", "rule_and_llm"}
return False
class CrawlRequest(BaseModel):
@@ -2490,35 +2584,37 @@ def register_routes(app, database_url: str) -> None:
.order_by(models.ExtractionLog.created_at.desc())
.limit(limit)
).all()
return [
{
payload = []
for log, page in rows:
raw_output = log.raw_output or {}
summary = extraction_log_summary(raw_output)
payload.append({
"id": log.id,
"page_url": page.url if page else None,
"extractor_name": log.extractor_name,
"provider": log.provider,
"error": log.error,
"created_at": log.created_at.isoformat(),
"validation": (log.raw_output or {}).get("validation"),
"page_context": (log.raw_output or {}).get("page_context"),
"candidate_count": len((log.raw_output or {}).get("candidate_claims") or []),
"extraction_mode": (log.raw_output or {}).get("extraction_mode"),
"effective_extraction_mode": (log.raw_output or {}).get("effective_extraction_mode"),
"comparison": (log.raw_output or {}).get("comparison"),
"rule_entity_count": (log.raw_output or {}).get("rule_entity_count"),
"rule_claim_count": (log.raw_output or {}).get("rule_claim_count"),
"llm_entity_count": (log.raw_output or {}).get("llm_entity_count"),
"llm_claim_count": (log.raw_output or {}).get("llm_claim_count"),
"agreement_claim_count": (log.raw_output or {}).get("agreement_claim_count"),
"rule_only_claim_count": (log.raw_output or {}).get("rule_only_claim_count"),
"llm_only_claim_count": (log.raw_output or {}).get("llm_only_claim_count"),
"conflict_claim_count": (log.raw_output or {}).get("conflict_claim_count"),
"llm_skipped": (log.raw_output or {}).get("llm_skipped"),
"llm_skip_reason": (log.raw_output or {}).get("llm_skip_reason"),
"fallback": (log.raw_output or {}).get("fallback"),
"validation": raw_output.get("validation"),
"page_context": raw_output.get("page_context"),
"candidate_count": summary["candidate_count"],
"extraction_mode": raw_output.get("extraction_mode"),
"effective_extraction_mode": raw_output.get("effective_extraction_mode"),
"comparison": summary["comparison"],
"rule_entity_count": summary["rule_entity_count"],
"rule_claim_count": summary["rule_claim_count"],
"llm_entity_count": summary["llm_entity_count"],
"llm_claim_count": summary["llm_claim_count"],
"agreement_claim_count": summary["agreement_claim_count"],
"rule_only_claim_count": summary["rule_only_claim_count"],
"llm_only_claim_count": summary["llm_only_claim_count"],
"conflict_claim_count": summary["conflict_claim_count"],
"llm_skipped": raw_output.get("llm_skipped"),
"llm_skip_reason": raw_output.get("llm_skip_reason"),
"fallback": raw_output.get("fallback"),
"raw_output": log.raw_output,
}
for log, page in rows
]
})
return payload
@app.patch("/claims/{claim_id}/confidence")
def update_claim_confidence(claim_id: int, request: UpdateClaimConfidenceRequest):