refactor: enforce market data quality contracts
This commit is contained in:
+104
-1
@@ -1,8 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from backend.data import DataPolicyError, DataSourcePolicy, build_data_gateway
|
||||
from backend.data import (
|
||||
DataPolicyError,
|
||||
DataQualityError,
|
||||
DataSourcePolicy,
|
||||
QualityEvidence,
|
||||
build_data_gateway,
|
||||
)
|
||||
from backend.data.quality import market_timezone
|
||||
|
||||
|
||||
class DataGatewayTests(unittest.TestCase):
|
||||
@@ -43,6 +51,101 @@ class DataGatewayTests(unittest.TestCase):
|
||||
self.assertEqual(source.count("TushareClient(self.token)"), 1)
|
||||
self.assertIn("return gateway.tushare()", source)
|
||||
|
||||
def test_quality_gate_accepts_matching_daily_evidence(self) -> None:
|
||||
timezone = market_timezone()
|
||||
now = datetime(2026, 7, 29, 16, 0, tzinfo=timezone)
|
||||
gateway = build_data_gateway({})
|
||||
report = gateway.require_quality(
|
||||
QualityEvidence(
|
||||
dataset_id="market.stock_daily",
|
||||
provider_id="tushare",
|
||||
data_time="2026-07-29",
|
||||
observed_at=now,
|
||||
actual_count=5000,
|
||||
expected_count=5000,
|
||||
adjustment="current-unadjusted",
|
||||
units={
|
||||
"open": "CNY/share", "high": "CNY/share", "low": "CNY/share",
|
||||
"close": "CNY/share", "pct_chg": "percent",
|
||||
"volume_shares": "share", "amount_yuan": "CNY",
|
||||
},
|
||||
),
|
||||
"calculation",
|
||||
now,
|
||||
)
|
||||
self.assertTrue(report.accepted)
|
||||
self.assertEqual(report.coverage_ratio, 1.0)
|
||||
|
||||
def test_quality_gate_rejects_stale_dynamic_auction(self) -> None:
|
||||
timezone = market_timezone()
|
||||
now = datetime(2026, 7, 29, 9, 24, tzinfo=timezone)
|
||||
gateway = build_data_gateway({})
|
||||
with self.assertRaises(DataQualityError):
|
||||
gateway.require_quality(
|
||||
QualityEvidence(
|
||||
dataset_id="market.auction_dynamic",
|
||||
provider_id="ifind",
|
||||
data_time=now - timedelta(seconds=30),
|
||||
observed_at=now - timedelta(seconds=29),
|
||||
units={
|
||||
"price": "CNY/share", "volume_shares": "share",
|
||||
"amount_yuan": "CNY", "pre_close": "CNY/share",
|
||||
"turnover_rate_pct": "percent", "volume_ratio": "ratio",
|
||||
"float_share": "share",
|
||||
},
|
||||
),
|
||||
"calculation",
|
||||
now,
|
||||
)
|
||||
|
||||
def test_quality_gate_rejects_low_coverage_and_wrong_adjustment(self) -> None:
|
||||
timezone = market_timezone()
|
||||
now = datetime(2026, 7, 29, 16, 0, tzinfo=timezone)
|
||||
gateway = build_data_gateway({})
|
||||
report = gateway.quality.evaluate(
|
||||
QualityEvidence(
|
||||
dataset_id="market.stock_daily",
|
||||
provider_id="tushare",
|
||||
data_time="2026-07-29",
|
||||
observed_at=now,
|
||||
actual_count=4000,
|
||||
expected_count=5000,
|
||||
adjustment="forward1",
|
||||
),
|
||||
"calculation",
|
||||
now,
|
||||
)
|
||||
self.assertFalse(report.accepted)
|
||||
self.assertTrue(any("Coverage" in issue for issue in report.issues))
|
||||
self.assertTrue(any("Adjustment" in issue for issue in report.issues))
|
||||
|
||||
def test_quality_gate_enforces_financial_point_in_time(self) -> None:
|
||||
timezone = market_timezone()
|
||||
now = datetime(2026, 7, 29, 16, 0, tzinfo=timezone)
|
||||
gateway = build_data_gateway({})
|
||||
report = gateway.quality.evaluate(
|
||||
QualityEvidence(
|
||||
dataset_id="market.fundamentals",
|
||||
provider_id="tushare",
|
||||
data_time="2026-06-30",
|
||||
observed_at=now,
|
||||
available_at="2026-08-15",
|
||||
),
|
||||
"calculation",
|
||||
now,
|
||||
)
|
||||
self.assertFalse(report.accepted)
|
||||
self.assertTrue(any("not available" in issue for issue in report.issues))
|
||||
|
||||
def test_provider_chain_never_silently_promotes_display_fallback(self) -> None:
|
||||
gateway = build_data_gateway({})
|
||||
self.assertEqual(
|
||||
gateway.provider_chain("chart.intraday", "display"),
|
||||
("ifind", "eastmoney"),
|
||||
)
|
||||
with self.assertRaises(RuntimeError):
|
||||
gateway.provider_chain("chart.intraday", "calculation")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user