""" ETF models: mapping tables and persisted ETF holdings snapshots """ from sqlalchemy import Column, String, DateTime, Index, UniqueConstraint, Float, ForeignKey, JSON from sqlalchemy.sql import func from sqlalchemy.dialects.postgresql import UUID, TIMESTAMP from datetime import datetime, timezone import uuid from app.core.database import Base class CusipMap(Base): __tablename__ = "cusip_map" # CUSIP is 9-character identifier; some sources may include shorter variants cusip = Column(String(20), primary_key=True, index=True) symbol = Column(String(32), index=True) description = Column(String, nullable=True) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) __table_args__ = ( Index("idx_cusip_symbol", "symbol"), ) class ETFCIKMap(Base): __tablename__ = "etf_cik_map" # Store uppercased ticker symbols and numeric CIK (string) ticker = Column(String(16), primary_key=True, index=True) cik = Column(String(20), index=True) name = Column(String, nullable=True) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) __table_args__ = ( Index("idx_etf_cik", "cik"), UniqueConstraint("ticker", name="uq_etf_ticker"), ) class ETFSeriesMap(Base): __tablename__ = "etf_series_map" ticker = Column(String(16), primary_key=True, index=True) series_id = Column(String(32), index=True) class_id = Column(String(32), nullable=True) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) __table_args__ = ( UniqueConstraint("ticker", name="uq_etf_series_ticker"), Index("idx_etf_series_series_id", "series_id"), ) class ETFHoldingsSnapshot(Base): __tablename__ = "etf_holdings_snapshot" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) ticker = Column(String(16), nullable=False, index=True) snapshot_date = Column(TIMESTAMP(timezone=True), nullable=False) source = Column(String(20), nullable=True) # 'SCRAPER' or 'SEC' cik = Column(String(20), nullable=True) filing_accession = Column(String(64), nullable=True) xml_url = Column(String, nullable=True) metadata_json = Column(JSON, nullable=True) created_at = Column(TIMESTAMP(timezone=True), default=lambda: datetime.now(timezone.utc)) updated_at = Column(TIMESTAMP(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) __table_args__ = ( UniqueConstraint("ticker", "snapshot_date", name="uq_etf_snapshot"), Index("idx_etf_snapshot_ticker_date", "ticker", "snapshot_date"), ) class ETFHolding(Base): __tablename__ = "etf_holdings" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) snapshot_id = Column(UUID(as_uuid=True), ForeignKey("etf_holdings_snapshot.id"), index=True, nullable=False) name = Column(String, nullable=True) cusip = Column(String(20), index=True, nullable=True) ticker = Column(String(32), index=True, nullable=True) shares = Column(Float, nullable=True) value = Column(Float, nullable=True) percentage = Column(Float, nullable=True) created_at = Column(TIMESTAMP(timezone=True), default=lambda: datetime.now(timezone.utc)) __table_args__ = ( Index("idx_etf_holding_snapshot", "snapshot_id"), Index("idx_etf_holding_cusip", "cusip"), Index("idx_etf_holding_ticker", "ticker"), )