Commit Graph
5 Commits
Author SHA1 Message Date
tamakyi a3cf99a8da fix(stats): 环比句点明月份与条数,不再写「上月…环比 -75%」
「上月提问量环比 -75.0%」这句要读者做两次翻译:先把「上月」翻成具体月份,
再把「环比」翻成「比上上月」。两个相对词叠在一起,站长本人看到就以为
是「上月同期 vs 本月同期」—— 而它其实比的是两个完整自然月。

  改前:上月提问量环比 -75.0%。
  改后:9 月收到 5,620 条提问,比 8 月的 4,890 条多了 14.9%。

口径一个字没改(仍是上月 vs 上上月,本月没过完不参与),只是把月份和条数
写进句子:读者不需要知道「环比」的定义也能判断这句话在说什么,还能顺手
核对百分比(5620/4890 确实是 +14.9%)。

实现:
- `monthText` 按语言出月份名 —— CJK 一律「9 月」,英文要 "September"
  ("month 9" 不是英语的说法)。不带年份:相比的两个月相邻。
- 一个词条拆成 momUp / momDown / momFlat:方向词塞不进同一个模板,
  四种语言里「多了 / 少了」的位置和词形都不一样。
- `|pct| < 0.05` 走持平,否则会出现「多了 0.0%」。

测试:`TestInsightMoMNamesBothMonths` 锁住「句子里同时有两个月份名和两个条数」,
覆盖上升/下降/持平;`TestMonthText` 锁语言差异。

顺手修预览数据:`zz_preview_test.go` 的 `GrowthMoM` 原来写死 12.3,和它造的
monthly 对不上。以前句子里只有百分比无所谓,现在会连条数一起印出来,
截图就成了「5,620 比 4,890 多了 12.3%」这种假证据,改成从数据算。
2026-10-25 09:27:57 +08:00
tamakyi 0226956feb fix(stats): 回答耗时改用 answered_at,修测试环境实测出的三处问题
在测试环境(box-dev-api)上逐项验算后发现「回答耗时」这一口径是错的。

1. 回答耗时不能用 updated_at - created_at(P0)
   GORM 的 Update 会自动刷新 updated_at,而已读、置顶、公开/私密切换全走 Update。
   实测:一条 10 天前的提问被 SetRead 标记已读后,updated_at 被推到当下(delta=240h)。
   于是「回答耗时」实际测的是「提问到最后一次被碰」:线上那 6 条「超过 7 天」
   和偏低的 24 小时回复占比都是这么来的。
   修法:新增 questions.answered_at(首次回答时间,可空),只在 answer 由空变非空时
   写一次,改写回答不重置;聚合改用 COALESCE(answered_at, updated_at),
   首次加列时用 updated_at 冻结历史一次(Migrator().HasColumn 判断是否刚加列),
   否则历史值会随着后续的已读/置顶继续变大。

2. 环比句式读不通
   中文「上月提问量比前一个月 -69.6%」、英文「...: -69.6% questions.」都是病句。
   改为「上月提问量环比 %s%%」/「Month-over-month change for last month: %s%%」。

3. 脚注说「当日 0 点的快照」不实
   实际是当天第一次访问时算、服务重启也会重算(实测算出时刻 01:11:50,
   基准日却写 2026-10-24,说明它含了 0 点之后的数据)。改成准确描述。

4. 数据库故障时请求会排成 N×30s
   ovRefresh 串行化 + 30 秒超时意味着数据库挂起时第 N 个请求要等 N×30 秒。
   加 30 秒失败冷却:冷却期内直接降级(有旧快照用旧快照),不再碰数据库。
   todayNumbers 失败也盖时间戳,不再每个请求都打两条 COUNT。

