feat: 添加线索引擎、NLQ、场景检测、前端界面等核心功能模块

This commit is contained in:
freedakgmail
2026-06-16 08:15:15 +08:00
parent 7b1e2b10a8
commit 48340f6011
62 changed files with 6772 additions and 65 deletions
+236
View File
@@ -0,0 +1,236 @@
"""R8 适配器:政企收入全链路穿透 / 拆单规避。
源明细:SrcContract / SrcContractApproval / SrcPayment
映射到:Entity(CONTRACT, CUSTOMER, ACCOUNT, ADDRESS, LEGAL_PERSON) + 关系 + 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 SrcContract, SrcContractApproval, SrcPayment
from app.ingest.base import BaseAdapter, IngestResult
from app.ingest.registry import register_adapter
@register_adapter
class ContractAdapter(BaseAdapter):
"""SrcContract → Entity(CONTRACT, CUSTOMER, ACCOUNT, ADDRESS, LEGAL_PERSON) + 关系。"""
source_system = "CONTRACT"
staging_table = "src_contract"
def ingest(
self,
session: Session,
data_version_id: uuid.UUID | None = None,
batch_size: int = 1000,
) -> IngestResult:
result = IngestResult()
query = session.query(SrcContract)
if data_version_id:
query = query.filter(SrcContract.data_version_id == data_version_id)
rows = query.limit(batch_size).all()
for row in rows:
try:
# 合同实体
contract_entity = upsert_entity(
session,
entity_type=EntityType.CONTRACT,
business_key=row.contract_no,
display_name=f"合同-{row.contract_no}",
attributes={
"amount": row.amount,
"sign_date": str(row.sign_date) if row.sign_date else None,
"approval_threshold": row.approval_threshold,
"approval_level": row.approval_level,
},
data_version_id=data_version_id,
)
result.entities.append(contract_entity)
# 客户实体 + 签约关系
cust_entity = upsert_entity(
session,
entity_type=EntityType.CUSTOMER,
business_key=row.customer_key,
display_name=row.customer_name,
data_version_id=data_version_id,
)
result.entities.append(cust_entity)
rel = add_relationship(
session, RelationshipType.SIGNED, cust_entity, contract_entity,
data_version_id=data_version_id,
)
result.relationships.append(rel)
# 回款账户 → Entity(ACCOUNT) + 关系 PAID_BY
if row.pay_account:
acct_entity = upsert_entity(
session,
entity_type=EntityType.ACCOUNT,
business_key=row.pay_account,
data_version_id=data_version_id,
)
result.entities.append(acct_entity)
rel = add_relationship(
session, RelationshipType.PAID_BY, contract_entity, acct_entity,
data_version_id=data_version_id,
)
result.relationships.append(rel)
# 注册地址
if row.register_address:
addr_entity = upsert_entity(
session,
entity_type=EntityType.ADDRESS,
business_key=row.register_address,
display_name=row.register_address,
data_version_id=data_version_id,
)
rel = add_relationship(
session, RelationshipType.REGISTERED_AT, cust_entity, addr_entity,
data_version_id=data_version_id,
)
result.relationships.append(rel)
# 法人
if row.legal_person:
lp_entity = upsert_entity(
session,
entity_type=EntityType.LEGAL_PERSON,
business_key=row.legal_person,
display_name=row.legal_person,
data_version_id=data_version_id,
)
rel = add_relationship(
session, RelationshipType.LEGAL_REP_OF, lp_entity, cust_entity,
data_version_id=data_version_id,
)
result.relationships.append(rel)
result.row_count += 1
except Exception:
result.error_count += 1
return result
@register_adapter
class ContractApprovalAdapter(BaseAdapter):
"""SrcContractApproval → MetricEvent(审批时序事件)。"""
source_system = "CONTRACT"
staging_table = "src_contract_approval"
def ingest(
self,
session: Session,
data_version_id: uuid.UUID | None = None,
batch_size: int = 1000,
) -> IngestResult:
result = IngestResult()
query = session.query(SrcContractApproval)
if data_version_id:
query = query.filter(SrcContractApproval.data_version_id == data_version_id)
rows = query.limit(batch_size).all()
for row in rows:
try:
if row.approval_time:
event = MetricEvent(
event_time=row.approval_time,
subject_type="contract",
subject_key=row.contract_no,
metric_name="approval_step",
metric_value=float(row.approval_step),
attributes={
"approver": row.approver,
"result": row.approval_result,
"remark": row.remark,
},
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 PaymentAdapter(BaseAdapter):
"""SrcPayment → MetricEvent(回款时序事件) + 关系补强。"""
source_system = "FIN"
staging_table = "src_payment"
def ingest(
self,
session: Session,
data_version_id: uuid.UUID | None = None,
batch_size: int = 1000,
) -> IngestResult:
result = IngestResult()
query = session.query(SrcPayment)
if data_version_id:
query = query.filter(SrcPayment.data_version_id == data_version_id)
rows = query.limit(batch_size).all()
for row in rows:
try:
if row.pay_date:
import datetime as dt
event_time = dt.datetime.combine(
row.pay_date, dt.time.min, tzinfo=dt.timezone.utc
)
event = MetricEvent(
event_time=event_time,
subject_type="contract",
subject_key=row.contract_no,
metric_name="payment",
metric_value=row.pay_amount,
attributes={
"pay_account": row.pay_account,
"pay_type": row.pay_type,
"overdue_flag": row.overdue_flag,
},
data_version_id=data_version_id,
)
session.add(event)
result.metric_events.append(event)
# 强化合同→账户关系
if row.pay_account:
contract_entity = upsert_entity(
session,
entity_type=EntityType.CONTRACT,
business_key=row.contract_no,
data_version_id=data_version_id,
)
acct_entity = upsert_entity(
session,
entity_type=EntityType.ACCOUNT,
business_key=row.pay_account,
data_version_id=data_version_id,
)
rel = add_relationship(
session, RelationshipType.PAID_BY, contract_entity, acct_entity,
data_version_id=data_version_id,
)
result.relationships.append(rel)
result.row_count += 1
except Exception:
result.error_count += 1
return result