/** * 票 67 闸门:全局「当前范围」(片区)与「行业」的筛选生效面。 * * 背景(2026-10-22 只读探针实测,原始载荷落在 artifacts/t67/):维护者反馈第 8 条 * 「当前范围没有影响到场景及物体的筛选及部分 UI 中的信息筛选」。逐端点双判据实测 * (「district=99 是否 400」+「district=0/1 响应是否真的不同」)的结论分两类: * * 真收片区(非法值 400):/map-snapshot、/infrastructure/base-archives、 * /infrastructure/management-information、/monitoring/distribution、 * 「/events/multidimensional-analysis」(唯一真收片区的事件端点) * 忽略片区(非法值也 200、响应只差 requestId):/events/current、/events/history-analysis、 * /events/handling-efficiency、/monitoring/overview、/monitoring/situation、 * /infrastructure/operating-information、/infrastructure/emergency-support、 * /overview/monitoring、/overview/warning-settings、/work-orders、/infrastructure/geometry * * 所以本票的交付不是「把所有端点都塞上 district」,而是分两类如实处理: * 1. 真收的端点接线(事件族里只有多维分析收片区); * 2. 忽略的端点不伪造参数,改成界面上如实说明「本块不随当前范围变化」。 * * 六组断言(不启浏览器、不联网;真实载荷用已落盘 artifacts): * R 后端事实复核:逐端点从 artifacts 复算「收/不收片区」的判据(不靠记忆)。 * A 参数面:注入 fetchImpl 捕获请求 URL,逐条断言「该带的带上、不该带的没有」 * (analysis 四个 scope 取值 + 90 天多窗每窗都带;current/history/efficiency 一律不带; * monitoring 三块分化;infrastructure 四块分化;overview 三块全不带)。 * B store 行为:真 pinia + 真 eventStore + 注入 transport。scope 是条件键—— * 变化时三个页码回 1 且请求带 district;同条件重取(定时刷新)不复位。 * C 外壳接线:stripComments 后源码断言 watch(scope) 调 eventStore、 * refreshCurrentModePanels 的 warningResponse 分支把 scope 一起传。 * D 如实说明:缺口句只有一处来源 domain/filterCaliber.js,由 domain/panelViews.js 的 * panelView() 出口(withFilterCaliber)统一并进视图的 gap —— 所以闸门也从 panelView() * 取视图(不另拼一份)。11 个未接线面板逐块都要有片区缺口句、已接线面板一块都不许有; * 全注册表再做一次正向普查(新增面板漏说明就红);SSR 断言 data-panel-gap 真的进 DOM; * 行业缺口只一处(报警效率面板的工单列表块 pageNote)。 * E 管线图层(维护者 2026-10-23 更正):管线编码是「片区 + 行业 + 时间编码」,所以片区 * **由编码前缀推导**(几何 properties 里确实没有片区字段,两件事不矛盾)。E1 推导正确率 * (6762/6762 与 name 的片区词一致、零反例)、E2 四个范围的筛选结果、E3 districtApplied * 语义(具体片区 true / 全域 false)、E4 推不出片区的对象如何计数与排除、E5 说明文案、 * E6 外壳与 store 接线、E7 行业×片区正交。 * F 不得伪造:反证组——current/history/efficiency 在任何 scope 下都不得出现 district * (后端实测忽略该参数,传了就是伪造一次筛选)。 * * 变异有牙见 t67-mutations.mjs。 * 运行:node .scratch/cockpit-real-data-wayfinding/verify/t67-filter-caliber.mjs */ import assert from "node:assert/strict" import { readFileSync } from "node:fs" import { registerHooks } from "node:module" import { parse, compileScript, compileTemplate } from "@vue/compiler-sfc" import { h } from "@vue/runtime-core" import { createSSRApp } from "vue" import { renderToString } from "@vue/server-renderer" import { createPinia, setActivePinia } from "pinia" const SRC = "../../../src/modules/cockpit/" const repoRoot = new URL("../../../", import.meta.url) const read = (rel) => readFileSync(new URL(rel, repoRoot), "utf8") /** 判据钉代码,不钉说明文字(t59 成例):注释里出现什么字样都不算命中。 */ const stripComments = (text) => String(text) .replace(/\/\*[\s\S]*?\*\//g, "") .replace(//g, "") .replace(/(^|[^:])\/\/.*$/gm, "$1") /** 从 text[start] 附近的花括号块取出完整片段(源码接线断言用;纯文本级别,够用)。 */ function braceBlock(text, start) { const open = text.indexOf("{", start) if (open < 0) return "" let depth = 0 for (let index = open; index < text.length; index += 1) { if (text[index] === "{") depth += 1 else if (text[index] === "}") { depth -= 1 if (depth === 0) return text.slice(start, index + 1) } } return "" } if (typeof globalThis.document === "undefined") globalThis.document = { activeElement: null } let passed = 0 let failed = 0 function check(name, condition, detail = "") { if (condition) { passed += 1; console.log("PASS " + name) } else { failed += 1; console.error("FAIL " + name + ": " + detail) } } // .vue 一律走编译钩子(默认 SSR 版)。本票只做 SSR 渲染判据,不启浏览器。 registerHooks({ load(url, context, nextLoad) { const isClient = url.endsWith(".vue?client") if (url.endsWith(".vue") || isClient) { const path = isClient ? url.slice(0, -"?client".length) : url const source = readFileSync(new URL(path), "utf8") const { descriptor } = parse(source, { filename: path }) const script = compileScript(descriptor, { id: "t67", genDefaultAs: "__sfc__" }) const template = compileTemplate({ source: descriptor.template.content, filename: path, id: "t67", ssr: !isClient, compilerOptions: { bindingMetadata: script.bindings }, }) assert.deepEqual(template.errors, [], path + " template") const tail = isClient ? "__sfc__.render = render; export default __sfc__;" : "__sfc__.ssrRender = ssrRender; export default __sfc__;" return { format: "module", shortCircuit: true, source: script.content + "\n" + template.code + "\n" + tail } } return nextLoad(url, context) }, }) // ---------------------------------------------------------------- 需要的模块 const { useEventStore } = await import(SRC + "stores/eventStore.js") const { fetchCurrentEvents } = await import(SRC + "services/eventApi.js") const { fetchEventAnalysis, fetchEventHistory, fetchHandlingEfficiency } = await import(SRC + "services/warningResponseApi.js") const { fetchMonitoringDistribution, fetchMonitoringOverview, fetchMonitoringSituation } = await import(SRC + "services/monitoringApi.js") const { fetchBaseArchives, fetchEmergencySupport, fetchManagementInformation, fetchOperatingInformation } = await import(SRC + "services/infrastructureApi.js") // 票 05–08(并行在制)把概览改成四个**不收入参**的新端点,本行按新导出取。 const { fetchPerception, fetchPipeNetwork, fetchWarningStudy, fetchAlarmDisposal } = await import(SRC + "services/overviewApi.js") const { fetchWorkOrderList } = await import(SRC + "services/detailApi.js") const { currentEventsView } = await import(SRC + "domain/eventList.js") const { responseEfficiencyPanel, eventAnalysisPanel } = await import(SRC + "domain/warningResponsePanels.js") const { panelView, hasPanelView, implementedPanelIds } = await import(SRC + "domain/panelViews.js") const { REGION_SCOPE_NOTE } = await import(SRC + "domain/monitoringPanels.js") const pipeNetworkModule = await import(SRC + "domain/pipeNetwork.js") const { DISTRICT_CODES } = await import(SRC + "domain/filterOptions.js") const { usePipeNetworkStore } = await import(SRC + "stores/pipeNetworkStore.js") /** * 管线领域的新接口用「命名空间 + 运行时探测」取:pipeDistrictOf / PIPE_DISTRICT_PREFIX * 还没落地时,E 组逐条报红(而不是整个闸门 import 失败、别的组的结论也看不到)。 */ const pipeFn = (name) => (typeof pipeNetworkModule[name] === "function" ? pipeNetworkModule[name] : null) const pipeDistrictOf = (code) => { const fn = pipeFn("pipeDistrictOf") return fn ? fn(code) : undefined } const filterPipeNetworkOf = (network, filters) => { const fn = pipeFn("filterPipeNetwork") return fn ? fn(network, filters) : null } const { SECTION_STATE, section } = await import(SRC + "domain/sectionState.js") const { default: PanelBlocks } = await import(SRC + "components/PanelBlocks.vue") // 单一口径来源(本票新增):文件还没落地时不要让整个闸门崩掉 —— D 组逐条报红更有用。 let caliber = null let caliberError = "" try { caliber = await import(SRC + "domain/filterCaliber.js") } catch (error) { caliberError = String((error && error.message) || error) } const caliberValue = (name, fallback) => (caliber && caliber[name] !== undefined ? caliber[name] : fallback) const gapSentence = (fnName, panelId) => { const fn = caliber && caliber[fnName] if (typeof fn !== "function") return "" try { return String(fn(panelId) || "") } catch { return "" } } const districtGapOf = (panelId) => gapSentence("districtGap", panelId) const industryGapOf = (panelId) => gapSentence("industryGap", panelId) /** 用户可见文案里不许出现接口字段名(票 05 纪律:读者是值班人员,不是接口调用方)。 */ const API_FIELD_TOKENS = [ "district", "districtApplied", "districtFilter", "geometry", "geometryType", "mapFilter", "workOrders", "pageSize", "snapshotAt", "pointStatus", "geoJson", ] const CAMEL_IDENTIFIER = /\b[a-z]+[A-Z][A-Za-z]{2,}\b/ const leakedFields = (text) => { const value = String(text || "") const hits = API_FIELD_TOKENS.filter((token) => value.toLowerCase().includes(token.toLowerCase())) const camel = CAMEL_IDENTIFIER.exec(value) if (camel) hits.push(camel[0]) return hits } /** 「不随当前范围变化」这类语义的措辞(任一命中即可;不钉具体句式,钉语义)。 */ const NOT_APPLIED = /不随|不作用|不生效|不参与|未提供|不提供|不含|没有/ const FALSELY_APPLIED = /已按片区|已按当前范围|片区筛选已生效/ const readySection = () => ({ ...section(SECTION_STATE.SUCCESS, { data: null }), loaded: true }) // ================================================================ R. 后端事实复核(artifacts 复算) const ARTIFACT = (name) => JSON.parse(readFileSync(new URL("./artifacts/t67/" + name, import.meta.url), "utf8")) /** * 请求级易变字段:两次探针必然不同(requestId 单调、窗口按当前时间重算、运行信息的 * 指标「已过去多少秒」随挂钟走),不算「响应不同」。 */ const VOLATILE = /(requestId|serverUpdatedAt|updatedAt|ageSeconds|\.range\.start$|\.range\.end$|geoWindow)/ function flatten(value, prefix = "", out = {}) { if (value && typeof value === "object") { for (const key of Object.keys(value)) flatten(value[key], prefix ? prefix + "." + key : key, out) return out } out[prefix] = value return out } /** district=0 与 district=1 的响应除易变字段外是否逐字节同(同=后端确实忽略片区)。 */ function sameModuloVolatile(left, right) { const a = flatten(left) const b = flatten(right) const keys = new Set([...Object.keys(a), ...Object.keys(b)]) const drift = [...keys].filter((key) => !VOLATILE.test(key) && JSON.stringify(a[key]) !== JSON.stringify(b[key])) return { same: drift.length === 0, drift } } { // R1 真收片区的五个端点:district=99 必须 400(非法值被校验=这个参数真的进了查询)。 const wired = ["map-snapshot", "infra-base-archives", "infra-management", "mon-distribution", "ev-analysis"] const verdicts = wired.map((name) => { const payload = ARTIFACT("v2-" + name + "-d99.json") return { name, code: payload && payload.code, msg: String((payload && payload.msg) || "") } }) check( "R1 真收片区的端点对 district=99 一律 400(参数确实生效)", verdicts.every((row) => Number(row.code) === 400 && /district/i.test(row.msg)), "实测:" + verdicts.map((row) => row.name + "=" + row.code).join(" "), ) // R2 被忽略的端点:district=99 也 200,且 district=0/1 只差易变字段。 const ignored = ["ev-current", "ev-history", "ev-efficiency", "mon-overview", "mon-situation", "infra-operating", "infra-emergency", "ov-monitoring", "ov-warning-settings", "work-orders"] const ignores = ignored.map((name) => { const d99 = ARTIFACT("v2-" + name + "-d99.json") const d0 = ARTIFACT("v2-" + name + "-d0.json") const d1 = ARTIFACT("v2-" + name + "-d1.json") const diff = sameModuloVolatile(d0, d1) return { name, code: d99 && d99.code, same: diff.same, drift: diff.drift } }) check( "R2 忽略片区的端点:district=99 也 200、且 district=0/1 响应除易变字段外完全一致", ignores.every((row) => Number(row.code) === 200 && row.same), "不符:" + ignores.filter((row) => Number(row.code) !== 200 || !row.same).map((row) => row.name + "(" + row.code + "," + row.drift.join("/") + ")").join(" "), ) // R3 geometry:6766 个 feature 没有任何片区字段 —— 这是「管线图层永远不随片区变化」的数据依据。 const geom = ARTIFACT("geom-none.json") const features = (geom.data && geom.data.features) || [] const propertyKeys = new Set() for (const feature of features) for (const key of Object.keys((feature && feature.properties) || {})) propertyKeys.add(key) const districtKeys = [...propertyKeys].filter((key) => /district|片区|area|region/i.test(key)) check( "R3 管线几何载荷里没有片区字段(管线图层不随片区变化是数据事实,不是前端偷懒)", features.length > 1000 && districtKeys.length === 0, "features=" + features.length + " 片区相关字段=" + (districtKeys.join(",") || "无"), ) } // ================================================================ 装配:注入 fetchImpl 捕获请求 URL const CONFIG = { dataSource: "real", api: { baseUrl: "http://stub.local/api/cockpit/v1", token: "" } } const REQUESTS = [] function eventItemRaw(no) { return { eventNo: no, eventName: "事件" + no, eventType: "WARN-WATER-LEVEL", level: 2, levelColor: "YELLOW", status: "PENDING", eventTime: "2026-10-21T10:00:00+08:00", deviceCode: "EQ-" + no, deviceName: "设备" + no, monitoringPointCode: "MP-1", location: "城区", industry: "GAS", value: 1.2, unit: "%LEL", } } function payloadFor(url) { const query = new URLSearchParams(url.split("?")[1] || "") const page = Number(query.get("page") || 1) const pageSize = Number(query.get("pageSize") || 10) const pagination = { page, pageSize, total: 25, totalPages: 3, hasMore: page < 3 } const range = { type: "TREND_WINDOW", startTime: query.get("startTime") || "", endTime: query.get("endTime") || "" } const family = query.get("eventFamily") || query.get("mode") || "WARNING" if (url.includes("/events/current")) { return { mode: family, displayName: "当前预警", updatedAt: "2026-10-21T10:00:00+08:00", filters: { mode: family, level: null, industry: null, status: null }, pagination, mapFilter: { mode: family, deviceCodes: [] }, sections: { events: { state: "SUCCESS", reason: "SUCCESS", message: null, data: [eventItemRaw("W-1")] }, workOrders: { state: "EMPTY", reason: "NO_WORK_ORDERS", message: null, data: null }, }, } } if (url.includes("/events/history-analysis")) { return { mode: family, range, events: [eventItemRaw("H-1")], rows: [{ timeBucket: range.startTime || "2026-10-21T00:00:00+08:00", eventCount: 1, handledCount: 0 }], pagination } } if (url.includes("/events/handling-efficiency")) { return { eventFamily: family, range, events: [eventItemRaw("E-1")], summary: { totalEvents: 1, linkedWorkOrderCount: 0, missingWorkOrderCount: 1, handlingEfficiency: { value: "0.5000", numerator: 1, denominator: 2, state: "AVAILABLE", reason: "READY" }, averageDuration: { value: "1.00", unit: "HOURS", state: "AVAILABLE", reason: "READY", sampleCount: 2 }, }, workOrderStateCounts: [], } } if (url.includes("/events/multidimensional-analysis")) { return { eventFamily: family, range, totalCount: 1, dimensions: [] } } if (url.includes("/work-orders")) { return { state: "SUCCESS", stateReason: "READY", stateDictionary: [], pagination, workOrders: [{ workOrderCode: "WO-1", stateCode: 0, stateName: "待派单", createTime: "2026-10-21T10:00:00+08:00", description: "样例工单" }], } } return {} } function captureFetch() { REQUESTS.length = 0 const fetchImpl = async (url) => { REQUESTS.push(String(url)) return { status: 200, ok: true, json: async () => ({ code: 200, msg: "ok", data: payloadFor(String(url)) }) } } return fetchImpl } const queryOf = (url) => new URLSearchParams(String(url).split("?")[1] || "") const districtOf = (url) => queryOf(url).get("district") const urlsWith = (fragment) => REQUESTS.filter((url) => String(url).includes(fragment)) const SCOPE_CASES = [ ["south", "0"], ["north", "1"], ["taichang", "2"], ["unassigned", "UNASSIGNED"], ] async function withCapture(run) { REQUESTS.length = 0 await run(captureFetch()) return [...REQUESTS] } // ================================================================ A. 参数面:该带的带上、不该带的没有 { // A1 多维分析(唯一真收片区的事件端点):四个片区取值逐个落成后端编码。 const analysis = [] for (const [scope, expected] of SCOPE_CASES) { const urls = await withCapture((fetchImpl) => fetchEventAnalysis({ mode: "WARNING", rangeType: "LAST_7_DAYS", scope, config: CONFIG, fetchImpl })) analysis.push({ scope, expected, got: districtOf(urls[0] || "") }) } check( "A1 多维分析把片区落成 district=0/1/2/UNASSIGNED(四个取值逐个对)", analysis.every((row) => row.got === row.expected), "实测:" + analysis.map((row) => row.scope + "=" + row.got).join(" "), ) // A2 全域 / 未传:不传 district(不是传空串占位,也不是传 0 冒充城南)。 const noneUrls = [ ...(await withCapture((fetchImpl) => fetchEventAnalysis({ mode: "WARNING", rangeType: "LAST_7_DAYS", scope: "all", config: CONFIG, fetchImpl }))), ...(await withCapture((fetchImpl) => fetchEventAnalysis({ mode: "WARNING", rangeType: "LAST_7_DAYS", config: CONFIG, fetchImpl }))), ] check( "A2 全域/未传片区时多维分析不带 district(全域不等于城南)", noneUrls.length === 2 && noneUrls.every((url) => queryOf(url).get("district") === null), "实测:" + noneUrls.map((url) => queryOf(url).get("district")).join(" / "), ) // A3 90 天多窗:后端单请求上限 31 天,前端切窗逐请求 —— 每一窗都要带同一个 district。 const windowUrls = await withCapture((fetchImpl) => fetchEventAnalysis({ mode: "WARNING", rangeType: "LAST_90_DAYS", scope: "south", config: CONFIG, fetchImpl })) const analysisWindows = urlsWith("/events/multidimensional-analysis") check( "A3 90 天多窗的每一窗都带同一个 district=0(不是只给第一窗带上)", windowUrls.length >= 2 && analysisWindows.length === windowUrls.length && analysisWindows.every((url) => districtOf(url) === "0"), "窗口数=" + analysisWindows.length + " district=" + analysisWindows.map((url) => districtOf(url)).join(","), ) // A4 当前/历史/效率三块事件端点:任何片区取值都不许带 district(后端实测忽略,传了就是伪造)。 const eventLeaks = [] for (const [scope] of [...SCOPE_CASES, ["all"]]) { await withCapture(async (fetchImpl) => { await fetchCurrentEvents({ mode: "WARNING", scope, config: CONFIG, fetchImpl }) await fetchEventHistory({ mode: "WARNING", rangeType: "LAST_7_DAYS", scope, config: CONFIG, fetchImpl }) await fetchHandlingEfficiency({ mode: "WARNING", rangeType: "LAST_7_DAYS", scope, config: CONFIG, fetchImpl }) }) for (const url of REQUESTS) { if (districtOf(url) !== null) eventLeaks.push(scope + ":" + url) } } check( "A4 当前事件/历史分析/处置效率在任意片区下都不带 district(后端忽略该参数)", eventLeaks.length === 0, "泄漏:" + eventLeaks.slice(0, 3).join(" | "), ) // A5 监测三块分化:分布真收片区,概览与运行概况不收。 const monitoringUrls = await withCapture(async (fetchImpl) => { await fetchMonitoringOverview({ scope: "north", config: CONFIG, fetchImpl }) await fetchMonitoringSituation({ scope: "north", config: CONFIG, fetchImpl }) await fetchMonitoringDistribution({ scope: "north", config: CONFIG, fetchImpl }) }) const distributionUrls = urlsWith("/monitoring/distribution") const noticeUrls = urlsWith("/monitoring/overview").concat(urlsWith("/monitoring/situation")) check( "A5 监测三块:分布带 district=1,概览与运行概况不带", distributionUrls.length === 1 && districtOf(distributionUrls[0]) === "1" && noticeUrls.length === 2 && noticeUrls.every((url) => districtOf(url) === null), "分布=" + districtOf(distributionUrls[0] || "") + " 概览/概况=" + noticeUrls.map((url) => districtOf(url)).join(",") + " 请求数=" + monitoringUrls.length, ) // A6 基础设施四块分化:基础档案与管理信息真收,运行信息与应急保障不收。 const infraUrls = await withCapture(async (fetchImpl) => { await fetchBaseArchives({ scope: "taichang", config: CONFIG, fetchImpl }) await fetchManagementInformation({ scope: "taichang", config: CONFIG, fetchImpl }) await fetchOperatingInformation({ scope: "taichang", config: CONFIG, fetchImpl }) await fetchEmergencySupport({ scope: "taichang", config: CONFIG, fetchImpl }) }) const archiveUrl = urlsWith("/infrastructure/base-archives")[0] const managementUrl = urlsWith("/infrastructure/management-information")[0] const silentInfra = urlsWith("/infrastructure/operating-information").concat(urlsWith("/infrastructure/emergency-support")) check( "A6 基础设施四块:档案与管理信息带 district=2,运行信息与应急保障不带", districtOf(archiveUrl || "") === "2" && districtOf(managementUrl || "") === "2" && silentInfra.length === 2 && silentInfra.every((url) => districtOf(url) === null), "档案=" + districtOf(archiveUrl || "") + " 管理=" + districtOf(managementUrl || "") + " 运行/应急=" + silentInfra.map((url) => districtOf(url)).join(",") + " 请求数=" + infraUrls.length, ) // A7 概览四块(票 05–08 的新四端点):按规格 §1 **都不收入参**,所以即便给了片区也不许带 district。 // 注:这四个端点在本机后端尚未部署(实测 404),因此这条判据钉的是**前端契约**(不擅自加参数), // 而不是"后端会忽略";等端点上线后由票 77 的复跑探针补实测证据。 const overviewUrls = await withCapture(async (fetchImpl) => { await fetchPerception({ scope: "south", config: CONFIG, fetchImpl }) await fetchPipeNetwork({ scope: "south", config: CONFIG, fetchImpl }) await fetchWarningStudy({ scope: "south", config: CONFIG, fetchImpl }) await fetchAlarmDisposal({ scope: "south", config: CONFIG, fetchImpl }) }) check( "A7 概览四块即便给了片区也不带 district(四个端点按规格都不收入参)", overviewUrls.length === 4 && overviewUrls.every((url) => districtOf(url) === null), "实测:" + overviewUrls.map((url) => districtOf(url)).join(","), ) // A8 同一批端点也不带行业:行业筛选在概览四块同样无处落脚(界面上由口径说明交代)。 const overviewIndustry = await withCapture(async (fetchImpl) => { await fetchPerception({ industry: "gas", config: CONFIG, fetchImpl }) await fetchPipeNetwork({ industry: "gas", config: CONFIG, fetchImpl }) await fetchWarningStudy({ industry: "gas", config: CONFIG, fetchImpl }) await fetchAlarmDisposal({ industry: "gas", config: CONFIG, fetchImpl }) }) check( "A8 概览四块即便给了行业也不带 industry(端点没有该入参,不得伪造)", overviewIndustry.length === 4 && overviewIndustry.every((url) => queryOf(url).get("industry") === null), "实测:" + overviewIndustry.map((url) => queryOf(url).get("industry")).join(","), ) // A9 工单列表(/work-orders)同样不收片区 —— 传了只会是一次静默无效的筛选。 const workOrderUrls = await withCapture((fetchImpl) => fetchWorkOrderList({ page: 1, pageSize: 5, scope: "south", industry: "gas" }, { config: CONFIG, fetchImpl })) check( "A9 工单列表不带 district(后端忽略该参数)", workOrderUrls.length === 1 && districtOf(workOrderUrls[0]) === null, "实测:" + workOrderUrls.map((url) => queryOf(url).get("district")).join(","), ) } // ================================================================ B. store 行为:scope 是条件键 { const pinia = createPinia() setActivePinia(pinia) const store = useEventStore() const boot = () => { store.reset() store.configure({ config: CONFIG, fetchImpl: captureFetch() }) } await boot() await store.loadPanels({ mode: "WARNING", range: "7d", scope: "south" }) // B1 首屏:四块面板各自的请求里,只有多维分析带片区。 const analysisUrl = urlsWith("/events/multidimensional-analysis")[0] const noDistrict = urlsWith("/events/current").concat( urlsWith("/events/history-analysis"), urlsWith("/events/handling-efficiency"), urlsWith("/work-orders"), ) check( "B1 store 首取:多维分析带 district=0,其余四块不带", districtOf(analysisUrl || "") === "0" && noDistrict.length === 4 && noDistrict.every((url) => districtOf(url) === null), "分析=" + districtOf(analysisUrl || "") + " 其余=" + noDistrict.map((url) => districtOf(url)).join(",") + "(请求 " + REQUESTS.length + " 个)", ) // B2 换片区:三个页码都回第 1 页,且请求带新片区的 district。 await store.setCurrentPage(2) await store.setHistoryPage(2) await store.setWorkOrderPage(2) const beforeSwitch = { current: store.currentPage, history: store.historyPage, workOrder: store.workOrderPage } REQUESTS.length = 0 await store.loadPanels({ scope: "north" }) const switchAnalysis = urlsWith("/events/multidimensional-analysis")[0] check( "B2 换片区(条件键变化):当前事件/历史/工单三个页码全部回 1,且请求带 district=1", beforeSwitch.current === 2 && store.currentPage === 1 && store.historyPage === 1 && store.workOrderPage === 1 && districtOf(switchAnalysis || "") === "1" && urlsWith("/events/current").every((url) => queryOf(url).get("page") === "1"), "切换前 " + JSON.stringify(beforeSwitch) + " → 切换后 current=" + store.currentPage + " history=" + store.historyPage + " workOrder=" + store.workOrderPage + ";分析 district=" + districtOf(switchAnalysis || "") + ";请求 " + REQUESTS.length + " 个", ) // B3 同条件重取(定时刷新,不带 conditions):页码保持、片区条件保持。 await store.setCurrentPage(2) REQUESTS.length = 0 await store.loadPanels() const refreshCurrent = urlsWith("/events/current")[0] check( "B3 同条件重取(定时刷新)不复位页码,且仍按当前片区取数", store.currentPage === 2 && queryOf(refreshCurrent || "").get("page") === "2" && districtOf(urlsWith("/events/multidimensional-analysis")[0] || "") === "1", "刷新后 page=" + store.currentPage + ";当前事件 page=" + queryOf(refreshCurrent || "").get("page") + ";分析 district=" + districtOf(urlsWith("/events/multidimensional-analysis")[0] || ""), ) // B4 条件键源码面(行为已在 B2 钉住;这条是给「谁把 scope 从表里拿掉」的直读信号)。 const storeSource = stripComments(read("src/modules/cockpit/stores/eventStore.js")) const conditionLine = /CONDITION_KEYS = \[([^\]]*)\]/.exec(storeSource) check( "B4 CONDITION_KEYS 含 scope(条件键表里点名,不只靠行为)", !!conditionLine && /['"]scope['"]/.test(conditionLine[1]), "CONDITION_KEYS=" + (conditionLine ? conditionLine[1].trim() : "未找到"), ) } // ================================================================ C. 外壳接线(stripComments 后源码) { const shell = stripComments(read("src/modules/cockpit/CockpitShell.vue")) const watchIndex = shell.indexOf("watch(scope") const watchBlock = watchIndex < 0 ? "" : braceBlock(shell, watchIndex) const watchCall = /eventStore\.loadPanels\(\{([^}]*)\}\)/.exec(watchBlock) check( "C1 watch(scope) 里调 eventStore.loadPanels 并把 scope 传下去", watchIndex >= 0 && !!watchCall && /\bscope\b/.test(watchCall[1]), watchIndex < 0 ? "没找到 watch(scope" : "watch(scope) 块:" + watchBlock.replace(/\s+/g, " ").slice(0, 240), ) const refreshIndex = shell.indexOf("function refreshCurrentModePanels") const refreshBlock = refreshIndex < 0 ? "" : braceBlock(shell, refreshIndex) const branchIndex = refreshBlock.indexOf("'warningResponse'") const branchTail = branchIndex < 0 ? "" : refreshBlock.slice(branchIndex) const branchCall = /eventStore\.loadPanels\(\{([^}]*)\}\)/.exec(branchTail) check( "C2 refreshCurrentModePanels 的 warningResponse 分支把 scope 一起传", refreshIndex >= 0 && branchIndex >= 0 && !!branchCall && /\bscope\b/.test(branchCall[1]), refreshIndex < 0 ? "没找到 refreshCurrentModePanels" : "分支调用:" + (branchCall ? branchCall[0].replace(/\s+/g, " ") : "未找到"), ) // C3 外壳仍守住「配置就绪后才发请求」(别为了接片区把既有的抢跑守卫拆了)。 check( "C3 watch(scope) 仍守住 lifecycleReady(不抢在配置就落地前发请求)", watchIndex >= 0 && /lifecycleReady\.value/.test(watchBlock), "watch(scope) 块里没有 lifecycleReady 守卫", ) } // ================================================================ D. 如实说明(单一口径来源 + 正向普查) /** * 逐面板产出真实视图。 * * 落点纪律(票 67 实施):缺口说明不是在各自 builder 里手写的,而是在 * domain/panelViews.js 的 panelView() 出口由 filterCaliber.withFilterCaliber() 统一并进 * 视图的 gap —— 所以闸门也**必须**从 panelView() 拿视图,才能验到真实产物(而不是另拼一份)。 * * currentEvents 是唯一例外:它由 EventListPane 自己读 store.view,不在注册表里, * 退到 currentEventsView 取视图(闸门只认「落到视图上」,不替它发明落点)。 */ function buildPanelView(panelId, extra) { const registered = hasPanelView(panelId) ? panelView(panelId, null, readySection(), extra) : null if (registered) return registered if (panelId === "currentEvents") return currentEventsView(null, readySection()) return null } /** 已接线(后端端点真收片区)的面板:不得出现片区缺口说明(说反了就是撒谎)。 */ const WIRED_DISTRICT = ["archive", "management", "monitoringDistribution", "monitoringPointLedger", "eventAnalysis"] /** 未接线(后端端点不认片区)的面板:每一块都必须有片区缺口说明。 */ const UNWIRED_DISTRICT = [ "currentEvents", "historyAnalysis", "responseEfficiency", "monitoringOverview", "operationOverview", "operation", "emergency", "perceptionDevices", "pipeNetwork", "warningStudy", "alarmDisposal", ] /** 行业缺口只登记一处:报警效率面板里走全量工单端点的「当前工单列表」块。 */ // 行业缺口有两类成因:① 工单端点实测不认行业;② 概览四块的端点按规格完全不收入参。 // 概览四块的片区/行业两条都登记 —— 维护者反馈第 8 条要求"行业也一并检查",只登记一半等于半答。 const UNWIRED_INDUSTRY = ["workOrderList", "perceptionDevices", "pipeNetwork", "warningStudy", "alarmDisposal"] /** 报警族的效率面板(工单列表块在这一支里)。 */ const alarmEfficiencyView = () => responseEfficiencyPanel({ family: "ALARM" }, readySection(), { mode: "ALARM", workOrderList: { ...section(SECTION_STATE.SUCCESS, { data: { orders: [], pagination: null, state: "", stateReason: "", dictionary: [] } }), loaded: true }, }) { const districtGaps = caliberValue("DISTRICT_GAPS", null) const industryGaps = caliberValue("INDUSTRY_GAPS", null) const declaredDistrict = districtGaps && typeof districtGaps === "object" ? Object.keys(districtGaps) : [] const declaredIndustry = industryGaps && typeof industryGaps === "object" ? Object.keys(industryGaps) : [] const registered = implementedPanelIds() // D0 口径表 vs 未接线普查集:双向一致(漏登记=面板漏说明;多登记=把真收片区的面板说成不收)。 const districtMismatch = [ ...UNWIRED_DISTRICT.filter((id) => !declaredDistrict.includes(id)), ...declaredDistrict.filter((id) => !UNWIRED_DISTRICT.includes(id)), ] check( "D0 DISTRICT_GAPS 的键集合=未接线面板集(漏登记/多登记都红)", declaredDistrict.length > 0 && districtMismatch.length === 0, "登记=" + declaredDistrict.join(",") + " 未接线=" + UNWIRED_DISTRICT.join(",") + (caliberError ? "(filterCaliber 加载失败:" + caliberError + ")" : ""), ) // D0b 行业缺口的两向锁定:工单列表必须在(它是实测缺口),且不许登记到概览四块 + 工单列表之外。 // 概览四块登记的依据写在 filterCaliber.js 里(票 05–08 规格 §1:四端点不收入参), // 与"实测后端忽略行业"是两种不同强度的证据,别混为一谈。 const industryMismatch = [ ...UNWIRED_INDUSTRY.filter((id) => !declaredIndustry.includes(id)), ...declaredIndustry.filter((id) => !UNWIRED_INDUSTRY.includes(id)), ] check( "D0b INDUSTRY_GAPS 的键集合=工单列表+概览四块(漏登记/多登记都红)", declaredIndustry.includes("workOrderList") && industryMismatch.length === 0, "登记=" + declaredIndustry.join(",") + " 应有=" + UNWIRED_INDUSTRY.join(","), ) // D0c 全注册表正向普查:**每一个**已实现面板要么有片区缺口、要么是已接线面板 —— // 新增面板却两样都不是 ⇒ 红(「谁漏了就红」不靠人工记得改这张表)。 const unclassified = registered.filter((id) => !declaredDistrict.includes(id) && !WIRED_DISTRICT.includes(id)) const phantomWired = WIRED_DISTRICT.filter((id) => !registered.includes(id)) check( "D0c 全注册表普查:每个已实现面板要么有片区缺口、要么是已接线(新增面板漏说明就红)", registered.length >= 10 && unclassified.length === 0 && phantomWired.length === 0, "未分类=" + unclassified.join(",") + " 已接线但未注册=" + phantomWired.join(",") + " 注册数=" + registered.length, ) // D1 每句片区缺口:含「当前范围」、说明不作用于本面板及原因、含片区语义、不含接口字段名。 for (const panelId of UNWIRED_DISTRICT) { const sentence = districtGapOf(panelId) check( "D-sentence:" + panelId, sentence.length >= 12 && sentence.includes("当前范围") && /片区/.test(sentence) && NOT_APPLIED.test(sentence) && !FALSELY_APPLIED.test(sentence) && leakedFields(sentence).length === 0, "句=" + (sentence || "(空)") + " 泄漏=" + leakedFields(sentence).join(","), ) } // D2 行业缺口句本身:含「行业」、说明不生效及原因、不含接口字段名。 for (const panelId of UNWIRED_INDUSTRY) { const sentence = industryGapOf(panelId) check( "D-industry-gap:" + panelId, sentence.length >= 12 && sentence.includes("行业") && NOT_APPLIED.test(sentence) && !FALSELY_APPLIED.test(sentence) && leakedFields(sentence).length === 0, "句=" + (sentence || "(空)") + " 泄漏=" + leakedFields(sentence).join(","), ) } // D3 正向普查(行为面):每一块未接线面板的**视图** gap 都要含它那句(谁漏了就红)。 const missingGaps = [] for (const panelId of UNWIRED_DISTRICT) { const view = buildPanelView(panelId) const sentence = districtGapOf(panelId) if (!view) missingGaps.push(panelId + "(无视图)") else if (!String(view.gap || "").includes(sentence)) missingGaps.push(panelId) } check( "D3 未接线面板的视图 gap 都含各自缺口句(正向普查,漏一个就红)", missingGaps.length === 0, "缺:" + missingGaps.join(","), ) for (const panelId of UNWIRED_DISTRICT) { const view = buildPanelView(panelId) const sentence = districtGapOf(panelId) check( "D-gap:" + panelId, !!view && !!sentence && String(view.gap || "").includes(sentence), (view ? "gap=" + String(view.gap || "(空)").slice(0, 100) : "视图未产出(panelView 返回 null)"), ) } // D3b 行业缺口落在工单列表块的 pageNote 上(data-list-page-note 渲染位); // 同一屏上方分布随行业变、下方列表不变,不说明就是看不出来的假象。 const alarmView = alarmEfficiencyView() const workOrderBlock = (alarmView.blocks || []).find((block) => String(block.pageNote || "").includes(industryGapOf("workOrderList"))) check( "D3b 报警效率面板的工单列表块 pageNote 含行业缺口句(含「行业」「未提供行业入参」)", !!workOrderBlock && /行业/.test(String(workOrderBlock.pageNote)) && /未提供行业入参/.test(String(workOrderBlock.pageNote)), "块 pageNote=" + (workOrderBlock ? String(workOrderBlock.pageNote).slice(0, 160) : "未找到携带行业缺口的 list 块"), ) // D4 反向普查:已接线面板**不得**出现"不作用于本面板"的片区缺口; // 监测分布自己的片区落点说明(REGION_SCOPE_NOTE)必须保留,别被当成缺口删掉。 const wronglyGapped = [] for (const panelId of WIRED_DISTRICT) { const view = hasPanelView(panelId) ? panelView(panelId, null, readySection(), panelId === "monitoringPointLedger" ? undefined : undefined) : null if (districtGapOf(panelId) !== "") wronglyGapped.push(panelId + "(登记了缺口)") else if (view && /不作用于本面板/.test(String(view.gap || ""))) wronglyGapped.push(panelId + "(视图里有缺口句)") } check( "D4 已接线面板(档案/管理/监测分布/点位台账/预警分析)都没有片区缺口句", wronglyGapped.length === 0, "误挂:" + wronglyGapped.join(","), ) const distributionView = hasPanelView("monitoringDistribution") ? panelView("monitoringDistribution", null, readySection()) : null const regionNote = String(REGION_SCOPE_NOTE || "") check( "D4b 监测分布的既有片区落点说明仍保留(那是「作用在哪」,不是缺口)", !!distributionView && String(distributionView.gap || "").includes(regionNote), "gap=" + String(distributionView && distributionView.gap), ) // D5 全量豁免扫描:所有缺口句 + 所有视图 gap 里都没有接口字段名。 const sentences = [ ...UNWIRED_DISTRICT.map(districtGapOf), ...UNWIRED_INDUSTRY.map(industryGapOf), String(caliberValue("PIPE_DISTRICT_NOTE", "")), ] const viewGaps = registered.map((panelId) => { try { return String((panelView(panelId, null, readySection()) || {}).gap || "") } catch (error) { return "" } }) const leaks = [...sentences, ...viewGaps].flatMap((text) => leakedFields(text).map((token) => token + "@" + String(text).slice(0, 20))) check( "D5 口径句里不出现接口字段名(district / geometry / mapFilter 这类英文词不上屏)", sentences.every((sentence) => typeof sentence === "string") && leaks.length === 0, "泄漏:" + leaks.slice(0, 5).join(" "), ) // D6 筛选条的措辞与说明用同一个词(单一口径来源)。 check( "D6 SCOPE_FILTER_LABEL 就是「当前范围」", caliberValue("SCOPE_FILTER_LABEL", null) === "当前范围", "实测=" + caliberValue("SCOPE_FILTER_LABEL", "(未导出)"), ) } // ---------------------------------------------------------------- D7 SSR:缺口说明真的进 DOM { const renderPanel = async (view) => renderToString(createSSRApp({ render: () => h(PanelBlocks, { view }) })) const domMisses = [] for (const panelId of UNWIRED_DISTRICT) { if (panelId === "currentEvents") continue // 事件列表是独立组件,见 D7b const view = buildPanelView(panelId) const sentence = districtGapOf(panelId) if (!view || !sentence) { domMisses.push(panelId + "(无视图/无句)"); continue } const html = await renderPanel(view) if (!/data-panel-gap/.test(html) || !html.includes(sentence.slice(0, 10))) domMisses.push(panelId) } check( "D7 SSR:PanelBlocks 十个面板的缺口说明都带 data-panel-gap 真的进了 DOM", domMisses.length === 0, "缺:" + domMisses.join(","), ) // D7b 当前预警走独立的事件列表组件:缺口说明也要有 DOM 出口(字段里有、屏上没有等于没说)。 const paneSource = stripComments(read("src/modules/cockpit/components/EventListPane.vue")) check( "D7b 事件列表组件里有 data-panel-gap 出口(当前预警的缺口说明不只存在字段里)", /data-panel-gap/.test(paneSource) && /view\.gap/.test(paneSource), "EventListPane 里没有 data-panel-gap / view.gap", ) } // ================================================================ E. 管线图层:片区由设施编码前缀推导 /** * 维护者 2026-10-23 更正:管线编码 network_code 的构成是「片区 + 行业 + 时间编码」 * (TCRQ202609080001 = 太常·燃气·管),所以片区**可以**从 code 前两位推出来。 * 上一版「管线图层永远不随片区变化」的结论因此作废 —— 那条只对「几何 properties 里有片区字段」 * 成立(实测 properties 确实没有片区字段),两件事不矛盾:**字段没有,编码里有**。 * * 本组从「如实说明做不到」改成五件事:推导正确性(E1)、筛选结果(E2)、 * districtApplied 语义(E3)、推不出片区时怎么办(E4)、说明文案与接线(E5/E6)、 * 行业×片区正交(E7)。真实载荷用 artifacts/t67/geom-none.json(6766 feature)。 */ const GEOM_PAYLOAD = ARTIFACT("geom-none.json") const REAL_FEATURES = (GEOM_PAYLOAD && GEOM_PAYLOAD.data && GEOM_PAYLOAD.data.features) || [] /** 片区号 → 中文(只用于和 name 里的片区词对照,不参与推导)。 */ const DISTRICT_NAME = { 0: "城南", 1: "城北", 2: "太常" } const codePrefix = (code) => { const matched = /^([A-Za-z]{2})/.exec(String(code || "")) return matched ? matched[1].toUpperCase() : "" } /** 真实载荷里出现过的编码前缀分布(现算,不写死「现网有什么」)。 */ const PREFIX_COUNTS = (() => { const counts = new Map() for (const feature of REAL_FEATURES) { const prefix = codePrefix(((feature && feature.properties) || {}).code) counts.set(prefix, (counts.get(prefix) || 0) + 1) } return counts })() const prefixCount = (prefix) => PREFIX_COUNTS.get(prefix) || 0 /** 真实载荷 → 图层领域对象(toPipeNetwork,票 24 的同一个入口,不另拼一份)。 */ const REAL_LAYER = (() => { const build = pipeFn("toPipeNetwork") if (!build) return null try { return build(GEOM_PAYLOAD.data) } catch (error) { return null } })() const withPrefix = (rows, prefix) => (rows || []).filter((row) => codePrefix(row.code) === prefix) /** 推不出片区的行(district 不是 0/1/2,含 null 与字段缺失)。 */ const unresolvedRows = (rows) => (rows || []).filter((row) => row.district !== 0 && row.district !== 1 && row.district !== 2) const filterLayer = (filters) => (REAL_LAYER ? filterPipeNetworkOf(REAL_LAYER, filters) : null) const layerObjects = (layer) => [...((layer && layer.pipes) || []), ...((layer && layer.components) || []), ...((layer && layer.blocked) || [])] const drawnObjects = (layer) => [...((layer && layer.pipes) || []), ...((layer && layer.components) || [])] { // E1 前缀表与全局片区编码同值(不另造一套片区号),且推导本身在真实载荷上 100% 对得上 name。 const prefixes = pipeNetworkModule.PIPE_DISTRICT_PREFIX || null check( "E1 前缀→片区表与全局片区编码同值(CN=城南0 / CB=城北1 / TC=太常2)", !!prefixes && prefixes.CN === DISTRICT_CODES.south && prefixes.CB === DISTRICT_CODES.north && prefixes.TC === DISTRICT_CODES.taichang, "前缀表=" + JSON.stringify(prefixes) + " DISTRICT_CODES=" + JSON.stringify(DISTRICT_CODES), ) const resolver = pipeFn("pipeDistrictOf") const resolved = [] const unresolved = [] for (const feature of REAL_FEATURES) { const properties = (feature && feature.properties) || {} const district = pipeDistrictOf(properties.code) if (district === 0 || district === 1 || district === 2) resolved.push({ code: properties.code, name: properties.name, district }) else unresolved.push({ code: properties.code, name: properties.name }) } const counterexamples = resolved.filter((row) => !String(row.name || "").includes(DISTRICT_NAME[row.district])) check( "E1b 真实载荷 6766 条:编码前缀推出的片区与 name 里的片区词 100% 一致、零反例", !!resolver && REAL_FEATURES.length === 6766 && resolved.length === 6762 && counterexamples.length === 0, "推出 " + resolved.length + "/" + REAL_FEATURES.length + " 条;反例=" + counterexamples.slice(0, 3).map((row) => row.code + "→" + DISTRICT_NAME[row.district] + " 但 name=" + row.name).join(" | "), ) const unresolvedCodes = unresolved.map((row) => row.code).sort() check( "E1c 编码推不出片区的只有 4 条(YL/GD 另一套编码),一律回 null、不猜成任何片区", unresolved.length === 4 && unresolvedCodes.join(",") === ["GD-001", "YL-YJ-001", "YL-YJ-002", "YL-YJ-003"].join(",") && unresolved.every((row) => pipeDistrictOf(row.code) === null), "推不出:" + unresolvedCodes.join(","), ) // 现网没有城北数据:按载荷现算(前缀 0 条 + name 含「城北」0 条),映射本身仍然在表里。 const chengbeiNames = REAL_FEATURES.filter((feature) => String(((feature && feature.properties) || {}).name || "").includes(DISTRICT_NAME[1])) check( "E1d 现网无城北数据(CB 前缀 0 条、name 含「城北」0 条)——按载荷现算,映射仍保留", prefixCount("CB") === 0 && chengbeiNames.length === 0 && !!prefixes && prefixes.CB === DISTRICT_CODES.north, "CB 前缀=" + prefixCount("CB") + " name 含城北=" + chengbeiNames.length + " 映射存在=" + Boolean(prefixes && prefixes.CB !== undefined), ) } { // E2 四个范围的筛选结果(期望值全部从真实载荷现算,不写死数字)。 const layer = REAL_LAYER const south = filterLayer({ scope: "south" }) const taichang = filterLayer({ scope: "taichang" }) const north = filterLayer({ scope: "north" }) const all = filterLayer({ scope: "all" }) const expectedSouth = withPrefix(layer && layer.pipes, "CN") const expectedTaichang = withPrefix(layer && layer.pipes, "TC") check( "E2 scope=south 只留 CN 编码管段、taichang 只留 TC(数量与载荷逐类一致)", !!layer && !!south && !!taichang && south.pipes.length === expectedSouth.length && south.pipes.length > 0 && south.pipes.every((row) => codePrefix(row.code) === "CN" && row.district === 0) && taichang.pipes.length === expectedTaichang.length && taichang.pipes.length > 0 && taichang.pipes.every((row) => codePrefix(row.code) === "TC" && row.district === 2), "城南=" + (south ? south.pipes.length : "null") + "/" + expectedSouth.length + " 太常=" + (taichang ? taichang.pipes.length : "null") + "/" + expectedTaichang.length, ) check( "E2b scope=all 保留全部管段(不裁剪),且管段仍带各自的片区号", !!layer && !!all && all.pipes.length === layer.pipes.length && all.pipes.every((row) => row.district === 0 || row.district === 2), "全域=" + (all ? all.pipes.length : "null") + "/" + (layer ? layer.pipes.length : "null"), ) check( "E2c scope=north 如实为空(现网没有城北编码),不是「接线错了」", !!north && north.pipes.length === 0 && (north.components || []).length === 0 && prefixCount("CB") === 0, "城北结果 管段=" + (north ? north.pipes.length : "null") + " 构件=" + (north ? (north.components || []).length : "null"), ) } { // E3 districtApplied 语义:具体片区 true、全域/空 false(不再恒定 false)。 const appliedOf = (scope) => { const result = filterPipeNetworkOf(REAL_LAYER || { pipes: [], components: [], blocked: [] }, { scope }) return result ? result.districtApplied : undefined } const concrete = ["south", "north", "taichang"].map((scope) => ({ scope, applied: appliedOf(scope) })) const broad = [{ scope: "all" }, { scope: undefined }, { scope: "" }].map((row) => ({ scope: String(row.scope), applied: appliedOf(row.scope) })) check( "E3 districtApplied 语义:具体片区=true、全域/空=false(管线片区真的生效了才说生效)", concrete.every((row) => row.applied === true) && broad.every((row) => row.applied === false), "具体=" + concrete.map((row) => row.scope + ":" + String(row.applied)).join(" ") + " 全域=" + broad.map((row) => row.scope + ":" + String(row.applied)).join(" "), ) } { // E4 推不出片区的对象:全域保留、选具体片区时排除,并如实计数(不静默保留、也不静默消失)。 // // 口径(维护者 2026-10-23 复核):真实载荷 6766 条 = pipes 6762 + components 1 + blocked 3, // 其中编码推不出片区的 4 个里只有 1 个是**可上图**对象(YL-YJ-003),另外 3 个本来就被 // 「无地图位置」拦下。所以 districtUnresolvedCount 只统计可上图对象(pipes + components)—— // blocked 已经在「无地图位置」清单里单独报告过,再计一次就是双报。 // 期望值一律**从载荷算**(不写死 4 / 1),并断言它 > 0,否则这条断言在「全都能推导」时变空转。 const layer = REAL_LAYER const universe = layerObjects(layer) const all = filterLayer({ scope: "all" }) const south = filterLayer({ scope: "south" }) const north = filterLayer({ scope: "north" }) const unassigned = filterLayer({ scope: "unassigned" }) const unresolvedTotal = unresolvedRows(universe).length const expectedDrawn = unresolvedRows(drawnObjects(layer)).length const fourCodes = ["GD-001", "YL-YJ-001", "YL-YJ-002", "YL-YJ-003"] const codesIn = (result) => new Set(layerObjects(result).map((row) => row.code)) check( "E4 全域时推不出片区的对象全都还在(不被静默丢掉),且不计缺口数", !!layer && !!all && unresolvedTotal === 4 && layerObjects(all).length === universe.length && fourCodes.every((code) => codesIn(all).has(code)) && all.districtUnresolvedCount === 0, "全域对象数=" + layerObjects(all).length + "/" + universe.length + " 推不出=" + unresolvedTotal + " 计数=" + (all && all.districtUnresolvedCount), ) check( "E4b 选具体片区时推不出片区的对象不进结果,districtUnresolvedCount 只报可上图对象里被排除的条数", !!south && !!north && expectedDrawn > 0 && unresolvedRows(drawnObjects(south)).length === 0 && unresolvedRows(drawnObjects(north)).length === 0 && fourCodes.every((code) => !unresolvedRows(drawnObjects(layer)).some((row) => row.code === code) || !codesIn(south).has(code)) && south.districtUnresolvedCount === expectedDrawn && north.districtUnresolvedCount === expectedDrawn, "期望缺口数(可上图推不出)=" + expectedDrawn + " 城南=" + (south && south.districtUnresolvedCount) + " 城北=" + (north && north.districtUnresolvedCount), ) // blocked 与行业同一条既有行为:也按片区过滤(推不出片区的那些在选具体片区时不出现)。 check( "E4c blocked(无地图位置清单)同样按片区过滤:选具体片区时只剩该片区的对象", !!south && !!north && (south.blocked || []).every((row) => row.district === 0) && (north.blocked || []).length === 0, "城南 blocked=" + (south ? (south.blocked || []).length : "null") + " 城北 blocked=" + (north ? (north.blocked || []).length : "null"), ) // 「未分区」是推不出片区那批对象的读法(与后端 district=null 同一口径),不是空集。 check( "E4d scope=unassigned 的语义=推不出片区的那批对象(可上图的那 1 个真被单独列出来)", !!unassigned && unassigned.districtApplied === true && unresolvedRows(drawnObjects(unassigned)).length === expectedDrawn && unresolvedRows(drawnObjects(unassigned)).length === drawnObjects(unassigned).length, "未分区 可上图=" + (unassigned ? drawnObjects(unassigned).length : "null") + "/" + expectedDrawn, ) } { // E5 管线片区说明:不再是「不作用于」,而是说清「由编码前缀推出、几何没有片区字段、推不出时不显示」。 const pipeNote = String(caliberValue("PIPE_DISTRICT_NOTE", "")) check( "E5 PIPE_DISTRICT_NOTE 改写如实(当前范围 + 编码前缀推出 + 几何无片区字段 + 推不出不显示,且不再写「不作用于管线」)", pipeNote.length >= 20 && pipeNote.includes("当前范围") && /编码/.test(pipeNote) && /前缀/.test(pipeNote) && /推出|推导/.test(pipeNote) && /几何/.test(pipeNote) && /(没有|不含|未提供).{0,8}片区字段|片区字段.{0,8}(没有|不含)/.test(pipeNote) && !/不作用于管线/.test(pipeNote) && !/不作用于(管线|构件)/.test(pipeNote) && !FALSELY_APPLIED.test(pipeNote) && !/[0-9]{3,}/.test(pipeNote) && leakedFields(pipeNote).length === 0, "句=" + (pipeNote || "(空)"), ) } { // E6 接线:外壳 watch(scope) 走本地推导(不重发请求),启动取数带 scope;store 派生随之生效。 const shell = stripComments(read("src/modules/cockpit/CockpitShell.vue")) const watchIndex = shell.indexOf("watch(scope") const watchBlock = watchIndex < 0 ? "" : braceBlock(shell, watchIndex) check( "E6 watch(scope) 里调 pipeNetworkStore.setScope(本地推导,不重新发请求)", watchIndex >= 0 && /pipeNetworkStore\.setScope\(/.test(watchBlock), watchIndex < 0 ? "没找到 watch(scope" : "watch(scope) 块:" + watchBlock.replace(/\s+/g, " ").slice(0, 240), ) const loadMatched = /pipeNetworkStore\.load\(\{([\s\S]{0,300}?)\}\)/.exec(shell) check( "E6b 启动取数把 scope 一起传(图层取数后即按当前范围推导)", !!loadMatched && /\bscope\b/.test(loadMatched[1]), loadMatched ? "load 调用:" + loadMatched[0].replace(/\s+/g, " ").slice(0, 200) : "没找到 pipeNetworkStore.load(", ) let storeResult = null let storeError = "" try { const pinia = createPinia() setActivePinia(pinia) const pipeStore = usePipeNetworkStore() if (typeof pipeStore.setScope !== "function") storeError = "store 没有 setScope" else if (!REAL_LAYER) storeError = "真实图层未产出" else { pipeStore.setLayer(section(SECTION_STATE.SUCCESS, { data: REAL_LAYER })) pipeStore.setScope("south") const southPipelines = [...(pipeStore.pipelines || [])] const southApplied = pipeStore.filtered ? pipeStore.filtered.districtApplied : undefined pipeStore.setScope("north") const northPipelines = [...(pipeStore.pipelines || [])] pipeStore.setScope("all") const allPipelines = [...(pipeStore.pipelines || [])] const allApplied = pipeStore.filtered ? pipeStore.filtered.districtApplied : undefined storeResult = { southPipelines, northPipelines, allPipelines, southApplied, allApplied } } } catch (error) { storeError = String((error && error.message) || error) } check( "E6c pipeNetworkStore 派生:城南只剩 CN、城北为空、全域恢复,districtApplied 随之翻转", !!storeResult && storeResult.southPipelines.length > 0 && storeResult.southPipelines.every((row) => row.district === 0) && storeResult.northPipelines.length === 0 && storeResult.allPipelines.length === (REAL_LAYER ? REAL_LAYER.pipes.length : -1) && storeResult.southApplied === true && storeResult.allApplied === false, storeError || "store 派生=" + JSON.stringify(storeResult && { south: storeResult.southPipelines.length, north: storeResult.northPipelines.length, all: storeResult.allPipelines.length, southApplied: storeResult.southApplied, allApplied: storeResult.allApplied }), ) } { // E7 行业 × 片区正交:两个筛选各管一维,结果应是交集而不是互相覆盖。 const layer = REAL_LAYER const gasSouth = filterLayer({ industry: "gas", scope: "south" }) const gasTaichang = filterLayer({ industry: "gas", scope: "taichang" }) const gasAll = filterLayer({ industry: "gas", scope: "all" }) const expectedSouth = withPrefix(layer && layer.pipes, "CN").filter((row) => row.industry === "GAS") const expectedTaichang = withPrefix(layer && layer.pipes, "TC").filter((row) => row.industry === "GAS") check( "E7 行业×片区正交:燃气∩城南 + 燃气∩太常 = 燃气全域,两集合不相交", !!gasSouth && !!gasTaichang && !!gasAll && expectedSouth.length > 0 && expectedTaichang.length > 0 && gasSouth.pipes.length === expectedSouth.length && gasTaichang.pipes.length === expectedTaichang.length && gasSouth.pipes.length + gasTaichang.pipes.length === gasAll.pipes.length && gasSouth.pipes.every((row) => row.district === 0 && row.industry === "GAS") && gasTaichang.pipes.every((row) => row.district === 2 && row.industry === "GAS") && gasAll.pipes.every((row) => row.industry === "GAS"), "燃气 城南=" + (gasSouth ? gasSouth.pipes.length : "null") + "/" + expectedSouth.length + " 太常=" + (gasTaichang ? gasTaichang.pipes.length : "null") + "/" + expectedTaichang.length + " 全域=" + (gasAll ? gasAll.pipes.length : "null"), ) } // ================================================================ F. 不得伪造(反证组) { // F1 三个事件端点:任何片区取值下都不得出现 district 参数(后端忽略,传了就是伪造一次筛选)。 const forbidden = [] for (const [scope] of [...SCOPE_CASES, ["all"], [undefined]]) { await withCapture(async (fetchImpl) => { await fetchCurrentEvents({ mode: "WARNING", scope, config: CONFIG, fetchImpl }) await fetchEventHistory({ mode: "WARNING", rangeType: "LAST_30_DAYS", scope, config: CONFIG, fetchImpl }) await fetchHandlingEfficiency({ mode: "WARNING", rangeType: "LAST_30_DAYS", scope, config: CONFIG, fetchImpl }) }) for (const url of REQUESTS) if (queryOf(url).has("district")) forbidden.push(String(scope) + ":" + url) } check( "F1 当前事件/历史/效率在全部片区取值下都不出现 district(含 30 天多窗)", forbidden.length === 0, "出现:" + forbidden.slice(0, 3).join(" | "), ) // F2 反向:真收片区的端点必须带(漏带=静默少筛一层,同样是假)。 await withCapture(async (fetchImpl) => { await fetchMonitoringDistribution({ scope: "south", config: CONFIG, fetchImpl }) await fetchBaseArchives({ scope: "south", config: CONFIG, fetchImpl }) await fetchManagementInformation({ scope: "south", config: CONFIG, fetchImpl }) await fetchEventAnalysis({ mode: "WARNING", rangeType: "LAST_7_DAYS", scope: "south", config: CONFIG, fetchImpl }) }) const wiredMisses = [] for (const fragment of ["/monitoring/distribution", "/infrastructure/base-archives", "/infrastructure/management-information", "/events/multidimensional-analysis"]) { const url = urlsWith(fragment)[0] if (!url || districtOf(url) !== "0") wiredMisses.push(fragment + "=" + (url ? districtOf(url) : "无请求")) } check( "F2 真收片区的四个端点都带上了 district=0(漏带也是假,不只是多带)", wiredMisses.length === 0, "漏:" + wiredMisses.join(" "), ) // F3 事件族只有多维分析一处接片区(别把「当前事件」也接上——那正是 M2 要证明能抓住的伪造)。 const warningSource = stripComments(read("src/modules/cockpit/services/warningResponseApi.js")) const eventSource = stripComments(read("src/modules/cockpit/services/eventApi.js")) const districtCalls = (warningSource.match(/districtQuery\(/g) || []).length check( "F3 片区入参在事件族只有多维分析一处(eventApi 源码里不出现 district)", !/district/i.test(eventSource) && districtCalls >= 1, "eventApi 出现 district=" + /district/i.test(eventSource) + ";warningResponseApi 的 districtQuery 次数=" + districtCalls, ) } // ================================================================ 结果 console.log("") console.log("T67 筛选口径闸门:" + passed + "/" + (passed + failed) + " 项通过。") if (failed) { console.error("未通过 " + failed + " 项。") process.exit(1) } console.log("T67 全部通过。")