feat(HEL-382): 搭建 datahub 底座和盘后正式数据链路
新增独立 xiaobai-datahub 服务(SQLite WAL、Tushare 盘后发布、/v1 契约和管理后台),不改现站页面与数据链路。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
c2ebc0ab91
commit
3498dd7a4b
@@ -0,0 +1,84 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from datahub.db import HubDB
|
||||
from datahub.timeutil import isoformat, now_shanghai
|
||||
|
||||
|
||||
class LastKnownGood:
|
||||
def __init__(self, db: HubDB) -> None:
|
||||
self.db = db
|
||||
|
||||
def store(self, cache_key: str, payload: Any, source: str) -> None:
|
||||
self.db.execute(
|
||||
"""
|
||||
INSERT INTO last_known_good(cache_key, payload, source, stored_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(cache_key) DO UPDATE SET
|
||||
payload=excluded.payload, source=excluded.source, stored_at=excluded.stored_at
|
||||
""",
|
||||
(cache_key, json.dumps(payload, ensure_ascii=False), source, isoformat()),
|
||||
)
|
||||
|
||||
def load(self, cache_key: str) -> dict[str, Any] | None:
|
||||
row = self.db.fetchone("SELECT * FROM last_known_good WHERE cache_key = ?", (cache_key,))
|
||||
if not row:
|
||||
return None
|
||||
return {
|
||||
"payload": json.loads(row["payload"]),
|
||||
"source": row["source"],
|
||||
"stored_at": row["stored_at"],
|
||||
}
|
||||
|
||||
def put_rt(self, cache_key: str, payload: Any, source: str, ttl_seconds: int) -> None:
|
||||
now = now_shanghai()
|
||||
expires = isoformat(now.replace(microsecond=0))
|
||||
# expires_at stored as iso; compute by adding ttl via timestamp
|
||||
from datetime import timedelta
|
||||
|
||||
self.db.execute(
|
||||
"""
|
||||
INSERT INTO rt_cache(cache_key, payload, source, stored_at, expires_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(cache_key) DO UPDATE SET
|
||||
payload=excluded.payload, source=excluded.source,
|
||||
stored_at=excluded.stored_at, expires_at=excluded.expires_at
|
||||
""",
|
||||
(
|
||||
cache_key,
|
||||
json.dumps(payload, ensure_ascii=False),
|
||||
source,
|
||||
isoformat(now),
|
||||
isoformat(now + timedelta(seconds=ttl_seconds)),
|
||||
),
|
||||
)
|
||||
self.store(cache_key, payload, source)
|
||||
|
||||
def get_rt(self, cache_key: str, max_stale_seconds: int | None = None) -> dict[str, Any] | None:
|
||||
row = self.db.fetchone("SELECT * FROM rt_cache WHERE cache_key = ?", (cache_key,))
|
||||
if not row:
|
||||
lkg = self.load(cache_key)
|
||||
if not lkg:
|
||||
return None
|
||||
return {**lkg, "stale": True}
|
||||
stored_at = row["stored_at"]
|
||||
expired = row["expires_at"] < isoformat()
|
||||
result = {
|
||||
"payload": json.loads(row["payload"]),
|
||||
"source": row["source"],
|
||||
"stored_at": stored_at,
|
||||
"stale": expired,
|
||||
}
|
||||
if expired and max_stale_seconds is not None:
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
stored = datetime.fromisoformat(stored_at)
|
||||
age = (now_shanghai() - stored).total_seconds()
|
||||
except ValueError:
|
||||
age = max_stale_seconds + 1
|
||||
if age > max_stale_seconds:
|
||||
return None
|
||||
return result
|
||||
Reference in New Issue
Block a user