refactor: simplify product copy

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
lofyer
2026-08-25 11:37:59 +08:00
co-authored by factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent 248078113c
commit b9fbd4c6af
22 changed files with 364 additions and 362 deletions
@@ -210,7 +210,7 @@
"maxLength": 100
},
"uniqueItems": true,
"description": "基于技能词典提取并归一的技能标签。"
"description": "使用技能词典提取并统一名称的技能标签。"
},
"keywords": {
"type": "array",
@@ -1,8 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.edu-industry.cn/school-employment/2.2.0/schema.json",
"title": "SCHOOL-EMPLOYMENT V2.2 校内就业事实",
"description": "职业院校毕业生就业事实统一契约,采用租户隔离和身份最小化设计。",
"title": "SCHOOL-EMPLOYMENT V2.2 校内就业数据",
"description": "职业院校毕业生就业数据契约,要求租户隔离,并将身份字段限制在业务必需的最少范围。",
"type": "object",
"additionalProperties": false,
"required": [
@@ -1,8 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.edu-industry.cn/school-internship/2.1.0/schema.json",
"title": "SCHOOL-INTERNSHIP V2.1 校内实习事实",
"description": "职业院校学生实习事实统一契约,采用租户隔离、身份最小化和证据引用设计。",
"title": "SCHOOL-INTERNSHIP V2.1 校内实习数据",
"description": "职业院校学生实习数据契约,要求租户隔离,将身份字段限制在业务必需的最少范围,并引用评价记录。",
"type": "object",
"additionalProperties": false,
"required": [
+9 -9
View File
@@ -44,7 +44,7 @@
| Data Query Service | 标准、源端、原始快照和隔离记录的分页检索、字段权限与证据追溯 | Lakehouse/OLAP + 检索引擎 |
| Audit Service | 统一 trace_id、事件签名、证据和留存 | WORM/Object Lock |
| Operations Service | 告警、问题、工单、SLA、指派、处置时间线和通知待办 | PostgreSQL + 事件总线 |
| Release Approval Service | 候选版本、质量门禁、职责分离审批、稳定别名发布与回滚 | PostgreSQL + 策略引擎 |
| Release Approval Service | 候选版本、发布质量检查、职责分离审批、稳定别名发布与回滚 | PostgreSQL + 策略引擎 |
| Tenant Delivery Service | 逐租户发送状态、幂等重试、回执验签、记录数/SHA校验和单租户回滚 | PostgreSQL + 消息队列 |
## 3. 连接器 SPI
@@ -62,13 +62,13 @@ parse(snapshot) 解析为来源 Schema
health() 返回入口可达性、延迟和错误分类
```
参考实现位于 [`../crawler/`](../crawler/),逐来源接入方式和授权门禁见 [`../../docs/crawler-source-implementation.md`](../../docs/crawler-source-implementation.md)。连接器运行时必须遵守以下硬约束:
参考实现位于 [`../crawler/`](../crawler/),逐来源接入方式和启用条件见 [`../../docs/crawler-source-implementation.md`](../../docs/crawler-source-implementation.md)。连接器运行时必须遵守以下要求:
- 所有外部来源默认禁用,只有来源审批状态为 `approved` 才允许公网运行;
- 商业平台仅接入官方合作 API 或授权数据包,不实现登录自动化、验证码处理、私有接口逆向和反自动化规避;
- 公开页面任务同时执行书面授权、robots.txt、域名 allowlist、公共地址解析和来源级限速检查;
- 重定向后的每个 URL 重新执行相同安全校验,禁止访问回环、内网、链路本地和云元数据地址;
- User-Agent 必须标识本系统并提供真实技术联系邮箱,不伪装普通浏览器;
- User-Agent 必须标识本系统并提供可联系的技术邮箱,不伪装普通浏览器;
- Authorization、Cookie 和签名密钥只从 Vault/KMS 引用注入,不写入任务、Raw、日志或异常;
- 登录页、验证码、安全验证、访问封禁和结构变化均触发停止或隔离,不尝试绕过。
@@ -148,7 +148,7 @@ RUNNING → THROTTLED → RUNNING
- 来源状态和平台状态;
- 原始版本、解析版本、标准版本和数据产品版本。
跨来源相似岗位不能直接删除,应形成岗位信号簇:
跨来源相似岗位不能直接删除,应归入同一个岗位信号组:
```text
JobSignalCluster
@@ -197,7 +197,7 @@ JobSignalCluster
- 每个文件的 SHA-256;
- 租户范围、截止日期和上一稳定版本。
### 候选、审批与租户交付闭环
### 候选、审批与租户交付流程
Manifest 生成与发布必须是两个不同事务:
@@ -216,17 +216,17 @@ ISSUE / WORK_ORDER
→ ACKNOWLEDGED(record_count 与 SHA-256 同时通过)
```
- `FIX_REPLAY`、`WAIVE`、`DISCARD` 必须记录原因、复核人、操作者、时间、trace_id 和证据;动作只追加,不可覆盖。只有 `FIX_REPLAY` 同时满足重放记录数一致、质量规则全量通过和独立复核签名时,才可写入 `VERIFIED` 并解除候选版本质量门禁。
- `FIX_REPLAY`、`WAIVE`、`DISCARD` 必须记录原因、复核人、操作者、时间、trace_id 和证据;动作只追加,不可覆盖。只有 `FIX_REPLAY` 同时满足重放记录数一致、质量规则全部通过和独立复核签名时,才可写入 `VERIFIED` 并允许候选版本继续提交。
- `WAIVE` 不等于质量验证通过,若产品允许风险放行,必须进入单独的风险豁免审批状态、风险接受人和到期时间;`DISCARD` 必须先验证丢弃范围、完整性阈值和候选差异并重建候选。两者不得复用普通 `VERIFIED/PASS`。
- `PACKAGE-MANIFEST V1.2` 增加 `release_id` 与 `release_status`。每个租户数据文件必须携带自身记录数和 SHA-256,`tenant_scope`、文件路径和租户授权清单必须一致。
- 候选 Manifest 的 `release_status` 必须为 `DRAFT`,并在提交审批时冻结;驳回后旧候选失效。发布事务生成 `PUBLISHED` Manifest,`approval_reference` 固定引用真实 `APPROVED` 审批单。
- 候选 Manifest 的 `release_status` 必须为 `DRAFT`,提交审批后不可修改;驳回后旧候选失效。发布事务生成 `PUBLISHED` Manifest,`approval_reference` 必须引用状态为 `APPROVED` 的审批单。
- 候选 Manifest 生成接口必须接受幂等键或服务端输入指纹;相同产品、租户、截止日期、格式、质量处置和交付摘要不得重复产生候选或审计事件。
- 后端必须强制申请人与审批人不同,前端校验只能作为交互提示。批准后再以原子别名切换发布稳定版本。
- 每个学校是独立交付状态机:`PENDING → SENDING → DELIVERED → ACKNOWLEDGED`;异常使用 `FAILED → RETRYING`,并支持单租户 `ROLLED_BACK`。成功租户的幂等键阻止重复发送。
- 只有回执记录数等于包记录数、回执 SHA-256 等于 Manifest 文件摘要且回执签名有效时,服务端才能写入 `ACKNOWLEDGED`。
- 全局版本回滚和单租户回滚是不同操作。全局回滚必须先创建独立回滚审批,审批人与申请人分离,再执行稳定别名切换;两类回滚均需目标上一稳定版本、范围锁与不可变审计事件。
原型中的 `localStorage` 只用于静态演示刷新持久化,不能作为跨端协议或生产事实源。
原型中的 `localStorage` 只用于静态演示刷新后继续操作,不能作为跨端协议或正式数据来源。
## 9. 安全与运维
@@ -250,7 +250,7 @@ ISSUE / WORK_ORDER
- `POST /issues/{id}/acknowledge|assign|investigate|resolve|close` 与带版本条件的工单更新;
- `POST /quality-dispositions`、`POST /quality-dispositions/{id}/verify`,处置动作写不可变事件存储;
- `POST /releases/{id}/manifest|submit|approve|reject|publish|rollback`,服务端执行质量门禁和职责分离;
- `POST /releases/{id}/manifest|submit|approve|reject|publish|rollback`,服务端检查发布质量条件和职责分离;
- `POST /deliveries/{tenant_id}/send|retry|acknowledge|rollback`,执行租户授权、幂等、签名和回执校验;
- `GET /notifications` 与统一审计事件查询/导出 API;
- OAuth/OIDC、RBAC/ABAC、审批人实时身份、请求签名、幂等键、乐观锁、限流和 WORM 审计。
+14 -14
View File
@@ -1,6 +1,6 @@
# 产教数据中台,厂商运营原型
本目录用于讨论厂商侧招聘数据采集与治理产品。商业招聘平台、公共就业平台和校内系统统一作为正式数据来源纳入数据生产与治理体系。
本目录展示厂商如何管理招聘数据采集、检查、发布和学校交付。数据来源包括商业招聘平台、公共就业平台和校内系统。
## 运行
@@ -14,7 +14,7 @@ python3 -m http.server 8080
## 产品定位
这不是多个招聘网站爬虫脚本的简单集合,而是面向产教分析的数据生产控制面:
该页面用于管理产教分析所需的数据:
1. 注册和版本化管理商业、公共、校内三类数据源;
2. 调度采集任务,管理地域、行业、岗位族、分片、失败隔离和增量水位;
@@ -49,40 +49,40 @@ python3 -m http.server 8080
- **数据标准**:外部岗位、校内就业、交付 Manifest 三套版本化契约。
- **导入与导出**:文件拖放、CSV 头部预检、样例下载、Manifest 生成和记录导出。
- **质量与归一**:质量规则、发布阈值、岗位别名人工确认和批量处理。
- **审计与交付**:采集、质量、发布、导入导出事件及证据链。
- **审计与交付**:采集、质量、发布、导入导出事件及相关记录。
## 原型黄金闭环
## 可重复演示流程
“运营处置”提供一条可重复演示、刷新后保留的最小生产黄金路径:
“运营处置”提供一条刷新后仍可继续的示例流程:
```text
告警 → 确认 → 指派 → 开始排障
→ 修复并重放 → 记录数 / 规则 / 复核签名 VERIFIED
→ 豁免 / 丢弃 → REVIEWED_BLOCKED,继续阻断
→ REL-20260823-01 候选版本 → 生成并冻结候选 Manifest → 提交审批
→ REL-20260823-01 候选版本 → 生成候选 Manifest → 提交后不可修改
→ 独立审批人批准 / 驳回 → 发布
→ 逐学校发送状态 → 记录数 + SHA-256 回执
→ ACKNOWLEDGED / 单校失败重试 / 单租户回滚
→ 全局回滚申请 → 独立回滚审批 → 执行稳定别名回滚
```
- 杭州人才网问题 `ISSUE-HZRC-20260823-01` 可完整流转。只有最后一次处置是 `FIX_REPLAY`,且重放记录数一致、质量规则通过、复核签名存在时,候选版本质量门禁才进入 `PASS`。
- 杭州人才网问题 `ISSUE-HZRC-20260823-01` 可走完上述流程。只有最后一次处置是 `FIX_REPLAY`,且重放记录数一致、质量规则通过、复核签名存在时,候选版本的发布质量检查才进入 `PASS`。
- `WAIVE` 与 `DISCARD` 的独立复核会写入 `REVIEWED_BLOCKED`,分别提示另行风险审批或重建候选,不会复用 `VERIFIED/PASS`。
- 发布申请人默认为陈致远,审批人若选择同一人会被四眼规则阻止。
- 候选发布必须同时满足质量门禁和候选 Manifest 已生成;审批中 Manifest 冻结,驳回后返回草稿并使旧候选失效。
- 候选版本必须通过发布质量检查并生成 Manifest 后才能提交;审批期间 Manifest 不可修改,驳回后返回草稿并使旧候选失效。
- 候选 Manifest 使用输入指纹保证重复生成幂等;产品、租户、截止日期、格式、质量处置和交付摘要未变化时复用原候选,不重复写审计或交换日志。
- 候选版本、数据版本和包分别为 `REL-20260823-01`、`V2026.08.23`、`EDU-PKG-20260823-01`。
- 杭州科技职业技术学院使用共享标识 `TENANT-HZVTC`。另外展示浙江机电职业技术大学和宁波城市职业技术学院,交付状态按租户隔离。
- 原型仓库使用 `localStorage` 命名空间 `eduIndustry.vendorOperations.v1`,当前 `schemaVersion=2`,包含 issues、workOrders、qualityDispositions、releases、approvals、deliveries、auditEvents。页面提供“重置演示数据”恢复种子。
- `PACKAGE-MANIFEST V1.2` 新增 `release_id` 和 `release_status`,并按三个租户输出彼此独立的记录数与 SHA-256 文件项。候选 Manifest 保持 `DRAFT`,正式发布时生成 `PUBLISHED` Manifest,`approval_reference` 固定引用 `APPROVED` 审批单。
- 全局回滚和单租户回滚严格分离。全局回滚必须填写原因、由不同于申请人的审批人批准后才可执行,审计证据保存目标稳定版本、审批引用和影响范围锁。
- 全局回滚和单租户回滚分开办理。全局回滚必须填写原因,并由另一名审批人批准后才可执行;审计记录保存目标稳定版本、审批引用和执行时确定的影响范围,执行过程中不得扩大范围。
- 静态演示的审计、质量处置、问题时间线和交换日志采用明确保留上限;正式系统必须使用服务端分页、归档和 WORM 审计。
## 静态跨端边界
本目录是**厂商端静态 SPA 原型**,与仓库根目录下的校园端 `/demo/` 没有运行时连接。双方仅通过约定标识(`TENANT-HZVTC`、`EDU-PKG-20260823-01`、`V2026.08.23`)对照展示,不共享浏览器存储,也不通过静态页面传输数据。
按钮中的发送、回执、重试与回滚均为本地状态机模拟:不会访问学校接口、不会写入校园端、不会发送真实数据。正式跨端同步必须由经过鉴权、签名、幂等和审计控制的服务端 API 执行。
按钮中的发送、回执、重试与回滚均为本地状态机模拟:不会访问学校接口、不会写入校园端,也不会发送数据。正式跨端同步必须由服务端 API 完成身份校验、签名、重复请求处理和操作记录。
## 标准文件
@@ -91,10 +91,10 @@ python3 -m http.server 8080
- 商业和公共招聘平台的统一岗位信号契约
- `schemas/school-employment.schema.json`
- `SCHOOL-EMPLOYMENT V2.2`
- 校内就业事实契约,包含租户隔离和学生令牌化要求
- 校内就业数据契约,包含租户隔离和学生令牌化要求
- `schemas/school-internship.schema.json`
- `SCHOOL-INTERNSHIP V2.1`
- 校内实习事实契约,包含租户隔离、学生令牌化、岗位任务和评价证据
- 校内实习数据契约,包含租户隔离、学生令牌化、岗位任务和评价记录
- `schemas/package-manifest.schema.json`
- `PACKAGE-MANIFEST V1.2`
- 版本化导入、导出和学校交付包清单
@@ -124,11 +124,11 @@ python3 -m http.server 8080
- 移动:`390 × 844`
- 可见业务文字不小于 13px
- 页面级无横向溢出,宽表仅在自身容器内横向滚动
- 所有深层按钮必须打开业务内容或形成列表状态变化
- 所有深层按钮必须打开业务内容或改变列表状态
- JavaScript 语法检查:`node --check app.js`
- JSON 文件检查:`python3 -m json.tool <file>`
新增闭环预览:
运营流程预览:
- `preview-admin-operations.png`
- `preview-admin-operations-mobile.png`
+50 -50
View File
@@ -738,7 +738,7 @@ const contracts = {
key: "JOB-SIGNAL",
title: "外部招聘岗位信号标准",
version: "V3.0.0",
description: "统一商业招聘平台、公共就业平台和活动聚合平台的岗位、企业、薪资、技能与来源证据,用于产教供需分析。",
description: "规定商业招聘平台、公共就业平台和招聘活动数据中的岗位、企业、薪资、技能与来源字段,供产教分析使用。",
schemaPath: "./schemas/external-job-source.schema.json",
fields: 39,
required: 19,
@@ -748,7 +748,7 @@ const contracts = {
compatibility: "向后兼容 V2.6",
fieldsPreview: [
["record_id", "string/uuid", "是", "平台记录唯一标识", "UUID v4"],
["source_platform", "string", "是", "来源平台代码", "受控代码表"],
["source_platform", "string", "是", "来源平台代码", "固定代码表"],
["source_record_id", "string", "是", "来源记录标识", "同平台唯一"],
["source_url", "string/uri", "是", "来源页面或接口定位", "http/https"],
["collected_at", "datetime", "是", "实际采集时间", "ISO-8601 +08:00"],
@@ -767,14 +767,14 @@ const contracts = {
["来源证据", "必须保留 source_url、collected_at、content_hash 和采集任务编号。"],
["薪资口径", "统一折算为税前月薪;面议、日薪、时薪保留原文并标注换算状态。"],
["时效与失效", "岗位状态和最后发现时间分开记录;失效采用版本化软删除。"],
["文本治理", "岗位职责原文进入原始层,分析层只保留去标记、去联系方式后的业务文本。"],
["文本处理", "岗位职责原文保存在原始层,分析层只保留去标记、去联系方式后的业务文本。"],
],
},
"school-employment": {
key: "SCHOOL-EMPLOYMENT",
title: "校内就业事实数据标准",
title: "校内就业数据标准",
version: "V2.2.0",
description: "统一学校毕业生就业状态、岗位、企业、薪资、专业相关度和去向确认事实,支持租户隔离和敏感字段最小化。",
description: "规定学校毕业生就业状态、岗位、企业、薪资、专业相关度和去向确认字段,并要求租户隔离,只保留业务必需的敏感字段。",
schemaPath: "./schemas/school-employment.schema.json",
fields: 32,
required: 16,
@@ -787,7 +787,7 @@ const contracts = {
["student_token", "string", "是", "学生不可逆令牌", "禁止明文学号"],
["cohort", "integer", "是", "毕业届次", "YYYY"],
["program_code", "string", "是", "专业代码", "教育部专业目录"],
["employment_status", "string", "是", "就业状态", "受控枚举"],
["employment_status", "string", "是", "就业状态", "固定选项"],
["job_title", "string", "否", "就业岗位", "业务原文"],
["enterprise_token", "string", "否", "企业统一标识", "信用代码令牌化"],
["salary_monthly", "number", "否", "税前月薪", "元/月"],
@@ -806,9 +806,9 @@ const contracts = {
},
"school-internship": {
key: "SCHOOL-INTERNSHIP",
title: "校内实习事实数据标准",
title: "校内实习数据标准",
version: "V2.1.0",
description: "统一学生实习岗位、企业、起止日期、状态、工资、岗位任务和评价事实,支持租户隔离和敏感身份最小化。",
description: "规定学生实习岗位、企业、起止日期、状态、工资、岗位任务和评价字段,并要求租户隔离,只保留业务必需的身份字段。",
schemaPath: "./schemas/school-internship.schema.json",
fields: 30,
required: 14,
@@ -826,7 +826,7 @@ const contracts = {
["job_tasks", "array<string>", "否", "实习岗位任务", "学校确认"],
["start_date", "date", "是", "开始日期", "YYYY-MM-DD"],
["end_date", "date", "否", "结束日期", "YYYY-MM-DD"],
["status", "string", "是", "实习状态", "受控枚举"],
["status", "string", "是", "实习状态", "固定选项"],
["salary_monthly", "number", "否", "税前月实习工资", "元/月"],
["evidence_reference", "string", "否", "校内证据引用", "不传附件"],
["source_updated_at", "datetime", "否", "业务更新时间", "增量水位"],
@@ -837,7 +837,7 @@ const contracts = {
["身份最小化", "平台只接收 student_token,拒绝姓名、身份证、手机号、邮箱和明文学号。"],
["企业要求", "进行中或已完成实习必须提供企业令牌或企业名称。"],
["证据引用", "敏感附件保留在学校系统,平台只保存 evidence_reference。"],
["增量幂等", "tenant_id + source_record_id + source_updated_at 形成增量与幂等边界。"],
["增量重复处理", "使用 tenant_id + source_record_id + source_updated_at 判断增量更新,并确保重复提交不会重复写入。"],
],
},
"package-manifest": {
@@ -1381,7 +1381,7 @@ const operationsStatusLabels = {
INVESTIGATING: "排障中",
QUALITY_REVIEW_PENDING: "待质量复核",
VERIFIED: "复核通过",
REVIEWED_BLOCKED: "复核后仍阻断",
REVIEWED_BLOCKED: "复核后仍不能发布",
RESOLVED: "已解决",
CLOSED: "已关闭",
OPEN: "已建单",
@@ -1401,7 +1401,7 @@ const operationsStatusLabels = {
PENDING_REVIEW: "待复核",
RECORDED: "已记录",
PASS: "通过",
BLOCKED: "阻断",
BLOCKED: "不能继续",
ROLLBACK_PENDING_APPROVAL: "回滚待审批",
ROLLBACK_APPROVED: "回滚已批准",
ROLLBACK_REJECTED: "回滚已驳回",
@@ -1537,7 +1537,7 @@ function renderOperationsMetrics() {
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 = `
<article><span>未闭环问题</span><strong>${openIssues}</strong><small>确认、指派、排障与质量处置</small></article>
<article><span>未处理完问题</span><strong>${openIssues}</strong><small>确认、指派、排障与质量处置</small></article>
<article><span>审批待办</span><strong>${pendingApprovals}</strong><small>申请人与审批人强制分离</small></article>
<article><span>交付待办 / 异常</span><strong>${deliveryAttention}</strong><small>按租户隔离,不重复发送成功学校</small></article>
<article><span>已核验回执</span><strong>${acknowledged}</strong><small>记录数与 SHA-256 均一致</small></article>
@@ -1552,8 +1552,8 @@ function renderOperationsMetrics() {
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" ? "质量证据已通过独立复核,可继续生成候选包" : "质量分达标不等于门禁通过,稳定版本继续保持不变";
if (qualityState) qualityState.textContent = release?.qualityGate === "PASS" ? "当前批次已满足发布质量要求" : "当前批次仍需修复重放复核";
if (qualityDetail) qualityDetail.textContent = release?.qualityGate === "PASS" ? "质量记录已经另一名复核人确认,可继续生成候选包" : "仅质量分达标还不能发布,当前稳定版本保持不变";
}
function renderOverviewIssues() {
@@ -1571,7 +1571,7 @@ function renderOverviewIssues() {
`,
)
.join("")
: `<div class="info-note">当前没有待处理问题,所有事项均已闭环。</div>`;
: `<div class="info-note">当前没有待处理问题。</div>`;
const countBadge = list.closest(".alert-panel").querySelector(".panel-head > em");
countBadge.textContent = `${issues.length} 项`;
}
@@ -1595,7 +1595,7 @@ function renderOperationsIssues() {
.join("");
document.getElementById("operationsIssueTable").innerHTML = rows;
const openCount = operationsStore.issues.filter((issue) => !["RESOLVED", "CLOSED"].includes(issue.status)).length;
document.getElementById("issueQueueSummary").textContent = `${openCount} 项未闭环 · 状态刷新后保留`;
document.getElementById("issueQueueSummary").textContent = `${openCount} 项未处理完 · 状态刷新后保留`;
}
function releaseNextAction(release, approval) {
@@ -1606,7 +1606,7 @@ function releaseNextAction(release, approval) {
if (release.status === "PENDING_APPROVAL") {
return `
<label class="approval-field"><span>独立审批人</span><select id="releaseApprover"><option value="林若溪">林若溪 · 数据产品负责人</option><option value="王海宁">王海宁 · 发布审批人</option><option value="陈致远">陈致远 · 当前申请人</option></select></label>
<label class="approval-field approval-comment"><span>审批意见</span><input id="approvalComment" value="质量门禁与租户范围已复核" /></label>
<label class="approval-field approval-comment"><span>审批意见</span><input id="approvalComment" value="质量检查结果与租户范围已复核" /></label>
<button class="button secondary" data-release-reject>驳回</button>
<button class="button primary" data-release-approve>批准</button>
`;
@@ -1654,7 +1654,7 @@ function renderReleaseWorkspace() {
<span><small>上一稳定版本</small><strong>${h(release.previousStableVersion)}</strong></span>
</div>
<div class="release-facts">
<span><small>质量门禁</small><strong class="${release.qualityGate === "PASS" ? "gate-pass" : "gate-blocked"}">${h(operationStatusLabel(release.qualityGate))} · ${release.qualityScore}</strong></span>
<span><small>发布质量检查</small><strong class="${release.qualityGate === "PASS" ? "gate-pass" : "gate-blocked"}">${h(operationStatusLabel(release.qualityGate))} · ${release.qualityScore}</strong></span>
<span><small>影响租户</small><strong>${release.impactTenants.length} 所学校</strong></span>
<span><small>申请人</small><strong>${h(release.applicant)}</strong></span>
<span><small>Manifest</small><strong>${release.publishedManifestGeneratedAt ? `发布版已生成 · ${h(operationTime(release.publishedManifestGeneratedAt))}` : release.manifestGeneratedAt ? `候选已生成 · ${h(operationTime(release.manifestGeneratedAt))}` : "尚未生成"}</strong></span>
@@ -1662,11 +1662,11 @@ function renderReleaseWorkspace() {
<div class="release-quality-note ${release.qualityGate === "PASS" ? "passed" : ""}">
${
release.status === "PUBLISHED"
? "质量证据、候选 Manifest 与独立审批已冻结,正式发布 Manifest 已生成;当前尚未代表学校接收。"
? "发布使用的质量记录、候选 Manifest 和审批结果均不可修改,正式发布 Manifest 已生成;这不表示学校已经接收。"
: release.status === "ROLLED_BACK"
? `稳定别名已回滚到 ${h(release.previousStableVersion)},原发布、审批和租户交付记录继续保留审计。`
: release.status === "PENDING_APPROVAL"
? "候选 Manifest 已冻结,正在等待不同于申请人的独立审批人处理。"
? "候选 Manifest 已提交,审批完成前不能修改,正在等待另一名审批人处理。"
: release.status === "APPROVED"
? "独立审批已通过,可以执行稳定别名切换并生成正式发布 Manifest。"
: release.status === "REJECTED"
@@ -1675,7 +1675,7 @@ function renderReleaseWorkspace() {
? release.manifestGeneratedAt
? "修复重放证据与候选 Manifest 已就绪,可以提交独立发布审批。"
: "修复重放已由独立复核人验证;还必须生成候选 Manifest 后才能提交审批。"
: "质量门禁阻断:只有 FIX_REPLAY 的记录数、规则结果与复核签名全部验证通过才能解锁;豁免或丢弃均保持阻断。"
: "当前不能发布:只有 FIX_REPLAY 的记录数、规则结果和复核签名全部通过检查才可继续;豁免或丢弃仍不能发布。"
}
</div>
<div class="candidate-actions">
@@ -1684,9 +1684,9 @@ function renderReleaseWorkspace() {
? `<button class="button secondary" data-release-official-manifest>下载发布 Manifest</button>`
: release.status === "DRAFT"
? `<button class="button secondary" data-release-manifest ${release.qualityGate === "PASS" ? "" : "disabled"}>${release.qualityGate === "PASS" ? (release.manifestGeneratedAt ? "重新生成候选 Manifest" : "生成候选包 / Manifest") : "质量通过后生成 Manifest"}</button>`
: `<span class="release-final-note">候选 Manifest 已冻结,审批中不可重建。</span>`
: `<span class="release-final-note">候选 Manifest 已提交,审批中不能重建。</span>`
}
<button class="text-button" data-open-issue="ISSUE-HZRC-20260823-01">查看阻断问题</button>
<button class="text-button" data-open-issue="ISSUE-HZRC-20260823-01">查看未解决问题</button>
</div>
</article>
<article class="panel approval-card">
@@ -1771,7 +1771,7 @@ function issueActionFooter(issue) {
const qualityReviewAction =
disposition?.action === "FIX_REPLAY"
? `<button class="button primary" data-issue-action="verify">验证重放并通过</button>`
: `<button class="button primary" data-issue-action="verify-blocked">完成复核并保持阻断</button>`;
: `<button class="button primary" data-issue-action="verify-blocked">完成复核,仍不允许发布</button>`;
const actions = {
ALERTED: `<button class="button primary" data-issue-action="acknowledge">确认告警</button>`,
ACKNOWLEDGED: `<label class="footer-field"><span>指派负责人</span><select id="issueAssignee"><option>周敏 · 区域源运维</option><option>赵亦辰 · 采集工程师</option><option>数据标准组</option></select></label><button class="button primary" data-issue-action="assign">确认指派</button>`,
@@ -1782,7 +1782,7 @@ function issueActionFooter(issue) {
QUALITY_REVIEW_PENDING: qualityReviewAction,
VERIFIED: `<button class="button primary" data-issue-action="resolve">标记已解决</button>`,
RESOLVED: `<button class="button primary" data-issue-action="close">关闭问题</button>`,
CLOSED: `<span class="release-final-note">问题已闭环,所有动作与证据已保留。</span>`,
CLOSED: `<span class="release-final-note">问题已完成处理,操作记录和附件已保留。</span>`,
};
return `<button class="button secondary" data-issue-close>关闭抽屉</button>${actions[issue.status] || ""}`;
}
@@ -2063,7 +2063,7 @@ function renderSourceMapping(source) {
</article>
<article class="drawer-card">
<h3>样本回归</h3>
<p>最近回归样本 1,000 条,结构通过率 100%,必填完整率 98.8%,岗位归一覆盖率 96.4%。未匹配岗位进入人工复核队列,不以模型推断覆盖原始事实。</p>
<p>最近回归样本 1,000 条,结构通过率 100%,必填完整率 98.8%,岗位归一覆盖率 96.4%。未匹配岗位进入人工复核队列,不用模型推断替换原始记录。</p>
</article>
`;
}
@@ -2103,7 +2103,7 @@ function renderSourceAudit(source) {
</article>
<article class="drawer-card">
<h3>数据使用边界</h3>
<p>平台只向产教分析产品交付经契约治理的岗位信号、企业画像和统计结果。原始层与交付层分区存储,租户交付范围由 Manifest 和平台数据范围策略共同控制。</p>
<p>平台只向产教分析产品交付符合数据契约的岗位信息、企业信息和统计结果。原始数据与交付数据分开存储,Manifest 和租户授权共同决定每所学校可收到的数据。</p>
</article>
`;
}
@@ -2461,7 +2461,7 @@ function openStandardRecord(recordId) {
</dl>
</article>
<article class="drawer-card">
<h3>全链路证据</h3>
<h3>来源与处理记录</h3>
<div class="record-lineage">
<span><b>01</b><span><strong>采集任务</strong><small>${h(record.taskId)} · ${h(record.collectorVersion)}</small></span></span>
<span><b>02</b><span><strong>原始快照</strong><small>${h(record.snapshotId)} · payload SHA-256 ${h(record.payloadHash)}</small></span></span>
@@ -2843,8 +2843,8 @@ function buildPackageManifest(options = {}) {
generated_by: "陈致远",
approval_reference: approval ? `${approval.id}:${approvalStatus}` : null,
notes: published
? "该文件是独立审批通过后生成的发布 Manifest;静态原型只记录本地稳定版本状态,不会向学校真实发送。"
: "该文件仅为候选包 Manifest;只有独立审批通过并执行发布后才成为稳定版本,静态原型不会向学校真实发送。",
? "该文件是独立审批通过后生成的发布 Manifest;静态原型只记录本地稳定版本状态,不会向学校发送。"
: "该文件仅为候选包 Manifest;只有独立审批通过并执行发布后才成为稳定版本,静态原型不会向学校发送。",
};
}
@@ -2852,11 +2852,11 @@ function generateCandidateManifest() {
const release = currentRelease();
updateReleaseQualityGate();
if (!release || release.status !== "DRAFT") {
showToast("候选已进入审批或发布流程,Manifest 已冻结;驳回后需返回草稿重建", "无法重建候选 Manifest");
showToast("候选已提交审批或发布,当前 Manifest 不能修改;驳回后可返回草稿重建", "无法重建候选 Manifest");
return;
}
if (release.qualityGate !== "PASS") {
showToast("只有 FIX_REPLAY 的完整验证证据通过后才能生成候选 Manifest", "质量门禁阻断");
showToast("只有 FIX_REPLAY 的记录数、规则结果和复核签名全部通过检查后,才能生成候选 Manifest", "当前不能生成候选");
return;
}
const inputFingerprint = candidateManifestInputFingerprint();
@@ -3428,7 +3428,7 @@ function setupAudit() {
openInfoModal(
"AUDIT POLICY",
"生产审计与留存策略",
`<div class="info-grid"><span>关键事件留存<strong>36个月</strong></span><span>审计存储<strong>WORM</strong></span><span>每日完整性校验<strong>SHA-256</strong></span><span>追踪覆盖率<strong>100%</strong></span></div><div class="info-note">采集请求、连接器版本、原始快照、映射规则、质量结果、审批和租户交付都写入统一 trace_id。敏感配置只记录密钥引用和变更摘要,不在日志中保存明文凭据。</div>`,
`<div class="info-grid"><span>关键事件留存<strong>36个月</strong></span><span>审计存储<strong>WORM</strong></span><span>每日完整性校验<strong>SHA-256</strong></span><span>追踪覆盖率<strong>100%</strong></span></div><div class="info-note">采集请求、连接器版本、原始快照、映射规则、质量结果、审批和租户交付都使用同一个 trace_id。敏感配置只记录密钥引用和变更摘要,不在日志中保存明文凭据。</div>`,
);
});
}
@@ -3482,7 +3482,7 @@ function setupOperations() {
return;
}
if (action === "investigate" && issue.status === "ASSIGNED") {
transitionIssue(issue, "INVESTIGATING", "已锁定源端时延与解析隔离记录,开始执行排障。", ["排障工作区快照", issue.relatedTask]);
transitionIssue(issue, "INVESTIGATING", "已确认问题来自源端延迟,并找到对应的解析隔离记录,开始排障。", ["排障工作区快照", issue.relatedTask]);
return;
}
if (action === "submit-quality" && issue.status === "INVESTIGATING") {
@@ -3548,7 +3548,7 @@ function setupOperations() {
renderOperations();
renderAudit();
openIssueDrawer(issue.id);
showToast("处置动作已固化并写入审计,等待独立质量复核", "质量处置已提交");
showToast("处置动作已保存且不能修改,等待另一名质量复核人处理", "质量处置已提交");
return;
}
if (action === "verify-blocked" && issue.status === "QUALITY_REVIEW_PENDING") {
@@ -3574,8 +3574,8 @@ function setupOperations() {
evidence: [...metadata.evidence],
note:
disposition.action === "WAIVE"
? "风险豁免不等同于质量验证通过;候选门禁保持阻断,必须另行提交风险审批。"
: "丢弃可能改变数据完整性;候选门禁保持阻断,必须重建候选并重新执行质量验证。",
? "风险豁免不等同于质量验证通过;候选版本仍不能发布,必须另行提交风险审批。"
: "丢弃可能改变数据完整性;候选版本仍不能发布,必须重建候选并重新执行质量检查。",
});
const workOrder = operationsStore.workOrders.find((item) => item.issueId === issue.id);
if (workOrder) migrateOperationsEntity(workOrder, "INVESTIGATING", metadata);
@@ -3584,13 +3584,13 @@ function setupOperations() {
renderOperations();
renderAudit();
openIssueDrawer(issue.id);
showToast(`${actionLabel}已复核,但不能解除发布门禁`, "质量门禁仍阻断");
showToast(`${actionLabel}已复核,但当前候选版本仍不能发布`, "仍不满足发布条件");
return;
}
if (action === "verify" && issue.status === "QUALITY_REVIEW_PENDING") {
const disposition = latestQualityDisposition(issue.id);
if (!disposition || disposition.action !== "FIX_REPLAY") {
showToast("只有修复并重放可以进入质量验证通过状态", "质量门禁仍阻断");
showToast("只有修复并重放可以进入质量检查通过状态", "仍不满足发布条件");
return;
}
const metadata = addOperationsAudit({
@@ -3616,7 +3616,7 @@ function setupOperations() {
time: metadata.time,
trace: metadata.trace,
evidence: [...metadata.evidence],
note: "记录数、质量规则与重放证据已复核,候选版本质量门禁解除。",
note: "记录数、质量规则和重放记录已复核,候选版本可以继续提交审批。",
});
const workOrder = operationsStore.workOrders.find((item) => item.issueId === issue.id);
if (workOrder) migrateOperationsEntity(workOrder, "VERIFIED", metadata);
@@ -3625,7 +3625,7 @@ function setupOperations() {
renderOperations();
renderAudit();
openIssueDrawer(issue.id);
showToast("质量处置已 VERIFIED,候选版本现在可提交审批", "质量门禁已通过");
showToast("质量处置已 VERIFIED,候选版本现在可提交审批", "已满足发布质量要求");
return;
}
if (action === "resolve" && (issue.status === "VERIFIED" || (issue.status === "INVESTIGATING" && !issue.qualityRequired))) {
@@ -3645,7 +3645,7 @@ function setupOperations() {
}
if (event.target.closest("[data-release-official-manifest]")) {
if (!release.publishedManifest) {
showToast("尚未形成发布 Manifest", "下载不可用");
showToast("尚未生成发布 Manifest", "下载不可用");
return;
}
downloadJson(`${release.packageId}-published-manifest.json`, release.publishedManifest);
@@ -3656,7 +3656,7 @@ function setupOperations() {
updateReleaseQualityGate();
if (!releaseReadyForApproval(release)) {
showToast(
release.qualityGate === "PASS" ? "质量门禁已通过,但必须先生成并冻结候选 Manifest" : "只有验证通过的 FIX_REPLAY 才能解除候选版本门禁",
release.qualityGate === "PASS" ? "质量检查已通过,但必须先生成候选 Manifest 并提交审批" : "只有通过检查的 FIX_REPLAY 才能让候选版本继续提交",
"候选尚不可提交",
);
return;
@@ -3666,7 +3666,7 @@ function setupOperations() {
event: "候选版本提交独立审批",
object: release.id,
result: "待审批",
evidence: ["候选 Manifest", "质量门禁 PASS", "影响租户清单"],
evidence: ["候选 Manifest", "发布质量检查 PASS", "影响租户清单"],
});
migrateOperationsEntity(release, "PENDING_APPROVAL", metadata);
migrateOperationsEntity(approval, "PENDING_APPROVAL", metadata);
@@ -3794,7 +3794,7 @@ function setupOperations() {
rejectEvent: "全局版本回滚独立审批驳回",
object: `${release.id} → ${release.previousStableVersion}`,
evidence: ["回滚审批意见", "审批人身份", "四眼规则校验", "影响租户清单"],
defaultApproveComment: "同意在受控窗口执行回滚",
defaultApproveComment: "同意在已安排的时间窗口执行回滚",
defaultRejectComment: "回滚依据不足",
successMessage: (actor, passed) => `${actor} 已${passed ? "批准" : "驳回"}全局回滚`,
successTitle: (passed) => (passed ? "回滚审批通过" : "回滚审批驳回"),
@@ -3807,7 +3807,7 @@ function setupOperations() {
rollbackApproval?.status !== "ROLLBACK_APPROVED" ||
!isIndependentApproval(rollbackApproval)
) {
showToast("全局版本回滚必须先完成职责分离的独立审批", "回滚执行被阻断");
showToast("全局版本回滚必须先由另一名审批人批准", "当前不能执行回滚");
return;
}
const metadata = addOperationsAudit({
@@ -3846,7 +3846,7 @@ function setupOperations() {
saveOperationsStore();
renderOperations();
renderAudit();
showToast(`${pending.length} 所待发送学校已进入原型队列,未发生真实网络发送`, "交付队列已启动");
showToast(`${pending.length} 所待发送学校已进入原型队列,没有发出网络请求`, "交付队列已启动");
});
document.getElementById("deliveryTable").addEventListener("click", (event) => {
const button =
@@ -3949,7 +3949,7 @@ function setupGlobalActions() {
const deliveryTodos = operationsStore.deliveries.filter((delivery) => ["PENDING", "SENDING", "FAILED", "RETRYING"].includes(delivery.status));
openInfoModal(
"OPERATIONS NOTIFICATIONS",
"真实运营待办",
"运营待办",
`<div class="runtime-list notification-list">
${openIssues
.map(
@@ -3963,7 +3963,7 @@ function setupGlobalActions() {
: ""
}
${deliveryTodos.length ? `<button data-operations-jump="deliveries"><strong>${deliveryTodos.length} 所学校交付待处理</strong><small>包含待发送、发送中、失败或重试状态</small></button>` : ""}
${!openIssues.length && !pendingApproval && !deliveryTodos.length ? `<span><strong>当前没有运营待办</strong><small>问题、审批和交付均已闭环</small></span>` : ""}
${!openIssues.length && !pendingApproval && !deliveryTodos.length ? `<span><strong>当前没有运营待办</strong><small>问题、审批和交付均已处理完成</small></span>` : ""}
</div>`,
);
});
+23 -23
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="产教数据中台,面向厂商数据运营团队管理招聘数据采集、标准契约、导入导出、质量治理与审计交付"
content="产教数据中台,供厂商数据运营团队管理招聘数据采集、标准、导入导出、质量检查和学校交付"
/>
<title>产教数据中台 · 厂商运营中心</title>
<link rel="icon" href="./favicon.svg" type="image/svg+xml" />
@@ -104,9 +104,9 @@
<section class="view active" id="view-overview" data-title="运行总览">
<div class="page-heading">
<div>
<div class="eyebrow"><i></i> DATA OPERATIONS CONTROL PLANE</div>
<div class="eyebrow"><i></i> DATA OPERATIONS</div>
<h1>招聘数据生产运行总览</h1>
<p>统一管理商业招聘平台、公共就业平台和校内业务系统,从采集到交付全链路可追溯。</p>
<p>集中查看商业招聘平台、公共就业平台和校内业务系统的数据,每批数据都保留采集、处理和交付记录。</p>
</div>
<div class="heading-actions">
<button class="button secondary" data-open-export>
@@ -160,7 +160,7 @@
<article class="panel flow-panel">
<div class="panel-head">
<div><span>处理链路</span><h2>今日数据流水线</h2></div>
<div><span>处理步骤</span><h2>今日数据流水线</h2></div>
<span class="live-label"><i></i>实时</span>
</div>
<div class="pipeline-flow">
@@ -194,7 +194,7 @@
<section class="view" id="view-operations" data-title="运营处置">
<div class="page-heading">
<div>
<div class="eyebrow"><i></i> OPERATIONS RESOLUTION LOOP</div>
<div class="eyebrow"><i></i> OPERATIONS WORKFLOW</div>
<h1>运营问题、发布与学校交付</h1>
<p>从告警确认、排障和质量复核,到独立审批、版本发布、逐学校回执与租户级回滚,全程保留状态与审计证据。</p>
</div>
@@ -237,7 +237,7 @@
<div><span>TENANT DELIVERY</span><h2>逐学校交付与回执</h2></div>
<button class="text-button" id="startPendingDeliveries">启动待发送学校</button>
</div>
<div class="delivery-boundary-note">静态原型只模拟并持久化交付状态,不会向学校网络、接口或业务系统真实发送数据。</div>
<div class="delivery-boundary-note">静态原型只记录交付状态,不会向学校网络、接口或业务系统发送数据。</div>
<div class="table-wrap">
<table class="data-table delivery-table">
<thead><tr><th>学校 / 租户</th><th>包与数据版本</th><th>交付状态</th><th>记录数回执</th><th>SHA-256 回执</th><th>最近动作</th><th>操作</th></tr></thead>
@@ -252,8 +252,8 @@
<div class="page-heading">
<div>
<div class="eyebrow"><i></i> SOURCE REGISTRY</div>
<h1>统一数据源注册中心</h1>
<p>商业招聘平台、公共就业平台、活动聚合平台和校内系统使用同一套资产、版本和运行模型。</p>
<h1>数据源管理</h1>
<p>商业招聘平台、公共就业平台、招聘活动和校内系统都按数据源、版本和运行记录管理。</p>
</div>
<div class="heading-actions">
<button class="button secondary" id="sourceCatalogExport">导出数据源目录</button>
@@ -291,7 +291,7 @@
<div>
<div class="eyebrow"><i></i> COLLECTION ORCHESTRATION</div>
<h1>采集任务编排中心</h1>
<p>按来源、地域、行业和岗位族编排任务,支持分片、限速、失败隔离、断点续采和版本化原始留存。</p>
<p>按来源、地域、行业和岗位族创建采集任务,支持分片、限速、失败隔离、断点续采,并保存每次原始数据版本。</p>
</div>
<div class="heading-actions">
<button class="button secondary" id="pauseAllJobs">暂停全部</button>
@@ -329,8 +329,8 @@
<div class="page-heading">
<div>
<div class="eyebrow"><i></i> DATA RECORD EXPLORER</div>
<h1>采集数据浏览与追溯</h1>
<p>按数据层、来源、地区和关键词检索岗位明细,向下追溯源端记录、原始快照、采集任务与内容哈希。</p>
<h1>采集数据与来源记录</h1>
<p>按数据层、来源、地区和关键词检索岗位明细,并查看对应的源端记录、原始快照、采集任务与内容哈希。</p>
</div>
<div class="heading-actions">
<button class="button secondary" id="recordRefresh">
@@ -382,9 +382,9 @@
<section class="view" id="view-contracts" data-title="数据标准">
<div class="page-heading">
<div>
<div class="eyebrow"><i></i> DATA CONTRACT GOVERNANCE</div>
<div class="eyebrow"><i></i> DATA CONTRACTS</div>
<h1>产教数据标准与契约中心</h1>
<p>第三方招聘数据和校内业务数据按版本化契约交付,字段、代码、质量、脱敏和变更策略一次定义、全链路执行。</p>
<p>第三方招聘数据和校内业务数据按版本化契约交付,字段、代码、质量、脱敏和变更规则统一设置。</p>
</div>
<div class="heading-actions">
<button class="button secondary" id="compareContractVersions">版本对比</button>
@@ -394,8 +394,8 @@
<div class="contract-tabs" id="contractTabs">
<button class="active" data-contract="external-job"><span>JOB-SIGNAL</span><strong>外部岗位信号</strong><small>V3.0 · 39字段</small></button>
<button data-contract="school-employment"><span>SCHOOL-EMPLOYMENT</span><strong>校内就业事实</strong><small>V2.2 · 32字段</small></button>
<button data-contract="school-internship"><span>SCHOOL-INTERNSHIP</span><strong>校内实习事实</strong><small>V2.1 · 30字段</small></button>
<button data-contract="school-employment"><span>SCHOOL-EMPLOYMENT</span><strong>校内就业数据</strong><small>V2.2 · 32字段</small></button>
<button data-contract="school-internship"><span>SCHOOL-INTERNSHIP</span><strong>校内实习数据</strong><small>V2.1 · 30字段</small></button>
<button data-contract="package-manifest"><span>PACKAGE-MANIFEST</span><strong>交付包清单</strong><small>V1.2 · 21字段</small></button>
</div>
<div id="contractContent"></div>
@@ -406,7 +406,7 @@
<div>
<div class="eyebrow"><i></i> DATA EXCHANGE</div>
<h1>数据导入与交付中心</h1>
<p>支持标准文件导入、Schema 预检、字段映射、质量干跑和带清单、校验和、质量报告的版本化导出。</p>
<p>支持标准文件导入、Schema 预检、字段对应、发布前质量检查,以及带清单、校验和与质量报告的数据导出。</p>
</div>
<div class="heading-actions">
<a class="button secondary" href="./samples/external-job-sample.csv" download>下载导入样例</a>
@@ -436,7 +436,7 @@
</article>
<article class="panel export-panel">
<div class="panel-head"><div><span>版本交付</span><h2>构建数据交付包</h2></div><em>可复核、可回滚</em></div>
<div class="panel-head"><div><span>版本交付</span><h2>生成数据交付包</h2></div><em>可查看检查记录,可回滚</em></div>
<div class="export-form">
<label><span>数据产品</span><select id="exportProduct"><option>区域招聘岗位基础包</option><option>薪资与企业画像增强包</option><option>产业趋势与政策信号包</option></select></label>
<label><span>目标租户</span><select id="exportTenant"><option>36所已授权学校</option><option>杭州科技职业技术学院</option><option>华东区域项目组</option></select></label>
@@ -467,7 +467,7 @@
<div>
<div class="eyebrow"><i></i> QUALITY & NORMALIZATION</div>
<h1>数据质量与岗位归一</h1>
<p>执行结构、完整性、唯一性、时效性和业务规则校验,将多来源岗位映射到统一岗位与技能体系。</p>
<p>检查结构、完整性、唯一性、时效性和业务规则,并把不同来源的岗位名称和技能名称对齐。</p>
</div>
<div class="heading-actions">
<button class="button secondary" id="qualityRuleButton">质量规则</button>
@@ -478,7 +478,7 @@
<div class="quality-layout">
<article class="quality-score-card">
<span class="quality-ring"><strong>94.7</strong><small>综合质量</small></span>
<div><span>生产发布阈值 ≥ 90</span><strong id="qualityReleaseState">当前批次仍需修复重放复核</strong><small id="qualityReleaseDetail">质量分达标不等于门禁通过,稳定版本继续保持不变</small></div>
<div><span>生产发布阈值 ≥ 90</span><strong id="qualityReleaseState">当前批次仍需修复重放复核</strong><small id="qualityReleaseDetail">仅质量分达标还不能发布,当前稳定版本保持不变</small></div>
</article>
<div class="quality-dimensions">
<span><small>完整性</small><strong>97.8%</strong><i><b style="width:97.8%"></b></i></span>
@@ -504,8 +504,8 @@
<div class="page-heading">
<div>
<div class="eyebrow"><i></i> AUDIT & DELIVERY</div>
<h1>全链路审计与生产交付</h1>
<p>记录来源、采集任务、代码版本、原始快照、处理规则、发布审批和租户交付,实现问题可定位、版本可回滚。</p>
<h1>审计记录与生产交付</h1>
<p>记录来源、采集任务、代码版本、原始快照、处理规则、发布审批和租户交付。出现问题时可查到相关记录,也可回滚版本。</p>
</div>
<div class="heading-actions">
<button class="button secondary" id="auditPolicyButton">审计策略</button>
@@ -516,7 +516,7 @@
<div class="audit-summary">
<article><small>今日审计事件</small><strong>2,846</strong><em>全部已签名</em></article>
<article><small>数据版本</small><strong>128</strong><em>保留 36 个月</em></article>
<article><small>可追踪任务</small><strong>100%</strong><em>原始快照已绑定</em></article>
<article><small>带追踪编号的任务</small><strong>100%</strong><em>已关联原始快照</em></article>
<article><small>交付租户</small><strong>36</strong><em>范围策略有效</em></article>
</div>
@@ -577,7 +577,7 @@
<div>
<span id="recordDrawerKicker">DATA RECORD</span>
<h2 id="recordDrawerTitle">采集记录详情</h2>
<p id="recordDrawerDescription">查看业务字段和全链路来源证据。</p>
<p id="recordDrawerDescription">查看业务字段、来源和处理记录。</p>
</div>
<button class="icon-button" id="closeRecordDrawer" aria-label="关闭采集记录详情">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18" /></svg>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 289 KiB

@@ -210,7 +210,7 @@
"maxLength": 100
},
"uniqueItems": true,
"description": "基于技能词典提取并归一的技能标签。"
"description": "使用技能词典提取并统一名称的技能标签。"
},
"keywords": {
"type": "array",
@@ -1,8 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.edu-industry.cn/school-employment/2.2.0/schema.json",
"title": "SCHOOL-EMPLOYMENT V2.2 校内就业事实",
"description": "职业院校毕业生就业事实统一契约,采用租户隔离和身份最小化设计。",
"title": "SCHOOL-EMPLOYMENT V2.2 校内就业数据",
"description": "职业院校毕业生就业数据契约,要求租户隔离,并将身份字段限制在业务必需的最少范围。",
"type": "object",
"additionalProperties": false,
"required": [
@@ -1,8 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.edu-industry.cn/school-internship/2.1.0/schema.json",
"title": "SCHOOL-INTERNSHIP V2.1 校内实习事实",
"description": "职业院校学生实习事实统一契约,采用租户隔离、身份最小化和证据引用设计。",
"title": "SCHOOL-INTERNSHIP V2.1 校内实习数据",
"description": "职业院校学生实习数据契约,要求租户隔离,将身份字段限制在业务必需的最少范围,并引用评价记录。",
"type": "object",
"additionalProperties": false,
"required": [