Skip to content

Commit 12c9b40

Browse files
committed
fix(contracts): 周契约完成度口径统一毫秒(恒零修复,REQ-200)
1 parent 1403368 commit 12c9b40

4 files changed

Lines changed: 26 additions & 15 deletions

File tree

‎app/src-tauri/src/db_contracts.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,20 @@ impl Db {
7272
/// @ai-context: JOIN flashcards 按组过滤(review_logs 无组概念,弹性承诺
7373
/// 日志只记卡);范围 [week_start, week_start+7d) 左闭右开——
7474
/// 下一周周一零点归下周,周界不重叠。
75+
/// @ai-context: 审查修复(2026-08-22):review_logs.reviewed_at 由 review_card
76+
/// 以毫秒写入(now_ms),而 week_start 为秒——边界须乘 1000
77+
/// 转毫秒,否则秒级边界永远小于毫秒级数据(完成度恒零)。
7578
pub fn review_ats_in_week(&self, group_id: i64, week_start: i64) -> Result<Vec<i64>> {
7679
self.with_conn(|conn| {
80+
let start_ms = week_start * 1000;
81+
let end_ms = (week_start + WEEK_SECS) * 1000;
7782
let mut stmt = conn.prepare(
7883
"SELECT l.reviewed_at FROM review_logs l
7984
JOIN flashcards c ON c.id = l.card_id
8085
WHERE c.group_id = ?1 AND l.reviewed_at >= ?2 AND l.reviewed_at < ?3
8186
ORDER BY l.reviewed_at ASC",
8287
)?;
83-
let rows = stmt.query_map(params![group_id, week_start, week_start + WEEK_SECS], |r| r.get(0))?;
88+
let rows = stmt.query_map(params![group_id, start_ms, end_ms], |r| r.get(0))?;
8489
rows.collect::<rusqlite::Result<Vec<_>>>().map_err(Into::into)
8590
})
8691
}

‎app/src-tauri/src/db_contracts_tests.rs‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,23 +87,24 @@ fn missing_contract_returns_none() {
8787

8888
#[test]
8989
fn review_ats_in_week_filters_group_and_range() {
90-
// Arrange:两组各一卡;本周 3 次复习 + 下周 1 次
90+
// Arrange:两组各一卡;本周 3 次复习 + 下周 1 次(reviewed_at 为毫秒口径)
9191
let db = mem_db();
9292
let g1 = make_group(&db, "组一");
9393
let g2 = make_group(&db, "组二");
9494
let card1 = make_card(&db, g1.id, "卡一");
9595
let card2 = make_card(&db, g2.id, "卡二");
9696
let ws = week_start_secs(1_767_225_600);
97-
db.add_review_log(card1.id, "good", ws + 3_600).expect("r1"); // 本周
98-
db.add_review_log(card1.id, "good", ws + 86_400).expect("r2"); // 本周
99-
db.add_review_log(card2.id, "easy", ws + 43_200).expect("r3"); // 本周·他组
100-
db.add_review_log(card1.id, "again", ws + 604_800 + 100).expect("r4"); // 下周
97+
let ws_ms = ws * 1000;
98+
db.add_review_log(card1.id, "good", ws_ms + 3_600_000).expect("r1"); // 本周
99+
db.add_review_log(card1.id, "good", ws_ms + 86_400_000).expect("r2"); // 本周
100+
db.add_review_log(card2.id, "easy", ws_ms + 43_200_000).expect("r3"); // 本周·他组
101+
db.add_review_log(card1.id, "again", ws_ms + 604_800_000 + 100).expect("r4"); // 下周
101102
// Act
102103
let ats = db.review_ats_in_week(g1.id, ws).expect("query");
103-
// Assert:仅组一本周 2 条(下周归下周、他组不计)
104+
// Assert:仅组一本周 2 条(下周归下周、他组不计;返回毫秒原值)
104105
assert_eq!(ats.len(), 2);
105-
assert!(ats.contains(&(ws + 3_600)));
106-
assert!(ats.contains(&(ws + 86_400)));
106+
assert!(ats.contains(&(ws_ms + 3_600_000)));
107+
assert!(ats.contains(&(ws_ms + 86_400_000)));
107108
}
108109

109110
#[test]

‎app/src-tauri/src/week_contract.rs‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
1111
/// 一周秒数(周一零点周界计算用)。
1212
pub const WEEK_SECS: i64 = 7 * 86_400;
13+
/// 一天毫秒数(聚合按天去重口径——review_logs.reviewed_at 为毫秒)。
14+
const DAY_MS: i64 = 86_400_000;
1315
/// 最小可行日徽标阈值:本周完成卡数达到此值即"成立"(N9/N11 低谷生存
1416
/// 的最轻形态——一天状态崩坏不否定整周,但至少 3 次提取才算成立)。
1517
pub const MINIMAL_DAY_CARDS: usize = 3;
@@ -34,14 +36,17 @@ pub struct WeekAggregate {
3436
pub review_cards: usize,
3537
}
3638

37-
/// review 记录(reviewed_at Unix 秒列表)→ 周聚合。
39+
/// review 记录(reviewed_at Unix 毫秒列表)→ 周聚合。
3840
///
39-
/// @ai-context: 天数按 (t / 86400) 去重——同一天多次复习只算一天(日历日口径,
41+
/// @ai-context: 天数按 (t / DAY_MS) 去重——同一天多次复习只算一天(日历日口径,
4042
/// 与 week_start 周界一致);卡数=复习次数(提取动作次数)。
43+
/// @ai-context: 审查修复(2026-08-22):口径统一毫秒——review_logs.reviewed_at
44+
/// 由 review_card 以毫秒写入,此前按秒除 86400 会致同一天内
45+
/// 全部去重为同一"毫秒天"(完成度失真)。
4146
pub fn aggregate_week(reviewed_ats: &[i64]) -> WeekAggregate {
4247
let mut days = std::collections::HashSet::new();
4348
for t in reviewed_ats {
44-
days.insert(t / 86_400);
49+
days.insert(t / DAY_MS);
4550
}
4651
WeekAggregate {
4752
review_days: days.len(),

‎app/src-tauri/src/week_contract_tests.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ fn week_start_cross_year_boundary() {
5050

5151
#[test]
5252
fn aggregate_dedup_same_day() {
53-
// Arrange:同一天 3 次复习 + 另一天 1 次
54-
let day1 = 1_767_225_600 + 3_600; // 周四 01:00
55-
let day2 = 1_767_225_600 + 86_400; // 周五
53+
// Arrange:同一天 3 次复习 + 另一天 1 次(reviewed_at 为毫秒口径)
54+
let day1 = 1_767_225_600_000 + 3_600_000; // 周四 01:00
55+
let day2 = 1_767_225_600_000 + 86_400_000; // 周五
5656
let reviews = [day1, day1 + 100, day1 + 200, day2];
5757
// Act
5858
let agg = aggregate_week(&reviews);

0 commit comments

Comments
 (0)