diff --git a/crawler_platform/app/core/extractor/ai_provider.py b/crawler_platform/app/core/extractor/ai_provider.py index 35f6faf..dc933ff 100644 --- a/crawler_platform/app/core/extractor/ai_provider.py +++ b/crawler_platform/app/core/extractor/ai_provider.py @@ -396,7 +396,7 @@ def parse_entities(items: list[dict[str, Any]]) -> list[ExtractedEntity]: entities: list[ExtractedEntity] = [] for item in items: name = item.get("name") - entity_type = item.get("entity_type") or item.get("type") + entity_type = normalize_entity_type(item.get("entity_type") or item.get("type")) if not name or not entity_type: continue entities.append( @@ -416,23 +416,73 @@ def parse_claims(items: list[dict[str, Any]]) -> list[ExtractedClaim]: claims: list[ExtractedClaim] = [] for item in items: subject_name = item.get("subject_name") - subject_type = item.get("subject_type") + subject_type = normalize_entity_type(item.get("subject_type")) predicate = item.get("predicate") if not subject_name or not subject_type or not predicate: continue - claims.append( - ExtractedClaim( - subject_name=str(subject_name), - subject_type=str(subject_type), - predicate=str(predicate), - object_name=item.get("object_name"), - object_type=item.get("object_type"), - object_value=item.get("object_value"), - evidence_text=item.get("evidence_text"), - evidence_summary=item.get("evidence_summary"), - confidence=float(item.get("confidence") or 0.55), - confidence_reason=item.get("confidence_reason") or "AI extractor output", - metadata={"ai_extracted": True}, + object_name = item.get("object_name") + object_type = normalize_entity_type(item.get("object_type")) if item.get("object_type") else None + object_value = item.get("object_value") + if not object_name and isinstance(object_value, dict) and len(object_value) == 1: + object_name = next(iter(object_value.keys())) + object_value = None + if object_name and not object_type: + object_type = infer_object_type(str(predicate)) + + object_names = split_object_names(object_name) if object_name and object_type else [object_name] + for resolved_object_name in object_names: + claims.append( + ExtractedClaim( + subject_name=str(subject_name), + subject_type=str(subject_type), + predicate=str(predicate), + object_name=resolved_object_name, + object_type=object_type if resolved_object_name else None, + object_value=None if resolved_object_name else object_value, + evidence_text=item.get("evidence_text"), + evidence_summary=item.get("evidence_summary"), + confidence=float(item.get("confidence") or 0.55), + confidence_reason=item.get("confidence_reason") or "AI extractor output", + metadata={"ai_extracted": True}, + ) ) - ) return claims + + +def normalize_entity_type(value: Any) -> str | None: + if not value: + return None + aliases = { + "perfume": "Perfume", + "product": "Perfume", + "brand": "Brand", + "note": "Note", + "accord": "Accord", + "mood": "Mood", + "season": "Season", + "occasion": "Occasion", + "review": "Review", + "price": "Price", + } + text = str(value).strip() + return aliases.get(text.lower(), text) + + +def infer_object_type(predicate: str) -> str | None: + return { + "hasBrand": "Brand", + "hasTopNote": "Note", + "hasMiddleNote": "Note", + "hasBaseNote": "Note", + "hasAccord": "Accord", + "evokesMood": "Mood", + "suitableForSeason": "Season", + "suitableForOccasion": "Occasion", + "hasReviewKeyword": "Review", + }.get(predicate) + + +def split_object_names(value: Any) -> list[str]: + text = str(value) + parts = re.split(r"[,/|·ㆍ]+|\band\b| 및 | 그리고 ", text, flags=re.IGNORECASE) + return [part.strip() for part in parts if part.strip()]