V10.621 sync auto candidates to growth layer
Some checks failed
CD Pipeline / deploy (push) Failing after 34s
Some checks failed
CD Pipeline / deploy (push) Failing after 34s
This commit is contained in:
@@ -247,6 +247,76 @@ def test_sync_legacy_momo_reference_offers_dry_run_does_not_write():
|
||||
assert count == 0
|
||||
|
||||
|
||||
def test_sync_targeted_momo_candidates_writes_unit_price_offer():
|
||||
from services.external_market_offer_service import sync_targeted_momo_candidates_to_external_offers
|
||||
|
||||
engine = create_engine("sqlite:///:memory:")
|
||||
_seed_external_offer_sync_tables(engine)
|
||||
|
||||
payload = sync_targeted_momo_candidates_to_external_offers(engine, [
|
||||
{
|
||||
"product_id": "10833188",
|
||||
"name": "MOMO B5 修復霜 40ml",
|
||||
"price": 468,
|
||||
"original_price": 835,
|
||||
"product_url": "https://momo.test/10833188",
|
||||
"image_url": "https://img.test/10833188.jpg",
|
||||
"brand": "理膚寶水",
|
||||
"target_pchome_product_id": "PCH-1",
|
||||
"target_pchome_name": "PChome B5 修復霜 40ml",
|
||||
"target_pchome_price": 920,
|
||||
"target_match_score": 0.74,
|
||||
"auto_compare_type": "unit_price",
|
||||
"target_price_basis": "unit_price",
|
||||
"target_match_reasons": ["unit_comparable"],
|
||||
"target_comparison_mode": "unit_comparable",
|
||||
"target_unit_price_comparison": {
|
||||
"comparable": True,
|
||||
"unit_label": "ml",
|
||||
"momo_total_quantity": 40,
|
||||
"competitor_total_quantity": 40,
|
||||
"momo_unit_price": 11.7,
|
||||
"competitor_unit_price": 23.0,
|
||||
"unit_gap_pct": -49.13,
|
||||
},
|
||||
},
|
||||
{
|
||||
"product_id": "REVIEW-1",
|
||||
"name": "仍需人工商品",
|
||||
"price": 500,
|
||||
"target_pchome_product_id": "PCH-2",
|
||||
"auto_compare_type": "manual_review",
|
||||
},
|
||||
])
|
||||
|
||||
assert payload["success"] is True
|
||||
assert payload["status"] == "synced"
|
||||
assert payload["candidate_count"] == 2
|
||||
assert payload["written_count"] == 1
|
||||
assert payload["unit_price_count"] == 1
|
||||
assert payload["skipped_reasons"] == {"不是可自動使用的候選": 1}
|
||||
|
||||
with engine.connect() as conn:
|
||||
row = conn.execute(text("""
|
||||
SELECT source_product_id, price, pchome_product_id, match_status,
|
||||
quality_score, data_quality_status, ingestion_method,
|
||||
raw_payload_json
|
||||
FROM external_offers
|
||||
""")).mappings().one()
|
||||
|
||||
raw_payload = __import__("json").loads(row["raw_payload_json"])
|
||||
assert row["source_product_id"] == "10833188"
|
||||
assert row["price"] == 468
|
||||
assert row["pchome_product_id"] == "PCH-1"
|
||||
assert row["match_status"] == "verified"
|
||||
assert row["quality_score"] == 82
|
||||
assert row["data_quality_status"] == "verified"
|
||||
assert row["ingestion_method"] == "targeted_momo_search"
|
||||
assert raw_payload["price_basis"] == "unit_price"
|
||||
assert raw_payload["pchome_public_price"] == 920
|
||||
assert raw_payload["unit_price_comparison"]["unit_gap_pct"] == -49.13
|
||||
|
||||
|
||||
def test_external_source_readiness_uses_legacy_momo_reference_cache():
|
||||
from services.external_market_offer_service import build_external_source_readiness
|
||||
|
||||
|
||||
@@ -479,6 +479,8 @@ def test_ai_intelligence_uses_v2_shell_and_real_runtime_apis():
|
||||
assert "compSourceSummary" in template
|
||||
assert "'external_offers':" in template
|
||||
assert "'自動同步'" in template
|
||||
assert "price_basis_label" in template
|
||||
assert "商品總價" in template
|
||||
assert "/api/ai/pchome-growth/opportunities" in template
|
||||
assert "最近處理紀錄" in template
|
||||
assert "處理紀錄" in template
|
||||
@@ -529,6 +531,8 @@ def test_price_comparison_page_is_action_oriented_and_plain_chinese():
|
||||
assert "renderMomoUnitComparePanel" in template
|
||||
assert "自動單位價比較" in template
|
||||
assert "查看單位價" in template
|
||||
assert "sync_external_offers: true" in template
|
||||
assert "已同步 ${syncedCount} 筆到作戰清單" in template
|
||||
assert "renderMomoReviewPanel" in template
|
||||
assert "/api/price_comparison/fetch_momo_for_pchome" in template
|
||||
assert "MOMO 候選待確認" in template
|
||||
|
||||
@@ -79,6 +79,7 @@ def test_search_momo_products_for_pchome_products_uses_each_pchome_product_as_ta
|
||||
assert products[0]["product_id"] == "12345678"
|
||||
assert products[0]["target_pchome_product_id"] == "PCH-1"
|
||||
assert products[0]["target_pchome_name"] == "【LA ROCHE-POSAY 理膚寶水】B5全面修復霜 40ml"
|
||||
assert products[0]["target_pchome_price"] == 920
|
||||
assert products[0]["target_match_score"] > 0
|
||||
assert products[0]["source_strategy"] == "pchome_targeted_momo_search"
|
||||
|
||||
|
||||
@@ -87,6 +87,46 @@ def _seed_growth_external_offers(engine):
|
||||
"""))
|
||||
|
||||
|
||||
def _seed_growth_unit_price_external_offer(engine):
|
||||
with engine.begin() as conn:
|
||||
conn.execute(text("""
|
||||
CREATE TABLE external_offers (
|
||||
id INTEGER PRIMARY KEY,
|
||||
source_code TEXT,
|
||||
platform_code TEXT,
|
||||
source_product_id TEXT,
|
||||
source_offer_key TEXT,
|
||||
title TEXT,
|
||||
price REAL,
|
||||
observed_at TEXT,
|
||||
expires_at TEXT,
|
||||
ingestion_method TEXT,
|
||||
pchome_product_id TEXT,
|
||||
momo_sku TEXT,
|
||||
match_status TEXT,
|
||||
quality_score REAL,
|
||||
data_quality_status TEXT,
|
||||
quality_notes_json TEXT,
|
||||
raw_payload_json TEXT
|
||||
)
|
||||
"""))
|
||||
conn.execute(text("""
|
||||
INSERT INTO external_offers (
|
||||
id, source_code, platform_code, source_product_id, source_offer_key,
|
||||
title, price, observed_at, expires_at, ingestion_method,
|
||||
pchome_product_id, momo_sku, match_status, quality_score,
|
||||
data_quality_status, quality_notes_json, raw_payload_json
|
||||
)
|
||||
VALUES (
|
||||
1, 'momo_reference', 'momo', 'MOMO-UNIT', 'momo_reference:MOMO-UNIT:PCH-1:unit_price',
|
||||
'MOMO 單位價商品', 468, '2026-06-14 12:00:00', NULL, 'targeted_momo_search',
|
||||
'PCH-1', 'MOMO-UNIT', 'verified', 82,
|
||||
'verified', '["自動單位價比較"]',
|
||||
'{"price_basis": "unit_price", "pchome_public_price": 920, "pchome_public_name": "PChome 公開商品", "tags": ["identity_v2", "price_basis_unit_price"], "unit_price_comparison": {"unit_label": "ml", "momo_unit_price": 11.7, "competitor_unit_price": 23.0, "momo_total_quantity": 40, "competitor_total_quantity": 40, "unit_gap_pct": -49.13}}'
|
||||
)
|
||||
"""))
|
||||
|
||||
|
||||
def test_pchome_growth_opportunities_use_plain_language_and_pause_shopee_coupang():
|
||||
from services.pchome_revenue_growth_service import build_pchome_growth_opportunities
|
||||
|
||||
@@ -138,6 +178,31 @@ def test_pchome_growth_prefers_external_offers_over_legacy_competitor_cache():
|
||||
assert payload["stats"]["external_data_source_counts"] == {"自動同步資料層": 1}
|
||||
|
||||
|
||||
def test_pchome_growth_understands_unit_price_external_offers():
|
||||
from services.pchome_revenue_growth_service import build_pchome_growth_opportunities
|
||||
|
||||
engine = create_engine("sqlite:///:memory:")
|
||||
_seed_growth_tables(engine)
|
||||
_seed_growth_unit_price_external_offer(engine)
|
||||
|
||||
payload = build_pchome_growth_opportunities(engine, limit=5)
|
||||
|
||||
assert payload["success"] is True
|
||||
pchome_1 = next(item for item in payload["opportunities"] if item["pchome_product_id"] == "PCH-1")
|
||||
external_price = pchome_1["external_price"]
|
||||
assert external_price["data_source"] == "external_offers"
|
||||
assert external_price["price_basis"] == "unit_price"
|
||||
assert external_price["price_basis_label"] == "單位價"
|
||||
assert external_price["momo_price"] == 468
|
||||
assert external_price["pchome_price"] == 920
|
||||
assert external_price["momo_unit_price"] == 11.7
|
||||
assert external_price["pchome_unit_price"] == 23.0
|
||||
assert external_price["gap_pct"] == -49.1
|
||||
assert pchome_1["recommended_action"]["label"] == "檢查售價與活動"
|
||||
assert pchome_1["data_quality"]["label"] == "資料可用單位價判斷"
|
||||
assert any("單位價" in line for line in pchome_1["reason_lines"])
|
||||
|
||||
|
||||
def test_ai_product_pick_sales_join_by_sku_disabled_by_default(monkeypatch):
|
||||
from services.ai_product_pick_agent import _sales_join_by_momo_sku_enabled
|
||||
|
||||
|
||||
@@ -172,3 +172,66 @@ def test_fetch_momo_for_pchome_endpoint_splits_auto_and_review_candidates(monkey
|
||||
assert payload["data"]["products"][0]["product_id"] == "AUTO-1"
|
||||
assert payload["data"]["unit_compare_candidates"][0]["product_id"] == "UNIT-1"
|
||||
assert payload["data"]["review_candidates"][0]["product_id"] == "REVIEW-1"
|
||||
assert payload["data"]["external_offer_sync"]["status"] == "not_requested"
|
||||
|
||||
|
||||
def test_fetch_momo_for_pchome_endpoint_syncs_auto_candidates_when_requested(monkeypatch):
|
||||
from routes import price_comparison_routes as routes
|
||||
|
||||
captured = {}
|
||||
|
||||
def fake_targeted_search(pchome_products, **kwargs):
|
||||
return True, "找到候選", [
|
||||
{
|
||||
"name": "可直接比價商品",
|
||||
"price": 890,
|
||||
"product_id": "AUTO-1",
|
||||
"auto_compare_type": "total_price",
|
||||
"can_auto_compare": True,
|
||||
},
|
||||
{
|
||||
"name": "自動單位價商品",
|
||||
"price": 468,
|
||||
"product_id": "UNIT-1",
|
||||
"auto_compare_type": "unit_price",
|
||||
"can_auto_compare": True,
|
||||
},
|
||||
{
|
||||
"name": "需確認商品",
|
||||
"price": 468,
|
||||
"product_id": "REVIEW-1",
|
||||
"can_auto_compare": False,
|
||||
},
|
||||
]
|
||||
|
||||
def fake_sync(candidates):
|
||||
captured["sync_candidates"] = candidates
|
||||
return {
|
||||
"success": True,
|
||||
"status": "synced",
|
||||
"written_count": len(candidates),
|
||||
"unit_price_count": 1,
|
||||
"total_price_count": 1,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(routes, "search_momo_products_for_pchome_products", fake_targeted_search)
|
||||
monkeypatch.setattr(routes, "_sync_targeted_candidates_to_external_offers", fake_sync)
|
||||
|
||||
app = Flask(__name__)
|
||||
with app.test_request_context(
|
||||
"/api/price_comparison/fetch_momo_for_pchome",
|
||||
method="POST",
|
||||
json={
|
||||
"sync_external_offers": True,
|
||||
"pchome_products": [
|
||||
{"name": "理膚寶水 B5 修復霜", "price": 920, "product_id": "PCH-1"},
|
||||
],
|
||||
},
|
||||
):
|
||||
response = routes.fetch_momo_for_pchome_products.__wrapped__()
|
||||
|
||||
payload = response.get_json()
|
||||
assert payload["success"] is True
|
||||
assert payload["data"]["external_offer_sync"]["status"] == "synced"
|
||||
assert payload["data"]["external_offer_sync"]["written_count"] == 2
|
||||
assert [item["product_id"] for item in captured["sync_candidates"]] == ["AUTO-1", "UNIT-1"]
|
||||
|
||||
Reference in New Issue
Block a user