You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

17 KiB

V43 候选评分公平性与稳定性监测:修订设计

文档类型:解释与技术参考。读者:负责人、后端开发及验收人员。 状态:独立代码复验与真实 MySQL/Docker 验收通过,待 Git 检查点。 编码前基线:V42,最新已核查迁移 V40,OpenAPI 167 paths / 205 operations;测试维护提交 498512d。实现后静态 OpenAPI 为 172 paths / 210 operations,自动化验证结果以本轮报告为准。

1. 第一版范围与修订结论

第一版只监测现存评分数据质量、分布、规则版本构成和申诉流程。仅 ADMIN 可读,无自动干预,无新 Flyway、表、调度器、feature flag 或公共错误码。不写 candidate_dimension_scores,不调用会触发评分重算/upsert的服务,不改变排名或申诉状态,不读取题目、答案、sub_scores_json、测评详情或 audit details,不接入 AI。

不推断性别、民族、年龄、家庭背景,不使用学校、城市、头像或姓名作为代理分组。结果不是人口统计公平性证明,也不能说明某条评分规则合理。置信标记只是运营样本等级,不是统计置信区间。

保留五个原提案 ADMIN GET 接口以保持监测契约;drift 和 ranking-stability 第一版明确不可用。不提供 jobId/companyId 或任意维度组合筛选;无 ACKNOWLEDGE/DISMISS,人工处置后续单独评审。实际编码前如发现与其他已批准文档冲突,先报告冲突。

2. 真实数据证据与可计算边界

来源/组件 实际结构或行为 V43 口径
candidate_dimension_scores / CandidateDimensionScore UNIQUE(student_user_id,dimension,period);score/status/weight/rules_version/calculated_at;period为yyyy-MM 指定period在本次读取时仍存在的快照
CandidateScoringCalculator.upsertSnapshots ON DUPLICATE KEY覆盖分数、状态、规则版本、calculated_at;治理可重写历史period calculated_at不是不可变评分事件时间;不据此重建过去窗口
CandidateScoringCalculator.overallScoreFromSnapshots OK/ESTIMATED且非空分数的等权算术平均,2位HALF_UP 只读复用纯计算语义;不是任取权重的第二套公式
CandidateScoringEngineService / GovernanceService 评分查询/重算可能写快照 不作为V43读取入口
candidate_scoring_appeals PENDING/APPROVED/REJECTED/CANCELLED/ROLLED_BACK;created_at、reviewed_at、rollback_at、rules_version 申诉创建队列当前状态,不冒充窗口结束时状态
candidate_scoring_appeal_history 状态转换、操作人、原因 本版无需读原因或操作人
candidate_scoring_snapshot_history ROLLBACK的before/after_score和before/after_rules_version 仅已记录回滚变分,非全部纠错历史
CompetitionRankingCalculator/Service 按学生选最新period快照,排除WITHDRAWN/REJECTED;无持久化rank历史 当前无分候选可以在候选池,但rank=null;不能把入池当异常排名
applications/jobs/companies 可关联岗位与企业,但会引入重复候选和切片风险 本版不连接作筛选
V37风险治理 / 企业评分Dashboard 安全事件与业务聚合服务 不复用风险事件表,不复制企业个人级DTO
SecurityConfig / ErrorCode / GlobalExceptionHandler 既有ADMIN认证、403/2002、参数400/1001约定 后续编码精确复用,验证真实响应

现有迁移索引:V24有(student_user_id,dimension,period)唯一键、(student_user_id,period)、(dimension,period);V25申诉有(candidate_user_id,status)、(status,created_at),回滚历史有(candidate_user_id,dimension,period,created_at)。这些并不保证全局period或created_at聚合高效。上线前必须做真实EXPLAIN,不能仅因存在索引宣称覆盖。

可以计算:指定period的现存快照质量、分布、版本构成;指定创建窗口申诉的当前流程情况;已记录回滚的变分情况。 不可计算:过去某时刻真实分布、等长历史分布漂移、长期PENDING持续时长、系统全体学生覆盖率、评分曝光口径的真实申诉率、实际历史rank变化、Top N进出率、“无有效评分却曾获rank”的事件数。统一用UNAVAILABLE及明确原因,不以0代替。

匿名聚合快照未来可支持分布趋势,但不能支持同一个人的rank变化或Top N成员进出;后者需另行评审受控关联标识和候选级历史,不能声称匿名汇总可以恢复。

