| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067 |
- /**
- * 票 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(/<!--[\s\S]*?-->/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 全部通过。")
|