fix(data): compute the OMM epoch day fraction numerically, not by string surgery
排查页面顺序问题时顺带审计发现: OMM/CSV 历元在子夜后约 86 秒内会被解析成
完全错误的值, 且不抛异常 —— 静默的错误数据。
## 根因
DataParser.parseCSV 用字符串拼接构造历元:
val frac = ((hour + min + sec + ms) / 86400000.0).toString().substring(1)
val epoch = "${year.substring(2)}$day$frac".toDouble()
substring(1) 的意图是切掉 "0.123" 的前导 0。但 Double.toString() 在数值
小于 1e-3 时切换为科学计数法, 于是被切掉的是【有效数字】, 剩下的指数后缀
让整个字符串重新变成一个合法但语义完全错误的 double。
Kotlin 侧实测(单测失败信息):
00:00:01.000 期望 25001.000011574073 实得 0.2500115740740741
00:01:00.000 期望 25001.000694444443 实得 2.5001944444444444
与 JDK 侧独立验证逐位一致。00:01:26.4 之后 frac >= 0.001, 不再用科学计数法,
所以这个 bug 只在每天前 86.4 秒的历元上出现(约占 0.1%), Celestrak OMM 数据
里整分历元并不罕见。
## 影响
runCatching 抓不到(没有异常), 该卫星的 juliandDateOfEpoch 会推出 year=2000
day≈0, tsince 偏差约 26 年 —— 方位/仰角/过境预报彻底失效, 不是精度下降。
且用户无从察觉。
## 修复
改为数值相加, 不经过字符串:
val dayFraction = (hour + min + sec + ms) / 86400000.0
val epoch = "${year.substring(2)}$day".toDouble() + dayFraction
"25001".toDouble() + 0.0000115 = 25001.0000115, 无科学计数法风险。
## 验证
DataParserTest 新增 5 个历元回归测试(子夜整点/子夜后 1 秒/子夜后 1 分钟/
正午/当日最后一毫秒)。先确认前两个在旧实现下失败(failures=2), 修复后:
- DataParserTest 24 个测试全绿(原有 19 个无回归)
- :core:domain:test 全量 105 个测试 0 失败 0 错误