rebuild(stage-8): deliver market insight workspaces
This commit is contained in:
@@ -79,6 +79,43 @@ class MarketRepository:
|
||||
],
|
||||
)
|
||||
|
||||
def replace_themes(
|
||||
self,
|
||||
connection: sqlite3.Connection,
|
||||
rows: list[dict[str, Any]],
|
||||
source: str,
|
||||
observed_at: str,
|
||||
) -> None:
|
||||
connection.executemany(
|
||||
"""
|
||||
INSERT INTO market_entities (
|
||||
entity_type, identifier, code, name, search_key,
|
||||
sector, active, source, observed_at
|
||||
) VALUES ('theme', ?, ?, ?, ?, NULL, 1, ?, ?)
|
||||
ON CONFLICT(entity_type, identifier) DO UPDATE SET
|
||||
code = excluded.code,
|
||||
name = excluded.name,
|
||||
search_key = excluded.search_key,
|
||||
active = 1,
|
||||
source = excluded.source,
|
||||
observed_at = excluded.observed_at
|
||||
""",
|
||||
[
|
||||
(
|
||||
str(row.get("code") or "").upper(),
|
||||
str(row.get("code") or "").split(".")[0],
|
||||
str(row.get("name") or "").strip(),
|
||||
_normalize(
|
||||
f"{row.get('code') or ''} {row.get('name') or ''}"
|
||||
),
|
||||
source,
|
||||
observed_at,
|
||||
)
|
||||
for row in rows
|
||||
if row.get("code") and row.get("name")
|
||||
],
|
||||
)
|
||||
|
||||
def search(
|
||||
self, connection: sqlite3.Connection, query: str, limit: int = 32
|
||||
) -> tuple[MarketEntity, ...]:
|
||||
@@ -135,6 +172,16 @@ class MarketRepository:
|
||||
).fetchone()
|
||||
return int(row["count"] if row else 0)
|
||||
|
||||
def stock_directory(self, connection: sqlite3.Connection) -> tuple[sqlite3.Row, ...]:
|
||||
return tuple(
|
||||
connection.execute(
|
||||
"""
|
||||
SELECT identifier, code, name, sector FROM market_entities
|
||||
WHERE entity_type = 'stock' AND active = 1
|
||||
"""
|
||||
).fetchall()
|
||||
)
|
||||
|
||||
def save_summary(
|
||||
self,
|
||||
connection: sqlite3.Connection,
|
||||
@@ -234,6 +281,128 @@ class MarketRepository:
|
||||
),
|
||||
)
|
||||
|
||||
def insight_snapshot(
|
||||
self,
|
||||
connection: sqlite3.Connection,
|
||||
kind: str,
|
||||
trade_date: str,
|
||||
entity_key: str = "",
|
||||
) -> sqlite3.Row | None:
|
||||
return connection.execute(
|
||||
"""
|
||||
SELECT * FROM market_insight_snapshots
|
||||
WHERE kind = ? AND trade_date = ? AND entity_key = ?
|
||||
""",
|
||||
(kind, trade_date, entity_key),
|
||||
).fetchone()
|
||||
|
||||
def latest_insight_snapshot(
|
||||
self,
|
||||
connection: sqlite3.Connection,
|
||||
kind: str,
|
||||
through: str,
|
||||
entity_key: str = "",
|
||||
) -> sqlite3.Row | None:
|
||||
return connection.execute(
|
||||
"""
|
||||
SELECT * FROM market_insight_snapshots
|
||||
WHERE kind = ? AND trade_date <= ? AND entity_key = ?
|
||||
ORDER BY trade_date DESC LIMIT 1
|
||||
""",
|
||||
(kind, through, entity_key),
|
||||
).fetchone()
|
||||
|
||||
def insight_snapshots(
|
||||
self, connection: sqlite3.Connection, kind: str, through: str, limit: int
|
||||
) -> tuple[sqlite3.Row, ...]:
|
||||
rows = connection.execute(
|
||||
"""
|
||||
SELECT * FROM market_insight_snapshots
|
||||
WHERE kind = ? AND trade_date <= ? AND entity_key = ''
|
||||
ORDER BY trade_date DESC LIMIT ?
|
||||
""",
|
||||
(kind, through, limit),
|
||||
).fetchall()
|
||||
return tuple(reversed(rows))
|
||||
|
||||
def save_insight_snapshot(
|
||||
self,
|
||||
connection: sqlite3.Connection,
|
||||
*,
|
||||
kind: str,
|
||||
trade_date: str,
|
||||
entity_key: str,
|
||||
observed_at: str,
|
||||
state: str,
|
||||
source: str,
|
||||
coverage: float,
|
||||
payload: dict[str, Any],
|
||||
) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO market_insight_snapshots (
|
||||
kind, trade_date, entity_key, observed_at, state, source, coverage, payload_json
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(kind, trade_date, entity_key) DO UPDATE SET
|
||||
observed_at = excluded.observed_at,
|
||||
state = excluded.state,
|
||||
source = excluded.source,
|
||||
coverage = excluded.coverage,
|
||||
payload_json = excluded.payload_json
|
||||
""",
|
||||
(
|
||||
kind,
|
||||
trade_date,
|
||||
entity_key,
|
||||
observed_at,
|
||||
state,
|
||||
source,
|
||||
coverage,
|
||||
json.dumps(payload, ensure_ascii=False, separators=(",", ":")),
|
||||
),
|
||||
)
|
||||
|
||||
def seat_aliases(self, connection: sqlite3.Connection) -> dict[str, str]:
|
||||
return {
|
||||
str(row["seat_name"]): str(row["alias_name"])
|
||||
for row in connection.execute(
|
||||
"SELECT seat_name, alias_name FROM seat_aliases ORDER BY seat_name"
|
||||
).fetchall()
|
||||
}
|
||||
|
||||
def save_seat_alias(
|
||||
self,
|
||||
connection: sqlite3.Connection,
|
||||
seat_name: str,
|
||||
alias_name: str,
|
||||
updated_at: str,
|
||||
updated_by: int,
|
||||
) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO seat_aliases (seat_name, alias_name, updated_at, updated_by)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(seat_name) DO UPDATE SET
|
||||
alias_name = excluded.alias_name,
|
||||
updated_at = excluded.updated_at,
|
||||
updated_by = excluded.updated_by
|
||||
""",
|
||||
(seat_name, alias_name, updated_at, updated_by),
|
||||
)
|
||||
|
||||
def watchlist(
|
||||
self, connection: sqlite3.Connection, user_id: int
|
||||
) -> tuple[sqlite3.Row, ...]:
|
||||
return tuple(
|
||||
connection.execute(
|
||||
"""
|
||||
SELECT identifier, name, sector FROM watchlist_entries
|
||||
WHERE user_id = ? ORDER BY created_at, identifier
|
||||
""",
|
||||
(user_id,),
|
||||
).fetchall()
|
||||
)
|
||||
|
||||
def save_chart(
|
||||
self,
|
||||
connection: sqlite3.Connection,
|
||||
|
||||
Reference in New Issue
Block a user