Livrare LOT 1 - Didi
This commit is contained in:
commit
5380c3fc63
990 changed files with 133308 additions and 0 deletions
124
ai_platform/modules/didi_brain/extractor/push.py
Normal file
124
ai_platform/modules/didi_brain/extractor/push.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""Push validated ExtractedClaim objects into Atomic as Type/Claim atoms.
|
||||
|
||||
Each claim becomes a tiny atom with:
|
||||
- source_url = `{parent_url}#claim={hash8}` so it dedups idempotently and
|
||||
so `parent_url = source_url.split("#")[0]` is trivial to recover later
|
||||
- tag inheritance from the parent (Country, Topic, SourceType, Credibility,
|
||||
Language) plus our two new tags: Type/Claim and Stance/<X>
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from shared.atomic_api import AtomicApiError, AtomicClient
|
||||
from shared.logging import get_logger
|
||||
from shared.taxonomy import TagResolver
|
||||
|
||||
from extractor.extract import ExtractedClaim
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
_STANCE_PATH = {
|
||||
"ASSERTS": "Stance/Asserts",
|
||||
"REPORTS": "Stance/Reports",
|
||||
"REFUTES": "Stance/Refutes",
|
||||
"QUESTIONS": "Stance/Questions",
|
||||
"NEUTRAL": "Stance/Neutral",
|
||||
}
|
||||
|
||||
|
||||
def build_claim_markdown(
|
||||
claim: ExtractedClaim,
|
||||
*,
|
||||
parent_title: str,
|
||||
parent_url: str,
|
||||
parent_atom_id: str,
|
||||
) -> str:
|
||||
"""Render a claim atom's body as Markdown.
|
||||
|
||||
The structure is intentionally consistent so it can be parsed back later
|
||||
by Didi or by re-indexing scripts.
|
||||
"""
|
||||
return (
|
||||
f"# Claim\n\n"
|
||||
f"{claim.claim}\n\n"
|
||||
f"## Quote\n"
|
||||
f"> {claim.quote}\n\n"
|
||||
f"## Source\n"
|
||||
f"- Document: [{parent_title}]({parent_url})\n"
|
||||
f"- Parent atom: `{parent_atom_id}`\n"
|
||||
f"- Stance in source: {claim.stance}\n"
|
||||
f"- Extraction confidence: {claim.confidence:.2f}\n"
|
||||
)
|
||||
|
||||
|
||||
def build_claim_url(parent_url: str, claim: ExtractedClaim) -> str:
|
||||
"""Stable hash-based URL fragment so re-extraction dedupes naturally."""
|
||||
base = parent_url.split("#", 1)[0]
|
||||
return f"{base}#claim={claim.stable_hash()}"
|
||||
|
||||
|
||||
def inherit_tag_ids(
|
||||
parent_atom: dict[str, Any],
|
||||
claim: ExtractedClaim,
|
||||
resolver: TagResolver,
|
||||
) -> list[str]:
|
||||
"""Build the tag-id list for a new claim atom.
|
||||
|
||||
Inherits all parent tags except Type/Document, and adds Type/Claim plus
|
||||
the appropriate Stance/<X>.
|
||||
"""
|
||||
type_doc_id = resolver.require("Type/Document")
|
||||
type_claim_id = resolver.require("Type/Claim")
|
||||
stance_id = resolver.require(_STANCE_PATH[claim.stance])
|
||||
|
||||
parent_tag_ids = [
|
||||
t["id"] for t in (parent_atom.get("tags") or []) if t.get("id") != type_doc_id
|
||||
]
|
||||
return parent_tag_ids + [type_claim_id, stance_id]
|
||||
|
||||
|
||||
async def push_claim(
|
||||
atomic: AtomicClient,
|
||||
*,
|
||||
parent_atom: dict[str, Any],
|
||||
claim: ExtractedClaim,
|
||||
parent_title: str,
|
||||
resolver: TagResolver,
|
||||
) -> tuple[dict[str, Any] | None, str]:
|
||||
"""Create one claim atom in Atomic. Returns (atom_dict, status).
|
||||
|
||||
Status is one of:
|
||||
- "created": new atom was created
|
||||
- "duplicate": same hash already exists, skipped
|
||||
- "error": creation failed (atom_dict is None)
|
||||
"""
|
||||
parent_url = parent_atom.get("source_url") or ""
|
||||
parent_id = parent_atom.get("id") or ""
|
||||
|
||||
claim_url = build_claim_url(parent_url, claim)
|
||||
|
||||
# Idempotency: same canonical hash → skip
|
||||
existing = await atomic.get_atom_by_source_url(claim_url)
|
||||
if existing:
|
||||
return existing, "duplicate"
|
||||
|
||||
md = build_claim_markdown(
|
||||
claim,
|
||||
parent_title=parent_title,
|
||||
parent_url=parent_url,
|
||||
parent_atom_id=parent_id,
|
||||
)
|
||||
tag_ids = inherit_tag_ids(parent_atom, claim, resolver)
|
||||
|
||||
try:
|
||||
atom = await atomic.create_atom(
|
||||
content=md,
|
||||
source_url=claim_url,
|
||||
tag_ids=tag_ids,
|
||||
)
|
||||
return atom, "created"
|
||||
except AtomicApiError as e:
|
||||
log.error("push_claim_failed", url=claim_url, status=e.status, body=e.body[:200])
|
||||
return None, "error"
|
||||
Loading…
Add table
Add a link
Reference in a new issue