# 分词、异步计算、发布与最终读取专项

当前旧P1作答区字段错误已修复，总开关绕过已修复。仍有跨内容位旧任务删除新行、数字混词、IPA残留。新增异步化使“首次发布早于JY分词完成→PROD缺骨架→每日也跳过”成为普通操作时序；同时正文更新到计算完成期间旧NORMAL行仍可被下发。详细分级与行号见segment-findings.json，均不等于已取得线上故障。

## 证据范围

HEAD 0baa9ebe8b7f9ecc46f675acbf1f56ac65378957，历史 e9f2fb1b58a2a0292e5a95a9cbeb6fae7f956682，master 873c4f2d7c63a947216ae92bdc393b73408b6b6e。master树不含英语分词包，故属于master上新增能力；历史版本仅用于演进对照，不作为验收依据。当前Spec798行按1–280、281–560、561–798连续读取，Plan04/08/09/10及prd-segment-read.json读取。Plan04/10完整输出可见，初次批量Plan输出截断后已单独重读08/09。旧findings仅作为待核线索。

未跑Maven、未调用真实业务写入、未创建CodeGraph。原EnglishWords与TextNormalizer加本地commons-lang3依赖隔离javac执行：`cat2dog 3look up4 => [cat2dog][3look][up4]`；`1972 low-temperature => [low-temperature]`。无DB/MQ/客户端联调证据。

## 业务入口与现行正常机制

业务操作是LABS同步四类英语题至课程平台，随后课程平台发布题目，下游调用标准题RPC取segments，再按正数词条ID查详情。后台直接改题不挂即时分词；每日/高权限手动全量用于重切历史题。

1. questionBackFill在表单全部落库后hookSave（QuestionBizServiceImpl:5784）。总开关关时不发布事件；事务回滚无AFTER_COMMIT消息，提交后仅投ENGLISH_SEGMENT_SYNC_COMPUTE_EVENT，不抽正文、不等百度/TTS。无事务fallbackExecution=true立即投递。
2. worker消息只带questionId/operatorId/requestTime；不携带正文与revision。消费者重新读教研库当前题，再forceVersionCheck(TEACHING)，在显式DEFAULT数据源执行segmentAndSave。旧消息晚到通常会算最新题，不能把消息requestTime当版本门。消费者吞执行异常不重投；未命中集合非空才投补录领域事件。JSON解析在try外，非法JSON可能进入框架重试，和业务计算失败不同。
3. 抽取先formData，若为空再output，均无则历史QuestionContent。选择题取questionInfo.material.materialContent[].text；填空/输入/完形取questionInfo.answerArea.textAnswerAreaContent（对象）；数组则逐项classifyName。富文本优先unity再unityText。历史question_info.text归type1，text_fill_area.area_text归type2，历史可能双内容位。
4. Jsoup去formula/img/script/style并留空格，document.text保留正文，IPA Unicode字符删为空格。按内容位TreeMap、同位块原顺序处理，各块独立扫描不跨块拼短语；正文指纹按位排序后用换行拼接。匹配先在token.start尝试最长短语，命中推进至短语结尾，否则整词；重复项逐次保留。每内容位单词去重后批查，未命中ID=-1，超99字符不登记补录。
5. 实际计算入口先查execution.enabled。非force剪枝条件是现存内容位集合相同、每行NORMAL、MD5相同，**当前不再要求revision相同**。命中直接库存回显、未命中集合为空，不查新词典不重投补录。force绕过剪枝。readySnapshot为空则不写新NORMAL，已有不对应正文/修订的NORMAL标FAILED；索引从未就绪且原无行不建失败占位。
6. 每内容位保存text/type快照，ID不入库；SQL唯一键(question_id,content_type)，revision>=stored才能覆盖，成功清retry/error，失败登记也用同revision门。抽取失败按题型推内容位兜底，错误记录截断990字符；异常继续抛给消费者/全量单题隔离。
7. 当前发布**不搬JY segments**（全仓无getQuestionSegmentDoList消费）。published完成公共后置后ensureProdSegmentSkeletons，只在JY全部行NORMAL时为PROD缺少的类型建INIT空骨架，主键decrement映射、revision取PROD题目。现存未删行一字段不改；骨架临时questionKey仍用JY值，成功重算saveResult以PROD题key改正。随后发发布重算MQ；消费者forceVersionCheck(PROD)，refreshOneInProd读正式题并允许MD5剪枝。PROD无行一律跳过，不补建。
8. 全量refreshAll先总开关再执行闸门，tryLock(0)+watchdog共用锁，先TEACHING再PROD，按题目候选id游标500页串行，每DAO动作显式切库；每题force=true，单题失败计数继续。PROD缺行日志跳过。末尾清题目不存在/已删除的孤儿分词，保留白名单外但仍存在题的行；软删除当前按ID更新。手动MQ与定时同执行体，消费者锁冲突丢弃不重投。
9. RPC装配读取库存，key去重并CoreService每100分批。按题key归组、contentType升序，任一行不是NORMAL或JSON坏整题segments=null。按当前线程数据库选TEACHING/PROD，忽略库存历史ID，取同一短语快照；短语精确失配才对该块局部最长优先重扫，单词集合批查一次。词典异常不阻断题目而占位ID；不在读路径写回。
10. C端契约：只在允许容器按归一化文本顺序定位；同词N次消费N项，负数只消费位置不高亮不查详情；正数按type+ID去重查详情，短语variantId仅定位返回变形。此仓不含C端实现，本次仅证明服务端序列，不声称客户端定位已验。

