This commit is contained in:
lasta
2026-05-22 00:22:03 +09:00
parent 8d77bc659f
commit d841fb823a
49 changed files with 2732 additions and 3763 deletions

View File

@@ -167,6 +167,7 @@ class PerfumeRuleBasedExtractor(GenericRuleBasedExtractor):
brand = next((entity for entity in entities if entity.entity_type == "Brand"), None)
for card in product_cards:
if brand:
evidence = brand_evidence_for_product(brand, str(card["name"]), page_text) or brand.evidence_text or brand.name
claims.append(
ExtractedClaim(
str(card["name"]),
@@ -174,7 +175,7 @@ class PerfumeRuleBasedExtractor(GenericRuleBasedExtractor):
"hasBrand",
brand.name,
"Brand",
evidence_text=brand.evidence_text or brand.name,
evidence_text=evidence,
confidence=0.72,
confidence_reason="site brand inferred from listing page",
)
@@ -199,6 +200,7 @@ class PerfumeRuleBasedExtractor(GenericRuleBasedExtractor):
claims: list[ExtractedClaim] = []
brand = next((entity for entity in entities if entity.entity_type == "Brand"), None)
if brand:
evidence = brand_evidence_for_product(brand, perfume.name, page_text) or brand.evidence_text or brand.name
claims.append(
ExtractedClaim(
perfume.name,
@@ -206,7 +208,7 @@ class PerfumeRuleBasedExtractor(GenericRuleBasedExtractor):
"hasBrand",
brand.name,
"Brand",
evidence_text=brand.evidence_text or brand.name,
evidence_text=evidence,
confidence=0.78,
confidence_reason="brand pattern matched",
)
@@ -273,14 +275,16 @@ def extract_brand(page_text: str, product_name: str) -> str | None:
for pattern in patterns:
match = re.search(pattern, page_text, flags=re.IGNORECASE)
if match:
return cleanup_value(match.group("brand"))
inferred = infer_site_brand(page_text)
brand = valid_brand(cleanup_value(match.group("brand")))
if brand:
return brand
inferred = infer_site_brand(page_text, product_name)
if inferred:
return inferred
lines = [line.strip() for line in page_text.splitlines() if line.strip()]
if len(lines) >= 2 and lines[1].lower() not in product_name.lower():
candidate = cleanup_value(lines[1])
if len(candidate) <= 80 and not looks_like_navigation(candidate) and product_line_score(candidate) <= 0:
if valid_brand(candidate) and len(candidate) <= 80 and not looks_like_navigation(candidate) and product_line_score(candidate) <= 0:
return candidate
return None
@@ -301,6 +305,19 @@ def product_line_score(value: str) -> int:
return score
def looks_like_price(value: str | None) -> bool:
if not value:
return False
clean = cleanup_value(str(value))
return bool(
re.fullmatch(
r"(?:[$€£]\s*)?\d{1,3}(?:,\d{3})*(?:\.\d+)?\s*(?:원|KRW|USD|EUR|JPY|\$|€|£)?",
clean,
flags=re.IGNORECASE,
)
)
def looks_like_metric_or_price(value: str) -> bool:
clean = value.replace(",", "").strip()
if re.fullmatch(r"\d+(?:\.\d+)?", clean):
@@ -385,7 +402,10 @@ def dedupe_product_cards(cards: list[dict[str, object]]) -> list[dict[str, objec
return result
def infer_site_brand(page_text: str) -> str | None:
def infer_site_brand(page_text: str, product_name: str = "") -> str | None:
brand_context = f"{product_name}\n{page_text[:1200]}".lower()
if "forment" in brand_context or "포맨트" in brand_context:
return "FORMENT"
if "912 공식 홈페이지" in page_text or "912" in page_text[:500]:
return "912"
return None
@@ -394,9 +414,19 @@ def infer_site_brand(page_text: str) -> str | None:
def valid_brand(value: str | None) -> str | None:
if not value or is_template_placeholder(value):
return None
if looks_like_price(value) or looks_like_metric_or_price(value):
return None
return value
def brand_evidence_for_product(brand: ExtractedEntity, product_name: str, page_text: str) -> str | None:
if brand.name == "FORMENT" and "포맨트" in product_name:
return product_name
if brand.name.lower() in page_text.lower():
return brand.name
return None
def extract_field_values(field: str, page_text: str) -> list[tuple[str, str]]:
if field in NOTE_LABELS:
return extract_labeled_values(page_text, NOTE_LABELS[field])