Improve ops monitoring and chart loading

This commit is contained in:
2569718930@qq.com
2026-05-31 03:21:58 +08:00
parent ca72072da0
commit 2a6d8748f4
38 changed files with 3110 additions and 637 deletions
+236 -1
View File
@@ -1,5 +1,5 @@
from datetime import datetime
from datetime import datetime, timedelta, timezone
from fastapi.testclient import TestClient
from starlette.requests import Request
@@ -71,6 +71,7 @@ def test_standard_growth_funnel_events_are_trackable():
"trial_created",
"payment_start",
"payment_success",
"degraded_auth_profile",
}.issubset(city_runtime.TRACKABLE_ANALYTICS_EVENTS)
@@ -85,6 +86,7 @@ def test_standard_growth_funnel_summary_order(monkeypatch):
{"id": 5, "event_type": "trial_created", "user_id": "u1", "client_id": "c1", "session_id": "s1"},
{"id": 6, "event_type": "payment_start", "user_id": "u1", "client_id": "c1", "session_id": "s1"},
{"id": 7, "event_type": "payment_success", "user_id": "u1", "client_id": "c1", "session_id": "s1"},
{"id": 8, "event_type": "degraded_auth_profile", "user_id": "", "client_id": "auth:u1", "session_id": "", "payload": {"reason": "backend_500"}},
]
monkeypatch.setattr(
DBManager,
@@ -103,8 +105,241 @@ def test_standard_growth_funnel_summary_order(monkeypatch):
"payment_success",
]
assert summary["rates"]["payment_success_rate"] == 1.0
assert summary["diagnostics"]["degraded_auth_profile"]["total"] == 1
assert summary["diagnostics"]["degraded_auth_profile"]["by_reason"][0] == {
"name": "backend_500",
"count": 1,
}
def test_growth_funnel_summarizes_traffic_sources(monkeypatch):
from src.database.db_manager import DBManager
rows = [
{
"id": 1,
"event_type": "landing_view",
"user_id": "",
"client_id": "c1",
"session_id": "s1",
"payload": {
"referrer": "https://x.com/polyweather",
"cf_country": "us",
"device_type": "mobile",
"path": "/",
},
},
{
"id": 2,
"event_type": "landing_view",
"user_id": "",
"client_id": "c2",
"session_id": "s2",
"payload": {
"referrer": "",
"cf_country": "hk",
"device_type": "desktop",
"path": "/?ref=abc",
},
},
]
monkeypatch.setattr(
DBManager,
"list_app_analytics_events",
lambda self, limit=20000, since_iso=None: rows,
)
summary = DBManager().get_app_analytics_funnel_summary(days=7)
assert summary["traffic"]["referrers"][0] == {"name": "x.com", "count": 1}
assert {"name": "(direct)", "count": 1} in summary["traffic"]["referrers"]
assert {"name": "US", "count": 1} in summary["traffic"]["countries"]
assert {"name": "mobile", "count": 1} in summary["traffic"]["devices"]
def test_ops_source_health_flags_expected_official_sources(monkeypatch):
class FakeCache:
def get_city_cache(self, kind, city):
if kind != "full":
return None
payloads = {
"ankara": {
"airport_primary": {
"source_code": "mgm",
"source_label": "MGM",
"obs_age_min": 80,
"temp": 17,
}
},
"amsterdam": {
"airport_primary": {
"source_code": "knmi",
"source_label": "KNMI",
"obs_age_min": 5,
"temp": 19,
}
},
"tel aviv": {
"airport_current": {
"source_code": "metar",
"source_label": "METAR",
"obs_age_min": 5,
"temp": 25,
}
},
}
payload = payloads.get(city)
if not payload:
return None
return {
"payload": payload,
"updated_at": "2026-05-31T10:00:00Z",
"updated_at_ts": 1,
}
monkeypatch.setattr(ops_api.legacy_routes, "_require_ops_admin", lambda request: {"email": "ops@example.com"})
monkeypatch.setattr(ops_api.legacy_routes, "_CACHE_DB", FakeCache())
monkeypatch.setattr(
ops_api.legacy_routes,
"CITIES",
{"ankara": {}, "amsterdam": {}, "tel aviv": {}},
raising=False,
)
payload = ops_api.get_ops_source_health(None, limit=10)
by_city = {row["city"]: row for row in payload["cities"]}
assert by_city["ankara"]["worst_status"] == "stale"
assert any(source["source_code"] == "mgm" for source in by_city["ankara"]["sources"])
assert by_city["amsterdam"]["worst_status"] == "fresh"
assert any(
source["source_code"] == "ims" and source["status"] == "missing"
for source in by_city["tel aviv"]["sources"]
)
def test_ops_billing_risk_surfaces_trial_payment_referral_and_points(monkeypatch):
from src.database.db_manager import DBManager
now = datetime.now(timezone.utc)
old = (now - timedelta(minutes=20)).isoformat()
recent = now.isoformat()
def fake_supabase_rows(table, params, *, timeout=10):
if table == "payment_intents":
return [
{
"id": "intent-stuck",
"user_id": "user-pay",
"plan_code": "pro_monthly",
"status": "submitted",
"updated_at": old,
"created_at": old,
"tx_hash": "0x" + "a" * 64,
"metadata": {},
},
{
"id": "intent-points",
"user_id": "user-points",
"plan_code": "pro_monthly",
"status": "confirmed",
"updated_at": recent,
"created_at": recent,
"metadata": {
"points_redemption": {
"applied": True,
"points_to_consume": 1500,
}
},
},
]
if table == "referral_attributions":
return [
{
"id": 1,
"code": "CAP1",
"referrer_user_id": "referrer-cap",
"referred_user_id": "referred-cap",
"status": "capped",
"updated_at": recent,
"created_at": recent,
},
{
"id": 2,
"code": "MISS1",
"referrer_user_id": "referrer-missing",
"referred_user_id": "referred-missing",
"status": "converted",
"converted_payment_intent_id": "intent-converted",
"converted_at": recent,
"updated_at": recent,
"created_at": recent,
},
]
if table == "referral_rewards":
return [
{
"id": 10,
"referral_attribution_id": 99,
"referrer_user_id": "referrer-ok",
"referred_user_id": "referred-ok",
"payment_intent_id": "intent-ok",
"reward_points": 3500,
"reward_days": 0,
"created_at": recent,
}
]
if table == "trial_claims":
return []
return []
monkeypatch.setattr(ops_api.legacy_routes, "_require_ops_admin", lambda request: {"email": "ops@example.com"})
monkeypatch.setattr(ops_api, "_supabase_rest_rows", fake_supabase_rows)
monkeypatch.setattr(
DBManager,
"list_app_analytics_events",
lambda self, limit=20000, since_iso=None: [
{
"id": 11,
"event_type": "signup_success",
"user_id": "user-trial-gap",
"client_id": "",
"session_id": "session-gap",
"created_at": recent,
"payload": {},
}
],
)
monkeypatch.setattr(
DBManager,
"list_payment_audit_events",
lambda self, limit=50, event_type=None: [
{
"id": 21,
"event_type": "payment_intent_failed",
"payload": {"reason": "receiver_mismatch"},
"created_at": recent,
}
],
)
payload = ops_api.get_ops_billing_risk(None, days=30, limit=20)
summary = payload["summary"]
assert summary["stuck_intents"] == 1
assert summary["trial_gaps"] == 1
assert summary["points_discount_issues"] == 1
assert summary["referral_settlement_issues"] == 1
assert summary["monthly_cap_hits"] == 1
assert summary["payment_incidents"] == 1
assert payload["recent_referral_rewards"][0]["reward_points"] == 3500
assert {
"payment_intent",
"signup_trial",
"points_redemption",
"referral",
}.issubset({issue["category"] for issue in payload["issues"]})
def test_cities_endpoint_uses_denver_display_name_for_aurora_market():
response = client.get("/api/cities")