## 历史与当前对照

| 维度 | master | 历史e9f2 | 当前HEAD与可见结果 |
|---|---|---|---|
| LABS同步 | 无分词新增模块 | AFTER_COMMIT线程直接计算 | AFTER_COMMIT只投MQ，接口不再承担计算耗时；接受与可用分离 |
| 作答区 | 无新增模块 | fill/cloze当数组，input读判定答案 | 正确读对象正文/数组分类；旧P1关闭 |
| 分词库存 | 无 | text/type与revision | 保持；未命中关联表已删，只留内存集合 |
| 重复同步 | 无 | 同正文同revision才剪枝 | 同正文NORMAL即剪枝；非正文更新时间不再触发计算 |
| 发布 | 无 | 搬运全部NORMAL快照 | 不搬，按JY成功行补PROD INIT骨架后异步算；首次失败/无行恢复须再次发布 |
| 全量 | 无 | 先JY后PROD；总开关遗漏 | 保持串行force与缺行门，补总开关及执行闸门 |
| 词典版本 | 无 | 本地TTL | 各worker单题入口和全库轮首forceVersionCheck；故障lastKnown策略由缓存专项讨论 |
| 下发 | 无 | 库存读时ID解析 | 保持；短语局部重扫单快照，正文一致性仍依赖写侧状态 |

Spec6/9.4和Plan10仍描述“发布搬行后重算”，与当前骨架机制相反；不能据此把PROD旧序列/无行都解释成当日自动收敛。Spec9.2仍写md5+修订剪枝，当前canSkip只比较MD5；应按现实现记录并将材料差异交主报告裁决。

## 状态与乱序、失败恢复

- 同内容位严格旧revision：isStale/SQL拒绝，不投旧未命中；这条正常防线成立。
- 同revision：SQL允许覆盖，未校验当前题目正文；题目update_time精度与实际并发须运行验证，不能把>=视为全部乱序已解决。
- 跨内容位：行门无法保护题级集合删除，旧type1可清掉新type2，见SEG03。
- 首次消息丢失：JY每日可补算；PROD若发布时无骨架每日也不补，见SEG01。
- 补录发送/接收失败：全量仅分词，不投补录；同MD5重复同步剪枝也不投。恢复靠正文变化后再次同步、别题遇同词、手工建词发布，不能承诺“下次保存/每日自愈补录”。
- 已算题正文变化但消息未执行：旧NORMAL没有入队失效步骤，下发仍可信，见SEG02。词典读时ID解析不能修正文序列。
- 所有内容消失：textsByType空，结果空且accepted=true，清旧内容位，最终无行/null；此为合理删除路径，但旧任务空集合也缺题级revision守卫，能删除更新成功行（SEG03同一机制）。

