V10.404 gate Hermes direct price alerts
All checks were successful
CD Pipeline / deploy (push) Successful in 1m6s

This commit is contained in:
OoO
2026-05-24 13:11:24 +08:00
committed by AiderHeal Bot
parent b2dbf98c3b
commit 1272c01ec6
6 changed files with 125 additions and 9 deletions

View File

@@ -325,7 +325,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.403"
SYSTEM_VERSION = "V10.404"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -13,6 +13,7 @@
## 📅 詳細更新日誌 (考古存檔)
### 2026-05-24PChome 近門檻身份回收第二輪
- **V10.404 Hermes 競價威脅漏斗只吃 direct alert**: `HermesAnalystService.fetch_candidates()``competitor_prices` JOIN 新增硬條件,只讓 `match_type=exact``price_basis=total_price``alert_tier=price_alert_exact` 的 identity_v2 配對進入 Hermes 競價威脅分析;`identity_review``unit_price_review``suppress` 仍保留在 dashboard / 人工覆核資料流,但不再消耗 Hermes token 或被上游視為直接價格威脅候選。production fresh 配對分布顯示直接告警約 497 筆、覆核型約 216 筆,本版將兩者在 AI 威脅入口切開。同版 matcher 追加 KATE 怪獸級持色唇膏、植村秀武士刀眉筆筆蕊、The Forest 焦糖楓葉擴香禮盒等近門檻 review-only 回收線,仍只進人工覆核,不直接價格告警。
- **V10.403 近門檻候選重驗擴池**: production 最新 72h attempt audit 顯示 7853 筆近門檻/被擋候選中,有 105 筆可被現行 matcher 回收,其中 46 筆已達 `price_alert_exact`、59 筆只進 `identity_review``CompetitorPriceFeeder._fetch_retryable_candidate_skus()` 擴大每日 revalidation 候選池,納入 `true_low_confidence``unit_comparable``refresh_unit_comparable` 與高分 `identity_veto`;但仍要求 PChome product_id、`best_match_score >= 0.70`,且正式寫入前必須由現行 matcher 重判、通過 hard-veto 與既有配對保護。
- **V10.402 catalog variant identity-review 回收**: marketplace matcher 新增 Johnsons 嬰兒潤膚乳 catalog 對同香型、IM MEME 涼感定妝噴霧、SO NATURAL FIXX 定妝噴霧三條 review-only focused identity讓同品線 catalog / variant 候選可脫離 `true_low_confidence` 但只進 `identity_review`,不直接價格告警;同版明確保留 MUJI 品牌缺漏護手霜等 brandless catalog 案例在低信心,避免品牌缺漏與多款任選同時存在時被過度救回。
- **V10.401 focused identity 邊界整理**: 將 Laundrin TOKYO 車用夾式消臭芳香劑判定抽成 `_has_laundrin_tokyo_car_freshener_alignment()`,並在 variant descriptor guard 中豁免同一條強身份線,避免 TOKYO / 車用 / 芳香劑語序差異造成誤擋。Yuskin 經典乳霜 30g 6 入組改用 `_has_exact_count_alignment()` 判斷包數,保留 6入 vs 6盒這類同數量但不同容器字詞的人工覆核路徑。

View File

@@ -449,6 +449,15 @@ class HermesAnalystService:
AND cp.expires_at > NOW()
AND cp.match_score >= 0.76
AND COALESCE(cp.tags, '[]'::jsonb) ? 'identity_v2'
AND (
cp.match_diagnostic_json @> '{{"match_type":"exact","price_basis":"total_price","alert_tier":"price_alert_exact"}}'::jsonb
OR (
cp.match_diagnostic_json IS NULL
AND COALESCE(cp.tags, '[]'::jsonb) ? 'match_type_exact'
AND COALESCE(cp.tags, '[]'::jsonb) ? 'price_basis_total_price'
AND COALESCE(cp.tags, '[]'::jsonb) ? 'alert_tier_price_alert_exact'
)
)
WHERE lmp.rn = 1
AND rs.sales_7d_prev > 0
AND (rs.sales_7d_curr - rs.sales_7d_prev) / rs.sales_7d_prev < -0.10

View File

