# 第三方风险评估与报告追问接口 当前第三方系统只需调用两个 POST 接口,均一次性返回 JSON: | 用途 | 路径 | 响应类型 | | --- | --- | --- | | 生成报告或重取原报告 | `/api/v1/integrations/risk-assessments/report` | `application/json` | | 针对原报告继续追问 | `/api/v1/integrations/risk-assessments/follow-up` | `application/json` | 没有单独的创建会话、历史查询、状态查询或流式接口。PLM 不需要管理 `conversationId`,只需保存报告 `requestId`。后端第一次追问时自动建立会话,后续同一调用系统、同一报告、同一报告操作人的多次追问沿用该会话。 ## 1. 鉴权与操作人 两个接口均使用系统 HMAC-SHA256 鉴权,不依赖平台用户 JWT。系统密钥只保存在双方后端;PLM 后端必须从真实登录态获取操作人身份,不允许浏览器自行冒用账号。 | 请求头 | 说明 | | --- | --- | | `Content-Type` | `application/json` | | `X-Client-Id` | 已开通的调用系统编号,例如 `dieya-plm` | | `X-Timestamp` | 当前 Unix 秒级时间戳,误差不超过 5 分钟 | | `X-Nonce` | 每次请求使用新的 8~64 位随机串,允许字母、数字、下划线和短横线 | | `X-Signature` | HMAC-SHA256 结果,标准 Base64 编码 | | `Idempotency-Key` | 生成报告时等于 `requestId`;追问时等于 `messageRequestId` | | `X-Signature-Version` | 仅追问接口必须提供,固定为 `2` | 生成报告沿用原签名,避免变更已有签名代码: ```text 时间戳 + 换行 + Nonce + 换行 + 原始请求正文 ``` 追问的 v2 签名绑定方法、路径和正文,使用真实换行字节(0x0A): ```text POST + 换行 + /api/v1/integrations/risk-assessments/follow-up + 换行 + 空查询串 + 换行 + 时间戳 + 换行 + Nonce + 换行 + 原始请求正文 ``` 追问签名 Python 示例;`body` 必须与实际发送的 UTF-8 字节完全一致: ```python import base64 import hashlib import hmac import secrets import time def sign_follow_up_request(method, path_and_query, body, client_id, client_secret): timestamp = str(int(time.time())) nonce = secrets.token_urlsafe(18) path, _, query = path_and_query.partition("?") canonical = b"\n".join(( method.upper().encode("ascii"), path.encode("ascii"), query.encode("ascii"), timestamp.encode("ascii"), nonce.encode("ascii"), body, )) signature = base64.b64encode( hmac.new(client_secret.encode("utf-8"), canonical, hashlib.sha256).digest() ).decode("ascii") return { "X-Signature-Version": "2", "X-Client-Id": client_id, "X-Timestamp": timestamp, "X-Nonce": nonce, "X-Signature": signature, } ``` `operator` 使用以下两种身份之一,不能混用: - 钉钉用户:`{"dingtalkUnionId":"真实UnionId","dingtalkUserId":"真实UserId"}`,其中 UserId 可省略。 - 外部用户:`{"externalUserId":"superadmin"}`,需要提前配置该调用系统到平台本地账号的映射。 `name`、`employeeNo` 不能单独作为身份依据。账号需启用并拥有 `risk.assess` 操作权限;每次追问都会重新校验报告归属,不能跨系统、跨用户访问,也不按名字猜测用户。 ## 2. 生成报告 ```http POST /api/v1/integrations/risk-assessments/report Content-Type: application/json Idempotency-Key: PLM-REPORT-001 ``` 另需提供系统签名请求头。请求正文: ```json { "requestId": "PLM-REPORT-001", "sourceSystem": "dieya-plm", "operator": {"externalUserId": "superadmin"}, "assessment": { "subjectId": "PRODUCT-001", "productName": "保湿面霜", "productCategory": "面霜/护肤类", "specification": "50g", "materialType": "塑料罐", "stageNew": "产品开发", "supplier": null, "factory": null, "description": "新品包装风险评估", "asOfDate": "2026-10-28" } } ``` 必填字段:`requestId`、`sourceSystem`、`operator`,以及 `assessment.subjectId`、`productCategory`、`materialType`。`sourceSystem` 必须与 `X-Client-Id` 一致。其他评估字段可省略:产品名、规格、供应商、加工厂、开发阶段、缺陷等级、描述和截止日期。 - `requestId` 为 1~128 位字母、数字、下划线、点、冒号或短横线。 - `stageNew` 可为需求发现、产品立项、产品开发、量产上市、产品生命管理。 - `defectLevel` 可为 A、B、C;代表已知信息,不预测新品缺陷等级。 - `asOfDate` 为 YYYY-MM-DD,不允许未来日期;省略时采用平台当天日期。 - 评分仍采用现有七维规则和风险平台本地数据。无法归类或缺少数据时会在报告里说明,不能把默认分理解为通过验证。 成功返回 `HTTP 200`: ```json { "code": 200, "message": "success", "data": { "requestId": "PLM-REPORT-001", "status": "completed", "assessmentId": 123, "score": 62.5, "level": "moderately_low", "modelVersion": "database-seven-dimension-v1", "asOfDate": "2026-10-28", "reportMarkdown": "# 新品风险评估报告\n\n完整报告正文……" } } ``` 示例分数和 ID 仅说明格式。报告正文仍是 Markdown,但放在 JSON 的 `data.reportMarkdown` 中,HTTP 响应不再是 `text/markdown`。已集成旧响应的调用方需要改为解析 JSON 后读取该字段。保留 `X-Request-Id`、`X-Assessment-Id`、`X-Risk-Score`、`X-Risk-Level`、`X-Model-Version` 响应头,便于现有日志衔接。 同一 `sourceSystem + requestId` 重复提交同一内容,返回已保存的同一份报告;相同编号换内容返回 `409`。获取原报告直接重复 POST 原参数,不再提供 GET 查询接口。尚在生成时返回 `202` 和 `Retry-After`,等待后重发相同参数,每次使用新的 Nonce 和签名。 ## 3. 对报告追问 ```http POST /api/v1/integrations/risk-assessments/follow-up Content-Type: application/json X-Signature-Version: 2 Idempotency-Key: PLM-QUESTION-001 ``` 另需提供对应的 HMAC v2 签名请求头。请求正文: ```json { "requestId": "PLM-REPORT-001", "messageRequestId": "PLM-QUESTION-001", "operator": {"externalUserId": "superadmin"}, "content": "为什么供应商这一项得分较低?" } ``` - `requestId` 是前一步已成功生成的原报告编号。 - `messageRequestId` 是本次问题的唯一编号,每个新问题换一个编号;重试同一个问题保持编号及内容不变。 - `operator` 必须具有原报告的访问权限。 - `content` 为 1~2000 字符,不能为空或纯空格。 - 无需传报告正文、完整聊天历史或会话 ID,后端自行加载。 完成后一次性返回 `HTTP 200` JSON,不发送 SSE 或增量文字: ```json { "code": 200, "message": "success", "data": { "requestId": "PLM-REPORT-001", "messageRequestId": "PLM-QUESTION-001", "status": "completed", "reply": "根据原报告,供应商维度的得分依据是……" } } ``` 继续问“应该怎么改善”,仍使用相同 `requestId`,换用 `messageRequestId=PLM-QUESTION-002` 即可。两次问题自动进入同一会话,模型同时获得原报告和近期聊天。 追问会复用现有 Prompt 语义路由,每轮固定提供原始产品资料、报告截止日期和结构化评分;Markdown 正文最多带入 32000 字符,截断时会标记。近期聊天超过窗口后,原报告仍会保留。追问不修改原报告,也不触发重新评分;正式重算应使用新报告编号提交更新后的评估参数。 ## 4. 重试与错误 - 同一个问题重复 POST:返回原答案,不重复插入用户消息,不重复生成已完成的回答。 - 同问题编号更改内容:`409`。 - 同一问题正在处理中:`202` JSON,`data.status=processing`、`reply=null`,携带 `Retry-After: 3`;之后仍重复调用本接口,不需要状态查询接口。 - 同一会话有另一条未完成问题:`409`,先用原问题编号完成重试,避免上下文乱序。 - 生成失败或超时:`503` JSON,可用原编号和原内容重试。 - 网络断开或服务重启后:重复调用同一接口及原参数;如果旧处理租约仍有效,会先返回 `202`,过期后重试恢复。 - 签名无效或重放 Nonce:`401`;账号未开通、停用或无权限:`403`;报告不存在或无权访问:`404`;参数不合法:`422`;超出频率:`429`。 处理中的追问示例: ```json { "code": 202, "message": "原问题正在生成,请用相同编号重试", "data": { "requestId": "PLM-REPORT-001", "messageRequestId": "PLM-QUESTION-001", "status": "processing", "reply": null } } ``` 普通错误统一为 `{"code":503,"message":"可阅读的错误说明","data":null}`。追问总生成时限默认 240 秒,处理租约 5 分钟。回答正文和完成状态在同一事务中提交;过期旧处理令牌不能覆盖新的回答。 ## 5. 服务端启用与调用脚本 发布前执行迁移并重启后端: ```powershell .\.venv\Scripts\python.exe -m alembic upgrade head ``` 新增 `integration_conversations` 和 `integration_conversation_requests` 仅用于后端关联会话和幂等,不需要第三方额外调用接口。已有内部用户会话的流式功能不受此处接口精简影响。 ```dotenv RISK_INTEGRATION_CLIENTS={"dieya-plm":"replace-with-at-least-32-random-characters"} RISK_INTEGRATION_OPERATOR_MAPPINGS={"dieya-plm":{"superadmin":"plm_superadmin"}} RISK_INTEGRATION_REQUESTS_PER_MINUTE=60 RISK_INTEGRATION_REPORT_TIMEOUT_SECONDS=110 RISK_INTEGRATION_CHAT_TIMEOUT_SECONDS=240 ``` 示例密钥必须替换为双方后端保管的独立密钥,至少 32 字符,不能提交真实 `.env`。默认空 JSON 不允许外部调用。可用 `scripts/setup_external_risk.py` 开通系统与批准操作人映射。 调用方终端设置 `RISK_INTEGRATION_CLIENT_SECRET` 后: ```powershell # report-request.json 使用第 2 节正文 .\.venv\Scripts\python.exe scripts/call_external_risk_report.py --url http://127.0.0.1:18474/api/v1/integrations/risk-assessments/report --body-file report-request.json --output report.md # follow-up.json 使用第 3 节正文 .\.venv\Scripts\python.exe scripts/call_external_follow_up.py --url http://127.0.0.1:18474/api/v1/integrations/risk-assessments/follow-up --body-file follow-up.json ``` 生成报告脚本读取 JSON 中的 `reportMarkdown` 保存为 Markdown 文件;追问脚本直接打印完整 JSON。PLM 后端按同一协议接入即可,HMAC 密钥不下发浏览器。