This commit is contained in:
LASTA_DEV01\lasta
2026-05-13 19:57:34 +09:00
parent 2e9204243d
commit 9e88f4c7ad
4310 changed files with 48538 additions and 905279 deletions

View File

@@ -118,7 +118,7 @@ class PerfumeRuleBasedExtractor(GenericRuleBasedExtractor):
price = find_price(page_text)
if price:
attrs["price"] = {k: v for k, v in price.items() if k != "evidence"}
entities = [ExtractedEntity("Perfume", product_name, attrs, confidence=0.68)]
entities = [ExtractedEntity("Perfume", product_name, attrs, confidence=0.74)]
if brand:
entities.append(ExtractedEntity("Brand", brand, confidence=0.62))
for field, entity_type in [
@@ -187,7 +187,7 @@ class PerfumeRuleBasedExtractor(GenericRuleBasedExtractor):
"hasPrice",
object_value=card["price"],
evidence_text=str(card.get("evidence") or card["name"]),
confidence=0.76,
confidence=0.84,
confidence_reason="Korean product listing price pattern matched",
)
)
@@ -236,7 +236,7 @@ class PerfumeRuleBasedExtractor(GenericRuleBasedExtractor):
"hasPrice",
object_value={k: v for k, v in price.items() if k != "evidence"},
evidence_text=price["evidence"],
confidence=0.7,
confidence=0.86,
confidence_reason="price pattern matched",
)
)
@@ -249,10 +249,19 @@ class PerfumeRuleBasedExtractor(GenericRuleBasedExtractor):
def extract_product_name(page_text: str) -> str:
for line in page_text.splitlines()[:8]:
candidates: list[tuple[int, str]] = []
for line in page_text.splitlines()[:12]:
clean = line.strip()
if clean and not looks_like_navigation(clean) and not is_template_placeholder(clean):
return clean[:240]
if not clean or looks_like_navigation(clean) or is_template_placeholder(clean):
continue
if looks_like_metric_or_price(clean):
continue
candidates.append((product_line_score(clean), clean[:240]))
strong = [candidate for candidate in candidates if candidate[0] > 0]
if strong:
return max(strong, key=lambda item: item[0])[1]
if candidates:
return candidates[0][1]
return first_non_empty_line(page_text) or "Unknown Perfume"
@@ -265,14 +274,40 @@ def extract_brand(page_text: str, product_name: str) -> str | None:
match = re.search(pattern, page_text, flags=re.IGNORECASE)
if match:
return cleanup_value(match.group("brand"))
inferred = infer_site_brand(page_text)
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):
if len(candidate) <= 80 and not looks_like_navigation(candidate) and product_line_score(candidate) <= 0:
return candidate
return None
def product_line_score(value: str) -> int:
lower = value.lower()
score = 0
if re.search(r"\d+\s*(?:ml|g|개입)", lower):
score += 4
if any(keyword in value for keyword in ["향수", "디퓨저", "스프레이", "핸드크림", "미스트", "샤쉐", "퍼퓸"]):
score += 3
if value.startswith("[") or any(keyword in value for keyword in ["기획", "추가할인", "모음"]):
score += 2
if "912" in value:
score += 2
if "시작" in value and not re.search(r"\d+\s*(?:ml|g|개입)", lower):
score -= 4
return score
def looks_like_metric_or_price(value: str) -> bool:
clean = value.replace(",", "").strip()
if re.fullmatch(r"\d+(?:\.\d+)?", clean):
return True
return bool(re.fullmatch(r"\d+(?:\.\d+)?\s*(?:원|krw|usd)?", clean, flags=re.IGNORECASE))
def extract_product_cards(page_text: str) -> list[dict[str, object]]:
lines = [line.strip() for line in page_text.splitlines() if line.strip()]
cards: list[dict[str, object]] = []