测试:新增 internal/db/answered_at_test.go(5 例:只记一次、私密路径也记、
分桶以 answered_at 为准、历史行回退、回填),并做过变异验证 —— 把口径改回
updated_at 后测试立即失败。internal/stats 补 3 例冷却测试。全量构建与测试通过
(internal/storage 的构建失败是 overlay 桩的既有问题)。
2026-10-24 01:45:34 +08:00
tamakyi 0c282914c2 fix(stats): 今日计数不再被每日缓存冻结,日期分组不依赖库会话时区
自查部署结果时发现两处正确性问题,都是本页上线时引入的:

1. 「今日提问 / 今日回答」原本随整页聚合一整天缓存,页面却写着「实时」,
   实际当天第一次访问后数字就冻住到次日。现拆出 TodayCounts 单独取,
   60 秒 TTL、跨日按日期失效;命中整页缓存时返回副本,不原地改缓存指针
   (那会被并发读踩成数据竞争)。

2. 日/月/时分组用 to_char/EXTRACT 直接作用在 timestamptz 上,结果跟着数据库
   会话时区走,而 PG 官方镜像默认 UTC、Go 侧按容器时区(Asia/Shanghai)切今天。
   实测同一条 2026-10-24 01:30+08 的提问,UTC 会话下被算成 10-23 17 点 ——
   日报表整体偏 8 小时、热力图时段错位,且与「今日」对不上。
   现统一经 analyticsLocalize() 加 AT TIME ZONE INTERVAL 换算,偏移取自
   time.Time.Zone()(Go 未设 TZ 时给 "Local",PG 不认该名,故不用 IANA 名)。
   过滤条件仍是裸列,索引照用。

另外两处顺手修:

- PeakDay 用 >= 比较,并列时会漂到最近的同值日期,与 busiestSlot 的取值规则不一致。
- 聚合改用 context.WithoutCancel + 30s 超时:第一个访客在聚合途中关掉页面时,
  排在后面等锁的请求不会各自重跑一遍全表 GROUP BY。

测试:新增 TestTodayNumbersNotFrozen(缓存命中后今日数仍更新、整页聚合当天只算一次)
与 TestTodayCacheExpiresAcrossMidnight;analytics 连真库通过。
2026-10-24 01:20:02 +08:00
tamakyi 64436d9d61 fix(stats): 数据速览用完整星期名,脚注时间改逐行对齐
- 「提问最集中在 三 19 点前后」:洞察复用了热力图列头的短标签
  stats.wd.*(一…日 / Mon…),那是为窄列头设计的单字词条,
  进句子就成了「三」。新增 stats.weekday.1..7 完整名(周一 / Monday /
  月曜日),洞察改用该组,stats.wd.* 只留给列头;补测试断言锁住。
- 页脚入口文案「数据」→「统计数据」(四语言同步)。
- 页面脚注四条时间由「基准日 x · 计算于 y」一行改为两列网格逐行排列,
  标签统一带冒号,下面两段说明保持独立段落。
2026-10-24 01:08:50 +08:00
tamakyi a5441c99fd feat(stats): 新增公开数据页 /stats,入口收敛到页脚
首页底部那张「站点统计」卡片下线,完整数据移到公开只读页 /stats:
今日实时数、概览六格、近 30 天折线与 12 个月柱状、三个占比环、
7×24 时段热力图、提问字数与回答耗时分布。

- 聚合在 internal/db/analytics.go(SUM(CASE WHEN),方言差异收敛在 analyticsDialectFor)
- 结果按自然日缓存,跨日重算一次后全站共用;重算失败降级返回上一份快照
- 图表零前端依赖:服务端算坐标的 SVG + CSS grid,禁用 JS 页面仍完整
- 环比取「上月 vs 上上月」:本月未过完,否则每月 1 号会得出 -95%
- 入口只在页脚「社区规则」与「找站长」之间;route.Home 不再计算统计
- 补 questions(created_at) 索引;启动后异步预热当天快照
- 测试:analytics 连真库验证软删除不计入、分桶下标、DOW 对齐
2026-10-24 00:57:08 +08:00