@@ -365,6 +365,8 @@ SEARCH_IDENTITY_ANCHORS = (
"嬰兒潤膚乳",
"我愛超磁妝定妝噴霧",
"全天候超完美定妝噴霧",
"怪獸級持色唇膏",
"焦糖楓葉香氛擴香花禮盒",
"香氛蠟燭20種香味",
"tokyo車用夾式消臭芳香劑",
"北歐簡樸融蠟燈桌面氣氛夜燈",
@@ -373,6 +375,7 @@ SEARCH_IDENTITY_ANCHORS = (
"細芯睛彩雙頭眉筆",
"雙頭旋轉極細眉筆",
"武士刀眉筆",
"自動武士刀眉筆筆蕊",
"無極限保濕防曬妝前乳",
"水凝光透 妝前防護乳",
"水凝光透妝前防護乳",
@@ -466,6 +469,13 @@ FOCUSED_IDENTITY_REVIEW_ONLY_REASONS = {
"johnsons_baby_lotion_variant_catalog",
"im_meme_fixx_cool_setting_spray",
"so_natural_fixx_setting_spray_catalog",
"kate_monster_lipstick_catalog",
"shu_auto_hard_formula_refill_catalog",
"the_forest_maple_diffuser_flower_brandless",
}
FOCUSED_IDENTITY_BRANDLESS_REVIEW_REASONS = {
"the_forest_maple_diffuser_flower_brandless",
}
SEARCH_BROAD_ANCHORS = {
@@ -1862,6 +1872,25 @@ def score_marketplace_match(
and sequence_score >= 0.40
and not variant_descriptor_conflict
)
focused_exact_review_boost_safe = (
focused_exact_line_reason
and not hard_veto
and spec_score >= 0.45
and token_score >= 0.30
and sequence_score >= 0.40
and not variant_descriptor_conflict
and (
brand_score >= 0.95
or (
focused_exact_line_reason in FOCUSED_IDENTITY_BRANDLESS_REVIEW_REASONS
and brand_score == 0.55
and bool(left.brand_tokens) != bool(right.brand_tokens)
and spec_score >= 0.85
and token_score >= 0.55
and sequence_score >= 0.50
)
)
)
comparison_mode = "exact_identity"
if _is_unit_comparable_candidate(
@@ -1974,14 +2003,8 @@ def score_marketplace_match(
score += 0.04
reasons.append("cushion_refill_pack_alignment_score")
if (
focused_exact_line_reason
and brand_score >= 0.95
and not hard_veto
focused_exact_review_boost_safe
and price_penalty == 0
and spec_score >= 0.45
and token_score >= 0.30
and sequence_score >= 0.40
and not variant_descriptor_conflict
):
score += 0.16
reasons.append(f"focused_exact_identity_{focused_exact_line_reason}")
@@ -2922,6 +2945,27 @@ def _has_focused_low_score_exact_identity_line(left: ProductIdentity, right: Pro
and "全天候超完美定妝噴霧" in right_text
):
return "so_natural_fixx_setting_spray_catalog"
if (
{"kate", "凱婷"} & (left.brand_tokens & right.brand_tokens)
and "怪獸級持色唇膏" in left_text
and "怪獸級持色唇膏" in right_text
):
return "kate_monster_lipstick_catalog"
if (
{"shu uemura", "植村秀"} & (left.brand_tokens & right.brand_tokens)
and "自動武士刀眉筆" in left_text
and "自動武士刀眉筆" in right_text
and "筆蕊" in left_text
and "筆蕊" in right_text
):
return "shu_auto_hard_formula_refill_catalog"
if (
"焦糖楓葉香氛擴香花禮盒" in left_text
and "焦糖楓葉香氛擴香花禮盒" in right_text
and _has_shared_volume(left, right, 30)
and bool(left.brand_tokens) != bool(right.brand_tokens)
):
return "the_forest_maple_diffuser_flower_brandless"
if (
"gatsby" in (left.brand_tokens & right.brand_tokens)
and "爆水擦澡濕巾" in left_text

View File

@@ -3,6 +3,7 @@
"""Hermes 分析師必須透過 OllamaService 三主機級聯。"""
import time
import inspect
from types import SimpleNamespace
import pytest
@@ -141,3 +142,15 @@ def test_hermes_batch_analyze_uses_ollama_service_and_logs_secondary(monkeypatch
assert rec['caller'] == 'hermes_analyst'
assert rec['provider'] == 'ollama_secondary'
assert rec['meta']['host_label'] == 'GCP-SSD-2'
def test_hermes_candidate_sql_only_joins_direct_price_alert_matches():
sql_text = inspect.getsource(hermes_mod.HermesAnalystService.fetch_candidates)
compact_sql = "".join(sql_text.split())
assert '"match_type":"exact"' in compact_sql
assert '"price_basis":"total_price"' in compact_sql
assert '"alert_tier":"price_alert_exact"' in compact_sql
assert "match_type_exact" in sql_text
assert "price_basis_total_price" in sql_text
assert "alert_tier_price_alert_exact" in sql_text

View File

@@ -875,6 +875,18 @@ def test_marketplace_matcher_keeps_catalog_variant_recoveries_in_identity_review
"【SO NATURAL】FIXX 全天候超完美定妝噴霧 經典款/光澤款/霧面款/夏日款",
"【SO NATURAL】FIXX 全天候超完美定妝噴霧 120ml",
),
(
"【KATE 凱婷】怪獸級持色唇膏-經典款(獨家技術持久不沾 高保濕)",
"【KATE 凱婷】怪獸級持色唇膏 (3g)",
),
(
"【植村秀】官方直營 自動武士刀眉筆筆蕊(Shu uemura/筆蕊 8色任選)",
"《Shu Uemura 植村秀》自動武士刀眉筆 -筆蕊 0.3g",
),
(
"【The Forest 癒森林】焦糖楓葉香氛擴香花禮盒 含30ml品牌香氛油(居家香氛/香水精油/香氛擴香花/擴香禮物)",
"焦糖楓葉香氛擴香花禮盒 含30ml品牌香氛油",
),
]
for momo_name, competitor_name in cases:
@@ -1172,6 +1184,21 @@ def test_marketplace_matcher_promotes_focused_low_score_exact_identity_lines():
"【SO NATURAL】FIXX 全天候超完美定妝噴霧 120ml",
"focused_exact_identity_so_natural_fixx_setting_spray_catalog",
),
(
"【KATE 凱婷】怪獸級持色唇膏-經典款(獨家技術持久不沾 高保濕)",
"【KATE 凱婷】怪獸級持色唇膏 (3g)",
"focused_exact_identity_kate_monster_lipstick_catalog",
),
(
"【植村秀】官方直營 自動武士刀眉筆筆蕊(Shu uemura/筆蕊 8色任選)",
"《Shu Uemura 植村秀》自動武士刀眉筆 -筆蕊 0.3g",
"focused_exact_identity_shu_auto_hard_formula_refill_catalog",
),
(
"【The Forest 癒森林】焦糖楓葉香氛擴香花禮盒 含30ml品牌香氛油(居家香氛/香水精油/香氛擴香花/擴香禮物)",
"焦糖楓葉香氛擴香花禮盒 含30ml品牌香氛油",
"focused_exact_identity_the_forest_maple_diffuser_flower_brandless",
),
(
"【GATSBY】爆水擦澡濕巾24張入(涼感乾洗澡)",
"GATSBY 爆水擦澡濕巾24張入(240g)",
@@ -1213,8 +1240,30 @@ def test_marketplace_matcher_keeps_high_variant_low_score_lines_outside_focused_
"【Schick 舒適牌】舒芙仕女除毛刀把刀片組 (1刀把2刀片+刀片3入)",
"【Schick舒適牌】舒綺 仕女除毛刀 敏感肌用 1刀把+2刀片 送卡娜赫拉束口袋",
)
lancome_line_gap = score_marketplace_match(
"【LANCOME 蘭蔻】官方直營 超極光活粹晶露150ml(LANCOME/四重酸極光水/化妝水/精華水)",
"LANCOME 蘭蔻 超極限肌因精華露150ml 專櫃公司貨",
)
mac_finish_gap = score_marketplace_match(
"【M.A.C】MACximal極自我柔霧唇膏3.5g(子彈唇膏 #柔霧大巨彈_全新質地_新色登場)",
"M.A.C MACximal 極自我緞光唇膏 3.5g 多款可選",
)
ysl_powder_variant_guard = score_marketplace_match(
"【YSL】官方直營 恆久完美持久柔霧蜜粉餅(任選1款/皮革蜜粉)",
"YSL 恆久完美持久柔霧蜜粉餅(7.5g) [百貨公司專櫃貨]",
)
for diagnostics in (lush, lactacyd, lunasol, muji_swab, kate_limited, schick_bundle_gap):
for diagnostics in (
lush,
lactacyd,
lunasol,
muji_swab,
kate_limited,
schick_bundle_gap,
lancome_line_gap,
mac_finish_gap,
ysl_powder_variant_guard,
):
assert diagnostics.score < 0.76
assert not any(reason.startswith("focused_exact_identity_") for reason in diagnostics.reasons)