fix(growth): align sales freshness with report SLA
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

This commit is contained in:
ogt
2026-07-15 01:38:57 +08:00
parent de6933c137
commit 11d3865dfc
17 changed files with 422 additions and 85 deletions

View File

@@ -406,6 +406,10 @@ def test_dashboard_v2_is_production_default_and_uses_real_dashboard_data():
assert "高業績商品作戰清單" in dashboard
assert "業績 × MOMO 價格 × 下一步" in dashboard
assert "growth.mapping_rate" in dashboard
assert "growth.sales_freshness" in dashboard
assert "freshness.label" in dashboard
assert "'sales_freshness': stats.get('sales_freshness') or {}" in route_source
assert ".dashboard-section-label .meta.is-grace" in dashboard_css
assert "找同款 · 決策 · 證據 · 落地 · 回讀 · 留存" in dashboard
assert "growth.ai_automation_first_viewport" in dashboard
assert "ai_first_viewport.title" in dashboard

View File

@@ -413,6 +413,27 @@ def test_sales_freshness_blocks_decisions_after_two_days():
assert stale["decision_ready"] is False
def test_sales_freshness_uses_report_cutoff_instead_of_midnight():
from datetime import datetime
import pytz
from services.pchome_revenue_growth_service import _sales_freshness
timezone = pytz.timezone("Asia/Taipei")
before_cutoff = _sales_freshness(
"2026-07-13",
now=timezone.localize(datetime(2026, 7, 15, 1, 5)),
)
after_cutoff = _sales_freshness(
"2026-07-13",
now=timezone.localize(datetime(2026, 7, 15, 20, 1)),
)
assert before_cutoff["status"] == "grace"
assert before_cutoff["decision_ready"] is True
assert after_cutoff["status"] == "warning"
assert after_cutoff["decision_ready"] is False
def test_pchome_growth_route_cache_respects_shared_invalidation_epoch(monkeypatch):
from routes import ai_routes as routes

View File

@@ -101,6 +101,9 @@ def test_local_drop_closes_import_and_archives_only_after_verification(monkeypat
assert result["source_type"] == "controlled_local_drop"
assert result["total_rows"] == 1
assert result["decision_ready"] is True
assert result["freshness_contract"] == "pchome_sales_freshness_sla_v1"
assert result["sla_lag_days"] <= 0
assert result["report_cutoff_hour"] == 20
assert result["receipt_persisted"] is True
assert len(result["trace_id"]) == 32
assert len(result["span_id"]) == 16
@@ -328,6 +331,13 @@ def test_scheduler_continues_when_legacy_hitl_pause_is_set(monkeypatch):
"receipt_persisted": True,
"latest_sales_date": "2026-07-10",
"data_lag_days": 1,
"sla_lag_days": 0,
"freshness_status": "fresh",
"expected_latest_sales_date": "2026-07-10",
"grace_period_active": False,
"report_cutoff_hour": 20,
"report_due_at": "2026-07-14T20:00:00+08:00",
"freshness_contract": "pchome_sales_freshness_sla_v1",
"decision_ready": True,
"safe_next_action": "scheduler_retry_authorized_sources",
"next_retry_at": "2026-07-14T19:00:00+08:00",
@@ -353,3 +363,6 @@ def test_scheduler_continues_when_legacy_hitl_pause_is_set(monkeypatch):
assert saved["stats"]["receipt_persisted"] is True
assert saved["stats"]["next_retry_at"] == "2026-07-14T19:00:00+08:00"
assert saved["stats"]["notification_policy"]["send"] is False
assert saved["stats"]["freshness_contract"] == "pchome_sales_freshness_sla_v1"
assert saved["stats"]["sla_lag_days"] == 0
assert saved["stats"]["report_cutoff_hour"] == 20

View File

@@ -0,0 +1,82 @@
from datetime import datetime
import pytz
from services.pchome_sales_freshness_policy import (
CONTRACT_VERSION,
evaluate_pchome_sales_freshness,
)
TAIPEI_TZ = pytz.timezone("Asia/Taipei")
def _taipei(value: str) -> datetime:
return TAIPEI_TZ.localize(datetime.fromisoformat(value))
def test_two_calendar_days_old_is_ready_before_report_cutoff():
result = evaluate_pchome_sales_freshness(
"2026-07-13",
now=_taipei("2026-07-15T01:05:00"),
)
assert result["contract_version"] == CONTRACT_VERSION
assert result["data_lag_days"] == 2
assert result["expected_latest_sales_date"] == "2026-07-13"
assert result["sla_lag_days"] == 0
assert result["status"] == "grace"
assert result["grace_period_active"] is True
assert result["decision_ready"] is True
assert result["report_due_at"] == "2026-07-15T20:00:00+08:00"
def test_same_snapshot_blocks_after_report_cutoff():
result = evaluate_pchome_sales_freshness(
"2026-07-13",
now=_taipei("2026-07-15T20:01:00"),
)
assert result["expected_latest_sales_date"] == "2026-07-14"
assert result["sla_lag_days"] == 1
assert result["status"] == "warning"
assert result["grace_period_active"] is False
assert result["decision_ready"] is False
assert result["requires_upstream_acquisition"] is True
def test_previous_day_snapshot_is_ready_after_cutoff():
result = evaluate_pchome_sales_freshness(
"2026-07-14",
now=_taipei("2026-07-15T20:01:00"),
)
assert result["sla_lag_days"] == 0
assert result["status"] == "fresh"
assert result["decision_ready"] is True
def test_future_snapshot_is_rejected_even_when_it_meets_sla():
result = evaluate_pchome_sales_freshness(
"2026-07-16",
now=_taipei("2026-07-15T10:00:00"),
)
assert result["status"] == "future"
assert result["decision_ready"] is False
def test_missing_and_invalid_snapshots_keep_public_safe_contract():
missing = evaluate_pchome_sales_freshness(
None,
now=_taipei("2026-07-15T10:00:00"),
)
invalid = evaluate_pchome_sales_freshness(
"not-a-date",
now=_taipei("2026-07-15T10:00:00"),
)
assert missing["status"] == "missing"
assert invalid["status"] == "invalid"
assert missing["decision_ready"] is False
assert invalid["decision_ready"] is False

View File

@@ -53,6 +53,29 @@ def test_stale_single_source_schedules_retry_and_reports_fallback_gap():
assert payload["sources"][1]["runtime_state"] == "disabled"
def test_arrival_sla_grace_closes_freshness_without_false_alert():
payload = build_source_reconciliation(
sources=[_source("google_drive")],
provider_attempts=[{"source": "google_drive", "status": "no_pending_file"}],
prior_receipts=[],
freshness={
"contract_version": "pchome_sales_freshness_sla_v1",
"latest_sales_date": "2026-07-13",
"data_lag_days": 2,
"sla_lag_days": 0,
"expected_latest_sales_date": "2026-07-13",
"grace_period_active": True,
"decision_ready": True,
},
now=NOW,
)
assert payload["status"] == "closed"
assert payload["safe_next_action"] == "none"
assert payload["notification_policy"]["severity"] == "info"
assert payload["notification_policy"]["send"] is False
def test_same_state_within_cooldown_suppresses_duplicate_notification():
first = build_source_reconciliation(
sources=[_source("google_drive")],