3. 新实施计划

  1. 固定现存快照样本、分母和统计纯函数;先写包含手算结果的测试。
  2. 实现受控只读聚合查询、混合版本检测和小样本抑制。
  3. 实现申诉队列状态及回滚变分指标;区分审核通过与实际变分。
  4. 接入ADMIN白名单DTO、五个接口及权限。
  5. 更新OpenAPI与联调文档,跑专项、全量和clean verify。
  6. 停止,报告待真实MySQL/Docker验收;后续独立验证EXPLAIN、性能、权限与无写入,再决定Git检查点。

4. 样本与分母

4.1 评分样本

period必填,yyyy-MM严格有效且不得晚于capturedNow所在月。统计一个period,禁止拼接多个period充足样本。响应声明basis=CURRENT_STORED_SNAPSHOTS、capturedAt和period,历史period也表示现在所存数据,不是当时数据。

U为该period至少有一条快照的独立student_user_id集合;N=COUNT(DISTINCT student_user_id)。不包含完全没有快照的学生,响应scope=SCORED_SNAPSHOT_POPULATION。八维固定为EDUCATION/INTERNSHIP/ABILITY/COMPLIANCE/STABILITY/GROWTH/INTEGRITY/ACTIVITY。

对每维d:

  • rowCount为有该维行的候选数;missingRowCount=N-rowCount。
  • 各状态占比=statusCount/N;nullScoreRate为有行且score=null人数/N;missingRowRate为missingRowCount/N,两者不得混淆。
  • 有效分:状态OK或ESTIMATED且分数在[0,100]。validCoverage=validCount/N。
  • OK或ESTIMATED但空分、不可用状态却非空分、未知状态、越界分、未知规则版本单独计数,不修正原表。
  • 统计分布的sampleSize=有效分对应的独立候选数。不能用八维行数当人数。

总览:至少一个有效维度的候选数/N,命名computableSnapshotRatio。缺失不当0。 sampleSize不足30的总体/切片隐藏比例和异常明细,仅返回人数、状态与原因。

4.2 综合分和规则版本

按同一候选、同一period组装全部现存八维,不跨月补齐。 规则版本统一性检查涵盖所有现存维度行,包括PENDING:

  • 恰好同一已知版本:按纯函数对有效维度求平均,2位HALF_UP;至少一个有效维度即可计算。
  • 多版本:MIXED;单独聚合候选数,排除V43综合分分布,不改V32的实际计算。
  • 单一未知版本:UNKNOWN;显示经脱敏的UNKNOWN汇总,不回原始异常字符串,不生成版本比较。
  • 已知版本v0.1/v0.2分别分组。综合分响应标明coherentVersionSubset;不能声称覆盖所有实际排名候选。
  • 维度分布按该行版本独立分组;这与综合分的全候选一致性检查分开。

不开放rulesVersion过滤参数,返回固定两种已知版本及UNKNOWN/MIXED汇总,避免构造自由切片。dimension可选但仅允许八维白名单。

5. 统计公式与输出

有效分排序x1…xn:

  • mean=Σx/n。
  • 分位数采用线性插值:h=(n-1)p,i=floor(h),f=h-i;Qp=x(i+1)+f*(x(min(i+2,n))-x(i+1)),p取0.1/0.25/0.5/0.75/0.9。
  • 总体标准差=sqrt(Σ(x-mean)^2/n),不是样本标准差。中间BigDecimal使用DECIMAL128;最终2位HALF_UP。极小负数舍入误差应有明确容差处理,不吞掉其他错误。
  • min/max来自有效分,空集合全部null。
  • 十个桶:[0,10)、[10,20)…[80,90)、[90,100],9.5归首桶,100归最后桶。
  • 比例=100*分子/分母,最终2位HALF_UP;分母0返回null与不可用原因,不能返回0%。
  • 风险比较使用未四舍五入的精确比例/数值,展示再舍入,避免69.995%显示70.00却触发70%边界。
  • 分桶集中率=max(bucketCount)/n。n>=30时>=70% WARNING、>=85% HIGH,优先HIGH;此为运营提示,不是歧视结论。

confidence:独立人数0–29 INSUFFICIENT_SAMPLE、30–99 LOW_CONFIDENCE、>=100 NORMAL_CONFIDENCE;riskLevel采用NORMAL/WARNING/HIGH/INSUFFICIENT_SAMPLE。UNAVAILABLE是availability字段,不混为风险级别。 不适用的风险字段为null。缺失率增加15/25pp、均值差8/12分、纠正率20/35%仅保留后续提案,本版不启用,因为历史/分母尚不支持。

完整性异常必须分开呈现:inconsistentRecordCount(状态与分值是否为空不一致)、unknownStatusCount(未知状态)和 outOfRangeScoreCount(非空但不在0–100)。任何一个细分异常及其真实集合补集进入同一独立候选人抑制判断,不能用字段缺失或固定0代替。分布桶触发抑制时连同该分布的 sampleSize、统计量和风险一并隐藏。

