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
+137
View File
@@ -0,0 +1,137 @@
"""R10 适配器:收入与成本跨期匹配。
源明细:SrcRevenueRecognition / SrcCostAmortization
映射到:MetricEvent(收入确认/成本摊销时序) + Entity(CONTRACT) 关联补强
"""
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 SrcCostAmortization, SrcRevenueRecognition
from app.ingest.base import BaseAdapter, IngestResult
from app.ingest.registry import register_adapter
@register_adapter
class RevenueRecognitionAdapter(BaseAdapter):
"""SrcRevenueRecognition → MetricEvent(收入确认时序)。"""
source_system = "FIN"
staging_table = "src_revenue_recognition"
def ingest(
self,
session: Session,
data_version_id: uuid.UUID | None = None,
batch_size: int = 1000,
) -> IngestResult:
result = IngestResult()
query = session.query(SrcRevenueRecognition)
if data_version_id:
query = query.filter(SrcRevenueRecognition.data_version_id == data_version_id)
rows = query.limit(batch_size).all()
for row in rows:
try:
# 确保合同实体存在
if row.contract_no:
upsert_entity(
session,
entity_type=EntityType.CONTRACT,
business_key=row.contract_no,
data_version_id=data_version_id,
)
if row.recognition_date:
event_time = dt.datetime.combine(
row.recognition_date, dt.time.min, tzinfo=dt.timezone.utc
)
event = MetricEvent(
event_time=event_time,
subject_type="contract",
subject_key=row.contract_no or row.voucher_no,
metric_name="revenue_recognition",
metric_value=row.recognition_amount,
attributes={
"voucher_no": row.voucher_no,
"billing_mode": row.billing_mode,
"period_start": str(row.period_start) if row.period_start else None,
"period_end": str(row.period_end) if row.period_end else None,
"prepaid_flag": row.prepaid_flag,
},
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 CostAmortizationAdapter(BaseAdapter):
"""SrcCostAmortization → MetricEvent(成本摊销时序)。"""
source_system = "FIN"
staging_table = "src_cost_amortization"
def ingest(
self,
session: Session,
data_version_id: uuid.UUID | None = None,
batch_size: int = 1000,
) -> IngestResult:
result = IngestResult()
query = session.query(SrcCostAmortization)
if data_version_id:
query = query.filter(SrcCostAmortization.data_version_id == data_version_id)
rows = query.limit(batch_size).all()
for row in rows:
try:
if row.contract_no:
upsert_entity(
session,
entity_type=EntityType.CONTRACT,
business_key=row.contract_no,
data_version_id=data_version_id,
)
if row.amortization_date:
event_time = dt.datetime.combine(
row.amortization_date, dt.time.min, tzinfo=dt.timezone.utc
)
event = MetricEvent(
event_time=event_time,
subject_type="contract",
subject_key=row.contract_no or row.voucher_no,
metric_name="cost_amortization",
metric_value=row.amortization_amount,
attributes={
"voucher_no": row.voucher_no,
"cost_type": row.cost_type,
"total_periods": row.total_periods,
"current_period": row.current_period,
"delivery_date": str(row.delivery_date) if row.delivery_date else None,
"acceptance_date": str(row.acceptance_date) if row.acceptance_date else None,
},
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