Skip to content

Commit 077b9ac

Browse files
committed
refactor(db_goals): 拆回顾取数域至 db_goals_retro.rs
1 parent d85d9fe commit 077b9ac

2 files changed

Lines changed: 104 additions & 86 deletions

File tree

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

Lines changed: 3 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ mod plan;
2424
/// 目标域:goals 实体 CRUD(含事务建目标)+ goals 行映射。
2525
#[path = "db_goals_goal.rs"]
2626
mod goal;
27+
/// 回顾域:结算快照 / 复习统计 / 成果物清单聚合取数。
28+
#[path = "db_goals_retro.rs"]
29+
mod retro;
2730

2831
/// 三表 DDL + 索引(幂等:CREATE TABLE IF NOT EXISTS;旧库升级自动补表)。
2932
pub(crate) fn init(conn: &Connection) -> Result<()> {
@@ -167,92 +170,6 @@ impl Db {
167170
rows.collect::<rusqlite::Result<Vec<_>>>().map_err(Into::into)
168171
})
169172
}
170-
171-
/// 组结算快照(绑定组维度:名称/历史计数/最近一次——毕业报告与时间线)。
172-
/// @ai-context: group_ids 先于 with_conn 取(闭包内严禁再调 self.*——锁不可重入,
173-
/// 见 db_goals_progress.rs 同款注释与修复先例)。
174-
pub fn goal_settlements_snapshot(
175-
&self,
176-
goal_id: i64,
177-
) -> Result<Vec<crate::goal_retro::GroupSettlementSnapshot>> {
178-
let group_ids = self.list_goal_group_ids(goal_id)?;
179-
self.with_conn(|conn| {
180-
let mut out = Vec::new();
181-
for gid in &group_ids {
182-
let name: Option<String> = conn
183-
.query_row("SELECT name FROM note_groups WHERE id = ?1", params![gid], |r| r.get(0))
184-
.ok();
185-
let (count, last): (i64, Option<i64>) = conn.query_row(
186-
"SELECT COUNT(*), MAX(created_at) FROM settlements WHERE group_id = ?1",
187-
params![gid],
188-
|r| Ok((r.get(0)?, r.get(1)?)),
189-
)?;
190-
out.push(crate::goal_retro::GroupSettlementSnapshot {
191-
group_id: *gid,
192-
group_name: name.unwrap_or_else(|| format!("组#{}", gid)),
193-
settlement_count: count as usize,
194-
last_settled_at: last,
195-
});
196-
}
197-
Ok(out)
198-
})
199-
}
200-
201-
/// 复习统计(毕业报告口径:卡数/复习次数/90 天活跃日/低稳定性卡数——现算)。
202-
pub fn goal_review_stats(&self, goal_id: i64, now_secs: i64) -> Result<crate::goal_retro::ReviewStats> {
203-
let window_ms = (now_secs - 90 * 86_400) * 1000;
204-
self.with_conn(|conn| {
205-
let (cards, logs, days90, weak): (i64, i64, i64, i64) = conn.query_row(
206-
"SELECT
207-
(SELECT COUNT(*) FROM flashcards WHERE group_id IN
208-
(SELECT group_id FROM goal_groups WHERE goal_id = ?1)),
209-
(SELECT COUNT(*) FROM review_logs l JOIN flashcards c ON c.id = l.card_id
210-
WHERE c.group_id IN (SELECT group_id FROM goal_groups WHERE goal_id = ?1)),
211-
(SELECT COUNT(DISTINCT l.reviewed_at / 86400000) FROM review_logs l
212-
JOIN flashcards c ON c.id = l.card_id
213-
WHERE c.group_id IN (SELECT group_id FROM goal_groups WHERE goal_id = ?1)
214-
AND l.reviewed_at >= ?2),
215-
(SELECT COUNT(*) FROM flashcards WHERE group_id IN
216-
(SELECT group_id FROM goal_groups WHERE goal_id = ?1)
217-
AND json_valid(state_json) = 1
218-
AND json_extract(state_json, '$.stability') < ?3)",
219-
params![goal_id, window_ms, crate::goal_progress::LOW_STABILITY_DAYS],
220-
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
221-
)?;
222-
Ok(crate::goal_retro::ReviewStats {
223-
card_total: cards as usize,
224-
review_logs_total: logs as usize,
225-
review_days_90: days90 as usize,
226-
weak_cards: weak as usize,
227-
})
228-
})
229-
}
230-
231-
/// 成果物清单(组/笔记/卡/概念——「我留下了什么」;概念经体系引用跨链)。
232-
pub fn goal_artifacts(&self, goal_id: i64) -> Result<crate::goal_retro::ArtifactsInventory> {
233-
self.with_conn(|conn| {
234-
let (groups, notes, cards, concepts): (i64, i64, i64, i64) = conn.query_row(
235-
"SELECT
236-
(SELECT COUNT(*) FROM goal_groups WHERE goal_id = ?1),
237-
(SELECT COUNT(*) FROM notes WHERE group_id IN
238-
(SELECT group_id FROM goal_groups WHERE goal_id = ?1)),
239-
(SELECT COUNT(*) FROM flashcards WHERE group_id IN
240-
(SELECT group_id FROM goal_groups WHERE goal_id = ?1)),
241-
(SELECT COUNT(DISTINCT l.concept_id) FROM knowledge_links l
242-
WHERE l.target_type = 'note_group'
243-
AND l.target_id IN (SELECT group_id FROM goal_groups WHERE goal_id = ?1)
244-
AND l.concept_id IS NOT NULL)",
245-
params![goal_id],
246-
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
247-
)?;
248-
Ok(crate::goal_retro::ArtifactsInventory {
249-
groups: groups as usize,
250-
notes: notes as usize,
251-
cards: cards as usize,
252-
concepts: concepts as usize,
253-
})
254-
})
255-
}
256173
}
257174

