238 lines
8.8 KiB
Python
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
|