# 主通风机批次固定工作流 当前启动批次、月度批次、批次 API 和 `scripts/run_main_fan_batch.py` 共用本工作流,执行版本为 `main-fan-workflow-v5`。自由对话的 Skill/沙箱入口保留为原有交互能力;它不参与当前批次研判。 ## 执行顺序 1. 沿用固定煤矿筛选、测点分页、40 天历史查询和预处理。安全监控提供开停,重大设备提供风量。给模型的开停区间过滤起止相差不超过 5 秒(含 5 秒)的记录,适用于“开”和“停”;原始状态观测继续留档。按实际开停变化配对切出、切入风机并计算停启间隔估算及采样上下界,可配对的停启允许跨观测组连接,超过 10 分钟的间隔也会进入候选。前后各 1 小时附近单点风量及变化百分比沿用原方法。 2. 按每份原 PDF 的物理页顺序提取全文。可用文本层由宿主 Python/PDFium 直接读取;空文本、只有水印或包含较大扫描图片的混合页渲染后发送 GLM OCR。页眉可读但表格是图片时也走 OCR,避免只读到页眉便声称全页完成。空白页单独记录。GLM 超时、HTTP 错误、空输出、输出截断或明显循环重复时,同一张页面图片再交给 Qwen3.5 识别一次。两者都失败则保留失败页及具体错误。PDFium 文本页使用显式 `close()` 释放,不作为上下文管理器。PDF 全部提取失败也继续使用已有开停和风量研判,正文记录提取受限,不写成用户未提供报告。 3. 提取后按页首标题过滤明确无关的声明、机构资质、检测仪器清单和确定的空白页;出现性能、风量、功率、频率、叶片、铭牌、风机身份等关键词时保守保留。封面、性能表、相关说明、失败页及用途不确定的页面保留,不重新编号,不建立工况索引,也不自动把表格重排配对。筛选只影响送模副本,原 PDF、完整逐页文字和每页识别记录继续归档。将筛选后的 PDF 页和完整的风量、开停和切换整理结果合并为一个 JSON。原预览超过 20 条的区间/候选由程序展开完整结果。保留 0、null、缺测、原件身份和模拟来源。 4. 使用配置的 Qwen3.5 发起一次非流式 `chat/completions` 生成请求,通过 `response_format.type=json_schema` 提交完整字段约束。保留最高思考配置。结构化解码能否在思考模式下实际生效还取决于模型服务的 parser 和 structured outputs 配置;客户端仍严格验证响应。不提供工具,不带检查点消息、历史聊天或旧研判,不再让模型写脚本、规划取数或调用提交工具。`OPENAI_MAX_RETRIES` 只影响原 Agent/SDK,工作流的唯一生成请求没有自动重试。 5. 用严格 schema 校验 Qwen 的研判对象、资料说明和 `judgment`。存在完整业务内容但 JSON 语法或字段结构错误时,只发送待修复 JSON、目标 Schema 和校验错误,请 Qwen 修复格式,最多额外 3 次。没有 PDF、监测输入或旧会话随修复请求发送,也不重跑研判。修复不得改结论、数值和正文;能解析的原业务字符串逐字段核对,语法损坏的原件保守核对结论编码、数值和中文字是否变化。缺少 `evidenceLink` 可按固定契约补空字符串;空回复、没有业务内容的空对象、正文截断不能通过修复器编造结果。修复上限耗尽报 `qwen_json_repair_exhausted`。通过后由宿主绑定矿井身份、智能体身份、任务 ID 和完成时刻,组装并校验 `PlatformResultPayload`,再保存数据库和本地文件。 “一次”指一次业务研判生成;OCR 补识别和格式修复是独立调用,分别计数。扫描页默认最多 2 路并发,GLM 每页失败后最多一次 Qwen 补识别。HTTP 客户端没有隐藏的自动重试,首次业务请求的 HTTP 错误不触发 JSON 修复。所有原始响应、筛选记录和修复请求/响应均保存,成功修复不会覆盖首次研判的错误原文。页面筛选不裁剪监测数据,不把报告压成未经核对的摘要;上游上下文不足时保留错误,供服务器调整真实上下文配置后重跑。 JSON 语法错误 `qwen_invalid_json` 记录行、列和偏移;可解析但字段不符合契约时使用 `qwen_schema_validation` 并记录字段位置。详细诊断同时写入 `failure.json` 和日志。 v5 将监测阈值判定交给固定代码:不重新选参考点或改变比例,按有效的现成变化率和可靠间隔下界计算监测告警。一般阈值采用“超过”,配置的严重阈值采用“达到”;严重间隔阈值为空时不会自行新增。输入新增 `monitoring_assessment`,记录每个候选的原数值、固定判定及最低综合等级,请求 Schema 同步限制编码。模型不能降低监测告警等级;有效风量从正数降至0形成100%变化,在本轮15%严重阈值下必须为 ABNORMAL。 模型仍只进行一次辅助研判。格式修复继续只修改格式、保留原业务内容;随后统一合并固定监测结论。存在固定监测告警且模型未给出更高等级时,正式报文的编码、摘要、正文和资料说明统一使用固定证据生成,避免“编码异常、正文正常”的矛盾。模型有更高级别的性能异常结论时保留该等级,并附上固定监测证据。首次响应、格式修复后的响应及模型原判定均保留;`监测规则判定.json` 和 `最终结论核对.json` 记录原值、阈值、模型编码、最终编码及采用的正文。本轮未形成明确监测告警时,保留原有的性能辅助研判和资料可用性口径。 停启间隔按本次净切换中,切出风机各参与测点最后一次“开→停”、切入风机各参与测点最后一次“停→开”计算。多级测点取各方最后完成变化的新状态观测时刻,启动抖动不使用第一次短暂启动。`duration_seconds=max(0, 启动观测时间-停机观测时间)`,`duration_minutes=duration_seconds/60`,状态为 `estimated`;先启后停时另存 `overlap_seconds`。`from_stopped_at`、`to_started_at` 是采用的观测时间,`duration_sources` 保留逐点依据;它们不宣称准确整机动作时刻。各变化的前一旧状态与首次新状态形成采样边界,据此计算 `duration_bounds_minutes.lower/upper`;跨未知或长缺测的变化标记 `duration_has_gaps=true`。仅下界也超限且不跨未知/缺测时据间隔告警。整个候选的 `time_range` 不当作停启间隔。 安全监控的明确开关标签兼容“开/停”和“开/关”,“关”统一为派生状态“停”,原始 `value_label` 保留在采样证据中;故障、报警等异常状态仍保持未知,不按数值 0/1 猜测开停。v4 修正了旧版把“关”误记未知的问题,重新研判时从原始采样重新归一化,旧版已完成的结论不能直接复用为本版结果。 `给Agent的数据.json` 和 `给Qwen的数据.json` 都包含上述计算结果。复用旧原始详情时也会从其中保留的实际状态变化重新派生间隔;只有缺少可配对的变化时间时才保留 `duration_minutes=null`、`duration_status=unknown`。本轮间隔数值是固定代码计算,模型直接采用,不重新推算。正文使用业务表述,不把内部字段名或 `null/unknown` 直接写给业务人员。 GLM 使用 `Text Recognition:` 提取全页内容;Qwen 补识别只转录原页文字与表格,不负责业务判定。复杂表头、上下标和数值仍可能识别错误;性能比较优先采用同角度、同频率、相近风量、相同口径的清晰原值。最终 schema 校验及格式修复不代替业务正确性核对。 ## 研判口径 本轮策略为 `main-fan-precision-first-v2-monitoring-rules`,以告警准确率优先,接受漏报;但已经满足冻结阈值的有效监测超限不能被模型忽略。它同时写入提示词与 `给Qwen的数据.json` 的 `judgment_policy`,不沿用自由对话 Skill 中“关键项资料不齐就无法综合判定”的旧口径。 - 开停、风量沿用冻结规则和固定程序的实际数值。有效风量变化比例按 `switch.flow_deviation_percent` 与 `severity.abnormal_flow_deviation_percent` 判断;停启间隔使用现成估算及其采样上下界,按对应间隔阈值判断。不添加稳定窗口、完整率或 CV 门槛,不把整个候选采样范围当作实际切换耗时。缺少 PDF 不影响有效监测超限形成告警。 - 性能部分做粗略辅助核对,不再要求逐工况精算、插值、所有指标及工况配齐。只有清晰、可比且确凿超限的原件证据才告警;识别不清、工况不明、粗估接近阈值、缺页或点数不足只保留限制,不认为已证实性能异常。 - 有确凿异常且达到已配置严重条件为 `ABNORMAL`;一般超限为 `WARNING`。没有配置的严重线不自行新增,资料缺失本身不告警。 - 任一类有可用数据且未发现确凿异常为 `NORMAL`,含义是“现有数据未发现确凿异常”。尚未核实的性能、间隔或时段须说明,不能表述为已确认同等能力或全时段合格。 - 仅报告、开停、风量三类均无可用数据时为 `UNDETERMINED`。宿主统计可读真实报告页、已识别开停区间、明确间隔、有效风量采样及参考值,保留零值;模拟 PDF 不作为本矿真实证据。统计写入输入和诊断中的 `data_availability`,并与固定监测等级一起收窄请求 Schema 的结论编码;合并固定告警后本地再次验证可用性口径,矛盾编码报 `workflow_result_policy`。监测结论的合并过程有独立原始/最终记录。 ## 配置 保留既有网关、报告服务、只读原件目录及模型地址/凭证。下面只列本工作流相关项: ```dotenv DEEPAGENT_MODEL=Qwen/Qwen3.5-35B-A3B-FP8 DEEPAGENT_THINKING=highest # OPENAI_BASE_URL / OPENAI_API_KEY 沿用服务器当前的 Qwen 配置 MAIN_FAN_OCR_BASE_URL=http://10.244.3.155:10803/v1 MAIN_FAN_OCR_MODEL=glm-ocr MAIN_FAN_OCR_API_KEY= MAIN_FAN_OCR_TIMEOUT_SECONDS=120 MAIN_FAN_OCR_MAX_TOKENS=4096 MAIN_FAN_OCR_CONCURRENCY=2 MAIN_FAN_QWEN_OCR_TIMEOUT_SECONDS=240 MAIN_FAN_QWEN_OCR_MAX_TOKENS=8192 MAIN_FAN_PDF_DPI=150 MAIN_FAN_PDF_MAX_SIDE=2200 MAIN_FAN_PDF_CACHE_POLICY=auto MAIN_FAN_WORKFLOW_MAX_TOKENS=16384 MAIN_FAN_WORKFLOW_TIMEOUT_SECONDS=600 MAIN_FAN_JSON_REPAIR_MAX_ATTEMPTS=3 # 本地测试保持关闭;不仅阻止本轮入队,也阻止后台结果推送服务启动。 RESULT_PLATFORM_ENABLED=False MAIN_FAN_WORKFLOW_PUBLISH_ENABLED=False ``` Qwen 沿用 `DEEPAGENT_THINKING`;`highest` 对当前 vLLM/Qwen 接口发送 `chat_template_kwargs.enable_thinking=true`,没有另外的最高深度档。`MAIN_FAN_WORKFLOW_MAX_TOKENS` 同时给思考及正文留输出额度,实际总上下文仍由模型服务决定。 GLM 鉴权独立配置,空密钥时不发送 Authorization,不把 Qwen 密钥送给 GLM。Qwen 补识别复用现有模型、地址、密钥和思考配置。格式修复次数配置仅允许 0~3,0 表示关闭。请求、响应和输入落盘文件不保存鉴权头。 提取成功的原始 PDF 文字按原件 SHA256、提取版本、GLM/Qwen 服务及模型、思考开关和图片参数缓存。仅复用原始提取内容,本轮矿井绑定、监测输入、页面筛选和研判始终重新生成;旧 Agent 的提取事实档案或历史结论不会作为工作流 PDF 正文。存在失败页的提取不进入成功缓存。`MAIN_FAN_PDF_CACHE_POLICY=refresh` 强制重新提取并更新缓存,`off` 则不读写缓存。 新工作流默认禁止入推送队列,即使旧环境打开了 `RESULT_PLATFORM_ENABLED` 也不自动上报。以后需要工作流上报时,须同时显式开启 `MAIN_FAN_WORKFLOW_PUBLISH_ENABLED` 与总开关,并配置结果平台;本次不执行该步骤。总开关还控制其他业务任务及历史队列,所以服务器本地测试务必保留 `RESULT_PLATFORM_ENABLED=False`。 ## 服务器本地运行 仅把代码放到服务器还不会让现有容器加载新代码;需要使用包含新代码的环境或重新构建镜像。镜像构建只交付程序,不表示服务器已部署或真实研判已经通过。 在服务器项目根目录,使用安装了项目依赖的 Python: ```bash python scripts/run_main_fan_batch.py \ --dept-id 1045 \ --output outputs/main_fan_workflow/local-1045 ``` 不传 `--mock-pdf` 就使用当前真实报告采集服务和配置的原件目录。脚本独立创建业务/检查点数据库,固定关闭自动批次和所有结果推送,不影响现有应用的业务数据库;会实际访问网关、报告服务、OCR 和 Qwen。默认最多两个矿。若要测试指定合成原件,才额外传 `--mock-pdf /path/to/report.pdf`,结果必须保留模拟来源,不能替代目标矿井正式结论。 同目录可恢复未完成项;`--reassess` 保留旧结果版本后重新研判,复用符合条件的监测原件和成功 PDF 提取。不同执行版本不能直接当成当前版本的已完成批次,建议本地测试使用新的输出目录;跨版本 reassess 会重新发现测点,避免沿用旧版本错误的风机编号。风机识别支持“1号主扇二级”等名称,一级/二级不会作为风机编号。 已有批次 API 和启动/月度调度入口不用改 URL。测试时也可关掉启动批次和 cron,再从批次 API 显式选择单矿:`MAIN_FAN_BATCH_RUN_ON_STARTUP=False`、`MAIN_FAN_BATCH_CRON=`。 全矿研判可用 `MAIN_FAN_BATCH_CRON=0 2 1 * *`、`MAIN_FAN_BATCH_SPREAD_DAYS=20`、`MAIN_FAN_BATCH_RUN_ON_STARTUP=False`:每月1日02:00冻结清单,尽量平均分成20份,每天同一时刻处理一份,份内串行。重启复用原计划和当日批次;漏掉的份额顺延,每天最多一份,失败记录不阻塞下一份。详见[分日部署说明](DOCKER.md#每月启动分20天研判)。 启动批次可通过 `MAIN_FAN_BATCH_STARTUP_EXCLUDE_DEPT_IDS` 配置部门排除列表,值为 JSON 数组,默认 `[]`;数字和数字字符串均支持,重复项自动去重,不在候选清单中的排除项忽略。过滤发生在按 `MAIN_FAN_BATCH_STARTUP_MAX_MINES` 和保护上限取前 N 矿之前,保留原顺序并继续向后补选;剩余不足 N 矿时只处理剩余矿,全部排除时跳过启动批次。月度与手动批次继续使用各自原有范围。日志 `batch.startup.filtered` 记录实际排除的部门,`batch.startup.queued.selected_dept_ids` 记录最终入选项。 例如在 `docker run` 的镜像名前加上,排除部门 1045、1106: ```bash -e MAIN_FAN_BATCH_STARTUP_EXCLUDE_DEPT_IDS='[1045,1106]' \ ``` 修改配置后重新创建容器生效;需要使用包含这段筛选代码的新镜像。 ## 本地输出 每个通风井目录包含: | 文件 | 内容 | |---|---| | `待审阅报文.json` | 通过平台 schema 校验的标准推送 JSON;本地保存不等于已推送 | | `结果.json` / `报告.md` | 内部归档结果及由同一研判内容生成的可读报告 | | `给Qwen的数据.json` | 一次业务研判的三类输入,PDF 为筛选后的页面,监测数据保持完整 | | `PDF提取结果.json` | 全部 PDF 的物理页、提取方法、正文、失败原因和缓存记录 | | `PDF筛选记录.json` | 每份原件保留页数、排除的原物理页码和原因 | | `Qwen原始响应.json` | 首次业务研判原始响应,格式不合格时也不被覆盖 | | `Qwen最终响应.json` | 格式校验后的模型响应,可能来自一次格式修复;正式结论还要合并固定监测告警 | | `JSON格式修复记录.json` | 每次修复的请求/响应路径、次数、状态及失败原因 | | `监测规则判定.json` | 固定阈值、逐候选监测判定和最低综合告警等级 | | `最终结论核对.json` | 模型原判定、固定监测等级、最终采用的编码和正文 | | `给Agent的数据.json` | 兼容保留的监测预处理预览,不再是完整研判上下文 | | `研判数据.zip` / `数据清单.json` | 原 PDF、监测证据、逐页文字/图片、OCR 响应、完整 Qwen 请求/响应和 SHA256 | | `推送记录.json` | 默认 `local_only` | 失败任务尚无正式报文,但矿级 `workspace/exploration//workflow/` 下仍保存已完成提取、`qwen-input.json`、实际 `qwen-request.json`、收到的 `qwen-response.json` 和 `failure.json`(仅在执行到对应步骤时存在)。补识别文件为 `pdf-N/page-NNNN.qwen.request.json`、`.qwen.response.json`;GLM 文件继续保留。格式修复文件为 `qwen-repair-N-request.json`、`qwen-repair-N-response.json`,全部进入数据归档。 矿级 `execution-.json` 的诊断包含:`judgment_calls`(业务研判最多 1)、`qwen_ocr_calls`(失败页补识别)、`json_repair_calls`(格式修复最多 3)、`qwen_calls`(前三项合计),以及原页数、保留页数、排除页数、最终失败页数、研判策略版本和 `data_availability`。各类调用用量累计记账,不把补识别或修复算成新一轮业务研判。 标准平台报文仍为 `agentCode`、`agentName`、`taskId`、`checkedAt`、`mineCode`、`mineName`、`objectDetail`、`dataDetail`、`judgment`。其中 `judgment` 含 `resultCode`、`result`、`reason`、`evidenceLink`;编码仍为 `NORMAL/WARNING/ABNORMAL/UNDETERMINED`,含义按上述告警优先策略执行。