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
+200
View File
@@ -0,0 +1,200 @@
"""R12 适配器:网络建设与工程采购。
源明细:SrcBidding / SrcProjectSignoff
映射到:Entity(SUPPLIER, WORK_ORDER) + 关系(BIDS_FOR, SUPPLIES) + MetricEvent
"""
from __future__ import annotations
import datetime as dt
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 SrcBidding, SrcProjectSignoff
from app.ingest.base import BaseAdapter, IngestResult
from app.ingest.registry import register_adapter
@register_adapter
class BiddingAdapter(BaseAdapter):
"""SrcBidding → Entity(SUPPLIER, WORK_ORDER) + 关系(BIDS_FOR) + MetricEvent。"""
source_system = "ERP"
staging_table = "src_bidding"
def ingest(
self,
session: Session,
data_version_id: uuid.UUID | None = None,
batch_size: int = 1000,
) -> IngestResult:
result = IngestResult()
query = session.query(SrcBidding)
if data_version_id:
query = query.filter(SrcBidding.data_version_id == data_version_id)
rows = query.limit(batch_size).all()
for row in rows:
try:
# 供应商(投标人)实体
supplier_entity = upsert_entity(
session,
entity_type=EntityType.SUPPLIER,
business_key=row.bidder_key,
display_name=row.bidder_name,
attributes={
"legal_person": row.legal_person,
"shareholder_info": row.shareholder_info,
},
data_version_id=data_version_id,
)
result.entities.append(supplier_entity)
# 工单/项目实体
wo_entity = upsert_entity(
session,
entity_type=EntityType.WORK_ORDER,
business_key=row.project_no,
display_name=row.project_name,
data_version_id=data_version_id,
)
result.entities.append(wo_entity)
# 投标关系
rel = add_relationship(
session, RelationshipType.BIDS_FOR, supplier_entity, wo_entity,
attributes={
"bid_amount": row.bid_amount,
"win_flag": row.win_flag,
"technical_score": row.technical_score,
},
data_version_id=data_version_id,
)
result.relationships.append(rel)
# 中标 → 补充 SUPPLIES 关系
if row.win_flag and row.win_flag.upper() == "Y":
rel2 = add_relationship(
session, RelationshipType.SUPPLIES, supplier_entity, wo_entity,
data_version_id=data_version_id,
)
result.relationships.append(rel2)
# 法人实体
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,
)
add_relationship(
session, RelationshipType.LEGAL_REP_OF, lp_entity, supplier_entity,
data_version_id=data_version_id,
)
# 投标事件
if row.bid_time:
event = MetricEvent(
event_time=row.bid_time,
subject_type="work_order",
subject_key=row.project_no,
metric_name="bid_submitted",
metric_value=row.bid_amount or 0.0,
attributes={
"bidder_key": row.bidder_key,
"bidder_name": row.bidder_name,
"win_flag": row.win_flag,
"technical_score": row.technical_score,
},
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 ProjectSignoffAdapter(BaseAdapter):
"""SrcProjectSignoff → MetricEvent(工程签证/巡检时序)。"""
source_system = "WO"
staging_table = "src_project_signoff"
def ingest(
self,
session: Session,
data_version_id: uuid.UUID | None = None,
batch_size: int = 1000,
) -> IngestResult:
result = IngestResult()
query = session.query(SrcProjectSignoff)
if data_version_id:
query = query.filter(SrcProjectSignoff.data_version_id == data_version_id)
rows = query.limit(batch_size).all()
for row in rows:
try:
# 确保工单实体存在
upsert_entity(
session,
entity_type=EntityType.WORK_ORDER,
business_key=row.project_no,
data_version_id=data_version_id,
)
# 签证事件
if row.signoff_date:
event_time = dt.datetime.combine(
row.signoff_date, dt.time.min, tzinfo=dt.timezone.utc
)
event = MetricEvent(
event_time=event_time,
subject_type="work_order",
subject_key=row.project_no,
metric_name="signoff_quantity",
metric_value=row.signoff_quantity or 0.0,
attributes={
"work_order_no": row.work_order_no,
"unit": row.unit,
"resource_consumed": row.resource_consumed,
"contractor_key": row.contractor_key,
},
data_version_id=data_version_id,
)
session.add(event)
result.metric_events.append(event)
# 巡检 GPS 事件
if row.inspection_time and row.inspection_lat:
event2 = MetricEvent(
event_time=row.inspection_time,
subject_type="work_order",
subject_key=row.project_no,
metric_name="inspection",
metric_value=1.0,
attributes={
"lat": row.inspection_lat,
"lng": row.inspection_lng,
"work_order_no": row.work_order_no,
},
data_version_id=data_version_id,
)
session.add(event2)
result.metric_events.append(event2)
result.row_count += 1
except Exception:
result.error_count += 1
return result