"""数据中台统一穿透查询 API(P1.2.5)。 作为各引擎与审计场景访问知识图谱的共同入口,对上层屏蔽底层是关系表还是图库。 对应需求 R2。 """ from __future__ import annotations import uuid from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app.api.schemas import ( EntityOut, PenetrateRequest, PenetrateResponse, RelatedEntityOut, ) from app.datahub.graph_repo import find_related_entities from app.datahub.models import Entity from app.db import get_session router = APIRouter(prefix="/datahub", tags=["datahub"]) @router.get("/entities/{entity_id}", response_model=EntityOut) def get_entity(entity_id: uuid.UUID, session: Session = Depends(get_session)) -> Entity: entity = session.get(Entity, entity_id) if entity is None: raise HTTPException(status_code=404, detail="实体不存在") return entity @router.post("/penetrate", response_model=PenetrateResponse) def penetrate( req: PenetrateRequest, session: Session = Depends(get_session) ) -> PenetrateResponse: """多跳穿透:返回与起点实体连通的关联实体(用于实控人/关联方/马甲识别)。""" start = session.get(Entity, req.start_entity_id) if start is None: raise HTTPException(status_code=404, detail="起点实体不存在") related_raw = find_related_entities(session, req.start_entity_id, max_depth=req.max_depth) # 批量取出关联实体详情,组装可解释结果 id_to_depth = {rid: depth for rid, depth in related_raw} entities = ( session.query(Entity).filter(Entity.id.in_(list(id_to_depth.keys()))).all() if id_to_depth else [] ) related = [ RelatedEntityOut(entity=EntityOut.model_validate(e), depth=id_to_depth[e.id]) for e in entities ] related.sort(key=lambda r: r.depth) return PenetrateResponse( start_entity_id=req.start_entity_id, max_depth=req.max_depth, related_count=len(related), related=related, )