"""R13 适配器:互联互通与网间结算。 源明细:SrcCdr / SrcInterconnectSettlement 映射到:Entity(MSISDN, SETTLEMENT) + MetricEvent """ from __future__ import annotations import datetime as dt import uuid from sqlalchemy.orm import Session from app.datahub.graph_repo import upsert_entity from app.datahub.models import MetricEvent from app.datahub.ontology import EntityType from app.datahub.staging import SrcCdr, SrcInterconnectSettlement from app.ingest.base import BaseAdapter, IngestResult from app.ingest.registry import register_adapter @register_adapter class CdrAdapter(BaseAdapter): """SrcCdr → Entity(MSISDN) + MetricEvent(话务时序)。""" source_system = "SIGNAL" staging_table = "src_cdr" def ingest( self, session: Session, data_version_id: uuid.UUID | None = None, batch_size: int = 1000, ) -> IngestResult: result = IngestResult() query = session.query(SrcCdr) if data_version_id: query = query.filter(SrcCdr.data_version_id == data_version_id) rows = query.limit(batch_size).all() for row in rows: try: # 确保主被叫号码实体存在 upsert_entity( session, entity_type=EntityType.MSISDN, business_key=row.caller, display_name=row.caller, data_version_id=data_version_id, ) upsert_entity( session, entity_type=EntityType.MSISDN, business_key=row.callee, display_name=row.callee, data_version_id=data_version_id, ) # 话务事件 event = MetricEvent( event_time=row.start_time, subject_type="msisdn", subject_key=row.caller, metric_name="cdr_duration", metric_value=float(row.duration_sec), attributes={ "callee": row.callee, "call_type": row.call_type, "peer_operator": row.peer_operator, "route_info": row.route_info, }, data_version_id=data_version_id, ) session.add(event) result.metric_events.append(event) result.row_count += 1 except Exception: result.error_count += 1 return result @register_adapter class InterconnectSettlementAdapter(BaseAdapter): """SrcInterconnectSettlement → Entity(SETTLEMENT) + MetricEvent。""" source_system = "FIN" staging_table = "src_interconnect_settlement" def ingest( self, session: Session, data_version_id: uuid.UUID | None = None, batch_size: int = 1000, ) -> IngestResult: result = IngestResult() query = session.query(SrcInterconnectSettlement) if data_version_id: query = query.filter(SrcInterconnectSettlement.data_version_id == data_version_id) rows = query.limit(batch_size).all() for row in rows: try: # 结算单实体 settle_entity = upsert_entity( session, entity_type=EntityType.SETTLEMENT, business_key=row.settlement_no, display_name=f"网间结算-{row.settlement_no}", attributes={ "peer_operator": row.peer_operator, "settle_type": row.settle_type, }, data_version_id=data_version_id, ) result.entities.append(settle_entity) # 结算时序事件 try: event_time = dt.datetime.strptime( row.settle_period, "%Y-%m" ).replace(tzinfo=dt.timezone.utc) except ValueError: event_time = dt.datetime.now(dt.timezone.utc) event = MetricEvent( event_time=event_time, subject_type="settlement", subject_key=row.settlement_no, metric_name="interconnect_settle", metric_value=row.settle_amount, attributes={ "peer_operator": row.peer_operator, "settle_type": row.settle_type, "volume": row.volume, "unit_price": row.unit_price, "sms_delivery_rate": row.sms_delivery_rate, }, data_version_id=data_version_id, ) session.add(event) result.metric_events.append(event) result.row_count += 1 except Exception: result.error_count += 1 return result