feat(CORE-001D2A): tighten quote provider cache normalization
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user