258175
#[cfg(test)]
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//! 回顾/毕业取数域:结算快照 · 复习统计 · 成果物清单(只读聚合)。
2+
//!
3+
//! @ai-context: 由 db_goals.rs 按域拆出(AGENTS.md §3 单文件 ≤300 行)。本文件全是
4+
//! 只读聚合;写路径在 db_goals_goal.rs / db_goals_binding.rs。
5+
//! @ai-context: goal_settlements_snapshot 的 group_ids 必须**在 with_conn 之前**
6+
//! 预取——list_goal_group_ids 自身也取锁,搬进闭包即运行期自锁死。
7+
//! @ai-context: 90 天窗口在本文件与 db_goals_progress.rs 各自硬编码(既有口径);
8+
//! 组名读取失败降级为 `组#<id>` 属既有防御性设计,不是空 catch。
9+
10+
use rusqlite::params;
11+
12+
use crate::db::Db;
13+
use crate::error::Result;
14+
15+
impl Db {
16+
/// 组结算快照(绑定组维度:名称/历史计数/最近一次——毕业报告与时间线)。
17+
/// @ai-context: group_ids 先于 with_conn 取(闭包内严禁再调 self.*——锁不可重入,
18+
/// 见 db_goals_progress.rs 同款注释与修复先例)。
19+
pub fn goal_settlements_snapshot(
20+
&self,
21+
goal_id: i64,
22+
) -> Result<Vec<crate::goal_retro::GroupSettlementSnapshot>> {
23+
let group_ids = self.list_goal_group_ids(goal_id)?;
24+
self.with_conn(|conn| {
25+
let mut out = Vec::new();
26+
for gid in &group_ids {
27+
let name: Option<String> = conn
28+
.query_row("SELECT name FROM note_groups WHERE id = ?1", params![gid], |r| r.get(0))
29+
.ok();
30+
let (count, last): (i64, Option<i64>) = conn.query_row(
31+
"SELECT COUNT(*), MAX(created_at) FROM settlements WHERE group_id = ?1",
32+
params![gid],
33+
|r| Ok((r.get(0)?, r.get(1)?)),
34+
)?;
35+
out.push(crate::goal_retro::GroupSettlementSnapshot {
36+
group_id: *gid,
37+
group_name: name.unwrap_or_else(|| format!("组#{}", gid)),
38+
settlement_count: count as usize,
39+
last_settled_at: last,
40+
});
41+
}
42+
Ok(out)
43+
})
44+
}
45+
46+
/// 复习统计(毕业报告口径:卡数/复习次数/90 天活跃日/低稳定性卡数——现算)。
47+
pub fn goal_review_stats(&self, goal_id: i64, now_secs: i64) -> Result<crate::goal_retro::ReviewStats> {
48+
let window_ms = (now_secs - 90 * 86_400) * 1000;
49+
self.with_conn(|conn| {
50+
let (cards, logs, days90, weak): (i64, i64, i64, i64) = conn.query_row(
51+
"SELECT
52+
(SELECT COUNT(*) FROM flashcards WHERE group_id IN
53+
(SELECT group_id FROM goal_groups WHERE goal_id = ?1)),
54+
(SELECT COUNT(*) FROM review_logs l JOIN flashcards c ON c.id = l.card_id
55+
WHERE c.group_id IN (SELECT group_id FROM goal_groups WHERE goal_id = ?1)),
56+
(SELECT COUNT(DISTINCT l.reviewed_at / 86400000) FROM review_logs l
57+
JOIN flashcards c ON c.id = l.card_id
58+
WHERE c.group_id IN (SELECT group_id FROM goal_groups WHERE goal_id = ?1)
59+
AND l.reviewed_at >= ?2),
60+
(SELECT COUNT(*) FROM flashcards WHERE group_id IN
61+
(SELECT group_id FROM goal_groups WHERE goal_id = ?1)
62+
AND json_valid(state_json) = 1
63+
AND json_extract(state_json, '$.stability') < ?3)",
64+
params![goal_id, window_ms, crate::goal_progress::LOW_STABILITY_DAYS],
65+
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
66+
)?;
67+
Ok(crate::goal_retro::ReviewStats {
68+
card_total: cards as usize,
69+
review_logs_total: logs as usize,
70+
review_days_90: days90 as usize,
71+
weak_cards: weak as usize,
72+
})
73+
})
74+
}
75+
76+
/// 成果物清单(组/笔记/卡/概念——「我留下了什么」;概念经体系引用跨链)。
77+
pub fn goal_artifacts(&self, goal_id: i64) -> Result<crate::goal_retro::ArtifactsInventory> {
78+
self.with_conn(|conn| {
79+
let (groups, notes, cards, concepts): (i64, i64, i64, i64) = conn.query_row(
80+
"SELECT
81+
(SELECT COUNT(*) FROM goal_groups WHERE goal_id = ?1),
82+
(SELECT COUNT(*) FROM notes WHERE group_id IN
83+
(SELECT group_id FROM goal_groups WHERE goal_id = ?1)),
84+
(SELECT COUNT(*) FROM flashcards WHERE group_id IN
85+
(SELECT group_id FROM goal_groups WHERE goal_id = ?1)),
86+
(SELECT COUNT(DISTINCT l.concept_id) FROM knowledge_links l
87+
WHERE l.target_type = 'note_group'
88+
AND l.target_id IN (SELECT group_id FROM goal_groups WHERE goal_id = ?1)
89+
AND l.concept_id IS NOT NULL)",
90+
params![goal_id],
91+
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
92+
)?;
93+
Ok(crate::goal_retro::ArtifactsInventory {
94+
groups: groups as usize,
95+
notes: notes as usize,
96+
cards: cards as usize,
97+
concepts: concepts as usize,
98+
})
99+
})
100+
}
101+
}

0 commit comments

Comments
 (0)