## 可执行测试矩阵（测试设计，未执行）

| 编号 | 构造与操作 | 可观察通过断言 |
|---|---|---|
| S01 | 选择题材料含She agrees with my opinion；词库有agree with原形及agrees with变形，LABS同步后等worker | 一type1 NORMAL，正文序she/phrase/my/opinion，库存无ID，下发PROD按实际词典ID；不是接口受理true即通过 |
| S02 | 填空/输入/完形各用真实answerArea对象，正文visible cat，determineInfo存hidden dog | 均只type2且只visible/cat，无ClassCastException，无hidden/dog；分类组件数组classifyName另验 |
| S03 | 两材料块分别look与up，库有look up；块内再放look up；同词重复 | 跨块不合并短语、块内合并，重复项数和顺序与正文一致 |
| S04 | 富文本加粗跨节点、中文、公式图片、emoji、1972、low-temperature、it's、cat2dog、ˈfəʊnəm | 标签属性/公式图片不入词；数字夹英文和IPA碎片按SEG04/05暴露差异，不能把现实现当验收正确 |
| S05 | 同步事务写表单后故意回滚；另一次正常提交 | 回滚无compute MQ/分词写；提交仅发一次，worker异步后写；补录另观察，不等待外部服务 |
| S06 | 成功题只改标题后重复同步，正文MD5相同；再真正改正文同步 | 前者行不更新且无补录；后者新MD5与新序列成功。隔离暂停worker读取新题RPC，复现SEG02旧序列窗口 |
| S07 | 首次同步后阻塞compute消费者，立即发布，随后释放compute，再跑每日全量 | 应验期望PROD最终可用；当前无骨架持续null，JY成功仍不补，重复发布恢复；覆盖SEG01 |
| S08 | JY草稿有look up to，PROD仅look up，先完成JY再发布 | INIT骨架不下发，发布worker后PROD序列按PROD词典look up+to；无JY ID透传 |
| S09 | 旧任务rev1 type1在保存前阻塞，新rev2 type2完成后恢复；再试旧空文本任务 | 新type2应保留且旧任务不删；当前SEG03可违反。相同type稳定对照旧rev写必须拒绝 |
| S10 | 索引冷启动失败，分别有旧同正文行、旧不同正文行、无行 | 同正文可保持；不同正文FAILED/null；无行不凭空NORMAL；索引恢复每日/同步可计算 |
| S11 | 全量JY候选含待发布、PROD含已发布，双库词典有差异；制造一题失败 | JY先PROD后，各库结果与本库词典对应，失败仅该题且后续题继续，retry/error可定位 |
| S12 | 并行手动与定时；再删一道题和保留白名单外存活题 | 仅一轮锁持有执行，孤儿删而存活题行不误删；锁冲突不等于完成所有题 |
| S13 | 总开关false分别同步/发布/全量/RPC；execution=false同样操作 | 总开关停分词且不下发；execution停计算而既有库存仍可下发；词典服务故障时题目主体仍返回 |
| S14 | 存量快照含历史ID、同词重复、-1词/短语、坏JSON、FAILED内容位 | 旧ID覆盖为本库ID；重复保留；坏JSON和任一FAILED整题null；详情调用按正数唯一ID去重，负数不查 |
| S15 | 先发布题后发布单词；新增跨块短语后只调下发，再跑全量 | 单词按缓存失效策略下次解析点亮；新跨词短语不会由读时自动合并，必须全量才改变块；短语失配可局部拆开 |

本矩阵为可评审测试设计；真实环境、调用方/C端容器、MQ消费控制与DB只读观察授权就绪后才能执行。没有通过Maven或生产链路验证的表述。
