"""Issuer Sync: populate issuer_master / symbol_master from Oracle company info.""" from __future__ import annotations import argparse import asyncio import datetime as dt import uuid from sqlalchemy.dialects.postgresql import insert from libs.common.config import get_settings from libs.common.ids import issuer_id_from_cik, new_job_run_id, symbol_id_from_ticker from libs.common.logging import bind_job_run_id, configure_logging, get_logger from libs.db.models import IssuerMaster, JobRun, SymbolMaster from libs.db.session import get_session from libs.oracle_client import FinancialService, make_oracle_client logger = get_logger(__name__) async def run_issuer_sync(run_id: str) -> dict[str, int]: settings = get_settings() symbols = settings.get_symbols() stats = {"seen": 0, "written": 0, "errors": 0} async with make_oracle_client() as client: svc = FinancialService(client) async with get_session() as session: job = JobRun( job_run_id=uuid.UUID(run_id), job_name="issuer_sync", source_name="oracle", run_date=dt.date.today(), status="running", ) session.add(job) await session.flush() for ticker in symbols: stats["seen"] += 1 try: info = await svc.get_company_info(ticker) cik = info.cik issuer_id_str = issuer_id_from_cik(cik) if cik else f"ISSUER::{ticker}" symbol_id_str = symbol_id_from_ticker(ticker, info.exchange or "US") # Upsert issuer issuer_stmt = ( insert(IssuerMaster) .values( issuer_id=issuer_id_str, cik=cik, ticker=ticker, issuer_name=info.name or ticker, exchange=info.exchange, country_code=info.country, is_active=True, created_at_utc=dt.datetime.now(tz=dt.UTC), updated_at_utc=dt.datetime.now(tz=dt.UTC), ) .on_conflict_do_update( index_elements=["issuer_id"], set_={ "ticker": ticker, "issuer_name": info.name or ticker, "exchange": info.exchange, "updated_at_utc": dt.datetime.now(tz=dt.UTC), }, ) ) await session.execute(issuer_stmt) # Upsert symbol symbol_stmt = ( insert(SymbolMaster) .values( symbol_id=symbol_id_str, issuer_id=issuer_id_str, ticker=ticker, venue=info.exchange or "US", asset_type="common_stock", currency="USD", is_primary=True, created_at_utc=dt.datetime.now(tz=dt.UTC), updated_at_utc=dt.datetime.now(tz=dt.UTC), ) .on_conflict_do_update( index_elements=["symbol_id"], set_={ "ticker": ticker, "updated_at_utc": dt.datetime.now(tz=dt.UTC), }, ) ) await session.execute(symbol_stmt) stats["written"] += 1 logger.info("issuer_synced", ticker=ticker, issuer_id=issuer_id_str) except Exception as exc: logger.error("issuer_sync_error", ticker=ticker, error=str(exc)) stats["errors"] += 1 job.status = "succeeded" if stats["errors"] == 0 else "partial" job.finished_at_utc = dt.datetime.now(tz=dt.UTC) job.records_seen = stats["seen"] job.records_written = stats["written"] job.error_count = stats["errors"] logger.info("issuer_sync_done", **stats) return stats def main() -> None: parser = argparse.ArgumentParser(description="Issuer Sync") parser.add_argument("--run-id", default=new_job_run_id()) args = parser.parse_args() settings = get_settings() configure_logging(settings.log_level) bind_job_run_id(args.run_id) asyncio.run(run_issuer_sync(args.run_id)) if __name__ == "__main__": main()