feat: 添加线索引擎、NLQ、场景检测、前端界面等核心功能模块
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user