不使用PSI或KS,不预留名为已计算但填0的假指标。 结构异常只作完整性统计;异常计数存在不等于HIGH,第一版只对上述集中率给风险等级。

6. 申诉流程与变分

只允许已结束的完整业务日窗口from/to(Asia/Shanghai,日期,to不含);默认最近30个完整日,最大90日,from<to<=今天。只查创建时间在[from,to)的申诉,称“该创建队列截至capturedAt的当前状态”;不是历史日终状态。重复请求可因后续审核而变化,响应写明该限制。

  • appealCount=该创建队列申诉记录数;sampleSize=独立申诉人数。
  • 五种状态计数;approvedEverCount=APPROVED+ROLLED_BACK,不能漏掉已回滚的审核通过。
  • 审核通过率=(APPROVED+ROLLED_BACK)/(APPROVED+ROLLED_BACK+REJECTED)。
  • 回滚执行率=ROLLED_BACK/(APPROVED+ROLLED_BACK)。
  • 重复申诉人数=同候选同dimension同period在此队列有>=2条记录的独立候选人数;重复率=该人数/此队列独立申诉人数。明确这是窗口内重复,不是终身重复。
  • actualScoreChangeRate:只关联该队列的action=ROLLBACK历史;按appeal_id预聚合后JOIN,防止状态计数被历史行放大。同一appeal多条ROLLBACK视为该申诉不可比,但不排除同一候选人的其他合法申诉;禁止任取首条或末条。
  • 有且仅一条历史、前后分数均非空且在范围内、前后规则版本相同且已知,才进入comparableRollbackCount。
  • changedCount为after_score!=before_score;actualScoreChangeRate=changedCount/comparableRollbackCount。
  • delta=after-before,展示meanDelta和meanAbsoluteDelta;0差也计入分母。前后null单列不可比,规则不同单列versionChanged,不把规则升级变分称为纠错。
  • 审核通过、回滚执行、分数变化均不能独立证明原评分错误。该指标名称不得写为“纠错准确率”。
  • 每种比率除事件分母外,必须检查其实际分母集合的独立人数>=30;同一候选人的多份合法申诉可贡献多条行,但候选人数只计一次;不足则抑制。
  • 真实申诉率需要可比的受评/曝光人群分母,本版UNAVAILABLE,不用当前快照数除历史申诉数。

第一版不返回申诉dimension/rulesVersion切片,避免样本稀疏和自由组合;后续另行评审。只读取scalar所需字段,不取reason、decision_reason、reviewer_id、actor_user_id、audit details。

7. 接口契约

GET路径 参数 返回核心
/admin/scoring/fairness/overview period 现存快照人数、完整性、综合分可计算比例、版本构成、综合分分布
/admin/scoring/fairness/dimensions period、可选dimension 固定八维及已知版本的覆盖率、状态、分布
/admin/scoring/fairness/drift 无业务参数 availability=UNAVAILABLE、reason=IMMUTABLE_SCORE_HISTORY_NOT_AVAILABLE
/admin/scoring/fairness/ranking-stability 无业务参数 availability=UNAVAILABLE、reason=RANKING_SNAPSHOT_HISTORY_NOT_AVAILABLE
/admin/scoring/fairness/appeals 可选成对from/to 创建队列当前状态、审核/回滚/变分指标

接口沿用ApiResponse,缺失指标为null附reason;样本不足使用正常200,不伪造零值。非法/未知查询参数按400/1001处理(包括jobId/companyId/rulesVersion/granularity),防止“参数被忽略”误解。非法角色403/2002,匿名401。

固定五个path/五个operation,若基线167/205不变则目标172/210。选择保留两个能力不可用接口是为了明确监测能力缺口,不代表历史监测实现。前端需显示“暂无历史数据”,不能显示正常绿色。

8. 隐私与查询限制

所有切片依据独立人数做门槛检查。主群体不足30时隐藏均值、极值、标准差、分位数、桶、比例、状态/版本/异常明细。只回sampleSize、availability和原因。

人数足够也需单元格抑制:桶、状态、版本、异常计数为1–29时隐藏计数及比例;总计与其他公开单元格能反推隐藏单元时做互补抑制(实现可直接抑制整组明细)。为减少小数精度恢复个体信息,包含被抑制桶的分布组一并隐藏其mean/min/max/stddev/quantiles和集中风险。这是保守聚合保护,不宣称差分隐私保证。

