const sources = [ { id: "boss", name: "BOSS直聘", short: "BOSS", category: "commercial", categoryLabel: "商业平台", domain: "www.bosszhipin.com", adapter: "BOSS-Collector 4.6", mode: "网页/API混合采集", schedule: "每 6 小时", status: "running", statusLabel: "正式运行", health: 98.6, today: 68420, total: "4.86M", updated: "10:42", coverage: "全国 · 重点产业岗位", contract: "JOB-SIGNAL V3.0", schema: "SOURCE-BOSS 2.5", version: "SRC-BOSS-20260823-04", owner: "商业源采集组", retention: "原始响应 180 天 · 解析结果 36 个月", rate: "自适应窗口 · 64 分片", retry: "指数退避 · 最多 5 次", change: "新增低空经济、具身智能岗位词表;更新城市筛选与增量水位。", repo: "connector-registry/boss-collector", codeVersion: "release/4.6.2 · 4f8d91a", runtime: [ ["10:42", "增量分片完成", "64/64 分片 · 68,420 条"], ["06:18", "原始快照固化", "RAW-BOSS-20260823-01 · SHA-256 已记录"], ["02:31", "解析规则发布", "PARSER-BOSS 4.6.2 · 回归样本 1,000 条"], ], }, { id: "zhaopin", name: "智联招聘", short: "智联", category: "commercial", categoryLabel: "商业平台", domain: "www.zhaopin.com", adapter: "ZHAOPIN-Collector 3.8", mode: "网页/API混合采集", schedule: "每 6 小时", status: "running", statusLabel: "正式运行", health: 97.9, today: 52360, total: "3.92M", updated: "10:38", coverage: "全国 · 校招与社会招聘", contract: "JOB-SIGNAL V3.0", schema: "SOURCE-ZL 2.2", version: "SRC-ZL-20260823-04", owner: "商业源采集组", retention: "原始响应 180 天 · 解析结果 36 个月", rate: "自适应窗口 · 48 分片", retry: "指数退避 · 最多 5 次", change: "补充企业性质与招聘人数映射;岗位有效期按源端状态增量维护。", repo: "connector-registry/zhaopin-collector", codeVersion: "release/3.8.1 · 96c2be7", runtime: [ ["10:38", "增量任务运行中", "44/48 分片 · 52,360 条"], ["06:05", "字段完整性预检", "完整率 98.8% · 通过"], ["02:16", "代码版本部署", "ZHAOPIN-Collector 3.8.1"], ], }, { id: "brzp", name: "全国中小企业招聘平台", short: "百日", category: "public", categoryLabel: "公共平台", domain: "brzp.chinasme.cn", adapter: "BRZP-Collector 2.1", mode: "活动聚合与岗位采集", schedule: "每日 01:30", status: "running", statusLabel: "正式运行", health: 99.2, today: 18640, total: "824K", updated: "09:55", coverage: "全国中小企业 · 校招 · 实习", contract: "JOB-SIGNAL V3.0", schema: "SOURCE-BRZP 1.4", version: "SRC-BRZP-20260823-01", owner: "公共源采集组", retention: "原始页面 12 个月 · 解析结果 36 个月", rate: "16 分片 · 活动页优先", retry: "失败分片次日补采", change: "同步全国中小企业招聘活动、地方专区、校园招聘和实习机会。", repo: "connector-registry/brzp-collector", codeVersion: "release/2.1.0 · c2d8f73", runtime: [ ["09:55", "活动专区补采完成", "新增活动 18 个 · 岗位 2,846 条"], ["03:08", "聚合来源校验", "9 个合作活动入口可访问"], ["01:42", "每日全量完成", "18,640 条 · 有效率 98.6%"], ], }, { id: "ncss", name: "国家大学生就业服务平台", short: "国就", category: "public", categoryLabel: "公共平台", domain: "www.ncss.cn", adapter: "NCSS-Collector 2.8", mode: "职位与专场采集", schedule: "每日 02:00", status: "running", statusLabel: "正式运行", health: 99.4, today: 14820, total: "1.32M", updated: "08:40", coverage: "高校毕业生 · 校招 · 实习", contract: "JOB-SIGNAL V3.0", schema: "SOURCE-NCSS 2.0", version: "SRC-NCSS-20260823-01", owner: "公共源采集组", retention: "原始页面 12 个月 · 解析结果 36 个月", rate: "24 分片 · 专场增量", retry: "指数退避 · 最多 3 次", change: "更新2026届国企岗位专题、联合招聘与实习岗位。", repo: "connector-registry/ncss-collector", codeVersion: "release/2.8.0 · 1a8d6e2", runtime: [ ["08:40", "职位增量完成", "14,820 条 · 1,286 家单位"], ["02:56", "专场活动同步", "新增 11 场联合招聘"], ["02:01", "调度任务启动", "24 个地域分片"], ], }, { id: "mohrss", name: "中国公共招聘网", short: "公招", category: "public", categoryLabel: "公共平台", domain: "job.mohrss.gov.cn", adapter: "MOHRSS-Collector 3.2", mode: "公共岗位与招聘会采集", schedule: "每日 02:30", status: "running", statusLabel: "正式运行", health: 99.6, today: 12680, total: "1.86M", updated: "08:26", coverage: "全国公共就业岗位 · 重点行业", contract: "JOB-SIGNAL V3.0", schema: "SOURCE-MOHRSS 2.3", version: "SRC-MOHRSS-20260823-01", owner: "公共源采集组", retention: "原始页面 12 个月 · 解析结果 36 个月", rate: "20 分片 · 地域增量", retry: "失败分片次日补采", change: "更新制造业招聘专区、名企招聘和中央企业招聘信息。", repo: "connector-registry/mohrss-collector", codeVersion: "release/3.2.4 · 66e102b", runtime: [ ["08:26", "增量任务完成", "12,680 条 · 有效率 99.1%"], ["03:12", "重点专区同步", "制造业与中央企业专区已更新"], ["02:31", "调度任务启动", "20 个地域分片"], ], }, { id: "iguopin", name: "国聘", short: "国聘", category: "public", categoryLabel: "公共平台", domain: "www.iguopin.com", adapter: "IGUOPIN-Collector 2.4", mode: "国资央企岗位采集", schedule: "每日 03:00", status: "running", statusLabel: "正式运行", health: 98.8, today: 9640, total: "642K", updated: "08:15", coverage: "央企国企 · 校园招聘", contract: "JOB-SIGNAL V3.0", schema: "SOURCE-GP 1.8", version: "SRC-GP-20260823-01", owner: "公共源采集组", retention: "原始响应 12 个月 · 解析结果 36 个月", rate: "12 分片 · 企业专题增量", retry: "指数退避 · 最多 3 次", change: "更新国资央企2026届岗位与重点企业专题。", repo: "connector-registry/iguopin-collector", codeVersion: "release/2.4.1 · a831bd0", runtime: [ ["08:15", "企业专题同步完成", "9,640 条 · 312 家企业"], ["03:44", "动态页面解析完成", "页面成功率 99.2%"], ["03:01", "调度任务启动", "12 个企业分片"], ], }, { id: "hzrc", name: "杭州人才网", short: "杭才", category: "public", categoryLabel: "公共平台", domain: "www.hzrc.com", adapter: "HZRC-Collector 2.7", mode: "本地人才岗位采集", schedule: "每 12 小时", status: "attention", statusLabel: "降速保护", health: 92.4, today: 3860, total: "386K", updated: "10:28", coverage: "杭州都市圈 · 本地企业", contract: "JOB-SIGNAL V3.0", schema: "SOURCE-HZRC 2.0", version: "SRC-HZRC-20260823-02", owner: "区域源采集组", retention: "原始页面 12 个月 · 解析结果 36 个月", rate: "源端延迟保护 · 4 分片", retry: "15分钟后自动重试", change: "响应时延升高,采集器自动降低并发;已完成数据保持可用。", repo: "connector-registry/hzrc-collector", codeVersion: "release/2.7.3 · d09f71c", runtime: [ ["10:28", "触发自动降速", "P95 4.8s · 并发 12 → 4"], ["09:52", "增量任务继续", "3,860 条 · 8 分片待执行"], ["02:48", "全量索引校验", "岗位唯一率 99.7%"], ], }, { id: "school-employment", name: "校内就业管理系统", short: "就业", category: "school", categoryLabel: "校内系统", domain: "school://employment-view", adapter: "School-DB-Connector 2.3", mode: "只读数据库视图", schedule: "每日 04:00", status: "running", statusLabel: "正式运行", health: 97.8, today: 4280, total: "928K", updated: "07:52", coverage: "36所学校 · 近三届毕业生", contract: "SCHOOL-EMPLOYMENT V2.2", schema: "EMPLOYMENT-VIEW 2.2", version: "SCH-EMP-20260823-01", owner: "学校数据接入组", retention: "明细按租户策略 · 汇总 36 个月", rate: "租户隔离 · 12 并行连接", retry: "单租户失败隔离", change: "新增就业质量、岗位相关度和去向确认字段。", repo: "connector-registry/school-db-connector", codeVersion: "release/2.3.0 · 310ae74", runtime: [ ["07:52", "36租户同步完成", "4,280 条变更 · 2 条隔离"], ["04:46", "跨表关联校验", "学生与专业关联率 99.6%"], ["04:01", "每日任务启动", "36 个租户分片"], ], }, { id: "school-internship", name: "校内实习管理系统", short: "实习", category: "school", categoryLabel: "校内系统", domain: "school://internship-api", adapter: "School-API-Connector 2.5", mode: "租户 API 接入", schedule: "每日 04:30", status: "running", statusLabel: "正式运行", health: 98.4, today: 5720, total: "1.18M", updated: "08:03", coverage: "36所学校 · 实习全过程", contract: "SCHOOL-INTERNSHIP V2.1", schema: "INTERNSHIP-API 2.1", version: "SCH-INT-20260823-01", owner: "学校数据接入组", retention: "明细按租户策略 · 汇总 36 个月", rate: "租户隔离 · 12 并行连接", retry: "单租户失败隔离", change: "补充企业导师评价、实习工资与岗位任务字段。", repo: "connector-registry/school-api-connector", codeVersion: "release/2.5.2 · 8b17a63", runtime: [ ["08:03", "36租户同步完成", "5,720 条变更 · 全部通过"], ["05:10", "接口水位提交", "watermark 2026-08-23T04:30:00+08:00"], ["04:31", "每日任务启动", "36 个租户分片"], ], }, ]; const collectionJobs = [ { id: "JOB-20260823-1042", name: "BOSS重点产业岗位增量", sourceId: "boss", scope: "全国 · 数字技术/智能制造", strategy: "增量 · 64分片 · 自适应", progress: 68, records: "68,420", valid: "98.5%", status: "running", statusLabel: "运行中", }, { id: "JOB-20260823-1038", name: "智联全国岗位增量", sourceId: "zhaopin", scope: "全国 · 校招与社招", strategy: "增量 · 48分片 · 自适应", progress: 91, records: "52,360", valid: "98.8%", status: "running", statusLabel: "运行中", }, { id: "JOB-20260823-1028", name: "杭州人才网区域补采", sourceId: "hzrc", scope: "杭州都市圈 · 全岗位", strategy: "补采 · 4分片 · 降速", progress: 42, records: "3,860", valid: "96.4%", status: "attention", statusLabel: "降速保护", }, { id: "JOB-20260824-0130", name: "百日招聘每日全量", sourceId: "brzp", scope: "全国 · 校招/实习/活动", strategy: "全量 · 16分片", progress: 0, records: "—", valid: "—", status: "scheduled", statusLabel: "等待调度", }, { id: "JOB-20260824-0200", name: "国家大学生就业职位增量", sourceId: "ncss", scope: "全国 · 2026届", strategy: "增量 · 24分片", progress: 0, records: "—", valid: "—", status: "scheduled", statusLabel: "等待调度", }, { id: "JOB-20260824-0400", name: "校内就业36租户同步", sourceId: "school-employment", scope: "36所学校 · 近三届", strategy: "增量 · 租户隔离", progress: 0, records: "—", valid: "—", status: "scheduled", statusLabel: "等待调度", }, ]; const standardJobRecords = [ { id: "e5a87030-3c5f-4d74-a3ca-89427e2f75a1", sourceId: "boss", sourceRecordId: "BOSS-20260823-884201", sourceUrl: "https://www.bosszhipin.com/job_detail/sample-884201.html", taskId: "JOB-20260823-1042", snapshotId: "RAW-BOSS-20260823-000884", collectorVersion: "BOSS-Collector-4.6.2", parserVersion: "PARSER-BOSS-4.6.2", jobTitleRaw: "云原生开发工程师", jobTitleNormalized: "云计算工程技术人员", jobCode: "IT.CLOUD.DEV", enterprise: "杭州示例数字科技有限公司", region: "杭州", regionCode: "330100", workplace: "杭州市滨江区", salaryMin: 12000, salaryMax: 18000, employmentType: "全职", publishedAt: "2026-08-21 08:56", collectedAt: "2026-08-23 10:42", normalizedAt: "2026-08-23 10:45", quality: 98.8, status: "有效", skills: ["Docker", "Kubernetes", "Java", "CI/CD"], description: "负责容器平台应用开发、持续集成和服务可观测性建设。", contentHash: "96c2be742dc43f8e7ee526db6358030873406c450f15e67225c605dc39b431a8", payloadHash: "4f8d91a47e2b6d82b2aaad136d344d61c83f9cd472185d2543725c91870e9e32", dataVersion: "V2026.08.23", }, { id: "57df68fe-2454-44d2-9f3d-20e91ac0912a", sourceId: "zhaopin", sourceRecordId: "ZL-20260823-620311", sourceUrl: "https://www.zhaopin.com/jobdetail/sample-620311.htm", taskId: "JOB-20260823-1038", snapshotId: "RAW-ZHAOPIN-20260823-000620", collectorVersion: "ZHAOPIN-Collector-3.8.1", parserVersion: "PARSER-ZHAOPIN-3.8.1", jobTitleRaw: "工业视觉应用工程师", jobTitleNormalized: "人工智能工程技术人员", jobCode: "AI.VISION.APP", enterprise: "浙江示例智能装备股份有限公司", region: "宁波", regionCode: "330200", workplace: "宁波市鄞州区", salaryMin: 10000, salaryMax: 16000, employmentType: "全职", publishedAt: "2026-08-22 14:05", collectedAt: "2026-08-23 10:38", normalizedAt: "2026-08-23 10:41", quality: 98.6, status: "有效", skills: ["Python", "OpenCV", "机器视觉", "PLC"], description: "负责机器视觉方案部署、相机标定、缺陷检测模型优化和产线集成。", contentHash: "66e102b99bf056868670ac971011740c6f66f62b86ea43cc66326ed9a0a5807e", payloadHash: "c2d8f733e6711219823110d7d35fdc5535f133ed60f60950f9dc78a228cb402d", dataVersion: "V2026.08.23", }, { id: "62eaa80d-0b30-462b-8eca-c6c9bb5e8446", sourceId: "brzp", sourceRecordId: "BRZP-20260823-110028", sourceUrl: "https://brzp.chinasme.cn/brzp/job/sample-110028", taskId: "JOB-20260823-0955", snapshotId: "RAW-BRZP-20260823-000110", collectorVersion: "BRZP-Collector-2.1.0", parserVersion: "PARSER-BRZP-2.1.0", jobTitleRaw: "跨境电商运营实习生", jobTitleNormalized: "电子商务师", jobCode: "EC.CROSSBORDER.OPS", enterprise: "浙江示例跨境服务有限公司", region: "嘉兴", regionCode: "330400", workplace: "嘉兴市南湖区", salaryMin: 3500, salaryMax: 5000, employmentType: "实习", publishedAt: "2026-08-22 18:00", collectedAt: "2026-08-23 09:55", normalizedAt: "2026-08-23 10:00", quality: 97.9, status: "有效", skills: ["跨境电商", "内容运营", "数据分析", "英语"], description: "协助店铺运营、海外内容制作、经营数据分析与合规资料整理。", contentHash: "310ae74d6d695235229da958f8fb8e59af6ea4bebef659dfdc1a6bc0764f3f30", payloadHash: "a831bd0d4e29cab1e650f36a4fc9c5d5cc8053f560b5d3dc7aac62b384b47e11", dataVersion: "V2026.08.23", }, { id: "84c7ac1a-2a5c-4bcc-b64a-fc9cdf42b081", sourceId: "ncss", sourceRecordId: "NCSS-20260823-381205", sourceUrl: "https://www.ncss.cn/student/jobs/sample-381205.html", taskId: "JOB-20260823-0840", snapshotId: "RAW-NCSS-20260823-000381", collectorVersion: "NCSS-Collector-2.8.0", parserVersion: "PARSER-NCSS-2.8.0", jobTitleRaw: "智能网联汽车测试工程师", jobTitleNormalized: "汽车工程技术人员", jobCode: "AUTO.ICV.TEST", enterprise: "浙江示例汽车技术研究院", region: "杭州", regionCode: "330100", workplace: "杭州市钱塘区", salaryMin: 9000, salaryMax: 14000, employmentType: "校招", publishedAt: "2026-08-22 10:18", collectedAt: "2026-08-23 08:40", normalizedAt: "2026-08-23 08:44", quality: 99.1, status: "有效", skills: ["CAN", "自动驾驶", "测试开发", "Python"], description: "承担智能网联汽车道路测试、数据采集、场景回放和测试报告编制。", contentHash: "18f4".repeat(16), payloadHash: "7ca2".repeat(16), dataVersion: "V2026.08.23", }, { id: "3852086b-33d7-4ed5-9d90-52f1fd6f3a14", sourceId: "mohrss", sourceRecordId: "MOHRSS-20260823-226801", sourceUrl: "https://job.mohrss.gov.cn/job/sample-226801", taskId: "JOB-20260823-0826", snapshotId: "RAW-MOHRSS-20260823-000226", collectorVersion: "MOHRSS-Collector-3.2.4", parserVersion: "PARSER-MOHRSS-3.2.4", jobTitleRaw: "工业机器人系统运维员", jobTitleNormalized: "工业机器人系统运维员", jobCode: "MFG.ROBOT.OPS", enterprise: "绍兴示例精密制造有限公司", region: "绍兴", regionCode: "330600", workplace: "绍兴市柯桥区", salaryMin: 8000, salaryMax: 12000, employmentType: "全职", publishedAt: "2026-08-21 16:35", collectedAt: "2026-08-23 08:26", normalizedAt: "2026-08-23 08:30", quality: 98.9, status: "有效", skills: ["工业机器人", "设备维护", "电气控制", "故障诊断"], description: "负责工业机器人工作站巡检、预防性维护、故障诊断与备件管理。", contentHash: "66a8".repeat(16), payloadHash: "04de".repeat(16), dataVersion: "V2026.08.23", }, { id: "c0f2185a-b718-4c47-82e5-21b082eeb2ed", sourceId: "iguopin", sourceRecordId: "GP-20260823-119640", sourceUrl: "https://www.iguopin.com/job/detail/sample-119640", taskId: "JOB-20260823-0815", snapshotId: "RAW-IGUOPIN-20260823-000119", collectorVersion: "IGUOPIN-Collector-2.4.1", parserVersion: "PARSER-IGUOPIN-2.4.1", jobTitleRaw: "数据治理工程师", jobTitleNormalized: "数据工程技术人员", jobCode: "IT.DATA.GOV", enterprise: "华东示例产业数字集团", region: "上海", regionCode: "310000", workplace: "上海市浦东新区", salaryMin: 15000, salaryMax: 24000, employmentType: "校招", publishedAt: "2026-08-22 09:20", collectedAt: "2026-08-23 08:15", normalizedAt: "2026-08-23 08:19", quality: 99.3, status: "有效", skills: ["数据标准", "元数据", "数据质量", "SQL"], description: "参与集团数据标准、元数据、主数据和数据质量体系建设。", contentHash: "9ec1".repeat(16), payloadHash: "81bd".repeat(16), dataVersion: "V2026.08.23", }, { id: "9dbf174b-4f0d-45ea-aa66-031a42ee802a", sourceId: "hzrc", sourceRecordId: "HZRC-20260823-038602", sourceUrl: "https://www.hzrc.com/job/sample-038602", taskId: "JOB-20260823-1028", snapshotId: "RAW-HZRC-20260823-000038", collectorVersion: "HZRC-Collector-2.7.3", parserVersion: "PARSER-HZRC-2.7.3", jobTitleRaw: "低空运营调度师", jobTitleNormalized: "航空运输服务人员", jobCode: "AVIATION.LOWALT.DISPATCH", enterprise: "杭州示例低空运营有限公司", region: "杭州", regionCode: "330100", workplace: "杭州市余杭区", salaryMin: 9000, salaryMax: 15000, employmentType: "全职", publishedAt: "2026-08-23 07:25", collectedAt: "2026-08-23 10:28", normalizedAt: "2026-08-23 10:31", quality: 92.8, status: "有效", skills: ["飞行计划", "运行控制", "空域管理", "应急处置"], description: "负责低空飞行任务排班、运行监控、空域协调和异常事件处置。", contentHash: "d09f".repeat(16), payloadHash: "375a".repeat(16), dataVersion: "V2026.08.23", }, { id: "f45cf9a9-b6e7-4249-bb11-fec640df9dae", sourceId: "boss", sourceRecordId: "BOSS-20260823-884733", sourceUrl: "https://www.bosszhipin.com/job_detail/sample-884733.html", taskId: "JOB-20260823-1042", snapshotId: "RAW-BOSS-20260823-000885", collectorVersion: "BOSS-Collector-4.6.2", parserVersion: "PARSER-BOSS-4.6.2", jobTitleRaw: "新能源电池工艺工程师", jobTitleNormalized: "电池制造工程技术人员", jobCode: "ENERGY.BATTERY.PROCESS", enterprise: "宁波示例新能源科技有限公司", region: "宁波", regionCode: "330200", workplace: "宁波市北仑区", salaryMin: 13000, salaryMax: 21000, employmentType: "全职", publishedAt: "2026-08-23 08:10", collectedAt: "2026-08-23 10:42", normalizedAt: "2026-08-23 10:46", quality: 98.4, status: "有效", skills: ["锂电池", "工艺设计", "DOE", "质量分析"], description: "负责电池制造工艺设计、参数优化、量产导入和制程质量改进。", contentHash: "bfd7".repeat(16), payloadHash: "41e2".repeat(16), dataVersion: "V2026.08.23", }, { id: "9286399d-da74-408d-9882-4e04fb84ce6a", sourceId: "zhaopin", sourceRecordId: "ZL-20260823-621006", sourceUrl: "https://www.zhaopin.com/jobdetail/sample-621006.htm", taskId: "JOB-20260823-1038", snapshotId: "RAW-ZHAOPIN-20260823-000621", collectorVersion: "ZHAOPIN-Collector-3.8.1", parserVersion: "PARSER-ZHAOPIN-3.8.1", jobTitleRaw: "跨境电商数据分析师", jobTitleNormalized: "商务数据分析师", jobCode: "EC.DATA.ANALYST", enterprise: "嘉兴示例国际贸易有限公司", region: "嘉兴", regionCode: "330400", workplace: "嘉兴市秀洲区", salaryMin: 8500, salaryMax: 14000, employmentType: "全职", publishedAt: "2026-08-22 17:30", collectedAt: "2026-08-23 10:38", normalizedAt: "2026-08-23 10:43", quality: 98.2, status: "有效", skills: ["SQL", "Power BI", "跨境电商", "指标体系"], description: "负责跨境业务经营分析、指标看板建设、用户分层和专题复盘。", contentHash: "92bd".repeat(16), payloadHash: "d371".repeat(16), dataVersion: "V2026.08.23", }, { id: "4c974cea-17fc-450e-8973-17a6be52851a", sourceId: "brzp", sourceRecordId: "BRZP-20260823-110309", sourceUrl: "https://brzp.chinasme.cn/brzp/job/sample-110309", taskId: "JOB-20260823-0955", snapshotId: "RAW-BRZP-20260823-000111", collectorVersion: "BRZP-Collector-2.1.0", parserVersion: "PARSER-BRZP-2.1.0", jobTitleRaw: "AI内容运营实习生", jobTitleNormalized: "互联网营销师", jobCode: "MEDIA.AI.CONTENT", enterprise: "苏州示例数字内容有限公司", region: "苏州", regionCode: "320500", workplace: "苏州市工业园区", salaryMin: 4000, salaryMax: 6000, employmentType: "实习", publishedAt: "2026-08-22 20:10", collectedAt: "2026-08-23 09:55", normalizedAt: "2026-08-23 10:02", quality: 96.9, status: "有效", skills: ["AIGC", "内容策划", "短视频", "数据复盘"], description: "协助生成式内容策划、短视频运营、素材管理和传播效果复盘。", contentHash: "ac53".repeat(16), payloadHash: "5b28".repeat(16), dataVersion: "V2026.08.23", }, ]; const rawSnapshots = [ ["RAW-BOSS-20260823-01", "boss", "JOB-20260823-1042", "text/html + JSON", "68,420", "8.42 GB", "2026-08-23 10:42", "4f8d91a47e2b6d82b2aaad136d344d61c83f9cd472185d2543725c91870e9e32", "已固化"], ["RAW-ZHAOPIN-20260823-01", "zhaopin", "JOB-20260823-1038", "text/html + JSON", "52,360", "6.18 GB", "2026-08-23 10:38", "c2d8f733e6711219823110d7d35fdc5535f133ed60f60950f9dc78a228cb402d", "已固化"], ["RAW-BRZP-20260823-01", "brzp", "JOB-20260823-0955", "text/html", "18,640", "1.86 GB", "2026-08-23 09:55", "a831bd0d4e29cab1e650f36a4fc9c5d5cc8053f560b5d3dc7aac62b384b47e11", "已固化"], ["RAW-NCSS-20260823-01", "ncss", "JOB-20260823-0840", "text/html", "14,820", "1.42 GB", "2026-08-23 08:40", "18f4".repeat(16), "已固化"], ["RAW-MOHRSS-20260823-01", "mohrss", "JOB-20260823-0826", "text/html + JSON", "12,680", "1.28 GB", "2026-08-23 08:26", "66a8".repeat(16), "已固化"], ["RAW-IGUOPIN-20260823-01", "iguopin", "JOB-20260823-0815", "text/html", "9,640", "968 MB", "2026-08-23 08:15", "9ec1".repeat(16), "已固化"], ["RAW-HZRC-20260823-02", "hzrc", "JOB-20260823-1028", "text/html", "3,860", "384 MB", "2026-08-23 10:28", "d09f".repeat(16), "已固化"], ].map(([id, sourceId, taskId, mediaType, records, size, capturedAt, sha256, status]) => ({ id, sourceId, taskId, mediaType, records, size, capturedAt, sha256, status, objectPath: `raw/2026/08/23/${sourceId}/${id.toLowerCase()}.jsonl.zst`, })); const recordRawSnapshots = standardJobRecords.map((record, index) => ({ id: record.snapshotId, sourceId: record.sourceId, taskId: record.taskId, mediaType: "text/html + JSON", records: "1", size: `${84 + index * 7} KB`, capturedAt: record.collectedAt, sha256: record.payloadHash, status: "已固化", objectPath: `raw/2026/08/23/${record.sourceId}/${record.taskId.toLowerCase()}/${record.snapshotId.toLowerCase()}.response.zst`, recordId: record.id, })); const allRawSnapshots = [...recordRawSnapshots, ...rawSnapshots]; const quarantineRecords = [ ["QUA-20260823-001286", "boss", "BOSS-20260823-884890", "智能装备技术支持", "薪资上下限倒置", "SL-004", "2026-08-23 10:43", "待自动重解析", "JOB-20260823-1042", "RAW-BOSS-20260823-000886", "杭州"], ["QUA-20260823-001285", "zhaopin", "ZL-20260823-621104", "工业软件实施顾问", "工作地区代码缺失", "RQ-012", "2026-08-23 10:39", "待字段补全", "JOB-20260823-1038", "RAW-ZHAOPIN-20260823-000622", "宁波"], ["QUA-20260823-001284", "brzp", "BRZP-20260823-110408", "电商运营", "来源详情页已失效", "TM-007", "2026-08-23 09:57", "等待次日复核", "JOB-20260823-0955", "RAW-BRZP-20260823-000112", "嘉兴"], ["QUA-20260823-001283", "hzrc", "HZRC-20260823-038711", "AI内容策略师", "岗位词典未匹配", "NM-021", "2026-08-23 10:30", "进入人工归一", "JOB-20260823-1028", "RAW-HZRC-20260823-000039", "杭州"], ["QUA-20260823-001282", "mohrss", "MOHRSS-20260823-226955", "设备维护员", "企业名称完整性不足", "RQ-018", "2026-08-23 08:28", "待规则复核", "JOB-20260823-0826", "RAW-MOHRSS-20260823-000227", "绍兴"], ].map(([id, sourceId, sourceRecordId, jobTitle, issue, rule, detectedAt, disposition, taskId, snapshotId, region]) => ({ id, sourceId, sourceRecordId, jobTitle, issue, rule, detectedAt, disposition, taskId, snapshotId, region, })); const contracts = { "external-job": { key: "JOB-SIGNAL", title: "外部招聘岗位信号标准", version: "V3.0.0", description: "规定商业招聘平台、公共就业平台和招聘活动数据中的岗位、企业、薪资、技能与来源字段,供产教分析使用。", schemaPath: "./schemas/external-job-source.schema.json", fields: 39, required: 19, owner: "平台数据标准委员会", status: "生产已发布", release: "2026-08-18", compatibility: "向后兼容 V2.6", fieldsPreview: [ ["record_id", "string/uuid", "是", "平台记录唯一标识", "UUID v4"], ["source_platform", "string", "是", "来源平台代码", "固定代码表"], ["source_record_id", "string", "是", "来源记录标识", "同平台唯一"], ["source_url", "string/uri", "是", "来源页面或接口定位", "http/https"], ["collected_at", "datetime", "是", "实际采集时间", "ISO-8601 +08:00"], ["published_at", "datetime", "否", "源端发布时间", "ISO-8601"], ["job_title_raw", "string", "是", "岗位原始名称", "1—200字符"], ["job_title_normalized", "string", "否", "统一岗位名称", "岗位词典 V4.3"], ["enterprise_name", "string", "是", "企业名称", "保留原文"], ["region_code", "string", "是", "工作地区代码", "GB/T 2260"], ["salary_min", "number", "否", "月薪下限(元)", "非负数"], ["salary_max", "number", "否", "月薪上限(元)", "≥ salary_min"], ["skills", "array", "否", "解析后的技能标签", "技能词典 V3.8"], ["content_hash", "string", "是", "原始内容哈希", "SHA-256"], ], rules: [ ["唯一与幂等", "source_platform + source_record_id 唯一;重复采集必须幂等更新。"], ["来源证据", "必须保留 source_url、collected_at、content_hash 和采集任务编号。"], ["薪资口径", "统一折算为税前月薪;面议、日薪、时薪保留原文并标注换算状态。"], ["时效与失效", "岗位状态和最后发现时间分开记录;失效采用版本化软删除。"], ["文本处理", "岗位职责原文保存在原始层,分析层只保留去标记、去联系方式后的业务文本。"], ], }, "school-employment": { key: "SCHOOL-EMPLOYMENT", title: "校内就业数据标准", version: "V2.2.0", description: "规定学校毕业生就业状态、岗位、企业、薪资、专业相关度和去向确认字段,并要求租户隔离,只保留业务必需的敏感字段。", schemaPath: "./schemas/school-employment.schema.json", fields: 32, required: 16, owner: "平台数据标准委员会", status: "待生产发布", release: "计划 2026-08-28", compatibility: "兼容 V2.1,新增5字段", fieldsPreview: [ ["tenant_id", "string", "是", "学校租户标识", "平台分配"], ["student_token", "string", "是", "学生不可逆令牌", "禁止明文学号"], ["cohort", "integer", "是", "毕业届次", "YYYY"], ["program_code", "string", "是", "专业代码", "教育部专业目录"], ["employment_status", "string", "是", "就业状态", "固定选项"], ["job_title", "string", "否", "就业岗位", "业务原文"], ["enterprise_token", "string", "否", "企业统一标识", "信用代码令牌化"], ["salary_monthly", "number", "否", "税前月薪", "元/月"], ["job_relevance", "string", "否", "岗位专业相关度", "高/中/低/未确认"], ["confirmed_at", "datetime", "是", "业务确认时间", "ISO-8601"], ["source_system", "string", "是", "校内来源系统", "系统代码"], ["updated_at", "datetime", "是", "业务更新时间", "增量水位"], ], rules: [ ["租户隔离", "所有记录必须携带 tenant_id,存储、任务、查询和导出均按租户强制隔离。"], ["身份最小化", "平台侧使用 student_token,不接收姓名、身份证和明文学号。"], ["业务确认", "就业状态必须具有确认人角色、确认时间和来源系统。"], ["增量更新", "updated_at 作为增量水位,主键冲突时以业务更新时间和版本号判定。"], ["质量阈值", "专业代码合法率100%,必填完整率≥95%,学生专业关联率≥98%。"], ], }, "school-internship": { key: "SCHOOL-INTERNSHIP", title: "校内实习数据标准", version: "V2.1.0", description: "规定学生实习岗位、企业、起止日期、状态、工资、岗位任务和评价字段,并要求租户隔离,只保留业务必需的身份字段。", schemaPath: "./schemas/school-internship.schema.json", fields: 30, required: 14, owner: "平台数据标准委员会", status: "生产已发布", release: "2026-08-24", compatibility: "兼容 V2.0,补充任务与评价证据", fieldsPreview: [ ["tenant_id", "string", "是", "学校租户标识", "平台分配"], ["internship_id", "string", "是", "实习业务编号", "租户内唯一"], ["student_token", "string", "是", "学生不可逆令牌", "禁止明文学号"], ["program_code", "string", "是", "专业代码", "教育部专业目录"], ["enterprise_token", "string", "否", "企业统一标识", "令牌化"], ["job_title", "string", "是", "实习岗位名称", "业务原文"], ["job_tasks", "array", "否", "实习岗位任务", "学校确认"], ["start_date", "date", "是", "开始日期", "YYYY-MM-DD"], ["end_date", "date", "否", "结束日期", "YYYY-MM-DD"], ["status", "string", "是", "实习状态", "固定选项"], ["salary_monthly", "number", "否", "税前月实习工资", "元/月"], ["evidence_reference", "string", "否", "校内证据引用", "不传附件"], ["source_updated_at", "datetime", "否", "业务更新时间", "增量水位"], ["content_hash", "string", "是", "业务内容哈希", "SHA-256"], ], rules: [ ["租户隔离", "任务、认证、水位、Raw、隔离区和输出目录均按 tenant_id 隔离。"], ["身份最小化", "平台只接收 student_token,拒绝姓名、身份证、手机号、邮箱和明文学号。"], ["企业要求", "进行中或已完成实习必须提供企业令牌或企业名称。"], ["证据引用", "敏感附件保留在学校系统,平台只保存 evidence_reference。"], ["增量重复处理", "使用 tenant_id + source_record_id + source_updated_at 判断增量更新,并确保重复提交不会重复写入。"], ], }, "package-manifest": { key: "PACKAGE-MANIFEST", title: "数据交付包清单标准", version: "V1.2.0", description: "定义每次导入、导出和租户交付必须携带的数据版本、文件、Schema、质量、血缘、范围和校验信息。", schemaPath: "./schemas/package-manifest.schema.json", fields: 21, required: 14, owner: "平台数据交付组", status: "生产已发布", release: "2026-08-23", compatibility: "兼容 V1.1,新增2个发布字段", fieldsPreview: [ ["package_id", "string", "是", "交付包唯一编号", "EDU-PKG-*"], ["release_id", "string", "是", "发布候选编号", "REL-*"], ["release_status", "string", "是", "候选或发布状态", "DRAFT/PUBLISHED"], ["data_product", "string", "是", "数据产品代码", "产品目录"], ["data_version", "string", "是", "业务数据版本", "SemVer/日期版"], ["contract_name", "string", "是", "数据契约名称", "契约注册中心"], ["contract_version", "string", "是", "契约版本", "SemVer"], ["data_as_of", "date", "是", "业务截止日期", "YYYY-MM-DD"], ["tenant_scope", "array", "是", "允许交付租户", "至少1个"], ["record_count", "integer", "是", "有效记录数", "非负整数"], ["files", "array", "是", "数据与附件文件清单", "含SHA-256"], ["quality_score", "number", "是", "综合质量评分", "0—100"], ["lineage_snapshot", "string", "是", "血缘快照编号", "LINEAGE-*"], ["generated_at", "datetime", "是", "生成时间", "ISO-8601"], ], rules: [ ["文件完整性", "Manifest 内每个文件必须记录路径、字节数、媒体类型和 SHA-256。"], ["范围约束", "tenant_scope 与数据服务授权同时校验,不允许客户端自行扩大范围。"], ["契约绑定", "数据文件必须绑定 contract_name 和精确 contract_version。"], ["质量绑定", "必须包含质量报告文件、质量评分和失败规则摘要。"], ["可回滚", "每次交付保留上一稳定版本引用和回滚入口。"], ], }, }; const exchangeLogs = [ ["EXP-20260823-0930", "导出", "区域招聘岗位基础包", "V2026.08.23", "2,846,320", "交付服务", "成功", "09:30"], ["IMP-20260823-0752", "导入", "校内就业36租户增量", "SCH-EMP-20260823", "4,280", "学校接入服务", "成功", "07:52"], ["EXP-20260822-1740", "导出", "薪资与企业画像增强包", "V2026.08.22", "1,286,420", "陈致远", "成功", "昨天 17:40"], ["IMP-20260822-1608", "导入", "企业确认需求批次", "ENT-20260822-03", "8,640", "企业接入服务", "隔离12条", "昨天 16:08"], ]; const qualityRules = [ ["UQ", "来源主键唯一", "source_platform + source_record_id", "99.96%", "通过"], ["RQ", "核心字段完整", "岗位、企业、地区、来源、采集时间", "98.84%", "通过"], ["TM", "岗位时效合法", "发布时间、最后发现时间与状态一致", "97.31%", "通过"], ["SL", "薪资区间合法", "币种、周期、上下限和换算状态", "96.72%", "通过"], ["NM", "岗位归一覆盖", "映射到岗位词典 V4.3", "96.40%", "27待审"], ["LN", "来源证据完整", "URL、任务、快照与内容哈希", "100%", "通过"], ]; let normalizationItems = [ ["具身智能应用工程师", "机器人应用工程技术人员", "0.91"], ["低空运营调度师", "航空运输服务人员", "0.84"], ["AI内容策略师", "互联网营销师", "0.82"], ["智能驾驶数据闭环工程师", "数据工程技术人员", "0.88"], ["跨境合规运营", "电子商务师", "0.86"], ]; const auditEvents = [ { id: "AUD-20260823-1042-8821", time: "10:42:18", type: "collect", event: "BOSS增量分片完成", object: "JOB-20260823-1042 · shard-64", actor: "collector-boss-prod", result: "成功", evidence: ["RAW快照", "响应哈希", "运行日志"], }, { id: "AUD-20260823-1038-7412", time: "10:38:06", type: "collect", event: "智联采集水位提交", object: "SRC-ZL-20260823-04", actor: "collector-zhaopin-prod", result: "成功", evidence: ["水位", "分片清单", "代码版本"], }, { id: "AUD-20260823-1028-5630", time: "10:28:42", type: "collect", event: "源端延迟触发降速", object: "杭州人才网 · P95 4.8s", actor: "scheduler-protection", result: "自动保护", evidence: ["指标快照", "策略版本"], }, { id: "AUD-20260823-1016-4482", time: "10:16:33", type: "quality", event: "岗位归一批次完成", object: "NORM-20260823-06", actor: "normalization-engine", result: "27项待审", evidence: ["输入版本", "词典版本", "模型版本"], }, { id: "AUD-20260823-0930-2290", time: "09:30:12", type: "publish", event: "区域岗位基础包发布", object: "V2026.08.23 · 36租户", actor: "陈致远", result: "成功", evidence: ["审批单", "Manifest", "质量报告"], }, { id: "AUD-20260823-0752-1108", time: "07:52:55", type: "exchange", event: "校内就业增量导入", object: "36租户 · 4,280条", actor: "school-ingestion-service", result: "成功", evidence: ["源版本", "字段映射", "错误明细"], }, ]; const OPERATIONS_STORAGE_KEY = "eduIndustry.vendorOperations.v1"; const OPERATIONS_SCHEMA_VERSION = 2; const OPERATIONS_ACTOR = "陈致远"; const OPERATIONS_LIMITS = { auditEvents: 300, qualityDispositions: 60, issueTimeline: 120, exchangeLogs: 100, }; function operationsSeed() { const baseEvidence = ["指标快照", "任务日志"]; return { schemaVersion: OPERATIONS_SCHEMA_VERSION, seededAt: "2026-08-23T10:30:00+08:00", issues: [ { id: "ISSUE-HZRC-20260823-01", status: "ALERTED", actor: "scheduler-protection", time: "2026-08-23T10:28:42+08:00", trace: "TRACE-HZRC-20260823-5630", evidence: [...baseEvidence, "P95延迟采样"], title: "杭州人才网响应时延升高", severity: "P1", owner: "待指派", sla: "2小时内恢复,剩余 01:42", impact: "杭州都市圈补采降速,8个分片待执行;稳定版本未受影响", relatedTask: "JOB-20260823-1028", quarantineId: "QUA-20260823-001283", sourceId: "hzrc", qualityRequired: true, timeline: [ { id: "STEP-HZRC-ALERT", status: "ALERTED", actor: "scheduler-protection", time: "2026-08-23T10:28:42+08:00", trace: "TRACE-HZRC-20260823-5630", evidence: ["指标快照", "降速策略 V2.4"], note: "P95 4.8s,系统自动将并发从12降至4。", }, ], }, { id: "ISSUE-NORM-20260823-01", status: "ACKNOWLEDGED", actor: "周敏", time: "2026-08-23T10:18:00+08:00", trace: "TRACE-NORM-20260823-4482", evidence: ["归一批次 NORM-20260823-06", "词典 V4.3"], title: "27 个岗位别名待人工归一", severity: "P1", owner: "周敏", sla: "今日 16:00 前完成", impact: "具身智能、低空经济等新增岗位暂未进入稳定分类", relatedTask: "NORM-20260823-06", quarantineId: "QUA-20260823-001283", sourceId: "hzrc", qualityRequired: false, timeline: [ { id: "STEP-NORM-ACK", status: "ACKNOWLEDGED", actor: "周敏", time: "2026-08-23T10:18:00+08:00", trace: "TRACE-NORM-20260823-4482", evidence: ["归一待审清单"], note: "已确认影响范围,等待词典负责人处理。", }, ], }, { id: "ISSUE-CONTRACT-20260823-01", status: "ASSIGNED", actor: "陈致远", time: "2026-08-23T09:48:00+08:00", trace: "TRACE-CONTRACT-20260823-2201", evidence: ["Schema差异报告", "下游影响清单"], title: "校内就业契约 V2.2 待发布", severity: "P2", owner: "数据标准组", sla: "2026-08-28 前完成", impact: "36所学校的新就业质量与去向确认字段尚未启用", relatedTask: "CONTRACT-SCHOOL-EMPLOYMENT-V2.2", quarantineId: "无", sourceId: "school-employment", qualityRequired: false, timeline: [ { id: "STEP-CONTRACT-ASSIGNED", status: "ASSIGNED", actor: "陈致远", time: "2026-08-23T09:48:00+08:00", trace: "TRACE-CONTRACT-20260823-2201", evidence: ["责任人指派记录"], note: "已指派数据标准组执行兼容性与样本回放。", }, ], }, ], workOrders: [ { id: "WO-HZRC-20260823-01", status: "OPEN", actor: "scheduler-protection", time: "2026-08-23T10:28:42+08:00", trace: "TRACE-HZRC-20260823-5630", evidence: ["自动建单规则"], issueId: "ISSUE-HZRC-20260823-01", assignee: "待指派", }, { id: "WO-NORM-20260823-01", status: "ASSIGNED", actor: "周敏", time: "2026-08-23T10:18:00+08:00", trace: "TRACE-NORM-20260823-4482", evidence: ["归一待审清单"], issueId: "ISSUE-NORM-20260823-01", assignee: "周敏", }, { id: "WO-CONTRACT-20260823-01", status: "ASSIGNED", actor: "陈致远", time: "2026-08-23T09:48:00+08:00", trace: "TRACE-CONTRACT-20260823-2201", evidence: ["Schema差异报告"], issueId: "ISSUE-CONTRACT-20260823-01", assignee: "数据标准组", }, ], qualityDispositions: [], releases: [ { id: "REL-20260823-01", status: "DRAFT", actor: "陈致远", time: "2026-08-23T10:30:00+08:00", trace: "TRACE-REL-20260823-01", evidence: ["候选数据快照", "质量报告 QUALITY-20260823-09"], dataVersion: "V2026.08.23", packageId: "EDU-PKG-20260823-01", previousStableVersion: "V2026.08.22", qualityGate: "BLOCKED", qualityScore: 94.7, impactTenants: ["TENANT-HZVTC", "TENANT-ZIME", "TENANT-NBCC"], applicant: "陈致远", approvalId: "APPROVAL-REL-20260823-01", manifestGeneratedAt: null, candidateManifest: null, candidateInputFingerprint: null, publishedManifestGeneratedAt: null, publishedManifest: null, publishedAt: null, }, ], approvals: [ { id: "APPROVAL-REL-20260823-01", status: "DRAFT", actor: "陈致远", time: "2026-08-23T10:30:00+08:00", trace: "TRACE-APPROVAL-20260823-01", evidence: ["审批规则 RBAC-RELEASE-2P"], releaseId: "REL-20260823-01", applicant: "陈致远", approver: null, comment: "", decidedAt: null, }, { id: "APPROVAL-ROLLBACK-REL-20260823-01", status: "DRAFT", actor: "陈致远", time: "2026-08-23T10:30:00+08:00", trace: "TRACE-ROLLBACK-APPROVAL-20260823-01", evidence: ["回滚审批规则 RBAC-ROLLBACK-2P"], releaseId: "REL-20260823-01", type: "ROLLBACK", applicant: "陈致远", approver: null, comment: "", reason: "", decidedAt: null, }, ], deliveries: [ { id: "DELIVERY-HZVTC-20260823-01", status: "PENDING", actor: "delivery-orchestrator", time: "2026-08-23T10:30:00+08:00", trace: "TRACE-DELIVERY-HZVTC-01", evidence: ["租户范围策略"], tenantId: "TENANT-HZVTC", school: "杭州科技职业技术学院", packageId: "EDU-PKG-20260823-01", dataVersion: "V2026.08.23", expectedRecords: 2846320, receiptRecords: null, expectedSha: "4f8d91a47e2b6d82b2aaad136d344d61c83f9cd472185d2543725c91870e9e32", receiptSha: null, attempts: 0, failureReason: "", }, { id: "DELIVERY-ZIME-20260823-01", status: "PENDING", actor: "delivery-orchestrator", time: "2026-08-23T10:30:00+08:00", trace: "TRACE-DELIVERY-ZIME-01", evidence: ["租户范围策略"], tenantId: "TENANT-ZIME", school: "浙江机电职业技术大学", packageId: "EDU-PKG-20260823-01", dataVersion: "V2026.08.23", expectedRecords: 2819044, receiptRecords: null, expectedSha: "96c2be742dc43f8e7ee526db6358030873406c450f15e67225c605dc39b431a8", receiptSha: null, attempts: 0, failureReason: "", }, { id: "DELIVERY-NBCC-20260823-01", status: "PENDING", actor: "delivery-orchestrator", time: "2026-08-23T10:30:00+08:00", trace: "TRACE-DELIVERY-NBCC-01", evidence: ["租户范围策略"], tenantId: "TENANT-NBCC", school: "宁波城市职业技术学院", packageId: "EDU-PKG-20260823-01", dataVersion: "V2026.08.23", expectedRecords: 2768420, receiptRecords: null, expectedSha: "c2d8f733e6711219823110d7d35fdc5535f133ed60f60950f9dc78a228cb402d", receiptSha: null, attempts: 0, failureReason: "", }, ], auditEvents: [ { id: "AUD-OPS-20260823-1028", status: "RECORDED", actor: "scheduler-protection", time: "2026-08-23T10:28:42+08:00", trace: "TRACE-HZRC-20260823-5630", evidence: ["指标快照", "策略版本"], type: "operations", event: "杭州人才网时延告警建单", object: "ISSUE-HZRC-20260823-01", result: "待确认", }, ], }; } function isOperationsEntityValid(entity) { return entity && typeof entity.id === "string" && typeof entity.status === "string" && typeof entity.actor === "string" && typeof entity.time === "string" && typeof entity.trace === "string" && Array.isArray(entity.evidence); } function trimOperationsStore(store) { store.auditEvents = store.auditEvents.slice(0, OPERATIONS_LIMITS.auditEvents); store.qualityDispositions = store.qualityDispositions.slice(-OPERATIONS_LIMITS.qualityDispositions); store.issues.forEach((issue) => { issue.timeline = issue.timeline.slice(-OPERATIONS_LIMITS.issueTimeline); }); return store; } function loadOperationsStore() { try { const parsed = JSON.parse(localStorage.getItem(OPERATIONS_STORAGE_KEY)); const collections = ["issues", "workOrders", "qualityDispositions", "releases", "approvals", "deliveries", "auditEvents"]; if ( parsed?.schemaVersion === OPERATIONS_SCHEMA_VERSION && collections.every((key) => Array.isArray(parsed[key]) && parsed[key].every(isOperationsEntityValid)) ) { return trimOperationsStore(parsed); } } catch { // Invalid or unavailable browser storage falls back to a reproducible seed. } const seed = operationsSeed(); try { localStorage.setItem(OPERATIONS_STORAGE_KEY, JSON.stringify(seed)); } catch { // The in-memory seed remains fully interactive when storage is blocked. } return seed; } let operationsStore = loadOperationsStore(); function saveOperationsStore() { trimOperationsStore(operationsStore); try { localStorage.setItem(OPERATIONS_STORAGE_KEY, JSON.stringify(operationsStore)); } catch { showToast("浏览器阻止了本地存储,本次状态仅在当前页面有效", "状态未持久化"); } } function operationsNow() { return new Date().toISOString(); } function operationToken(prefix) { return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2, 7).toUpperCase()}`; } function addOperationsAudit({ type = "operations", event, object, actor = OPERATIONS_ACTOR, result, evidence = [] }) { const time = operationsNow(); const trace = operationToken("TRACE"); operationsStore.auditEvents.unshift({ id: operationToken("AUD"), status: "RECORDED", actor, time, trace, evidence: [...evidence], type, event, object, result, }); return { actor, time, trace, evidence: [...evidence] }; } function migrateOperationsEntity(entity, status, metadata) { entity.status = status; entity.actor = metadata.actor; entity.time = metadata.time; entity.trace = metadata.trace; entity.evidence = [...metadata.evidence]; } const state = { sourceFilter: "all", sourceSearch: "", sourceId: "boss", sourceTab: "overview", jobFilter: "all", recordLayer: "standard", recordSource: "all", recordRegion: "all", recordSearch: "", recordId: "", contractKey: "external-job", auditFilter: "all", operationsTab: "issues", issueId: "", }; function h(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function showToast(message, title = "操作已完成") { const toast = document.createElement("div"); toast.className = "toast"; toast.innerHTML = `✓${h(title)}${h(message)}`; document.getElementById("toastRegion").appendChild(toast); setTimeout(() => toast.remove(), 3600); } function openModal(modal) { modal.classList.add("open"); modal.setAttribute("aria-hidden", "false"); document.body.style.overflow = "hidden"; } function closeModal(modal) { modal.classList.remove("open"); modal.setAttribute("aria-hidden", "true"); document.body.style.overflow = ""; } function switchView(viewName) { document.querySelectorAll(".view").forEach((view) => view.classList.toggle("active", view.id === `view-${viewName}`)); document.querySelectorAll(".nav-item[data-view]").forEach((button) => button.classList.toggle("active", button.dataset.view === viewName)); const activeView = document.getElementById(`view-${viewName}`); document.getElementById("breadcrumbCurrent").textContent = activeView?.dataset.title || "运行总览"; document.body.classList.remove("sidebar-open"); window.scrollTo({ top: 0, behavior: "smooth" }); } function formatNumber(value) { return Number(value).toLocaleString("zh-CN"); } function sourceById(id) { return sources.find((source) => source.id === id) || sources[0]; } function sourceStatusPill(source) { const tone = source.status === "attention" ? "attention" : source.status === "failed" ? "failed" : "running"; return `${h(source.statusLabel)}`; } const operationsStatusLabels = { ALERTED: "待确认", ACKNOWLEDGED: "已确认", ASSIGNED: "已指派", INVESTIGATING: "排障中", QUALITY_REVIEW_PENDING: "待质量复核", VERIFIED: "复核通过", REVIEWED_BLOCKED: "复核后仍不能发布", RESOLVED: "已解决", CLOSED: "已关闭", OPEN: "已建单", DRAFT: "草稿", PENDING_APPROVAL: "待审批", APPROVED: "已批准", REJECTED: "已驳回", PUBLISHED: "已发布", ROLLED_BACK: "已回滚", PENDING: "待发送", SENDING: "发送中", DELIVERED: "已送达", ACKNOWLEDGED_DELIVERY: "已确认回执", ACKNOWLEDGED: "已确认", FAILED: "失败", RETRYING: "重试中", PENDING_REVIEW: "待复核", RECORDED: "已记录", PASS: "通过", BLOCKED: "不能继续", ROLLBACK_PENDING_APPROVAL: "回滚待审批", ROLLBACK_APPROVED: "回滚已批准", ROLLBACK_REJECTED: "回滚已驳回", ROLLBACK_EXECUTED: "回滚已执行", }; function operationStatusLabel(status) { return operationsStatusLabels[status] || status; } function operationStatusTone(status) { if (["FAILED", "REJECTED", "BLOCKED", "REVIEWED_BLOCKED", "ROLLBACK_REJECTED"].includes(status)) return "failed"; if (["ALERTED", "PENDING_APPROVAL", "QUALITY_REVIEW_PENDING", "PENDING", "RETRYING", "ASSIGNED", "ROLLBACK_PENDING_APPROVAL"].includes(status)) return "attention"; if ( [ "VERIFIED", "RESOLVED", "CLOSED", "APPROVED", "PUBLISHED", "DELIVERED", "ACKNOWLEDGED", "ROLLED_BACK", "PASS", "ROLLBACK_APPROVED", "ROLLBACK_EXECUTED", ].includes(status) ) return "running"; return ""; } function operationTime(value) { if (!value) return "—"; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleString("zh-CN", { hour12: false }); } function currentRelease() { return operationsStore.releases.find((release) => release.id === "REL-20260823-01"); } function currentApproval() { const release = currentRelease(); return operationsStore.approvals.find((approval) => approval.id === release?.approvalId); } function currentRollbackApproval() { const release = currentRelease(); return operationsStore.approvals.find((approval) => approval.releaseId === release?.id && approval.type === "ROLLBACK"); } function isIndependentApproval(approval, approver = approval?.approver) { return Boolean(approval && approver && approver !== approval.applicant); } function decideIndependentApproval({ approval, target, approver, comment, approved, approveStatus, rejectStatus, approveEvent, rejectEvent, object, evidence, defaultApproveComment, defaultRejectComment, resultLabel = (status) => status, successMessage, successTitle, }) { if (!isIndependentApproval(approval, approver)) { const rollback = approval?.type === "ROLLBACK"; showToast(rollback ? "回滚申请人与审批人相同,四眼规则阻止了该操作" : "申请人和审批人相同,四眼审批规则阻止了该操作", rollback ? "无法批准自己的回滚" : "无法批准自己的申请"); return false; } const nextStatus = approved ? approveStatus : rejectStatus; const metadata = addOperationsAudit({ type: "publish", event: approved ? approveEvent : rejectEvent, object, actor: approver, result: resultLabel(nextStatus), evidence, }); approval.approver = approver; approval.comment = comment || (approved ? defaultApproveComment : defaultRejectComment); approval.decidedAt = metadata.time; migrateOperationsEntity(approval, nextStatus, metadata); if (target) migrateOperationsEntity(target, nextStatus, metadata); saveOperationsStore(); renderOperations(); renderAudit(); showToast(successMessage(approver, approved), successTitle(approved)); return true; } function latestQualityDisposition(issueId = "ISSUE-HZRC-20260823-01") { return operationsStore.qualityDispositions.filter((disposition) => disposition.issueId === issueId).at(-1); } function isVerifiedFixReplay(disposition) { return Boolean( disposition?.action === "FIX_REPLAY" && disposition.status === "VERIFIED" && disposition.verification?.replayedRecords === disposition.verification?.expectedRecords && disposition.verification?.rulesPassed === true && disposition.verification?.reviewerSigned === true, ); } function qualityGatePassed() { return isVerifiedFixReplay(latestQualityDisposition()); } function updateReleaseQualityGate() { const release = currentRelease(); if (!release || ["PUBLISHED", "ROLLED_BACK"].includes(release.status)) return; release.qualityGate = qualityGatePassed() ? "PASS" : "BLOCKED"; } function releaseReadyForApproval(release = currentRelease()) { return Boolean(release?.qualityGate === "PASS" && release.manifestGeneratedAt && release.candidateManifest); } function renderOperationsMetrics() { const openIssues = operationsStore.issues.filter((issue) => !["RESOLVED", "CLOSED"].includes(issue.status)).length; const pendingApprovals = operationsStore.approvals.filter((approval) => ["PENDING_APPROVAL", "ROLLBACK_PENDING_APPROVAL"].includes(approval.status)).length; const deliveryAttention = operationsStore.deliveries.filter((delivery) => ["PENDING", "SENDING", "FAILED", "RETRYING"].includes(delivery.status)).length; const acknowledged = operationsStore.deliveries.filter((delivery) => delivery.status === "ACKNOWLEDGED").length; document.getElementById("operationsMetrics").innerHTML = `
未处理完问题${openIssues}确认、指派、排障与质量处置
审批待办${pendingApprovals}申请人与审批人强制分离
交付待办 / 异常${deliveryAttention}按租户隔离,不重复发送成功学校
已核验回执${acknowledged}记录数与 SHA-256 均一致
`; document.getElementById("operationsNavCount").textContent = openIssues + pendingApprovals + deliveryAttention; document.getElementById("notificationButton").querySelector("b").textContent = openIssues + pendingApprovals + deliveryAttention; const release = currentRelease(); const releaseReady = releaseReadyForApproval(release); const overviewState = document.getElementById("overviewReleaseState"); const overviewDetail = document.getElementById("overviewReleaseDetail"); const qualityState = document.getElementById("qualityReleaseState"); const qualityDetail = document.getElementById("qualityReleaseDetail"); if (overviewState) overviewState.textContent = release?.status === "PUBLISHED" ? "稳定版本已发布" : releaseReady ? "候选可提交审批" : "候选发布受阻"; if (overviewDetail) overviewDetail.textContent = release?.qualityGate === "PASS" ? (release?.manifestGeneratedAt ? "质量与候选 Manifest 已就绪" : "质量通过,待生成候选 Manifest") : "杭州人才网修复重放尚未验证"; if (qualityState) qualityState.textContent = release?.qualityGate === "PASS" ? "当前批次已满足发布质量要求" : "当前批次仍需修复重放复核"; if (qualityDetail) qualityDetail.textContent = release?.qualityGate === "PASS" ? "质量记录已经另一名复核人确认,可继续生成候选包" : "仅质量分达标还不能发布,当前稳定版本保持不变"; } function renderOverviewIssues() { const issues = operationsStore.issues.filter((issue) => !["RESOLVED", "CLOSED"].includes(issue.status)).slice(0, 3); const list = document.getElementById("overviewIssueList"); list.innerHTML = issues.length ? issues .map( (issue) => ` `, ) .join("") : `
当前没有待处理问题。
`; const countBadge = list.closest(".alert-panel").querySelector(".panel-head > em"); countBadge.textContent = `${issues.length} 项`; } function renderOperationsIssues() { const rows = operationsStore.issues .map((issue) => { const last = issue.timeline.at(-1); return ` ${h(issue.title)}${h(issue.id)} · ${h(issue.trace)} ${h(issue.severity)}${h(issue.sla)} ${h(issue.impact)} ${h(issue.owner)} ${h(operationStatusLabel(issue.status))} ${h(last?.actor || issue.actor)}${h(operationTime(last?.time || issue.time))}
`; }) .join(""); document.getElementById("operationsIssueTable").innerHTML = rows; const openCount = operationsStore.issues.filter((issue) => !["RESOLVED", "CLOSED"].includes(issue.status)).length; document.getElementById("issueQueueSummary").textContent = `${openCount} 项未处理完 · 状态刷新后保留`; } function releaseNextAction(release, approval) { if (release.status === "DRAFT") { const ready = releaseReadyForApproval(release); return ``; } if (release.status === "PENDING_APPROVAL") { return ` `; } if (release.status === "REJECTED") { return ``; } if (release.status === "APPROVED") { return ``; } if (release.status === "PUBLISHED") { const rollbackApproval = currentRollbackApproval(); if (rollbackApproval?.status === "ROLLBACK_PENDING_APPROVAL") { return `
全局回滚申请待独立审批 ${h(rollbackApproval.reason)} · 申请人 ${h(rollbackApproval.applicant)} 不能自批
`; } if (rollbackApproval?.status === "ROLLBACK_APPROVED") { return ``; } return ` `; } return `当前候选版本已回滚,交付记录仍按租户保留审计。`; } function renderReleaseWorkspace() { updateReleaseQualityGate(); const release = currentRelease(); const approval = currentApproval(); const rollbackApproval = currentRollbackApproval(); document.getElementById("releaseWorkspace").innerHTML = `
RELEASE CANDIDATE

${h(release.id)}

${h(operationStatusLabel(release.status))}
数据版本${h(release.dataVersion)} 候选包${h(release.packageId)} 上一稳定版本${h(release.previousStableVersion)}
发布质量检查${h(operationStatusLabel(release.qualityGate))} · ${release.qualityScore} 影响租户${release.impactTenants.length} 所学校 申请人${h(release.applicant)} Manifest${release.publishedManifestGeneratedAt ? `发布版已生成 · ${h(operationTime(release.publishedManifestGeneratedAt))}` : release.manifestGeneratedAt ? `候选已生成 · ${h(operationTime(release.manifestGeneratedAt))}` : "尚未生成"}
${ release.status === "PUBLISHED" ? "发布使用的质量记录、候选 Manifest 和审批结果均不可修改,正式发布 Manifest 已生成;这不表示学校已经接收。" : release.status === "ROLLED_BACK" ? `稳定别名已回滚到 ${h(release.previousStableVersion)},原发布、审批和租户交付记录继续保留审计。` : release.status === "PENDING_APPROVAL" ? "候选 Manifest 已提交,审批完成前不能修改,正在等待另一名审批人处理。" : release.status === "APPROVED" ? "独立审批已通过,可以执行稳定别名切换并生成正式发布 Manifest。" : release.status === "REJECTED" ? "独立审批已驳回,旧候选不可继续发布;返回草稿后必须修订并重新生成 Manifest。" : release.qualityGate === "PASS" ? release.manifestGeneratedAt ? "修复重放证据与候选 Manifest 已就绪,可以提交独立发布审批。" : "修复重放已由独立复核人验证;还必须生成候选 Manifest 后才能提交审批。" : "当前不能发布:只有 FIX_REPLAY 的记录数、规则结果和复核签名全部通过检查才可继续;豁免或丢弃仍不能发布。" }
${ release.status === "PUBLISHED" && release.publishedManifest ? `` : release.status === "DRAFT" ? `` : `候选 Manifest 已提交,审批中不能重建。` }
FOUR-EYES APPROVAL

独立审批

${h(operationStatusLabel(approval.status))}
审批编号
${h(approval.id)}
申请人
${h(approval.applicant)}
审批人
${h(approval.approver || "待独立审批人处理")}
审批意见
${h(approval.comment || "—")}
审批引用
${h(`${approval.id}:${approval.status}`)}
${ rollbackApproval?.status !== "DRAFT" ? `
回滚审批
${h(`${rollbackApproval.id}:${rollbackApproval.status}`)} · ${h(rollbackApproval.approver || "待独立审批")}
` : "" }
${releaseNextAction(release, approval)}
`; } function deliveryActionButtons(delivery) { if (delivery.status === "PENDING") return `发布后可启动`; if (["SENDING", "RETRYING"].includes(delivery.status)) { return `
`; } if (delivery.status === "FAILED") return `
`; if (delivery.status === "DELIVERED") { return `
`; } if (delivery.status === "ACKNOWLEDGED") return `
`; return `已完成租户级回滚`; } function renderDeliveries() { const published = currentRelease()?.status === "PUBLISHED"; const button = document.getElementById("startPendingDeliveries"); button.disabled = !published || !operationsStore.deliveries.some((delivery) => delivery.status === "PENDING"); button.textContent = published ? "启动待发送学校" : "候选发布后可启动"; document.getElementById("deliveryTable").innerHTML = operationsStore.deliveries .map( (delivery) => ` ${h(delivery.school)}${h(delivery.tenantId)} ${h(delivery.packageId)}${h(delivery.dataVersion)} ${h(operationStatusLabel(delivery.status))}${delivery.failureReason ? `${h(delivery.failureReason)}` : ""} ${ delivery.status === "DELIVERED" ? `` : `${delivery.receiptRecords === null ? "—" : formatNumber(delivery.receiptRecords)}期望 ${formatNumber(delivery.expectedRecords)}` } ${ delivery.status === "DELIVERED" ? `` : `${h(delivery.receiptSha || "—")}` } ${h(delivery.actor)}${h(operationTime(delivery.time))} · ${delivery.attempts} 次 ${deliveryActionButtons(delivery)} `, ) .join(""); } function renderOperations() { renderOperationsMetrics(); renderOverviewIssues(); renderOperationsIssues(); renderReleaseWorkspace(); renderDeliveries(); document.querySelectorAll("[data-operations-tab]").forEach((button) => button.classList.toggle("active", button.dataset.operationsTab === state.operationsTab)); document.querySelectorAll(".operations-workspace").forEach((workspace) => workspace.classList.toggle("active", workspace.id === `operations-${state.operationsTab}`)); } function issueActionFooter(issue) { const disposition = latestQualityDisposition(issue.id); const qualityReviewAction = disposition?.action === "FIX_REPLAY" ? `` : ``; const actions = { ALERTED: ``, ACKNOWLEDGED: ``, ASSIGNED: ``, INVESTIGATING: issue.qualityRequired ? `填写质量处置并提交复核` : ``, QUALITY_REVIEW_PENDING: qualityReviewAction, VERIFIED: ``, RESOLVED: ``, CLOSED: `问题已完成处理,操作记录和附件已保留。`, }; return `${actions[issue.status] || ""}`; } function openIssueDrawer(issueId) { const issue = operationsStore.issues.find((item) => item.id === issueId); if (!issue) return; state.issueId = issueId; const disposition = operationsStore.qualityDispositions.filter((item) => item.issueId === issue.id).at(-1); document.getElementById("issueDrawerKicker").textContent = `${issue.severity} · ${issue.id}`; document.getElementById("issueDrawerTitle").textContent = issue.title; document.getElementById("issueDrawerDescription").textContent = `${operationStatusLabel(issue.status)} · ${issue.owner} · ${issue.sla}`; document.getElementById("issueDrawerContent").innerHTML = `
负责人${h(issue.owner)} SLA${h(issue.sla)} 关联任务${h(issue.relatedTask)} 隔离记录${h(issue.quarantineId)}

影响与证据

${h(issue.impact)}

${issue.evidence.map((item) => `${h(item)}`).join("")}
${ issue.status === "INVESTIGATING" && issue.qualityRequired ? `

提交质量处置复核

` : "" } ${ disposition ? `

不可变质量动作

动作编号
${h(disposition.id)}
处置方式
${h({ FIX_REPLAY: "修复并重放", WAIVE: "豁免", DISCARD: "丢弃" }[disposition.action])}
原因
${h(disposition.reason)}
复核人 / 状态
${h(disposition.reviewer)} · ${h(operationStatusLabel(disposition.status))}
追踪 / 证据
${h(disposition.trace)} · ${h(disposition.evidence.join("、"))}
` : "" }

处置时间线

${issue.timeline .map( (step) => ` ${h(operationStatusLabel(step.status))} · ${h(step.actor)}${h(operationTime(step.time))} · ${h(step.note)}${h(step.trace)} · ${h(step.evidence.join("、"))} `, ) .join("")}
`; document.getElementById("issueDrawerFooter").innerHTML = issueActionFooter(issue); const drawer = document.getElementById("issueDrawer"); drawer.classList.add("open"); drawer.setAttribute("aria-hidden", "false"); document.body.style.overflow = "hidden"; } function closeIssueDrawer() { const drawer = document.getElementById("issueDrawer"); drawer.classList.remove("open"); drawer.setAttribute("aria-hidden", "true"); document.body.style.overflow = ""; } function transitionIssue(issue, status, note, evidence, actor = OPERATIONS_ACTOR) { const metadata = addOperationsAudit({ event: `${issue.title}:${operationStatusLabel(status)}`, object: issue.id, actor, result: operationStatusLabel(status), evidence, }); migrateOperationsEntity(issue, status, metadata); issue.timeline.push({ id: operationToken("STEP"), status, actor: metadata.actor, time: metadata.time, trace: metadata.trace, evidence: [...metadata.evidence], note, }); const workOrder = operationsStore.workOrders.find((item) => item.issueId === issue.id); if (workOrder) { migrateOperationsEntity(workOrder, status === "CLOSED" ? "CLOSED" : status, metadata); workOrder.assignee = issue.owner; } saveOperationsStore(); renderOperations(); renderAudit(); openIssueDrawer(issue.id); } function renderOverviewSources() { const selected = sources.slice(0, 5); document.getElementById("overviewSourceHealth").innerHTML = selected .map( (source) => ` `, ) .join(""); } function filteredSources() { const keyword = state.sourceSearch.trim().toLowerCase(); return sources.filter((source) => { const matchesFilter = state.sourceFilter === "all" || source.category === state.sourceFilter; const text = `${source.name}${source.domain}${source.adapter}${source.categoryLabel}`.toLowerCase(); return matchesFilter && text.includes(keyword); }); } function renderSources() { const filtered = filteredSources(); document.getElementById("sourceGrid").innerHTML = filtered .map( (source) => `
${h(source.name)}${h(source.domain)} ${h(source.categoryLabel)}
健康度${source.health.toFixed(1)} 今日新增${formatNumber(source.today)} 累计有效${h(source.total)}
${h(source.mode)}${h(source.schedule)}${h(source.contract)}
${h(source.statusLabel)} · 更新于 ${h(source.updated)}
${source.category === "school" ? "" : ``}
`, ) .join(""); if (!filtered.length) { document.getElementById("sourceGrid").innerHTML = `
没有找到符合条件的数据源,请调整分类或搜索关键词。
`; } document.getElementById("sourceListSummary").textContent = `当前展示 ${filtered.length} 个数据源`; document.getElementById("sourceTotal").textContent = sources.length; document.getElementById("sourceRunning").textContent = sources.filter((source) => source.status === "running").length; document.getElementById("sourceNavCount").textContent = sources.length; } function drawerStats(source) { return `
当前源版本${h(source.version)}${h(source.updated)} 更新 综合健康度${source.health.toFixed(1)}${h(source.statusLabel)} 今日有效记录${formatNumber(source.today)}累计 ${h(source.total)} 目标契约${h(source.contract)}${h(source.schema)}
`; } function renderSourceOverview(source) { return ` ${drawerStats(source)}
${h(source.categoryLabel.toUpperCase())} · ${h(source.adapter)} ${h(source.coverage)} ${h(source.change)}

资产责任与运行策略

来源入口
${h(source.domain)}
接入方式
${h(source.mode)}
更新频率
${h(source.schedule)}
技术责任人
${h(source.owner)}
原始数据留存
${h(source.retention)}
连接器资产
${h(source.repo)} · ${h(source.codeVersion)}

进入生产的控制点

  • 先保存原始响应与内容哈希,再执行解析、清洗和岗位归一。
  • 解析规则、代码版本、任务分片和增量水位与数据版本绑定。
  • 新采集批次未通过质量验收前,不覆盖当前稳定交付版本。
`; } function renderSourceAcquisition(source) { return ` ${drawerStats(source)}

生产采集配置

采集模式
${h(source.mode)}
调度计划
${h(source.schedule)}
并发与速率
${h(source.rate)}
失败重试
${h(source.retry)}
采集范围
${h(source.coverage)}
目标数据契约
${h(source.contract)}

任务执行要求

  • 所有请求必须携带 task_id、shard_id、collector_version 和 trace_id。
  • 源端异常触发自动降速、熔断或分片隔离,不影响其他来源继续运行。
  • 增量任务必须提交已验收水位;失败重试保持幂等。
  • 动态页面、接口响应和文件下载均进入原始留存层,解析过程可重放。
`; } function renderSourceMapping(source) { const rows = source.category === "school" ? [ ["student_key", "student_token", "令牌化", "必填"], ["major_code", "program_code", "专业代码字典", "必填"], ["job_name", "job_title", "去空白与标准化", "条件必填"], ["company_code", "enterprise_token", "企业标识令牌化", "条件必填"], ["salary", "salary_monthly", "统一元/月", "可选"], ["update_time", "updated_at", "ISO-8601", "必填"], ] : [ ["source_id", "source_record_id", "原样保留", "必填"], ["job_name", "job_title_raw", "去标记与空白", "必填"], ["company_name", "enterprise_name", "原文 + 企业归一", "必填"], ["city", "region_code", "GB/T 2260", "必填"], ["salary", "salary_min/max", "统一税前月薪", "可选"], ["description", "job_description", "去联系方式", "必填"], ["publish_time", "published_at", "ISO-8601", "可选"], ["detail_url", "source_url", "绝对URL", "必填"], ]; return `
FIELD MAPPING · ${h(source.schema)} ${h(source.name)} → ${h(source.contract)} 映射版本与连接器代码独立版本化,支持回放历史原始数据进行回归验证。

核心字段映射

${rows.map((row) => `${row.map((cell) => ``).join("")}`).join("")}
源字段目标字段转换规则要求
${h(cell)}

样本回归

最近回归样本 1,000 条,结构通过率 100%,必填完整率 98.8%,岗位归一覆盖率 96.4%。未匹配岗位进入人工复核队列,不用模型推断替换原始记录。

`; } function renderSourceRuntime(source) { return ` ${drawerStats(source)}

最近运行事件

${source.runtime.map(([time, title, detail]) => `${h(time)} · ${h(title)}${h(detail)}`).join("")}

稳定版本保护

运行中的采集、解析和质量任务只写入暂存版本。完整规则通过并完成发布审批后,才更新供下游分析引用的稳定别名。

`; } function renderSourceAudit(source) { return `
TRACEABLE SOURCE ASSET ${h(source.version)} · ${h(source.codeVersion)} 来源、代码、任务、原始快照、规则、质量和交付版本均可通过追踪编号关联。

当前审计证据

来源注册记录${h(source.domain)} · 资产责任人 ${h(source.owner)} 连接器代码版本${h(source.repo)} · ${h(source.codeVersion)} 原始数据版本RAW-${h(source.id.toUpperCase())}-20260823 · SHA-256 已固化 字段映射与契约${h(source.schema)} → ${h(source.contract)} 生产运行策略${h(source.rate)} · ${h(source.retry)}

数据使用边界

平台只向产教分析产品交付符合数据契约的岗位信息、企业信息和统计结果。原始数据与交付数据分开存储,Manifest 和租户授权共同决定每所学校可收到的数据。

`; } function updateSourceDrawer() { const source = sourceById(state.sourceId); document.getElementById("sourceDrawerKicker").textContent = `${source.categoryLabel.toUpperCase()} · ${source.adapter}`; document.getElementById("sourceDrawerTitle").textContent = source.name; document.getElementById("sourceDrawerDescription").textContent = `${source.domain} · ${source.coverage} · ${source.statusLabel}`; document.querySelectorAll("#sourceDrawerTabs button").forEach((button) => { button.classList.toggle("active", button.dataset.sourceTab === state.sourceTab); }); const renderers = { overview: renderSourceOverview, acquisition: renderSourceAcquisition, mapping: renderSourceMapping, runtime: renderSourceRuntime, audit: renderSourceAudit, }; document.getElementById("sourceDrawerContent").innerHTML = renderers[state.sourceTab](source); document.getElementById("sourceDrawerFooter").innerHTML = ` ${source.category === "school" ? "" : ``} `; } function openSourceDrawer(sourceId) { state.sourceId = sourceId; state.sourceTab = "overview"; updateSourceDrawer(); const drawer = document.getElementById("sourceDrawer"); drawer.classList.add("open"); drawer.setAttribute("aria-hidden", "false"); document.body.style.overflow = "hidden"; } function closeSourceDrawer() { const drawer = document.getElementById("sourceDrawer"); drawer.classList.remove("open"); drawer.setAttribute("aria-hidden", "true"); document.body.style.overflow = ""; } function renderCollectionJobs() { const jobs = collectionJobs.filter((job) => state.jobFilter === "all" || job.status === state.jobFilter); document.getElementById("collectionJobTable").innerHTML = jobs .map((job) => { const source = sourceById(job.sourceId); const statusTone = job.status === "attention" ? "attention" : job.status === "running" ? "running" : ""; return ` ${h(job.name)}${h(job.id)} · ${h(source.name)} ${h(job.scope)} ${h(job.strategy)}${h(source.contract)}
${job.progress ? "已完成" : "等待开始"}${job.progress}%
${h(job.records)} 条有效率 ${h(job.valid)} ${job.records === "—" ? "" : ``} ${h(job.statusLabel)}
`; }) .join(""); if (!jobs.length) { document.getElementById("collectionJobTable").innerHTML = `当前分类暂无采集任务。`; } document.getElementById("runningJobCount").textContent = collectionJobs.filter((job) => job.status === "running").length; } function populateRecordSources() { const select = document.getElementById("recordSourceFilter"); select.innerHTML = [ ``, ...sources.filter((source) => source.category !== "school").map((source) => ``), ].join(""); if (!sources.some((source) => source.id === state.recordSource && source.category !== "school")) state.recordSource = "all"; select.value = state.recordSource; } function recordSearchMatches(values) { const keyword = state.recordSearch.trim().toLowerCase(); return !keyword || values.filter(Boolean).join(" ").toLowerCase().includes(keyword); } function filteredStandardRecords() { return standardJobRecords.filter((record) => { const source = sourceById(record.sourceId); const sourceMatch = state.recordSource === "all" || record.sourceId === state.recordSource; const regionMatch = state.recordRegion === "all" || record.region === state.recordRegion; return ( sourceMatch && regionMatch && recordSearchMatches([ record.id, record.sourceRecordId, record.taskId, record.snapshotId, record.jobTitleRaw, record.jobTitleNormalized, record.enterprise, record.region, record.workplace, record.jobCode, record.skills.join(" "), source.name, ]) ); }); } function filteredRawSnapshots() { return allRawSnapshots.filter((snapshot) => { const source = sourceById(snapshot.sourceId); const sourceMatch = state.recordSource === "all" || snapshot.sourceId === state.recordSource; return sourceMatch && recordSearchMatches([snapshot.id, snapshot.taskId, snapshot.objectPath, snapshot.sha256, snapshot.mediaType, source.name]); }); } function filteredQuarantineRecords() { return quarantineRecords.filter((record) => { const source = sourceById(record.sourceId); const sourceMatch = state.recordSource === "all" || record.sourceId === state.recordSource; const regionMatch = state.recordRegion === "all" || record.region === state.recordRegion; return sourceMatch && regionMatch && recordSearchMatches([record.id, record.sourceRecordId, record.taskId, record.snapshotId, record.jobTitle, record.issue, record.rule, source.name]); }); } function recordSourceCell(source, secondary) { return ` ${h(source.short.slice(0, 2))} ${h(source.name)}${h(secondary)} `; } function recordSalary(record) { return `¥${formatNumber(record.salaryMin)}–${formatNumber(record.salaryMax)} / 月`; } function renderStandardRecordTable(records, sourceLayer = false) { if (!records.length) return `
当前查询条件下没有匹配记录,请调整来源、地区或关键词。
`; const rows = records .map((record) => { const source = sourceById(record.sourceId); if (sourceLayer) { return ` ${recordSourceCell(source, record.sourceRecordId)} ${h(record.jobTitleRaw)}${h(record.enterprise)} ${h(record.sourceUrl.replace(/^https?:\/\//, ""))}来源详情定位 ${h(record.taskId)}${h(record.snapshotId)} ${h(record.collectorVersion)}${h(record.parserVersion)} ${h(record.collectedAt)}
`; } return ` ${recordSourceCell(source, record.sourceRecordId)} ${h(record.jobTitleNormalized)}原始:${h(record.jobTitleRaw)} ${h(record.enterprise)}${h(record.workplace)} ${h(recordSalary(record))}${h(record.employmentType)} · ${h(record.jobCode)} ${h(record.publishedAt)}采集 ${h(record.collectedAt)} ${record.quality.toFixed(1)}
`; }) .join(""); return `
${ sourceLayer ? "" : "" } ${rows}
来源与源记录源端岗位与企业来源URL任务与快照采集器与解析器采集时间操作
来源与源记录标准岗位企业与工作地点薪资与分类发布时间质量分操作
`; } function renderRawSnapshotTable(snapshots) { if (!snapshots.length) return `
当前查询条件下没有匹配的原始快照。
`; return `
${snapshots .map((snapshot) => { const source = sourceById(snapshot.sourceId); return ` `; }) .join("")}
来源与快照采集任务对象格式记录与大小对象存储定位固化时间校验状态操作
${recordSourceCell(source, snapshot.id)} ${h(snapshot.taskId)} ${h(snapshot.mediaType)} ${h(snapshot.records)} 条${h(snapshot.size)} ${h(snapshot.objectPath)}WORM 原始对象区 ${h(snapshot.capturedAt)} ${h(snapshot.status)}
`; } function renderQuarantineTable(records) { if (!records.length) return `
当前查询条件下没有匹配的隔离记录。
`; return `
${records .map((record) => { const source = sourceById(record.sourceId); return ` `; }) .join("")}
隔离编号来源记录岗位质量问题任务与快照发现时间处置状态操作
${h(record.id)}${h(record.rule)} ${recordSourceCell(source, record.sourceRecordId)} ${h(record.jobTitle)} ${h(record.issue)}未进入稳定版本 ${h(record.taskId)}${h(record.snapshotId)} ${h(record.detectedAt)} ${h(record.disposition)}
`; } function currentRecordResults() { if (state.recordLayer === "raw") return filteredRawSnapshots(); if (state.recordLayer === "quarantine") return filteredQuarantineRecords(); return filteredStandardRecords(); } function renderRecords() { document.querySelectorAll("#recordLayers [data-record-layer]").forEach((button) => { button.classList.toggle("active", button.dataset.recordLayer === state.recordLayer); }); document.getElementById("recordSourceFilter").value = state.recordSource; document.getElementById("recordRegionFilter").value = state.recordRegion; document.getElementById("recordSearch").value = state.recordSearch; const results = currentRecordResults(); const renderers = { standard: () => renderStandardRecordTable(results), source: () => renderStandardRecordTable(results, true), raw: () => renderRawSnapshotTable(results), quarantine: () => renderQuarantineTable(results), }; document.getElementById("recordTableContainer").innerHTML = renderers[state.recordLayer](); const layerLabel = { standard: "标准岗位", source: "源端记录", raw: "原始快照", quarantine: "隔离记录" }[state.recordLayer]; document.getElementById("recordQuerySummary").textContent = `${layerLabel} · 当前展示 ${results.length} 条结构样例`; } function openRecordExplorer(sourceId = "all", search = "", layer = "standard") { state.recordSource = sources.some((source) => source.id === sourceId && source.category !== "school") ? sourceId : "all"; state.recordSearch = search; state.recordRegion = "all"; state.recordLayer = layer; populateRecordSources(); renderRecords(); switchView("records"); } function openRecordDrawer({ kicker, title, description, content, footer }) { document.getElementById("recordDrawerKicker").textContent = kicker; document.getElementById("recordDrawerTitle").textContent = title; document.getElementById("recordDrawerDescription").textContent = description; document.getElementById("recordDrawerContent").innerHTML = content; document.getElementById("recordDrawerFooter").innerHTML = footer; const drawer = document.getElementById("recordDrawer"); drawer.classList.add("open"); drawer.setAttribute("aria-hidden", "false"); document.body.style.overflow = "hidden"; } function closeRecordDrawer() { const drawer = document.getElementById("recordDrawer"); drawer.classList.remove("open"); drawer.setAttribute("aria-hidden", "true"); document.body.style.overflow = ""; } function openStandardRecord(recordId) { const record = standardJobRecords.find((item) => item.id === recordId); if (!record) return; const source = sourceById(record.sourceId); const payload = { record_id: record.id, source_platform: record.sourceId.toUpperCase(), source_record_id: record.sourceRecordId, source_url: record.sourceUrl, collector_task_id: record.taskId, collector_version: record.collectorVersion, raw_snapshot_id: record.snapshotId, collected_at: record.collectedAt, published_at: record.publishedAt, job_title_raw: record.jobTitleRaw, job_title_normalized: record.jobTitleNormalized, job_code: record.jobCode, job_description: record.description, enterprise_name: record.enterprise, region_code: record.regionCode, workplace: record.workplace, salary_min: record.salaryMin, salary_max: record.salaryMax, salary_currency: "CNY", salary_period: "month", skills: record.skills, content_hash: record.contentHash, parsing_version: record.parserVersion, normalized_at: record.normalizedAt, data_version: record.dataVersion, }; openRecordDrawer({ kicker: `STANDARD RECORD · ${record.dataVersion}`, title: record.jobTitleNormalized, description: `${source.name} · ${record.sourceRecordId} · ${record.status}`, content: `
${h(source.name)} · ${h(record.jobCode)}${h(record.jobTitleRaw)} → ${h(record.jobTitleNormalized)}${h(record.enterprise)} · ${h(record.workplace)} · ${h(recordSalary(record))}
${record.quality.toFixed(1)}

标准业务字段

记录标识
${h(record.id)}
企业与岗位
${h(record.enterprise)} · ${h(record.jobTitleNormalized)}
工作地点
${h(record.workplace)} · ${h(record.regionCode)}
薪资与性质
${h(recordSalary(record))} · ${h(record.employmentType)}
技能标签
${record.skills.map((skill) => `${h(skill)}`).join("")}

来源与处理记录

01采集任务${h(record.taskId)} · ${h(record.collectorVersion)} 02原始快照${h(record.snapshotId)} · payload SHA-256 ${h(record.payloadHash)} 03解析映射${h(record.parserVersion)} · ${h(record.sourceRecordId)} 04质量与归一质量分 ${record.quality.toFixed(1)} · 岗位代码 ${h(record.jobCode)} 05稳定版本${h(record.dataVersion)} · content SHA-256 ${h(record.contentHash)}

JOB-SIGNAL V3.0 契约记录

${h(JSON.stringify(payload, null, 2))}
`, footer: ` `, }); } function openRawSnapshot(snapshotId) { const snapshot = allRawSnapshots.find((item) => item.id === snapshotId); if (!snapshot) return; const source = sourceById(snapshot.sourceId); const manifest = { snapshot_id: snapshot.id, source_id: snapshot.sourceId, collector_task_id: snapshot.taskId, captured_at: snapshot.capturedAt, media_type: snapshot.mediaType, record_count: Number(snapshot.records.replaceAll(",", "")), object_path: snapshot.objectPath, compressed_size: snapshot.size, sha256: snapshot.sha256, retention_policy: source.retention, immutable: true, }; openRecordDrawer({ kicker: "RAW SNAPSHOT · IMMUTABLE", title: snapshot.id, description: `${source.name} · ${snapshot.records} 条 · ${snapshot.status}`, content: `
${h(source.name)} · ${h(snapshot.mediaType)}${h(snapshot.records)} 条原始记录${h(snapshot.objectPath)} · ${h(snapshot.size)}
RAW

原始对象元数据

采集任务
${h(snapshot.taskId)}
固化时间
${h(snapshot.capturedAt)}
对象格式
${h(snapshot.mediaType)}
对象定位
${h(snapshot.objectPath)}
SHA-256
${h(snapshot.sha256)}
留存策略
${h(source.retention)}

快照清单

${h(JSON.stringify(manifest, null, 2))}
`, footer: ` `, }); } function openQuarantineRecord(recordId) { const record = quarantineRecords.find((item) => item.id === recordId); if (!record) return; const source = sourceById(record.sourceId); openRecordDrawer({ kicker: `QUARANTINE · ${record.rule}`, title: record.jobTitle, description: `${source.name} · ${record.sourceRecordId} · 未进入稳定版本`, content: `
${h(record.rule)} · 质量隔离${h(record.issue)}${h(record.disposition)} · 发现于 ${h(record.detectedAt)}
隔离

隔离处置明细

隔离编号
${h(record.id)}
来源记录
${h(record.sourceRecordId)}
质量问题
${h(record.issue)}
命中规则
${h(record.rule)}
当前处置
${h(record.disposition)}
工作地区
${h(record.region)}

来源证据

01采集任务${h(record.taskId)} 02原始快照${h(record.snapshotId)} 03质量规则${h(record.rule)} · ${h(record.issue)} 04隔离区不覆盖稳定版本,修复后按原任务参数重放
`, footer: ` `, }); } function renderContract() { const contract = contracts[state.contractKey]; document.querySelectorAll("#contractTabs button").forEach((button) => { button.classList.toggle("active", button.dataset.contract === state.contractKey); }); document.getElementById("contractContent").innerHTML = `
${h(contract.key)} · ${h(contract.version)}

${h(contract.title)}

${h(contract.description)}

字段总数${contract.fields} 个 必填字段${contract.required} 个 标准责任人${h(contract.owner)}
CONTRACT RELEASE ${h(contract.status)} ${h(contract.release)} · ${h(contract.compatibility)}
起草评审测试发布
字段定义

核心字段预览

下载 JSON Schema
${contract.fieldsPreview .map( ([field, type, required, description, rule]) => ``, ) .join("")}
字段类型必填业务说明约束/代码
${h(field)}${h(type)}${h(required)}${h(description)}${h(rule)}
执行规则

契约控制点

${contract.rules.length} 组
${contract.rules.map(([title, detail]) => `${h(title)}${h(detail)}`).join("")}
`; } function renderExchangeLogs() { document.getElementById("exchangeLogTable").innerHTML = exchangeLogs .map( (row) => ` ${row .map((cell, index) => `${index === 6 ? `${h(cell)}` : h(cell)}`) .join("")} `, ) .join(""); } function renderQuality() { document.getElementById("qualityRuleList").innerHTML = qualityRules .map( ([code, title, detail, score, status]) => ` ${h(code)}${h(title)}${h(detail)}${h(score)} · ${h(status)} `, ) .join(""); document.getElementById("normalizationList").innerHTML = normalizationItems .map( ([raw, target, confidence], index) => ` ${h(raw)}来源岗位别名→ ${h(target)}建议映射 · 置信度 ${h(confidence)} `, ) .join(""); if (!normalizationItems.length) { document.getElementById("normalizationList").innerHTML = `
当前高优先级待审项已处理完成,其余低优先级项将在下一批次继续复核。
`; } } function renderAudit() { const mergedEvents = [ ...operationsStore.auditEvents.map((event) => ({ ...event, time: operationTime(event.time) })), ...auditEvents, ]; const events = mergedEvents.filter((event) => state.auditFilter === "all" || event.type === state.auditFilter); document.getElementById("auditTable").innerHTML = events .map( (event) => ` ${h(event.time)}${h(event.id)} ${h(event.event)} ${h(event.object)} ${h(event.actor)} ${h(event.result)}
${event.evidence.map((item) => `${h(item)}`).join("")}
`, ) .join(""); } function openInfoModal(kicker, title, content) { document.getElementById("infoModalKicker").textContent = kicker; document.getElementById("infoModalTitle").textContent = title; document.getElementById("infoModalContent").innerHTML = content; openModal(document.getElementById("infoModal")); } function csvEscape(value) { const text = String(value ?? ""); return /[",\n]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text; } function downloadText(filename, content, type = "text/plain;charset=utf-8") { const blob = new Blob([content], { type }); const url = URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = url; anchor.download = filename; document.body.appendChild(anchor); anchor.click(); anchor.remove(); setTimeout(() => URL.revokeObjectURL(url), 500); } function downloadJson(filename, value) { downloadText(filename, JSON.stringify(value, null, 2), "application/json;charset=utf-8"); } function exportSourceCatalog() { const headers = ["source_id", "name", "category", "domain", "adapter", "mode", "schedule", "status", "contract", "owner", "version"]; const rows = sources.map((source) => [ source.id, source.name, source.category, source.domain, source.adapter, source.mode, source.schedule, source.statusLabel, source.contract, source.owner, source.version, ]); const csv = [headers, ...rows].map((row) => row.map(csvEscape).join(",")).join("\n"); downloadText("edu-data-source-catalog-20260823.csv", `\uFEFF${csv}`, "text/csv;charset=utf-8"); showToast(`已导出${sources.length}个数据源及其连接器、契约、责任人与生产版本`, "数据源目录已生成"); } function candidateManifestInputFingerprint() { const release = currentRelease(); const disposition = latestQualityDisposition(); return JSON.stringify({ releaseId: release?.id, dataVersion: release?.dataVersion, product: document.getElementById("exportProduct").value, tenant: document.getElementById("exportTenant").value, asOf: document.getElementById("exportAsOf").value, format: document.getElementById("exportFormat").value, impactTenants: release?.impactTenants, deliveries: operationsStore.deliveries.map((delivery) => [delivery.tenantId, delivery.expectedRecords, delivery.expectedSha]), qualityDisposition: disposition ? { id: disposition.id, action: disposition.action, status: disposition.status, trace: disposition.trace, verification: disposition.verification, } : null, }); } function buildPackageManifest(options = {}) { const release = currentRelease(); const approval = currentApproval(); const releaseStatus = options.releaseStatus || release?.status || "DRAFT"; const approvalStatus = options.approvalStatus || approval?.status || "DRAFT"; const published = releaseStatus === "PUBLISHED"; const product = document.getElementById("exportProduct").value; const tenantLabel = document.getElementById("exportTenant").value; const asOf = document.getElementById("exportAsOf").value; const formatLabel = document.getElementById("exportFormat").value; const format = { "Parquet + Manifest": "parquet", "CSV UTF-8 + Manifest": "csv_utf8", "JSONL + Manifest": "jsonl", }[formatLabel]; const formatMeta = { parquet: { extension: "parquet", mediaType: "application/vnd.apache.parquet", compression: "zstd", bytesPerRecord: 240 }, csv_utf8: { extension: "csv", mediaType: "text/csv", compression: "none", bytesPerRecord: 410 }, jsonl: { extension: "jsonl", mediaType: "application/x-ndjson", compression: "none", bytesPerRecord: 620 }, }[format]; const tenantDeliveries = release?.impactTenants?.length ? release.impactTenants.map((tenantId) => operationsStore.deliveries.find((delivery) => delivery.tenantId === tenantId)).filter(Boolean) : []; const tenantScope = tenantDeliveries.length ? tenantDeliveries.map((delivery) => delivery.tenantId) : [tenantLabel]; const tenantDataFiles = tenantDeliveries.length ? tenantDeliveries.map((delivery) => ({ path: `data/${delivery.tenantId}/job-signal.${formatMeta.extension}`, media_type: formatMeta.mediaType, bytes: delivery.expectedRecords * formatMeta.bytesPerRecord, sha256: delivery.expectedSha, record_count: delivery.expectedRecords, compression: formatMeta.compression, })) : [ { path: `data/job-signal.${formatMeta.extension}`, media_type: formatMeta.mediaType, bytes: 2846320 * formatMeta.bytesPerRecord, sha256: "4f8d91a47e2b6d82b2aaad136d344d61c83f9cd472185d2543725c91870e9e32", record_count: 2846320, compression: formatMeta.compression, }, ]; const recordCount = tenantDataFiles.reduce((total, file) => total + file.record_count, 0); return { manifest_version: "1.2.0", package_id: release?.packageId || `EDU-PKG-${asOf.replaceAll("-", "")}-CANDIDATE`, ...(release ? { release_id: release.id, release_status: releaseStatus } : {}), data_product: product, data_version: release?.dataVersion || `V${asOf.replaceAll("-", ".")}`, contract_name: "JOB-SIGNAL", contract_version: "3.0.0", data_as_of: asOf, tenant_scope: tenantScope, output_format: format, record_count: recordCount, quality_score: 94.7, lineage_snapshot: `LINEAGE-${asOf.replaceAll("-", "")}-09`, previous_stable_version: release?.previousStableVersion || "V2026.08.22", files: [ ...tenantDataFiles, { path: "schema/external-job-source.schema.json", media_type: "application/schema+json", bytes: 18420, sha256: "96c2be742dc43f8e7ee526db6358030873406c450f15e67225c605dc39b431a8", record_count: null, compression: "none", }, { path: "quality/quality-report.json", media_type: "application/json", bytes: 32640, sha256: "c2d8f733e6711219823110d7d35fdc5535f133ed60f60950f9dc78a228cb402d", record_count: null, compression: "none", }, { path: "lineage/source-lineage.json", media_type: "application/json", bytes: 84210, sha256: "66e102b99bf056868670ac971011740c6f66f62b86ea43cc66326ed9a0a5807e", record_count: null, compression: "none", }, ], quality_report_path: "quality/quality-report.json", generated_at: new Date().toISOString(), generated_by: "陈致远", approval_reference: approval ? `${approval.id}:${approvalStatus}` : null, notes: published ? "该文件是独立审批通过后生成的发布 Manifest;静态原型只记录本地稳定版本状态,不会向学校发送。" : "该文件仅为候选包 Manifest;只有独立审批通过并执行发布后才成为稳定版本,静态原型不会向学校发送。", }; } function generateCandidateManifest() { const release = currentRelease(); updateReleaseQualityGate(); if (!release || release.status !== "DRAFT") { showToast("候选已提交审批或发布,当前 Manifest 不能修改;驳回后可返回草稿重建", "无法重建候选 Manifest"); return; } if (release.qualityGate !== "PASS") { showToast("只有 FIX_REPLAY 的记录数、规则结果和复核签名全部通过检查后,才能生成候选 Manifest", "当前不能生成候选"); return; } const inputFingerprint = candidateManifestInputFingerprint(); if (release.candidateManifest && release.candidateInputFingerprint === inputFingerprint) { downloadJson(`${release.candidateManifest.package_id}-candidate-manifest.json`, release.candidateManifest); showToast("候选输入与质量证据未变化,已复用原 Manifest;未新增审计或交换记录", "候选 Manifest 已复用"); return; } const manifest = buildPackageManifest(); const metadata = addOperationsAudit({ type: "publish", event: "生成候选包 Manifest", object: `${release.id} · ${release.packageId}`, result: "候选已生成", evidence: ["Manifest", "质量报告", "血缘快照", "SHA-256"], }); release.manifestGeneratedAt = metadata.time; release.candidateManifest = manifest; release.candidateInputFingerprint = inputFingerprint; release.actor = metadata.actor; release.time = metadata.time; release.trace = metadata.trace; release.evidence = [...metadata.evidence]; saveOperationsStore(); downloadJson(`${manifest.package_id}-candidate-manifest.json`, manifest); exchangeLogs.unshift([manifest.package_id, "候选导出", manifest.data_product, manifest.data_version, formatNumber(manifest.record_count), "陈致远", "候选已生成", "刚刚"]); exchangeLogs.splice(OPERATIONS_LIMITS.exchangeLogs); renderExchangeLogs(); renderOperations(); renderAudit(); showToast(`${manifest.package_id} 仅生成候选文件,尚未发布或向学校发送`, "候选包 / Manifest 已生成"); } function exportDailyReport() { const report = { report_id: "OPS-DAILY-20260823", generated_at: new Date().toISOString(), environment: "production-east-cn", batch: "EDU-JOB-20260823", source_count: sources.length, running_sources: sources.filter((source) => source.status === "running").length, running_jobs: collectionJobs.filter((job) => job.status === "running").length, records_collected_today: sources.reduce((sum, source) => sum + source.today, 0), quality_score: 94.7, normalization_coverage: 96.4, stable_data_version: "V2026.08.23", tenant_delivery_count: 36, attention_items: [ "27个岗位别名待人工归一", "杭州人才网处于自动降速保护", "SCHOOL-EMPLOYMENT V2.2待生产发布", ], }; downloadJson("edu-data-operations-daily-20260823.json", report); showToast("运行日报包含生产批次、来源、采集、质量、归一和交付状态", "运行日报已生成"); } function exportExchangeLog() { const headers = ["trace_id", "type", "object", "version", "records", "actor", "result", "time"]; const csv = [headers, ...exchangeLogs].map((row) => row.map(csvEscape).join(",")).join("\n"); downloadText("edu-data-exchange-log-20260823.csv", `\uFEFF${csv}`, "text/csv;charset=utf-8"); showToast("导入与交付记录已按当前筛选范围导出", "交换记录已生成"); } function exportAuditLog() { const headers = ["trace_id", "time", "type", "event", "object", "actor", "result", "evidence"]; const rows = [...operationsStore.auditEvents, ...auditEvents].map((event) => [ event.trace || event.id, event.time, event.type, event.event, event.object, event.actor, event.result, event.evidence.join("|"), ]); const csv = [headers, ...rows].map((row) => row.map(csvEscape).join(",")).join("\n"); downloadText("edu-data-audit-log-20260823.csv", `\uFEFF${csv}`, "text/csv;charset=utf-8"); showToast("审计日志已携带追踪编号、操作者、结果和证据清单", "审计日志已生成"); } function setupNavigation() { document.querySelectorAll(".nav-item[data-view]").forEach((button) => button.addEventListener("click", () => switchView(button.dataset.view))); document.addEventListener("click", (event) => { const jump = event.target.closest("[data-view-jump]"); if (jump) switchView(jump.dataset.viewJump); const issueButton = event.target.closest("[data-open-issue]"); if (issueButton) { const issueId = issueButton.dataset.openIssue; const modal = issueButton.closest(".modal-backdrop"); if (modal) closeModal(modal); state.operationsTab = "issues"; renderOperations(); switchView("operations"); openIssueDrawer(issueId); } const operationsJump = event.target.closest("[data-operations-jump]"); if (operationsJump) { const operationsTab = operationsJump.dataset.operationsJump; const modal = operationsJump.closest(".modal-backdrop"); if (modal) closeModal(modal); state.operationsTab = operationsTab; renderOperations(); switchView("operations"); } const sourceButton = event.target.closest("[data-source-detail]"); if (sourceButton) openSourceDrawer(sourceButton.dataset.sourceDetail); const sourceRecordsButton = event.target.closest("[data-source-records]"); if (sourceRecordsButton) { if (document.getElementById("sourceDrawer").classList.contains("open")) closeSourceDrawer(); openRecordExplorer(sourceRecordsButton.dataset.sourceRecords); } const sourceModalButton = event.target.closest("[data-open-source-modal]"); if (sourceModalButton) openModal(document.getElementById("sourceModal")); }); document.getElementById("mobileMenu").addEventListener("click", () => document.body.classList.add("sidebar-open")); document.getElementById("sidebarScrim").addEventListener("click", () => document.body.classList.remove("sidebar-open")); document.getElementById("globalSearch").addEventListener("keydown", (event) => { if (event.key !== "Enter") return; state.sourceSearch = event.currentTarget.value; document.getElementById("sourceSearch").value = state.sourceSearch; switchView("sources"); renderSources(); }); } function setupSourceRegistry() { document.getElementById("sourceFilters").addEventListener("click", (event) => { const button = event.target.closest("[data-source-filter]"); if (!button) return; state.sourceFilter = button.dataset.sourceFilter; document.querySelectorAll("[data-source-filter]").forEach((item) => item.classList.toggle("active", item === button)); renderSources(); }); document.getElementById("sourceSearch").addEventListener("input", (event) => { state.sourceSearch = event.target.value; renderSources(); }); document.getElementById("sourceCatalogExport").addEventListener("click", exportSourceCatalog); document.getElementById("sourceForm").addEventListener("submit", (event) => { event.preventDefault(); const name = document.getElementById("newSourceName").value.trim(); const domainInput = document.getElementById("newSourceDomain").value.trim(); let domain; try { const url = new URL(domainInput); if (!["http:", "https:", "jdbc:", "sftp:"].includes(url.protocol)) throw new Error("unsupported"); domain = url.host || domainInput; } catch { showToast("请输入有效的 HTTP、HTTPS、JDBC 或 SFTP 数据源地址", "数据源地址无效"); return; } if (sources.some((source) => source.domain === domain)) { showToast("该入口已登记,请进入现有数据源创建新版本或采集任务", "数据源已存在"); return; } const category = document.getElementById("newSourceCategory").value; const categoryLabel = { commercial: "商业平台", public: "公共平台", school: "校内系统" }[category]; const source = { id: `source-${Date.now()}`, name, short: name.slice(0, 2), category, categoryLabel, domain, adapter: `Custom-Collector 1.0`, mode: document.getElementById("newSourceMode").value, schedule: document.getElementById("newSourceSchedule").value, status: "running", statusLabel: "连接测试", health: 100, today: 0, total: "0", updated: "刚刚", coverage: document.getElementById("newSourceScope").value.trim(), contract: document.getElementById("newSourceContract").value, schema: "CUSTOM-SOURCE 1.0", version: `SRC-CUSTOM-${new Date().toISOString().slice(0, 10).replaceAll("-", "")}-01`, owner: document.getElementById("newSourceOwner").value.trim(), retention: "原始数据 180 天 · 解析结果 36 个月", rate: "初次样本验证 · 1 分片", retry: "指数退避 · 最多 3 次", change: "新数据源已注册,等待小样本连接测试和字段映射确认。", repo: "connector-registry/custom-collector", codeVersion: "draft/1.0.0 · pending", runtime: [["刚刚", "数据源完成注册", "连接测试任务已创建"]], }; sources.unshift(source); populateJobSources(); populateRecordSources(); collectionJobs.unshift({ id: `JOB-${Date.now()}`, name: `${name}连接与样本预检`, sourceId: source.id, scope: source.coverage, strategy: "样本验证 · 1分片", progress: 0, records: "—", valid: "—", status: "scheduled", statusLabel: "等待调度", }); event.currentTarget.reset(); closeModal(document.getElementById("sourceModal")); renderSources(); renderOverviewSources(); renderCollectionJobs(); switchView("sources"); showToast(`${name}已进入数据源注册中心,连接测试任务已创建`, "数据源注册成功"); }); } function setupSourceDrawer() { document.getElementById("closeSourceDrawer").addEventListener("click", closeSourceDrawer); document.getElementById("sourceDrawer").addEventListener("click", (event) => { if (event.target === event.currentTarget) closeSourceDrawer(); }); document.getElementById("sourceDrawerTabs").addEventListener("click", (event) => { const button = event.target.closest("[data-source-tab]"); if (!button) return; state.sourceTab = button.dataset.sourceTab; updateSourceDrawer(); }); document.getElementById("sourceDrawerFooter").addEventListener("click", (event) => { if (event.target.closest("[data-drawer-close]")) { closeSourceDrawer(); return; } const test = event.target.closest("[data-source-test]"); if (test) { const source = sourceById(test.dataset.sourceTest); test.disabled = true; test.textContent = "正在测试…"; setTimeout(() => { test.disabled = false; test.textContent = "测试连接"; showToast(`${source.name}入口可达,样本解析和契约映射验证通过`, "连接测试通过"); }, 900); return; } const jobButton = event.target.closest("[data-source-job]"); if (jobButton) { const source = sourceById(jobButton.dataset.sourceJob); closeSourceDrawer(); document.getElementById("jobSource").value = source.id; document.getElementById("jobName").value = `${source.name}增量采集`; openModal(document.getElementById("jobModal")); } }); } function setupCollectionJobs() { document.getElementById("jobFilters").addEventListener("click", (event) => { const button = event.target.closest("[data-job-filter]"); if (!button) return; state.jobFilter = button.dataset.jobFilter; document.querySelectorAll("[data-job-filter]").forEach((item) => item.classList.toggle("active", item === button)); renderCollectionJobs(); }); document.getElementById("collectionJobTable").addEventListener("click", (event) => { const toggle = event.target.closest("[data-job-toggle]"); if (toggle) { const job = collectionJobs.find((item) => item.id === toggle.dataset.jobToggle); if (!job) return; if (job.status === "running") { job.status = "scheduled"; job.statusLabel = "已暂停"; } else { job.status = "running"; job.statusLabel = "运行中"; if (!job.progress) job.progress = 4; } renderCollectionJobs(); showToast(`${job.name}当前状态:${job.statusLabel}`, "任务状态已更新"); return; } const detail = event.target.closest("[data-job-detail]"); if (detail) { const job = collectionJobs.find((item) => item.id === detail.dataset.jobDetail); const source = sourceById(job.sourceId); openInfoModal( "COLLECTION JOB DETAIL", job.name, `
追踪编号${h(job.id)}数据来源${h(source.name)}任务范围${h(job.scope)}运行策略${h(job.strategy)}当前进度${job.progress}%目标契约${h(source.contract)}
任务绑定连接器 ${h(source.codeVersion)}、源版本 ${h(source.version)} 和原始快照。失败分片进入隔离队列,可按原任务参数重放。
`, ); return; } const records = event.target.closest("[data-job-records]"); if (records) { const job = collectionJobs.find((item) => item.id === records.dataset.jobRecords); if (!job) return; openRecordExplorer(job.sourceId, job.id, "source"); } }); document.getElementById("newCollectionJob").addEventListener("click", () => openModal(document.getElementById("jobModal"))); document.getElementById("jobForm").addEventListener("submit", (event) => { event.preventDefault(); const sourceId = document.getElementById("jobSource").value; const job = { id: `JOB-${Date.now()}`, name: document.getElementById("jobName").value.trim(), sourceId, scope: `${document.getElementById("jobRegion").value.trim()} · ${document.getElementById("jobScope").value.trim()}`, strategy: `${document.getElementById("jobMode").value} · 预检分片`, progress: document.getElementById("jobSchedule").value === "立即执行" ? 4 : 0, records: "—", valid: "—", status: document.getElementById("jobSchedule").value === "立即执行" ? "running" : "scheduled", statusLabel: document.getElementById("jobSchedule").value === "立即执行" ? "预检中" : "等待调度", }; collectionJobs.unshift(job); closeModal(document.getElementById("jobModal")); renderCollectionJobs(); switchView("collectors"); showToast(`${job.name}已创建,任务先执行小样本预检`, "采集任务已进入队列"); }); document.getElementById("pauseAllJobs").addEventListener("click", (event) => { const resume = event.currentTarget.dataset.mode === "resume"; collectionJobs.forEach((job) => { if (resume && job.status === "scheduled") { job.status = "running"; job.statusLabel = "运行中"; } else if (!resume && job.status === "running") { job.status = "scheduled"; job.statusLabel = "已暂停"; } }); event.currentTarget.dataset.mode = resume ? "" : "resume"; event.currentTarget.textContent = resume ? "暂停全部" : "恢复全部"; renderCollectionJobs(); showToast(resume ? "已恢复所有可运行采集任务" : "运行中任务已安全暂停并保存水位", "调度器状态已更新"); }); } function exportCurrentRecords() { const results = currentRecordResults(); if (!results.length) { showToast("当前查询条件下没有可导出的记录", "没有查询结果"); return; } if (state.recordLayer === "raw") { downloadJson("raw-snapshot-query-20260823.json", { query: { source_id: state.recordSource, keyword: state.recordSearch }, stable_version: "V2026.08.23", records: results, }); } else if (state.recordLayer === "quarantine") { const headers = ["quarantine_id", "source_id", "source_record_id", "job_title", "issue", "rule", "detected_at", "disposition", "task_id", "snapshot_id"]; const rows = results.map((record) => [record.id, record.sourceId, record.sourceRecordId, record.jobTitle, record.issue, record.rule, record.detectedAt, record.disposition, record.taskId, record.snapshotId]); const csv = [headers, ...rows].map((row) => row.map(csvEscape).join(",")).join("\n"); downloadText("quarantine-record-query-20260823.csv", `\uFEFF${csv}`, "text/csv;charset=utf-8"); } else { const headers = state.recordLayer === "source" ? ["record_id", "source_id", "source_record_id", "source_url", "task_id", "snapshot_id", "job_title_raw", "enterprise_name", "collected_at", "collector_version", "parser_version"] : ["record_id", "source_id", "source_record_id", "job_title_raw", "job_title_normalized", "job_code", "enterprise_name", "region_code", "workplace", "salary_min", "salary_max", "published_at", "collected_at", "quality_score", "content_hash", "data_version"]; const rows = results.map((record) => state.recordLayer === "source" ? [record.id, record.sourceId, record.sourceRecordId, record.sourceUrl, record.taskId, record.snapshotId, record.jobTitleRaw, record.enterprise, record.collectedAt, record.collectorVersion, record.parserVersion] : [record.id, record.sourceId, record.sourceRecordId, record.jobTitleRaw, record.jobTitleNormalized, record.jobCode, record.enterprise, record.regionCode, record.workplace, record.salaryMin, record.salaryMax, record.publishedAt, record.collectedAt, record.quality, record.contentHash, record.dataVersion], ); const csv = [headers, ...rows].map((row) => row.map(csvEscape).join(",")).join("\n"); downloadText(`${state.recordLayer}-job-record-query-20260823.csv`, `\uFEFF${csv}`, "text/csv;charset=utf-8"); } showToast(`已按当前条件导出 ${results.length} 条${{ standard: "标准岗位", source: "源端记录", raw: "原始快照", quarantine: "隔离记录" }[state.recordLayer]}`, "查询结果已生成"); } function setupRecords() { document.getElementById("recordLayers").addEventListener("click", (event) => { const button = event.target.closest("[data-record-layer]"); if (!button) return; state.recordLayer = button.dataset.recordLayer; renderRecords(); }); document.getElementById("recordSourceFilter").addEventListener("change", (event) => { state.recordSource = event.target.value; renderRecords(); }); document.getElementById("recordRegionFilter").addEventListener("change", (event) => { state.recordRegion = event.target.value; renderRecords(); }); document.getElementById("recordSearch").addEventListener("input", (event) => { state.recordSearch = event.target.value; renderRecords(); }); document.getElementById("recordTableContainer").addEventListener("click", (event) => { const record = event.target.closest("[data-record-id]"); if (record) { openStandardRecord(record.dataset.recordId); return; } const snapshot = event.target.closest("[data-snapshot-id]"); if (snapshot) { openRawSnapshot(snapshot.dataset.snapshotId); return; } const quarantine = event.target.closest("[data-quarantine-id]"); if (quarantine) openQuarantineRecord(quarantine.dataset.quarantineId); }); document.getElementById("recordRefresh").addEventListener("click", () => { renderRecords(); showToast("查询已重新执行,当前绑定稳定数据版本 V2026.08.23", "采集数据已刷新"); }); document.getElementById("recordExport").addEventListener("click", exportCurrentRecords); document.getElementById("closeRecordDrawer").addEventListener("click", closeRecordDrawer); document.getElementById("recordDrawer").addEventListener("click", (event) => { if (event.target === event.currentTarget) closeRecordDrawer(); }); document.getElementById("recordDrawerFooter").addEventListener("click", (event) => { if (event.target.closest("[data-record-close]")) { closeRecordDrawer(); return; } const source = event.target.closest("[data-record-source]"); if (source) { closeRecordDrawer(); openSourceDrawer(source.dataset.recordSource); return; } const raw = event.target.closest("[data-record-raw]"); if (raw) { state.recordSource = raw.dataset.recordSourceId; state.recordLayer = "raw"; state.recordSearch = raw.dataset.recordRaw; closeRecordDrawer(); populateRecordSources(); renderRecords(); showToast(`${raw.dataset.recordRaw} 已在原始快照层定位`, "已切换数据层"); } }); } function setupContracts() { document.getElementById("contractTabs").addEventListener("click", (event) => { const button = event.target.closest("[data-contract]"); if (!button) return; state.contractKey = button.dataset.contract; renderContract(); }); document.getElementById("downloadStandardKit").addEventListener("click", () => { const kit = { product: "产教数据中台标准包", version: "2026.08", generated_at: new Date().toISOString(), contracts: Object.values(contracts).map((contract) => ({ name: contract.key, version: contract.version, status: contract.status, schema: contract.schemaPath, })), dictionaries: ["GB/T 2260 地区代码", "教育部职业教育专业目录", "岗位分类词典 V4.3", "技能词典 V3.8"], delivery_requirements: ["JSON Schema", "数据样例", "字段说明", "质量门槛", "变更日志", "Manifest"], }; downloadJson("edu-industry-data-standard-kit-2026.08.json", kit); showToast("标准包索引已生成,可继续下载每个契约的 JSON Schema 与样例文件", "数据标准包已下载"); }); document.getElementById("compareContractVersions").addEventListener("click", () => { const contract = contracts[state.contractKey]; openInfoModal( "CONTRACT VERSION DIFF", `${contract.key} 版本变更`, `
当前版本${h(contract.version)}兼容策略${h(contract.compatibility)}发布时间${h(contract.release)}影响来源${state.contractKey === "external-job" ? "7个外部来源" : "36所学校租户"}
正式发布前执行 Schema 兼容性、历史样本回放、字段映射和下游影响测试。破坏性变更必须提升主版本并提供双写迁移窗口。
`, ); }); } function setupExchange() { const input = document.getElementById("importFile"); const zone = document.getElementById("uploadZone"); const preview = document.getElementById("importPreview"); const validateButton = document.getElementById("validateImport"); const handleFile = (file) => { if (!file) return; input._selectedFile = file; preview.querySelector("small").textContent = `${(file.size / 1024).toFixed(1)} KB · ${file.type || "application/octet-stream"}`; preview.querySelector("strong").textContent = file.name; validateButton.disabled = false; }; input.addEventListener("change", () => handleFile(input.files[0])); ["dragenter", "dragover"].forEach((type) => zone.addEventListener(type, (event) => { event.preventDefault(); zone.classList.add("dragging"); }), ); ["dragleave", "drop"].forEach((type) => zone.addEventListener(type, (event) => { event.preventDefault(); zone.classList.remove("dragging"); if (type === "drop") handleFile(event.dataTransfer.files[0]); }), ); validateButton.addEventListener("click", async () => { const file = input._selectedFile || input.files[0]; if (!file) return; validateButton.disabled = true; validateButton.textContent = "正在预检…"; let result = { passed: true, message: "文件结构可读取,已进入服务端分片与质量干跑" }; if (file.name.toLowerCase().endsWith(".csv")) { const text = await file.text(); const [headerLine, ...lines] = text.split(/\r?\n/).filter(Boolean); const headers = headerLine.split(",").map((item) => item.trim().replace(/^"|"$/g, "")); const contractKey = document.getElementById("importContract").value; const required = contractKey === "external-job" ? ["record_id", "source_platform", "source_record_id", "source_url", "collected_at", "job_title_raw", "enterprise_name", "region_code", "content_hash"] : ["tenant_id", "student_token", "cohort", "program_code", "employment_status", "confirmed_at", "source_system", "updated_at"]; const missing = required.filter((field) => !headers.includes(field)); result = { passed: missing.length === 0, message: missing.length ? `缺少必填字段:${missing.join("、")}` : `识别 ${headers.length} 个字段、${lines.length} 条样本记录,核心必填字段完整`, }; } preview.querySelector("small").textContent = result.passed ? "结构预检通过" : "结构预检未通过"; preview.querySelector("strong").textContent = result.message; validateButton.disabled = false; validateButton.textContent = result.passed ? "重新预检" : "修复后重试"; if (result.passed) { exchangeLogs.unshift([`IMP-${Date.now()}`, "导入", file.name, "暂存版本", "样本预检", "陈致远", "预检通过", "刚刚"]); renderExchangeLogs(); } showToast(result.message, result.passed ? "导入预检通过" : "导入预检失败"); }); document.getElementById("buildExportPackage").addEventListener("click", () => { document.querySelector(".export-panel").scrollIntoView({ behavior: "smooth", block: "center" }); }); document.getElementById("generatePackage").addEventListener("click", () => { generateCandidateManifest(); }); document.getElementById("exportExchangeLog").addEventListener("click", exportExchangeLog); } function setupQuality() { document.getElementById("normalizationList").addEventListener("click", (event) => { const button = event.target.closest("[data-normalize-index]"); if (!button) return; const index = Number(button.dataset.normalizeIndex); const item = normalizationItems[index]; normalizationItems.splice(index, 1); renderQuality(); showToast(`“${item[0]}”已映射到“${item[1]}”,规则将在下一批次生效`, "岗位归一已确认"); }); document.getElementById("normalizationBatch").addEventListener("click", () => { const count = normalizationItems.length; normalizationItems = []; renderQuality(); showToast(`已确认 ${count} 个高置信度岗位映射,变更记录已写入审计`, "批量归一完成"); }); document.getElementById("qualityRuleButton").addEventListener("click", () => { openInfoModal( "QUALITY POLICY", "生产质量规则策略", `
阻断规则12条告警规则20条综合发布阈值≥ 90规则版本QUALITY 3.6
阻断规则失败时任务进入隔离区,不更新稳定版本;告警规则允许带限制发布,但必须在质量报告中披露样本、影响范围和处置计划。
`, ); }); } function setupAudit() { document.getElementById("auditFilters").addEventListener("click", (event) => { const button = event.target.closest("[data-audit-filter]"); if (!button) return; state.auditFilter = button.dataset.auditFilter; document.querySelectorAll("[data-audit-filter]").forEach((item) => item.classList.toggle("active", item === button)); renderAudit(); }); document.getElementById("exportAuditLog").addEventListener("click", exportAuditLog); document.getElementById("auditPolicyButton").addEventListener("click", () => { openInfoModal( "AUDIT POLICY", "生产审计与留存策略", `
关键事件留存36个月审计存储WORM每日完整性校验SHA-256追踪覆盖率100%
采集请求、连接器版本、原始快照、映射规则、质量结果、审批和租户交付都使用同一个 trace_id。敏感配置只记录密钥引用和变更摘要,不在日志中保存明文凭据。
`, ); }); } function setupOperations() { document.getElementById("operationsTabs").addEventListener("click", (event) => { const button = event.target.closest("[data-operations-tab]"); if (!button) return; state.operationsTab = button.dataset.operationsTab; renderOperations(); }); document.getElementById("operationsPrimaryAction").addEventListener("click", () => { const issue = operationsStore.issues.find((item) => !["RESOLVED", "CLOSED"].includes(item.status)); if (issue) openIssueDrawer(issue.id); else { state.operationsTab = "releases"; renderOperations(); } }); document.getElementById("resetOperationsSeed").addEventListener("click", () => { operationsStore = operationsSeed(); saveOperationsStore(); closeIssueDrawer(); renderOperations(); renderAudit(); showToast("问题、工单、质量、审批、发布、交付与审计已恢复到可重复演示种子", "运营处置数据已重置"); }); document.getElementById("closeIssueDrawer").addEventListener("click", closeIssueDrawer); document.getElementById("issueDrawer").addEventListener("click", (event) => { if (event.target === event.currentTarget) closeIssueDrawer(); }); document.getElementById("issueDrawer").addEventListener("click", (event) => { if (event.target.closest("[data-issue-close]")) { closeIssueDrawer(); return; } const button = event.target.closest("[data-issue-action]"); if (!button) return; const issue = operationsStore.issues.find((item) => item.id === state.issueId); if (!issue) return; const action = button.dataset.issueAction; if (action === "acknowledge" && issue.status === "ALERTED") { transitionIssue(issue, "ACKNOWLEDGED", "运营人员已确认告警、影响与SLA。", ["告警确认记录"]); return; } if (action === "assign" && issue.status === "ACKNOWLEDGED") { const assignee = document.getElementById("issueAssignee")?.value; if (!assignee) return; issue.owner = assignee; transitionIssue(issue, "ASSIGNED", `已指派给 ${assignee}。`, ["责任人指派记录"]); return; } if (action === "investigate" && issue.status === "ASSIGNED") { transitionIssue(issue, "INVESTIGATING", "已确认问题来自源端延迟,并找到对应的解析隔离记录,开始排障。", ["排障工作区快照", issue.relatedTask]); return; } if (action === "submit-quality" && issue.status === "INVESTIGATING") { const dispositionAction = document.getElementById("qualityDispositionAction")?.value; const reviewer = document.getElementById("qualityReviewer")?.value; const reason = document.getElementById("qualityDispositionReason")?.value.trim(); if (!reason) { showToast("必须填写根因、处置范围与复核依据", "缺少处置原因"); return; } if (reviewer === OPERATIONS_ACTOR) { showToast("质量复核人必须独立于处置提交人", "复核职责未分离"); return; } const evidence = dispositionAction === "FIX_REPLAY" ? ["不可变处置动作", "修复补丁 HZRC-2.7.4", "重放任务 REPLAY-HZRC-20260823-01"] : dispositionAction === "WAIVE" ? ["不可变处置动作", "风险影响评估", "豁免理由"] : ["不可变处置动作", "拟丢弃范围", "完整性影响评估"]; const metadata = addOperationsAudit({ type: "quality", event: `${issue.title}提交质量复核`, object: issue.id, result: "待复核", evidence, }); operationsStore.qualityDispositions.push({ id: operationToken("QD"), status: "PENDING_REVIEW", actor: metadata.actor, time: metadata.time, trace: metadata.trace, evidence: [...metadata.evidence], issueId: issue.id, action: dispositionAction, reason, reviewer, immutable: true, verification: dispositionAction === "FIX_REPLAY" ? { expectedRecords: 3860, replayedRecords: null, rulesPassed: false, reviewerSigned: false, } : null, }); migrateOperationsEntity(issue, "QUALITY_REVIEW_PENDING", metadata); issue.timeline.push({ id: operationToken("STEP"), status: "QUALITY_REVIEW_PENDING", actor: metadata.actor, time: metadata.time, trace: metadata.trace, evidence: [...metadata.evidence], note: `${{ FIX_REPLAY: "修复并重放", WAIVE: "豁免", DISCARD: "丢弃" }[dispositionAction]}已提交 ${reviewer} 复核。`, }); const workOrder = operationsStore.workOrders.find((item) => item.issueId === issue.id); if (workOrder) migrateOperationsEntity(workOrder, "QUALITY_REVIEW_PENDING", metadata); saveOperationsStore(); renderOperations(); renderAudit(); openIssueDrawer(issue.id); showToast("处置动作已保存且不能修改,等待另一名质量复核人处理", "质量处置已提交"); return; } if (action === "verify-blocked" && issue.status === "QUALITY_REVIEW_PENDING") { const disposition = latestQualityDisposition(issue.id); if (!disposition || disposition.action === "FIX_REPLAY") return; const actionLabel = disposition.action === "WAIVE" ? "风险豁免" : "丢弃异常数据"; const metadata = addOperationsAudit({ type: "quality", event: `${issue.title}${actionLabel}复核完成`, object: disposition.id, actor: disposition.reviewer, result: "REVIEWED_BLOCKED", evidence: disposition.action === "WAIVE" ? ["风险影响评估", "独立复核签名", "需另行风险审批"] : ["丢弃范围清单", "完整性影响评估", "需重建候选并复核"], }); migrateOperationsEntity(disposition, "REVIEWED_BLOCKED", metadata); migrateOperationsEntity(issue, "INVESTIGATING", metadata); issue.timeline.push({ id: operationToken("STEP"), status: "REVIEWED_BLOCKED", actor: metadata.actor, time: metadata.time, trace: metadata.trace, evidence: [...metadata.evidence], note: disposition.action === "WAIVE" ? "风险豁免不等同于质量验证通过;候选版本仍不能发布,必须另行提交风险审批。" : "丢弃可能改变数据完整性;候选版本仍不能发布,必须重建候选并重新执行质量检查。", }); const workOrder = operationsStore.workOrders.find((item) => item.issueId === issue.id); if (workOrder) migrateOperationsEntity(workOrder, "INVESTIGATING", metadata); updateReleaseQualityGate(); saveOperationsStore(); renderOperations(); renderAudit(); openIssueDrawer(issue.id); showToast(`${actionLabel}已复核,但当前候选版本仍不能发布`, "仍不满足发布条件"); return; } if (action === "verify" && issue.status === "QUALITY_REVIEW_PENDING") { const disposition = latestQualityDisposition(issue.id); if (!disposition || disposition.action !== "FIX_REPLAY") { showToast("只有修复并重放可以进入质量检查通过状态", "仍不满足发布条件"); return; } const metadata = addOperationsAudit({ type: "quality", event: `${issue.title}质量复核通过`, object: disposition.id, actor: disposition.reviewer, result: "VERIFIED", evidence: ["重放结果 3,860/3,860", "质量规则全量通过", "复核签名"], }); disposition.verification = { expectedRecords: 3860, replayedRecords: 3860, rulesPassed: true, reviewerSigned: true, }; migrateOperationsEntity(disposition, "VERIFIED", metadata); migrateOperationsEntity(issue, "VERIFIED", metadata); issue.timeline.push({ id: operationToken("STEP"), status: "VERIFIED", actor: metadata.actor, time: metadata.time, trace: metadata.trace, evidence: [...metadata.evidence], note: "记录数、质量规则和重放记录已复核,候选版本可以继续提交审批。", }); const workOrder = operationsStore.workOrders.find((item) => item.issueId === issue.id); if (workOrder) migrateOperationsEntity(workOrder, "VERIFIED", metadata); updateReleaseQualityGate(); saveOperationsStore(); renderOperations(); renderAudit(); openIssueDrawer(issue.id); showToast("质量处置已 VERIFIED,候选版本现在可提交审批", "已满足发布质量要求"); return; } if (action === "resolve" && (issue.status === "VERIFIED" || (issue.status === "INVESTIGATING" && !issue.qualityRequired))) { transitionIssue(issue, "RESOLVED", "影响已消除,稳定版本与后续任务验证正常。", ["恢复指标", "验证报告"]); return; } if (action === "close" && issue.status === "RESOLVED") { transitionIssue(issue, "CLOSED", "问题复盘完成,工单正式关闭。", ["关闭检查单", "复盘摘要"]); } }); document.getElementById("releaseWorkspace").addEventListener("click", (event) => { const release = currentRelease(); const approval = currentApproval(); if (event.target.closest("[data-release-manifest]")) { generateCandidateManifest(); return; } if (event.target.closest("[data-release-official-manifest]")) { if (!release.publishedManifest) { showToast("尚未生成发布 Manifest", "下载不可用"); return; } downloadJson(`${release.packageId}-published-manifest.json`, release.publishedManifest); showToast(`${release.packageId} 发布 Manifest 已下载,审批引用为 ${release.publishedManifest.approval_reference}`, "发布 Manifest"); return; } if (event.target.closest("[data-release-submit]")) { updateReleaseQualityGate(); if (!releaseReadyForApproval(release)) { showToast( release.qualityGate === "PASS" ? "质量检查已通过,但必须先生成候选 Manifest 并提交审批" : "只有通过检查的 FIX_REPLAY 才能让候选版本继续提交", "候选尚不可提交", ); return; } const metadata = addOperationsAudit({ type: "publish", event: "候选版本提交独立审批", object: release.id, result: "待审批", evidence: ["候选 Manifest", "发布质量检查 PASS", "影响租户清单"], }); migrateOperationsEntity(release, "PENDING_APPROVAL", metadata); migrateOperationsEntity(approval, "PENDING_APPROVAL", metadata); saveOperationsStore(); renderOperations(); renderAudit(); showToast(`${release.id} 已提交,申请人不能批准自己的申请`, "审批单已创建"); return; } if (event.target.closest("[data-release-resubmit]")) { const metadata = addOperationsAudit({ type: "publish", event: "驳回候选返回草稿重建", object: release.id, result: "草稿", evidence: ["审批驳回意见", "候选失效记录", "质量报告"], }); migrateOperationsEntity(release, "DRAFT", metadata); migrateOperationsEntity(approval, "DRAFT", metadata); release.manifestGeneratedAt = null; release.candidateManifest = null; release.candidateInputFingerprint = null; approval.approver = null; approval.comment = ""; approval.decidedAt = null; saveOperationsStore(); renderOperations(); renderAudit(); showToast("旧候选 Manifest 已失效,请完成修订后重新生成", "候选已返回草稿"); return; } if (event.target.closest("[data-release-approve]") || event.target.closest("[data-release-reject]")) { if (release.status !== "PENDING_APPROVAL" || approval.status !== "PENDING_APPROVAL") return; const approver = document.getElementById("releaseApprover")?.value; const comment = document.getElementById("approvalComment")?.value.trim(); const approved = Boolean(event.target.closest("[data-release-approve]")); decideIndependentApproval({ approval, target: release, approver, comment, approved, approveStatus: "APPROVED", rejectStatus: "REJECTED", approveEvent: "候选版本独立审批通过", rejectEvent: "候选版本独立审批驳回", object: release.id, evidence: ["审批意见", "审批人身份", "四眼规则校验"], defaultApproveComment: "审批通过", defaultRejectComment: "需修订后重提", resultLabel: operationStatusLabel, successMessage: (actor, passed) => `${actor} 已${passed ? "批准" : "驳回"} ${release.id}`, successTitle: (passed) => (passed ? "独立审批通过" : "审批已驳回"), }); return; } if (event.target.closest("[data-release-publish]") && release.status === "APPROVED") { if ( approval.status !== "APPROVED" || !isIndependentApproval(approval) || !release.candidateManifest || !release.manifestGeneratedAt ) { showToast("发布需要有效候选 Manifest 和已完成职责分离的独立审批", "发布前置校验失败"); return; } const metadata = addOperationsAudit({ type: "publish", event: "候选版本发布为稳定版本", object: `${release.id} · ${release.dataVersion}`, result: "已发布", evidence: [`独立审批单 ${approval.id}:APPROVED`, "候选 Manifest", "稳定别名切换记录"], }); migrateOperationsEntity(release, "PUBLISHED", metadata); release.publishedAt = metadata.time; release.publishedManifest = buildPackageManifest({ releaseStatus: "PUBLISHED", approvalStatus: "APPROVED" }); release.publishedManifestGeneratedAt = release.publishedManifest.generated_at; saveOperationsStore(); downloadJson(`${release.packageId}-published-manifest.json`, release.publishedManifest); state.operationsTab = "deliveries"; renderOperations(); renderAudit(); showToast(`稳定别名已切换,发布 Manifest 引用 ${approval.id}:APPROVED;尚未向学校发送`, "候选版本已发布"); return; } if (event.target.closest("[data-rollback-submit]") && release.status === "PUBLISHED") { const rollbackApproval = currentRollbackApproval(); const reason = document.getElementById("rollbackReason")?.value.trim(); if (!reason) { showToast("必须填写回滚原因与生产影响", "缺少回滚依据"); return; } const metadata = addOperationsAudit({ type: "publish", event: "全局版本回滚提交独立审批", object: `${release.id} → ${release.previousStableVersion}`, result: "ROLLBACK_PENDING_APPROVAL", evidence: ["回滚原因", "影响租户清单", "目标稳定版本"], }); rollbackApproval.reason = reason; rollbackApproval.approver = null; rollbackApproval.comment = ""; rollbackApproval.decidedAt = null; migrateOperationsEntity(rollbackApproval, "ROLLBACK_PENDING_APPROVAL", metadata); saveOperationsStore(); renderOperations(); renderAudit(); showToast("全局回滚申请已提交,申请人不能批准自己的回滚", "等待独立回滚审批"); return; } if (event.target.closest("[data-rollback-approve]") || event.target.closest("[data-rollback-reject]")) { const rollbackApproval = currentRollbackApproval(); if (release.status !== "PUBLISHED" || rollbackApproval?.status !== "ROLLBACK_PENDING_APPROVAL") return; const approver = document.getElementById("rollbackApprover")?.value; const comment = document.getElementById("rollbackApprovalComment")?.value.trim(); const approved = Boolean(event.target.closest("[data-rollback-approve]")); decideIndependentApproval({ approval: rollbackApproval, approver, comment, approved, approveStatus: "ROLLBACK_APPROVED", rejectStatus: "ROLLBACK_REJECTED", approveEvent: "全局版本回滚独立审批通过", rejectEvent: "全局版本回滚独立审批驳回", object: `${release.id} → ${release.previousStableVersion}`, evidence: ["回滚审批意见", "审批人身份", "四眼规则校验", "影响租户清单"], defaultApproveComment: "同意在已安排的时间窗口执行回滚", defaultRejectComment: "回滚依据不足", successMessage: (actor, passed) => `${actor} 已${passed ? "批准" : "驳回"}全局回滚`, successTitle: (passed) => (passed ? "回滚审批通过" : "回滚审批驳回"), }); return; } if (event.target.closest("[data-release-rollback]") && release.status === "PUBLISHED") { const rollbackApproval = currentRollbackApproval(); if ( rollbackApproval?.status !== "ROLLBACK_APPROVED" || !isIndependentApproval(rollbackApproval) ) { showToast("全局版本回滚必须先由另一名审批人批准", "当前不能执行回滚"); return; } const metadata = addOperationsAudit({ type: "publish", event: "发布版本全局回滚", object: release.id, result: "已回滚", evidence: [`稳定别名恢复 ${release.previousStableVersion}`, `回滚审批 ${rollbackApproval.id}:${rollbackApproval.status}`, "影响范围锁"], }); migrateOperationsEntity(release, "ROLLED_BACK", metadata); migrateOperationsEntity(rollbackApproval, "ROLLBACK_EXECUTED", metadata); saveOperationsStore(); renderOperations(); renderAudit(); showToast(`稳定别名已恢复为 ${release.previousStableVersion}`, "版本已回滚"); } }); document.getElementById("startPendingDeliveries").addEventListener("click", () => { if (currentRelease()?.status !== "PUBLISHED") { showToast("只有已发布版本才能启动学校交付", "交付尚不可用"); return; } const pending = operationsStore.deliveries.filter((delivery) => delivery.status === "PENDING"); pending.forEach((delivery) => { const metadata = addOperationsAudit({ type: "publish", event: "租户交付进入发送队列", object: `${delivery.tenantId} · ${delivery.packageId}`, actor: "delivery-orchestrator", result: "SENDING", evidence: ["租户范围校验", "包签名", "幂等键"], }); migrateOperationsEntity(delivery, "SENDING", metadata); delivery.attempts += 1; }); saveOperationsStore(); renderOperations(); renderAudit(); showToast(`${pending.length} 所待发送学校已进入原型队列,没有发出网络请求`, "交付队列已启动"); }); document.getElementById("deliveryTable").addEventListener("click", (event) => { const button = event.target.closest("[data-delivery-delivered]") || event.target.closest("[data-delivery-failed]") || event.target.closest("[data-delivery-retry]") || event.target.closest("[data-delivery-ack]") || event.target.closest("[data-delivery-rollback]"); if (!button) return; const id = button.dataset.deliveryDelivered || button.dataset.deliveryFailed || button.dataset.deliveryRetry || button.dataset.deliveryAck || button.dataset.deliveryRollback; const delivery = operationsStore.deliveries.find((item) => item.id === id); if (!delivery) return; if (button.dataset.deliveryDelivered) { const metadata = addOperationsAudit({ type: "publish", event: "租户交付包模拟送达", object: delivery.tenantId, actor: "delivery-orchestrator", result: "DELIVERED", evidence: ["传输完成标记", "包签名"], }); migrateOperationsEntity(delivery, "DELIVERED", metadata); delivery.failureReason = ""; } else if (button.dataset.deliveryFailed) { const metadata = addOperationsAudit({ type: "publish", event: "租户交付失败隔离", object: delivery.tenantId, actor: "delivery-orchestrator", result: "FAILED", evidence: ["模拟超时日志", "租户隔离策略"], }); migrateOperationsEntity(delivery, "FAILED", metadata); delivery.failureReason = "模拟租户端接收超时,其他学校未受影响"; } else if (button.dataset.deliveryRetry) { const metadata = addOperationsAudit({ type: "publish", event: "失败租户单独重试", object: delivery.tenantId, actor: OPERATIONS_ACTOR, result: "RETRYING", evidence: ["原包幂等键", "失败原因", "单租户重试指令"], }); migrateOperationsEntity(delivery, "RETRYING", metadata); delivery.attempts += 1; delivery.failureReason = ""; } else if (button.dataset.deliveryAck) { const countInput = document.getElementById(`receipt-count-${delivery.id}`); const shaInput = document.getElementById(`receipt-sha-${delivery.id}`); const receiptRecords = Number(countInput?.value); const receiptSha = shaInput?.value.trim(); if (receiptRecords !== delivery.expectedRecords || receiptSha !== delivery.expectedSha) { showToast("记录数与 SHA-256 必须同时等于候选包期望值,当前回执未确认", "回执校验失败"); return; } const metadata = addOperationsAudit({ type: "publish", event: "学校回执校验通过", object: delivery.tenantId, actor: "receipt-verifier", result: "ACKNOWLEDGED", evidence: [`记录数 ${receiptRecords}`, `SHA-256 ${receiptSha}`, "回执签名"], }); migrateOperationsEntity(delivery, "ACKNOWLEDGED", metadata); delivery.receiptRecords = receiptRecords; delivery.receiptSha = receiptSha; } else if (button.dataset.deliveryRollback) { const metadata = addOperationsAudit({ type: "publish", event: "单租户交付回滚", object: `${delivery.tenantId} · ${delivery.dataVersion}`, actor: OPERATIONS_ACTOR, result: "ROLLED_BACK", evidence: ["租户范围锁", `恢复 ${currentRelease().previousStableVersion}`, "回滚记录"], }); migrateOperationsEntity(delivery, "ROLLED_BACK", metadata); } saveOperationsStore(); renderOperations(); renderAudit(); showToast(`${delivery.school}:${operationStatusLabel(delivery.status)}`, "学校交付状态已更新"); }); } function setupGlobalActions() { document.querySelectorAll("[data-close-modal]").forEach((button) => button.addEventListener("click", () => closeModal(button.closest(".modal-backdrop")))); document.querySelectorAll(".modal-backdrop").forEach((backdrop) => { backdrop.addEventListener("click", (event) => { if (event.target === backdrop) closeModal(backdrop); }); }); document.getElementById("notificationButton").addEventListener("click", () => { const openIssues = operationsStore.issues.filter((issue) => !["RESOLVED", "CLOSED"].includes(issue.status)); const pendingApproval = operationsStore.approvals.find((approval) => ["PENDING_APPROVAL", "ROLLBACK_PENDING_APPROVAL"].includes(approval.status)); const deliveryTodos = operationsStore.deliveries.filter((delivery) => ["PENDING", "SENDING", "FAILED", "RETRYING"].includes(delivery.status)); openInfoModal( "OPERATIONS NOTIFICATIONS", "运营待办", `
${openIssues .map( (issue) => ``, ) .join("")} ${ pendingApproval ? `` : "" } ${deliveryTodos.length ? `` : ""} ${!openIssues.length && !pendingApproval && !deliveryTodos.length ? `当前没有运营待办问题、审批和交付均已处理完成` : ""}
`, ); }); document.getElementById("operatorButton").addEventListener("click", () => { openInfoModal( "OPERATOR PROFILE", "厂商数据运营管理员", `
姓名陈致远角色数据运营管理员权限范围全国招聘数据域最近登录今天 08:12
可管理数据源、采集任务、标准契约、质量规则和数据交付;生产发布需要数据产品负责人复核。
`, ); }); document.addEventListener("keydown", (event) => { if (event.key !== "Escape") return; if (document.getElementById("issueDrawer").classList.contains("open")) { closeIssueDrawer(); return; } if (document.getElementById("recordDrawer").classList.contains("open")) { closeRecordDrawer(); return; } if (document.getElementById("sourceDrawer").classList.contains("open")) { closeSourceDrawer(); return; } const modal = document.querySelector(".modal-backdrop.open"); if (modal) { closeModal(modal); return; } document.body.classList.remove("sidebar-open"); }); } function setupOverviewActions() { document.querySelector("[data-open-export]").addEventListener("click", exportDailyReport); } function populateJobSources() { document.getElementById("jobSource").innerHTML = sources.map((source) => ``).join(""); } function init() { renderOverviewSources(); renderSources(); renderCollectionJobs(); populateRecordSources(); renderRecords(); renderContract(); renderExchangeLogs(); renderQuality(); renderOperations(); renderAudit(); populateJobSources(); setupNavigation(); setupSourceRegistry(); setupSourceDrawer(); setupCollectionJobs(); setupRecords(); setupContracts(); setupExchange(); setupQuality(); setupOperations(); setupAudit(); setupGlobalActions(); setupOverviewActions(); } document.addEventListener("DOMContentLoaded", init);