Files
InternalAuditInterprise/backend/app/ingest/adapters_r15.py
T

238 lines
8.8 KiB
Python

"""R15 适配器:员工内部舞弊与资源滥用。
源明细:SrcEmployeeOperation / SrcInternalMsisdn / SrcPointsTransaction
映射到:Entity(EMPLOYEE, MSISDN) + 关系(OPERATES) + MetricEvent
"""
from __future__ import annotations
import uuid
from sqlalchemy.orm import Session
from app.datahub.graph_repo import add_relationship, upsert_entity
from app.datahub.models import MetricEvent
from app.datahub.ontology import EntityType, RelationshipType
from app.datahub.staging import (
SrcEmployeeOperation,
SrcInternalMsisdn,
SrcPointsTransaction,
)
from app.ingest.base import BaseAdapter, IngestResult
from app.ingest.registry import register_adapter
@register_adapter
class EmployeeOperationAdapter(BaseAdapter):
"""SrcEmployeeOperation → Entity(EMPLOYEE) + 关系(OPERATES) + MetricEvent。"""
source_system = "BSS"
staging_table = "src_employee_operation"
def ingest(
self,
session: Session,
data_version_id: uuid.UUID | None = None,
batch_size: int = 1000,
) -> IngestResult:
result = IngestResult()
query = session.query(SrcEmployeeOperation)
if data_version_id:
query = query.filter(SrcEmployeeOperation.data_version_id == data_version_id)
rows = query.limit(batch_size).all()
for row in rows:
try:
# 员工实体
emp_entity = upsert_entity(
session,
entity_type=EntityType.EMPLOYEE,
business_key=row.employee_key,
display_name=row.employee_name,
attributes={
"position": row.position,
"department": row.department,
"role_permissions": row.role_permissions,
},
data_version_id=data_version_id,
)
result.entities.append(emp_entity)
# 操作目标 → OPERATES 关系(如操作对象是号码或账户)
if row.operation_target:
# 尝试识别操作目标类型(简单启发式:以1开头长度11为号码,否则为账户)
target_key = row.operation_target.strip()
if target_key.isdigit() and len(target_key) == 11:
target_entity = upsert_entity(
session,
entity_type=EntityType.MSISDN,
business_key=target_key,
data_version_id=data_version_id,
)
rel = add_relationship(
session, RelationshipType.OPERATES, emp_entity, target_entity,
attributes={"operation_type": row.operation_type},
data_version_id=data_version_id,
)
result.relationships.append(rel)
# 操作日志事件
if row.operation_time:
event = MetricEvent(
event_time=row.operation_time,
subject_type="employee",
subject_key=row.employee_key,
metric_name="operation_log",
metric_value=1.0,
attributes={
"operation_type": row.operation_type,
"operation_target": row.operation_target,
"position": row.position,
"department": row.department,
},
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 InternalMsisdnAdapter(BaseAdapter):
"""SrcInternalMsisdn → Entity(MSISDN, EMPLOYEE) + 关系(OPERATES) + MetricEvent。"""
source_system = "BSS"
staging_table = "src_internal_msisdn"
def ingest(
self,
session: Session,
data_version_id: uuid.UUID | None = None,
batch_size: int = 1000,
) -> IngestResult:
result = IngestResult()
query = session.query(SrcInternalMsisdn)
if data_version_id:
query = query.filter(SrcInternalMsisdn.data_version_id == data_version_id)
rows = query.limit(batch_size).all()
for row in rows:
try:
# 内部号码实体
msisdn_entity = upsert_entity(
session,
entity_type=EntityType.MSISDN,
business_key=row.msisdn,
display_name=row.msisdn,
attributes={"purpose": row.purpose, "internal": True},
data_version_id=data_version_id,
)
result.entities.append(msisdn_entity)
# 分配员工 → OPERATES 关系
if row.assigned_employee:
emp_entity = upsert_entity(
session,
entity_type=EntityType.EMPLOYEE,
business_key=row.assigned_employee,
data_version_id=data_version_id,
)
rel = add_relationship(
session, RelationshipType.OPERATES, emp_entity, msisdn_entity,
attributes={"purpose": row.purpose},
data_version_id=data_version_id,
)
result.relationships.append(rel)
# 内部号用量事件
import datetime as dt
try:
event_time = dt.datetime.strptime(
row.report_month, "%Y-%m"
).replace(tzinfo=dt.timezone.utc) if row.report_month else dt.datetime.now(dt.timezone.utc)
except ValueError:
event_time = dt.datetime.now(dt.timezone.utc)
event = MetricEvent(
event_time=event_time,
subject_type="msisdn",
subject_key=row.msisdn,
metric_name="internal_usage",
metric_value=row.traffic_mb,
attributes={
"voice_min": row.voice_min,
"revenue_attributed": row.revenue_attributed,
"assigned_employee": row.assigned_employee,
"purpose": row.purpose,
},
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 PointsTransactionAdapter(BaseAdapter):
"""SrcPointsTransaction → MetricEvent(积分发放/兑换时序)。"""
source_system = "BSS"
staging_table = "src_points_transaction"
def ingest(
self,
session: Session,
data_version_id: uuid.UUID | None = None,
batch_size: int = 1000,
) -> IngestResult:
result = IngestResult()
query = session.query(SrcPointsTransaction)
if data_version_id:
query = query.filter(SrcPointsTransaction.data_version_id == data_version_id)
rows = query.limit(batch_size).all()
for row in rows:
try:
# 确保操作人实体存在
upsert_entity(
session,
entity_type=EntityType.EMPLOYEE,
business_key=row.operator_key,
data_version_id=data_version_id,
)
# 积分事件
if row.transaction_time:
event = MetricEvent(
event_time=row.transaction_time,
subject_type="employee",
subject_key=row.operator_key,
metric_name="points_transaction",
metric_value=row.points_amount,
attributes={
"transaction_no": row.transaction_no,
"target_account": row.target_account,
"transaction_type": row.transaction_type,
"cash_value": row.cash_value,
},
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