feat(short): narración — el grounding la cubre, el contrato la admite y el prompt la guía
Build & Deploy ResearchOwl / build-and-push (push) Successful in 10s
Build & Deploy ResearchOwl / build-and-push (push) Successful in 10s
El comprobador va primero, antes que el campo (fase 2 §12): la narración es prosa que el modelo redacta, no una etiqueta que copia, y es donde se cuela una cifra sin fuente. De paso, la huella de una cifra pasa a ser número + unidad canónica: con la voz repitiendo la pantalla, '35,000 FT' y '35,000 feet' son el mismo dato y contarlos dos veces inflaría el informe del que depende la revisión humana. editorial_notes estima la duración CON la voz: la declarada es un suelo y sin esto el modelo escribiría 40 s de shots, les colgaría narración y se enteraría del Short de 65 s cuando ya está pagado. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
93a506b636
commit
a13c3062b6
@@ -168,6 +168,24 @@ class Claim:
|
||||
def norm(self) -> str:
|
||||
return normalize(self.text)
|
||||
|
||||
@property
|
||||
def fingerprint(self) -> tuple:
|
||||
"""Identidad del dato, no de su redacción — para no contar dos veces.
|
||||
|
||||
Una cifra se identifica por su número y su unidad CANÓNICA: "35,000 FT"
|
||||
dibujado en pantalla y "35,000 feet" dicho en la narración son el mismo
|
||||
dato, y con la narración esa coincidencia pasa a ser lo normal, no la
|
||||
excepción. Contarlos por separado inflaría justo el informe del que
|
||||
depende la revisión humana.
|
||||
|
||||
Lo demás se identifica por su forma normalizada: una cita reformulada
|
||||
NO es la misma cita, y ahí la literalidad es el criterio correcto.
|
||||
"""
|
||||
if self.kind == "figure":
|
||||
number = normalize(self.text.split()[0]) if self.text.split() else ""
|
||||
return ("figure", number, self.unit)
|
||||
return (self.kind, self.norm)
|
||||
|
||||
|
||||
_QUOTED = re.compile(r'["“„‟«]([^"“”„‟«»]{3,})'
|
||||
r'["”„‟»]')
|
||||
@@ -315,6 +333,16 @@ def extract_claims(spec: dict) -> list[Claim]:
|
||||
|
||||
`meta` queda fuera a propósito: el título del spec no se dibuja en ningún
|
||||
fotograma, es el nombre del fichero.
|
||||
|
||||
`narration` SÍ entra, y es de lo más importante que entra: lo que se dibuja
|
||||
en pantalla son etiquetas cortas que el modelo copia, pero la narración es
|
||||
prosa que redacta — el sitio natural para deslizar una cifra de más. Se
|
||||
trata como cualquier prosa del spec: aporta sus citas, cifras y fechas. No
|
||||
aporta nombres propios, por lo mismo que no los aportan `headline` o
|
||||
`caption`: una frase entera no es una etiqueta identificadora, y sacar
|
||||
nombres de dentro de la prosa exigiría adivinar por mayúsculas y llenaría
|
||||
el informe de ruido. La cifra y la cita, que son lo que se fabrica, están
|
||||
cubiertas.
|
||||
"""
|
||||
claims: list[Claim] = []
|
||||
for i, shot in enumerate(spec.get("shots") or []):
|
||||
@@ -328,12 +356,16 @@ def extract_claims(spec: dict) -> list[Claim]:
|
||||
# Reconstruye "232 FT" a partir de {value: 232, unit: "FT"}.
|
||||
for path_prefix, sub in _dicts_with_value_and_unit(props, base):
|
||||
shot_claims = _attach_units(sub, shot_claims, path_prefix)
|
||||
narration = shot.get("narration")
|
||||
if isinstance(narration, str):
|
||||
shot_claims.extend(
|
||||
_claims_from_text(narration, f"shots.{i}.narration", "narration"))
|
||||
claims.extend(shot_claims)
|
||||
|
||||
seen: set[tuple[str, str]] = set()
|
||||
seen: set[tuple] = set()
|
||||
unique: list[Claim] = []
|
||||
for claim in claims:
|
||||
fingerprint = (claim.kind, claim.norm)
|
||||
fingerprint = claim.fingerprint
|
||||
if not claim.norm or fingerprint in seen:
|
||||
continue
|
||||
seen.add(fingerprint)
|
||||
|
||||
Reference in New Issue
Block a user