版本分布按“版本总体—该版本有效集合—该版本无效补集”独立判定:某个版本的有效集合或补集不足30人时,该版本的Distribution返回SUPPRESSED且不含任何数值统计;其他满足门槛的版本仍可返回可用分布。只要存在版本级受保护单元,versionCounts及其可推算的整体版本计数一并隐藏;若全局总体本身触发抑制,则整个版本分布组隐藏。这样既不暴露99/1版本内补集,也不因一个不安全版本而无差别吞掉安全的70/30版本统计。

相邻窗口/月份集合仍可能差分推断。第一版限制参数组合、仅完整日窗口、复用ADMIN访问限流;不提供候选下钻、导出或自由groupBy。该方案不是任意重复查询下的匿名性证明。

禁止学生/企业及公开入口,禁止所有个人ID、姓名、学校、联系方式、性别/年龄、简历、测评、题目、答案、审计详情和凭据出现在DTO、响应和应用日志。内部COUNT DISTINCT可使用用户ID,但不得从Repository返回到HTTP。

9. 查询实现与性能

每请求同一capturedNow和同一只读事务;不得调用computeAll、getOverview或其他可能重算的既有入口。

  • 快照总体:period过滤,SQL按学生内部去重,生成N;使用固定八维集合LEFT JOIN补missingRow。
  • 维度状态:GROUP BY dimension,rules_version,status;可比得分GROUP BY dimension,rules_version,score形成频数。
  • 分位数/标准差从score频数累积计算,不展开为每候选列表;中间数据仅score+frequency。
  • 综合分可通过SQL按候选period作无身份返回的内部聚合,再输出score频数;必须与既有纯函数对账,不能改变生产排名公式。
  • 申诉:各分母集合COUNT DISTINCT,回滚历史先按appeal预聚合并形成候选级聚合记录;固定次数查询,不逐人/逐维查询,不把重复历史申诉关联的候选人从其他合法申诉中整体剔除。
  • 建议预算总览<=4、维度<=3、申诉<=5、不可用接口0条业务查询;权限查询单列计数。测试在30/300/3000人及8维下验证。
  • 对真实MySQL核查period、created_at扫描/排序/临时表、执行耗时与数据量。最大90日不等于行数上限;建议查询timeout=5秒、已有错误映射返回失败且不截断样本,不伪装无数据。性能不达标先报告,不擅自新增索引/迁移。
  • 现有索引不足的迁移建议必须另行批准;本版绝不修改V1–V40。

10. 测试与验收计划

  1. 0/29/30/99/100独立人数;同一人8维和多period不凑30人。
  2. 缺行与空分分别统计;全无快照人不在分母且scope准确。
  3. 9.5/10/89.99/90/100分桶、分位数手算样例、总体标准差、HALF_UP。
  4. 零分母null;精确70/85%风险边界;69.995%不提前升级。
  5. OK/ESTIMATED空分、PENDING非空、越界、未知状态、未知版本。
  6. MIXED候选排除V43综合分;与生产纯函数同版本结果对账。
  7. 同月upsert后记录变化,drift仍UNAVAILABLE,不伪造之前分数;历史月也声明现存快照。
  8. APPROVED但未回滚、ROLLED_BACK但分不变、真实变分、跨规则/空分、重复回滚历史、JOIN不放大。
  9. 申诉队列状态变更后口径正确;重复申诉去重;事件数>=30但人数<30仍抑制。
  10. 小样本和互补抑制,响应不出现被抑制明细或可反推比例。
  11. ADMIN/匿名/STUDENT/COMPANY;真实HTTP JSON脱敏;不直接返回Entity。
  12. 非法period/未来period/非法dimension/未知参数/超90日/from>=to/仅一个日期参数。
  13. 固定查询次数、无答案/JSON明细表读取;无新业务查询的UNAVAILABLE接口。
  14. candidate_dimension_scores及申诉/审计/风险表前后内容与版本不变(不能只测行数)。
  15. V25/V32/V37/V38/V39/V41/V42回归;OpenAPI静态/运行时172/210对账。
  16. 真实MySQL EXPLAIN、并发只读查询一致性、超时不返回假成功。

四项阻断的针对性补证已完成:正式 Service 在30/300/3000人下业务SQL为2/2/1/0/0;精确60人61份申诉HTTP夹具、五接口逐一完整内容只读检查及综合分舍入/PENDING版本对账均已通过。独立代码复验、真实 MySQL/Docker、运行时 OpenAPI、EXPLAIN 和 30/300/3000 人性能验收均已执行;EXPLAIN 暴露的全表扫描/filesort 作为后续容量风险保留。历史漂移与排名稳定性继续按 UNAVAILABLE 返回。