feat(CORE-001D2A): tighten quote provider cache normalization

This commit is contained in:
Bu5hm4nn
2026-03-25 17:10:11 +01:00
parent dbcc6a1ea0
commit 442a0cd702
5 changed files with 403 additions and 8 deletions

View File

@@ -53,10 +53,14 @@ class DataService:
cache_key = f"quote:{normalized_symbol}"
cached = await self.cache.get_json(cache_key)
if cached and isinstance(cached, dict):
normalized_cached = self._normalize_quote_payload(cached, normalized_symbol)
if normalized_cached != cached:
await self.cache.set_json(cache_key, normalized_cached)
return normalized_cached
try:
normalized_cached = self._normalize_quote_payload(cached, normalized_symbol)
except ValueError:
normalized_cached = None
if normalized_cached is not None:
if normalized_cached != cached:
await self.cache.set_json(cache_key, normalized_cached)
return normalized_cached
quote = self._normalize_quote_payload(await self._fetch_quote(normalized_symbol), normalized_symbol)
await self.cache.set_json(cache_key, quote)
@@ -365,11 +369,43 @@ class DataService:
@staticmethod
def _normalize_quote_payload(payload: dict[str, Any], symbol: str) -> dict[str, Any]:
normalized = dict(payload)
"""Normalize provider/cache quote payload to explicit contract.
This is the named boundary adapter between external float-heavy provider
payloads and internal quote handling. It ensures:
- symbol is always present and uppercased
- GLD quotes have explicit quote_unit='share' metadata
- Non-GLD symbols pass through without auto-assigned units
Fail-closed: missing/invalid fields are preserved for upstream handling
rather than silently defaulted. Type conversion is not performed here.
Args:
payload: Raw quote dict from cache or provider (float-heavy)
symbol: Expected symbol (used as fallback if missing from payload)
Returns:
Normalized quote dict with explicit symbol and GLD quote_unit
"""
normalized: dict[str, Any] = dict(payload)
normalized_symbol = symbol.upper()
normalized["symbol"] = str(normalized.get("symbol", normalized_symbol)).upper()
# Ensure symbol is always present and normalized.
# Missing symbol is repaired from the requested key; explicit mismatches are rejected.
raw_symbol = normalized.get("symbol", normalized_symbol)
normalized_payload_symbol = str(raw_symbol).upper() if raw_symbol is not None else normalized_symbol
if raw_symbol is not None and normalized_payload_symbol != normalized_symbol:
raise ValueError(
f"Quote payload symbol mismatch: expected {normalized_symbol}, got {normalized_payload_symbol}"
)
normalized["symbol"] = normalized_payload_symbol
# Add explicit quote_unit for GLD (CORE-002A/B compatibility)
# Repair missing or empty unit metadata, but preserve explicit non-empty values
if normalized["symbol"] == "GLD" and not normalized.get("quote_unit"):
normalized["quote_unit"] = "share"
return normalized
@staticmethod