fix: handle null fields from NuExtract3 in extraction normalization
- Replace setdefault() with explicit None checks in _normalize_extraction_data() - Coerce null top-level fields (summary, novelty_score, confidence) to defaults - Coerce null company fields (ticker, impact_score, impact_horizon, etc.) to defaults - Filter out company entries with empty ticker after normalization - Prevents schema validation failures when model returns null for required fields
This commit is contained in:
@@ -362,15 +362,19 @@ _HORIZON_MAP: dict[str, str] = {
|
|||||||
|
|
||||||
def _normalize_extraction_data(data: dict[str, Any]) -> dict[str, Any]:
|
def _normalize_extraction_data(data: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Fix common model output issues before Pydantic validation."""
|
"""Fix common model output issues before Pydantic validation."""
|
||||||
# Fill missing top-level required fields with defaults
|
# Fill missing or null top-level required fields with defaults
|
||||||
data.setdefault("summary", "")
|
if not data.get("summary"):
|
||||||
data.setdefault("companies", [])
|
data["summary"] = ""
|
||||||
data.setdefault("macro_themes", [])
|
if not isinstance(data.get("companies"), list):
|
||||||
if "novelty_score" not in data:
|
data["companies"] = []
|
||||||
|
if not isinstance(data.get("macro_themes"), list):
|
||||||
|
data["macro_themes"] = []
|
||||||
|
if data.get("novelty_score") is None:
|
||||||
data["novelty_score"] = 0.5
|
data["novelty_score"] = 0.5
|
||||||
if "confidence" not in data:
|
if data.get("confidence") is None:
|
||||||
data["confidence"] = 0.3
|
data["confidence"] = 0.3
|
||||||
data.setdefault("extraction_warnings", ["incomplete_model_output"])
|
if not isinstance(data.get("extraction_warnings"), list):
|
||||||
|
data["extraction_warnings"] = ["incomplete_model_output"]
|
||||||
|
|
||||||
# Clamp novelty_score and confidence to [0, 1]
|
# Clamp novelty_score and confidence to [0, 1]
|
||||||
for field in ("novelty_score", "confidence"):
|
for field in ("novelty_score", "confidence"):
|
||||||
@@ -384,17 +388,22 @@ def _normalize_extraction_data(data: dict[str, Any]) -> dict[str, Any]:
|
|||||||
for comp in companies:
|
for comp in companies:
|
||||||
if not isinstance(comp, dict):
|
if not isinstance(comp, dict):
|
||||||
continue
|
continue
|
||||||
# Fill missing required company fields with defaults
|
# Replace None values with defaults (model returns null for fields)
|
||||||
comp.setdefault("ticker", "")
|
_company_defaults = {
|
||||||
comp.setdefault("company_name", "")
|
"ticker": "",
|
||||||
comp.setdefault("relevance", 0.5)
|
"company_name": "",
|
||||||
comp.setdefault("sentiment", "neutral")
|
"relevance": 0.5,
|
||||||
comp.setdefault("impact_score", 0.5)
|
"sentiment": "neutral",
|
||||||
comp.setdefault("impact_horizon", "1d_30d")
|
"impact_score": 0.5,
|
||||||
comp.setdefault("catalyst_type", "other")
|
"impact_horizon": "1d_30d",
|
||||||
comp.setdefault("key_facts", [])
|
"catalyst_type": "other",
|
||||||
comp.setdefault("risks", [])
|
"key_facts": [],
|
||||||
comp.setdefault("evidence_spans", [])
|
"risks": [],
|
||||||
|
"evidence_spans": [],
|
||||||
|
}
|
||||||
|
for key, default in _company_defaults.items():
|
||||||
|
if comp.get(key) is None:
|
||||||
|
comp[key] = default
|
||||||
# Clamp numeric fields
|
# Clamp numeric fields
|
||||||
for f in ("relevance", "impact_score"):
|
for f in ("relevance", "impact_score"):
|
||||||
v = comp.get(f)
|
v = comp.get(f)
|
||||||
@@ -412,6 +421,13 @@ def _normalize_extraction_data(data: dict[str, Any]) -> dict[str, Any]:
|
|||||||
mapped_cat = _CATALYST_ALIASES.get(cat.lower().strip(), "other")
|
mapped_cat = _CATALYST_ALIASES.get(cat.lower().strip(), "other")
|
||||||
comp["catalyst_type"] = mapped_cat
|
comp["catalyst_type"] = mapped_cat
|
||||||
|
|
||||||
|
# Filter out company entries with no ticker (model returned null/empty)
|
||||||
|
# — keep the rest of the extraction (summary, macro_themes, etc.)
|
||||||
|
data["companies"] = [
|
||||||
|
c for c in companies
|
||||||
|
if isinstance(c, dict) and c.get("ticker")
|
||||||
|
]
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user