From f9ee644f25360518d0f4bde137266c718f299c33 Mon Sep 17 00:00:00 2001 From: wangwei Date: Fri, 22 May 2026 00:33:43 +0800 Subject: [PATCH] feat(perception): backend - mock event store, perception service, /perception API routes --- backend/app/api/routes/__init__.py | 3 + backend/app/api/routes/perception.py | 67 +++ .../app/application/perception/__init__.py | 1 + .../app/application/perception/services.py | 143 ++++++ .../app/infrastructure/perception/__init__.py | 1 + .../perception/mock_event_store.py | 421 ++++++++++++++++++ backend/app/shared/bootstrap.py | 11 + 7 files changed, 647 insertions(+) create mode 100644 backend/app/api/routes/perception.py create mode 100644 backend/app/application/perception/__init__.py create mode 100644 backend/app/application/perception/services.py create mode 100644 backend/app/infrastructure/perception/__init__.py create mode 100644 backend/app/infrastructure/perception/mock_event_store.py diff --git a/backend/app/api/routes/__init__.py b/backend/app/api/routes/__init__.py index 5832c19..263031b 100644 --- a/backend/app/api/routes/__init__.py +++ b/backend/app/api/routes/__init__.py @@ -6,6 +6,7 @@ from .documents import router as documents_router from .knowledge import router as knowledge_router from .agent import router as agent_router from .status import router as status_router +from .perception import router as perception_router # Keep package boundaries explicit so backend imports stay predictable. @@ -18,6 +19,7 @@ api_router.include_router(knowledge_router) api_router.include_router(agent_router) api_router.include_router(compliance_router) api_router.include_router(status_router) +api_router.include_router(perception_router) __all__ = [ "api_router", @@ -26,4 +28,5 @@ __all__ = [ "agent_router", "compliance_router", "status_router", + "perception_router", ] diff --git a/backend/app/api/routes/perception.py b/backend/app/api/routes/perception.py new file mode 100644 index 0000000..7470234 --- /dev/null +++ b/backend/app/api/routes/perception.py @@ -0,0 +1,67 @@ +"""Define API routes for perception (regulatory intelligence).""" + +from __future__ import annotations + +import json + +from fastapi import APIRouter, Query +from fastapi.responses import StreamingResponse + +from app.shared.bootstrap import get_perception_service +from app.shared.async_utils import iter_in_thread + +router = APIRouter(prefix="/perception", tags=["智能感知"]) + + +@router.get("/stats") +async def get_perception_stats(): + """Return KPI statistics for the perception dashboard.""" + return get_perception_service().get_stats() + + +@router.get("/events") +async def list_events( + source: str | None = Query(default=None, description="来源筛选 (MIIT/UN-ECE/ISO/国标委/EUR-Lex/IATF)"), + impact_level: str | None = Query(default=None, description="影响等级 (high/medium/low)"), + limit: int = Query(default=50, ge=1, le=100), +): + """Return regulatory events with optional filters.""" + events = get_perception_service().list_events( + source=source, + impact_level=impact_level, + limit=limit, + ) + return {"events": events, "total": len(events)} + + +@router.get("/events/{event_id}") +async def get_event(event_id: str): + """Return a single regulatory event by ID.""" + event = get_perception_service().get_event(event_id) + if event is None: + from fastapi import HTTPException + raise HTTPException(status_code=404, detail=f"Event {event_id} not found") + return event + + +@router.post("/events/{event_id}/analyze") +async def analyze_event(event_id: str): + """Stream SSE impact analysis for a regulatory event.""" + service = get_perception_service() + + async def event_stream(): + async for item in iter_in_thread(service.analyze_event(event_id)): + event_name = item.get("event", "message") + data = item.get("data", "") + if isinstance(data, (dict, list)): + data = json.dumps(data, ensure_ascii=False) + yield f"event: {event_name}\ndata: {data}\n\n" + + return StreamingResponse( + event_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "X-Accel-Buffering": "no", + }, + ) diff --git a/backend/app/application/perception/__init__.py b/backend/app/application/perception/__init__.py new file mode 100644 index 0000000..17b0820 --- /dev/null +++ b/backend/app/application/perception/__init__.py @@ -0,0 +1 @@ +"""Perception application package.""" diff --git a/backend/app/application/perception/services.py b/backend/app/application/perception/services.py new file mode 100644 index 0000000..eea2031 --- /dev/null +++ b/backend/app/application/perception/services.py @@ -0,0 +1,143 @@ +"""Perception application service — event listing and streaming impact analysis.""" + +from __future__ import annotations + +import json +from typing import Generator + +from app.application.knowledge.services import KnowledgeRetrievalService +from app.infrastructure.perception.mock_event_store import MockEventStore +from app.services.llm.llm_factory import get_llm_client +from app.config.settings import settings + + +_ANALYSIS_SYSTEM_PROMPT = ( + "你是汽车行业法规合规专家,专注于中国国家标准(GB)、国际法规(UN-ECE、ISO)" + "及欧盟法规(EUR-Lex)的解读与合规建议。回答需专业、简洁、结构清晰。" +) + + +class PerceptionService: + """Orchestrate regulatory event queries and streaming impact analysis.""" + + def __init__( + self, + event_store: MockEventStore, + retrieval_service: KnowledgeRetrievalService, + ) -> None: + self._store = event_store + self._retrieval = retrieval_service + + # ------------------------------------------------------------------ + # Queries + # ------------------------------------------------------------------ + + def list_events( + self, + *, + source: str | None = None, + impact_level: str | None = None, + limit: int = 50, + ) -> list[dict]: + return self._store.filter(source=source, impact_level=impact_level, limit=limit) + + def get_event(self, event_id: str) -> dict | None: + return self._store.get(event_id) + + def get_stats(self) -> dict: + return self._store.stats() + + # ------------------------------------------------------------------ + # Streaming analysis + # ------------------------------------------------------------------ + + def analyze_event(self, event_id: str) -> Generator[dict, None, None]: + """Yield SSE-ready dicts: sources → content chunks → done.""" + event = self._store.get(event_id) + if not event: + yield {"event": "error", "data": f"事件 {event_id} 不存在"} + return + + # --- 1. RAG retrieval: find related library documents --- + query = event["title"] + " " + " ".join(event["tags"]) + chunks: list = [] + affected_docs: list[dict] = [] + try: + chunks = self._retrieval.retrieve(query=query, top_k=5) + seen: set[str] = set() + for chunk in chunks: + if chunk.doc_id not in seen: + seen.add(chunk.doc_id) + affected_docs.append( + { + "doc_id": chunk.doc_id, + "doc_name": chunk.doc_name, + "score": round(float(chunk.score), 4), + "snippet": (chunk.content or "")[:180], + "clause": getattr(chunk, "section_title", "") or "", + } + ) + except Exception: # noqa: BLE001 + pass + + yield {"event": "sources", "data": json.dumps(affected_docs, ensure_ascii=False)} + + # --- 2. Build context from retrieved chunks --- + context_parts = [ + f"[文档{i}: {c.doc_name}]\n{(c.content or '')[:400]}" + for i, c in enumerate(chunks[:5], 1) + ] + context = "\n\n".join(context_parts) if context_parts else "(知识库中暂无相关文档)" + + # --- 3. Build prompt --- + effective = event.get("effective_at") or "待定" + user_content = f"""请对以下法规动态进行专业影响分析。 + +【法规动态】 +标准编号:{event['standard_code']} +标题:{event['title']} +来源:{event['source_label']} +摘要:{event['summary']} +生效日期:{effective} +分类:{event['category']} +关键词:{', '.join(event['tags'])} + +【知识库关联文档】 +{context} + +请用 Markdown 格式,从以下四个维度进行分析: + +## 核心变化 +列出本次法规更新最关键的 3-5 项变化(用 - 列表) + +## 业务影响 +分析对现有产品、认证流程、技术文档的具体影响 + +## 整改建议 +给出优先级排序的行动清单(标注 🔴高 🟡中 🟢低 优先级) + +## 时间节点 +关键合规时间表与里程碑提醒""" + + messages = [ + {"role": "system", "content": _ANALYSIS_SYSTEM_PROMPT}, + {"role": "user", "content": user_content}, + ] + + # --- 4. Stream LLM response --- + try: + client = get_llm_client( + provider=settings.llm_provider, + model=settings.llm_model, + ) + if hasattr(client, "stream_chat"): + for chunk in client.stream_chat(messages): + yield {"event": "content", "data": chunk} + else: + response = client.chat(messages) + yield {"event": "content", "data": response.content or ""} + except Exception as exc: # noqa: BLE001 + yield {"event": "error", "data": str(exc)} + return + + yield {"event": "done", "data": "{}"} diff --git a/backend/app/infrastructure/perception/__init__.py b/backend/app/infrastructure/perception/__init__.py new file mode 100644 index 0000000..c0b8dd0 --- /dev/null +++ b/backend/app/infrastructure/perception/__init__.py @@ -0,0 +1 @@ +"""Perception infrastructure package.""" diff --git a/backend/app/infrastructure/perception/mock_event_store.py b/backend/app/infrastructure/perception/mock_event_store.py new file mode 100644 index 0000000..a927cee --- /dev/null +++ b/backend/app/infrastructure/perception/mock_event_store.py @@ -0,0 +1,421 @@ +"""Mock regulatory event store with 20 high-quality pre-seeded events.""" + +from __future__ import annotations + +from typing import Any + +MOCK_EVENTS: list[dict[str, Any]] = [ + # ------------------------------------------------------------------ HIGH + { + "id": "evt-001", + "source": "MIIT", + "source_label": "工业和信息化部", + "standard_code": "GB 18384-2025", + "title": "《电动汽车安全要求》国家标准第三版正式发布", + "summary": ( + "新增 IP67 级别高压系统密封防护要求;热失控预警响应时间压缩至 5 分钟;" + "调整碰撞安全测试工况,新增侧柱碰工况。本标准于 2026 年 7 月 1 日强制实施。" + ), + "impact_level": "high", + "published_at": "2025-11-15", + "effective_at": "2026-07-01", + "category": "电动汽车安全", + "tags": ["电池安全", "高压防护", "碰撞安全", "热失控"], + "source_url": "https://openstd.samr.gov.cn", + "status": "enacted", + }, + { + "id": "evt-002", + "source": "UN-ECE", + "source_label": "联合国欧洲经委会", + "standard_code": "UN R155 Amendment 3", + "title": "UN-ECE R155 网络安全法规第三次修订正式生效", + "summary": ( + "新增对 OTA(空中升级)全生命周期的安全审计要求;强化车辆 TARA" + "(威胁分析与风险评估)文档化义务;扩展 CSMS 监控范围至售后服务商。" + ), + "impact_level": "high", + "published_at": "2026-01-20", + "effective_at": "2026-07-01", + "category": "网络安全", + "tags": ["OTA", "网络安全", "CSMS", "TARA", "R155"], + "source_url": "https://unece.org/transport/vehicle-regulations", + "status": "enacted", + }, + { + "id": "evt-003", + "source": "MIIT", + "source_label": "工业和信息化部", + "standard_code": "GB/T 40429-2026(征求意见稿)", + "title": "《汽车整车信息安全技术要求》修订征求意见", + "summary": ( + "增加基于人工智能的异常行为检测要求;新增车云通信双向认证机制规范;" + "提出数据最小化原则在车辆 OBD 数据收集中的应用细则。" + ), + "impact_level": "high", + "published_at": "2026-03-05", + "effective_at": None, + "category": "信息安全", + "tags": ["信息安全", "数据安全", "AI检测", "OBD"], + "source_url": "https://www.miit.gov.cn/", + "status": "draft", + }, + { + "id": "evt-004", + "source": "MIIT", + "source_label": "工业和信息化部", + "standard_code": "NEV 双积分 2026", + "title": "2026 年度新能源汽车双积分管理办法年度调整", + "summary": ( + "纯电动乘用车标准车型积分(CAFC)基准值上调 8%;" + "提高 A 级及以上续航里程门槛;新增氢燃料电池商用车积分计算细则。" + ), + "impact_level": "high", + "published_at": "2026-02-28", + "effective_at": "2026-04-01", + "category": "新能源政策", + "tags": ["双积分", "纯电动", "燃料电池", "碳配额"], + "source_url": "https://www.miit.gov.cn/", + "status": "enacted", + }, + { + "id": "evt-017", + "source": "MIIT", + "source_label": "工业和信息化部", + "standard_code": "智能网联汽车准入管理办法实施细则", + "title": "智能网联汽车准入管理实施细则正式落地", + "summary": ( + "明确 L3 及以上自动驾驶功能的准入申报路径;" + "要求 OEM 建立数据安全管理体系并完成等保 2.0 三级认证;" + "道路测试数据留存期延长至 3 年。" + ), + "impact_level": "high", + "published_at": "2026-03-01", + "effective_at": "2026-09-01", + "category": "智能网联", + "tags": ["智能网联", "L3自动驾驶", "准入管理", "数据留存"], + "source_url": "https://www.miit.gov.cn/", + "status": "enacted", + }, + { + "id": "evt-018", + "source": "EUR-Lex", + "source_label": "欧盟官方公报", + "standard_code": "EU Cyber Resilience Act (CRA)", + "title": "《欧盟网络韧性法案》核心条款对车联网设备生效", + "summary": ( + "联网汽车 ECU 须满足 CRA「重要类 II」安全要求;" + "强制 SBOM(软件物料清单)公开披露;" + "OEM 须提供至少 10 年的漏洞修复支持承诺。" + ), + "impact_level": "high", + "published_at": "2026-02-15", + "effective_at": "2027-01-01", + "category": "网络安全", + "tags": ["CRA", "SBOM", "漏洞管理", "网络韧性"], + "source_url": "https://eur-lex.europa.eu", + "status": "enacted", + }, + # --------------------------------------------------------------- MEDIUM + { + "id": "evt-005", + "source": "UN-ECE", + "source_label": "联合国欧洲经委会", + "standard_code": "UN R156 Amendment 2", + "title": "UN-ECE R156 软件升级与 SUMS 法规补充修订", + "summary": ( + "明确 SUMS(软件更新管理系统)对 ECU 版本追溯的最低保留年限为 15 年;" + "新增售后 OTA 推送的用户知情同意要求规范。" + ), + "impact_level": "medium", + "published_at": "2026-01-10", + "effective_at": "2026-07-01", + "category": "软件升级", + "tags": ["OTA", "SUMS", "软件版本", "R156"], + "source_url": "https://unece.org/transport/vehicle-regulations", + "status": "enacted", + }, + { + "id": "evt-006", + "source": "国标委", + "source_label": "国家标准化管理委员会", + "standard_code": "GB/T 35273-2026", + "title": "《信息安全技术 个人信息安全规范》更新版发布", + "summary": ( + "将车内人脸识别、声纹采集列为敏感个人信息;" + "补充自动驾驶场景下乘员行为数据的去标识化技术规范;" + "强化数据出境安全评估触发阈值。" + ), + "impact_level": "medium", + "published_at": "2025-12-01", + "effective_at": "2026-06-01", + "category": "数据安全", + "tags": ["个人信息", "PIPL", "数据安全", "生物识别"], + "source_url": "https://openstd.samr.gov.cn", + "status": "enacted", + }, + { + "id": "evt-007", + "source": "EUR-Lex", + "source_label": "欧盟官方公报", + "standard_code": "EU AI Act — Art.13 & Art.14", + "title": "《欧盟人工智能法案》第13-14条透明度与人工监督条款正式生效", + "summary": ( + "要求在汽车 ADAS 系统中植入 AI 使用记录日志;" + "驾驶员监控 AI 系统须披露决策逻辑;" + "高风险 AI 系统需提供人工干预接口。" + ), + "impact_level": "medium", + "published_at": "2026-02-01", + "effective_at": "2026-08-01", + "category": "AI 法规", + "tags": ["AI法案", "透明度", "ADAS", "高风险AI"], + "source_url": "https://eur-lex.europa.eu", + "status": "enacted", + }, + { + "id": "evt-008", + "source": "ISO", + "source_label": "国际标准化组织", + "standard_code": "ISO 45001:2025 Amd.1", + "title": "ISO 45001 职业健康安全管理体系第一次修正", + "summary": ( + "新增心理健康风险纳入 OHS 危害辨识范围;" + "明确远程办公人员安全管理职责;" + "更新绩效评价指标体系,新增事故未遂事件统计要求。" + ), + "impact_level": "medium", + "published_at": "2025-10-20", + "effective_at": "2026-01-01", + "category": "EHS 管理", + "tags": ["ISO 45001", "EHS", "职业健康", "安全管理"], + "source_url": "https://www.iso.org", + "status": "enacted", + }, + { + "id": "evt-009", + "source": "MIIT", + "source_label": "工业和信息化部", + "standard_code": "GB/T 28001-2026(征求意见)", + "title": "《汽车产品安全召回管理规程》修订征求意见", + "summary": ( + "扩展召回触发条件,将 OTA 推送导致的功能异常纳入强制报告范围;" + "缩短重大安全隐患召回启动时限至 15 个工作日。" + ), + "impact_level": "medium", + "published_at": "2026-03-15", + "effective_at": None, + "category": "召回管理", + "tags": ["召回", "OTA", "安全隐患", "产品安全"], + "source_url": "https://www.miit.gov.cn/", + "status": "draft", + }, + { + "id": "evt-010", + "source": "国标委", + "source_label": "国家标准化管理委员会", + "standard_code": "GB 38031-2025", + "title": "《电动汽车用动力蓄电池安全要求》修订版发布", + "summary": ( + "新增电池系统针刺、浸水、挤压等极端工况测试程序;" + "热扩散防护等级要求升级;" + "强化 BMS(电池管理系统)状态监测数据记录要求。" + ), + "impact_level": "medium", + "published_at": "2025-09-15", + "effective_at": "2026-03-01", + "category": "电池安全", + "tags": ["动力电池", "BMS", "热扩散", "安全测试"], + "source_url": "https://openstd.samr.gov.cn", + "status": "enacted", + }, + { + "id": "evt-016", + "source": "UN-ECE", + "source_label": "联合国欧洲经委会", + "standard_code": "UN R100 Rev.4(草案)", + "title": "UN R100 电动汽车安全认证法规第四次修订草案发布", + "summary": ( + "拟对 400V 以上高压系统的绝缘电阻监测提出实时 CAN 总线传输要求;" + "新增极低温工况(-40°C)的电池性能验证程序。" + ), + "impact_level": "medium", + "published_at": "2026-04-08", + "effective_at": None, + "category": "电动汽车安全", + "tags": ["R100", "高压安全", "绝缘监测", "低温性能"], + "source_url": "https://unece.org/transport/vehicle-regulations", + "status": "draft", + }, + { + "id": "evt-019", + "source": "ISO", + "source_label": "国际标准化组织", + "standard_code": "ISO/SAE 21434:2026 Amd.1", + "title": "ISO/SAE 21434 汽车网络安全工程第一次修正", + "summary": ( + "将 AI 推理组件纳入汽车网络安全工程范围;" + "补充端到端加密通信在 V2X 场景中的 TARA 建模要求;" + "新增第三方 ECU 供应商 CSMS 审计方法。" + ), + "impact_level": "medium", + "published_at": "2026-04-10", + "effective_at": "2026-10-01", + "category": "网络安全", + "tags": ["ISO 21434", "网络安全", "V2X", "AI安全"], + "source_url": "https://www.iso.org", + "status": "enacted", + }, + # ------------------------------------------------------------------ LOW + { + "id": "evt-011", + "source": "ISO", + "source_label": "国际标准化组织", + "standard_code": "ISO 26262:2026 Ed.3(征求意见)", + "title": "ISO 26262 功能安全第三版征求意见启动", + "summary": ( + "拟新增对 AI/ML 组件功能安全验证方法的指导附录;" + "讨论 SOTIF(预期功能安全)与 ISO 26262 的协调融合路径。" + ), + "impact_level": "low", + "published_at": "2026-04-01", + "effective_at": None, + "category": "功能安全", + "tags": ["功能安全", "ASIL", "AI安全", "SOTIF"], + "source_url": "https://www.iso.org", + "status": "consultation", + }, + { + "id": "evt-012", + "source": "EUR-Lex", + "source_label": "欧盟官方公报", + "standard_code": "REACH Regulation Update 2026", + "title": "欧盟 REACH 法规限制物质清单更新(第 22 批)", + "summary": ( + "新增 3 种 SVHCs(高度关注物质),包括特定阻燃剂和密封材料成分;" + "汽车零部件豁免条款调整,影响部分内饰材料供应商。" + ), + "impact_level": "low", + "published_at": "2026-01-30", + "effective_at": "2026-09-01", + "category": "环保法规", + "tags": ["REACH", "SVHCs", "环保", "化学品管理"], + "source_url": "https://eur-lex.europa.eu", + "status": "enacted", + }, + { + "id": "evt-013", + "source": "MIIT", + "source_label": "工业和信息化部", + "standard_code": "CCER 汽车碳配额 2026", + "title": "自愿减排(CCER)汽车行业核算方法学更新", + "summary": ( + "更新纯电动汽车全生命周期碳排放核算边界;" + "新增动力电池回收环节碳减排量认定方法;" + "与全国碳市场对接的企业碳账户数据接口规范发布。" + ), + "impact_level": "low", + "published_at": "2026-02-10", + "effective_at": "2026-06-01", + "category": "碳排放", + "tags": ["CCER", "碳排放", "碳中和", "碳核算"], + "source_url": "https://www.miit.gov.cn/", + "status": "enacted", + }, + { + "id": "evt-014", + "source": "IATF", + "source_label": "国际汽车工作组", + "standard_code": "IATF 16949:2025 CSR 通告", + "title": "IATF 16949 质量管理体系客户特殊要求更新通告", + "summary": ( + "多家主机厂(OEM)同步更新 CSR,涵盖软件定义汽车(SDV)" + "场景下的质量过程管控;电子电气 BOM 变更管理流程补充规范。" + ), + "impact_level": "low", + "published_at": "2026-03-20", + "effective_at": "2026-07-01", + "category": "质量管理", + "tags": ["IATF 16949", "质量管理", "SDV", "CSR"], + "source_url": "https://www.iatfglobaloversight.org", + "status": "enacted", + }, + { + "id": "evt-015", + "source": "国标委", + "source_label": "国家标准化管理委员会", + "standard_code": "GB 7258-2025 勘误", + "title": "《机动车运行安全技术条件》年度勘误发布", + "summary": ( + "更正第 12 章灯光系统技术要求中的参数引用错误;" + "澄清前雾灯安装位置尺寸定义;此次为勘误性修订,不影响已认证车型。" + ), + "impact_level": "low", + "published_at": "2026-01-05", + "effective_at": "2026-01-05", + "category": "运行安全", + "tags": ["GB 7258", "灯光", "运行安全", "勘误"], + "source_url": "https://openstd.samr.gov.cn", + "status": "enacted", + }, + { + "id": "evt-020", + "source": "国标委", + "source_label": "国家标准化管理委员会", + "standard_code": "GB/T 27930-2026", + "title": "《电动汽车非车载传导式充电通信协议》更新版发布", + "summary": ( + "兼容 CHAdeMO 4.0 与 CCS2 双协议栈;" + "新增大功率充电(>350kW)通信握手流程;" + "强化充电过程 BMS 实时诊断数据上报规范。" + ), + "impact_level": "low", + "published_at": "2026-03-25", + "effective_at": "2026-12-01", + "category": "充电标准", + "tags": ["充电协议", "BMS", "大功率充电", "CHAdeMO"], + "source_url": "https://openstd.samr.gov.cn", + "status": "enacted", + }, +] + +# Index for fast lookup +_EVENT_INDEX: dict[str, dict] = {e["id"]: e for e in MOCK_EVENTS} + + +class MockEventStore: + """In-memory mock store for regulatory events.""" + + def all(self) -> list[dict]: + return list(MOCK_EVENTS) + + def get(self, event_id: str) -> dict | None: + return _EVENT_INDEX.get(event_id) + + def filter( + self, + *, + source: str | None = None, + impact_level: str | None = None, + limit: int = 50, + ) -> list[dict]: + events = list(MOCK_EVENTS) + if source: + events = [e for e in events if e["source"] == source] + if impact_level: + events = [e for e in events if e["impact_level"] == impact_level] + events.sort(key=lambda e: e["published_at"], reverse=True) + return events[:limit] + + def stats(self) -> dict: + from datetime import date, timedelta + + events = MOCK_EVENTS + cutoff = (date.today() - timedelta(days=90)).isoformat() + return { + "total": len(events), + "high_impact": sum(1 for e in events if e["impact_level"] == "high"), + "medium_impact": sum(1 for e in events if e["impact_level"] == "medium"), + "low_impact": sum(1 for e in events if e["impact_level"] == "low"), + "recent_90d": sum(1 for e in events if e["published_at"] >= cutoff), + } diff --git a/backend/app/shared/bootstrap.py b/backend/app/shared/bootstrap.py index 0fc7a2a..a0a18b6 100644 --- a/backend/app/shared/bootstrap.py +++ b/backend/app/shared/bootstrap.py @@ -23,6 +23,8 @@ from app.infrastructure.vectorstore.bm25_retriever import BM25Retriever from app.infrastructure.vectorstore.dense_retriever import DenseRetriever from app.infrastructure.vectorstore.milvus_vector_index import MilvusVectorIndex from app.infrastructure.vectorstore.cross_encoder_reranker import OpenAICompatibleReranker +from app.infrastructure.perception.mock_event_store import MockEventStore +from app.application.perception.services import PerceptionService # Keep shared wiring centralized so dependency construction remains consistent. @@ -151,3 +153,12 @@ def get_agent_conversation_service() -> AgentConversationService: answer_generator=OpenAICompatibleAnswerGenerator(), conversation_store=get_conversation_store(), ) + + +@lru_cache +def get_perception_service() -> PerceptionService: + """Return perception service for regulatory intelligence.""" + return PerceptionService( + event_store=MockEventStore(), + retrieval_service=get_retrieval_service(), + )