Skip to content

Commit 5893c8d

Browse files
committed
feat(contracts): 周契约 commands 接线与注册(REQ-200)
1 parent 5f9056f commit 5893c8d

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//! 周契约 commands(v0.11.4 REQ-200;弹性承诺呈现层系统层)。
2+
//!
3+
//! @ai-context: 弹性承诺纪律(v4 §8.2 P31+N10)——契约是用户自设本周目标,
4+
//! 不是打卡 KPI:无 streak、无惩罚、欠账不追。本层只做
5+
//! 参数校验、调用数据层/纯函数、错误映射(AGENTS.md §6)。
6+
//! @ai-context: week_start 由命令层按当前时刻计算(周界口径单一在
7+
//! week_contract.rs 纯函数)——前端不传时间,只传目标值。
8+
9+
use serde::Serialize;
10+
use tauri::State;
11+
12+
use crate::commands::AppState;
13+
use crate::db::unix_seconds;
14+
use crate::types::WeekContract;
15+
use crate::week_contract::{aggregate_week, minimal_day_met, week_start_secs};
16+
17+
/// 承诺天数合法区间(1..=7——一周最多七天,诚实有界)。
18+
const TARGET_DAYS_RANGE: std::ops::RangeInclusive<i64> = 1..=7;
19+
/// 承诺卡数上限(有界防超大 payload;一周 200 次复习已远超合理承诺)。
20+
const TARGET_CARDS_MAX: i64 = 200;
21+
22+
/// 周契约状态读数(承诺 vs 实际;UI 周契约卡数据源)。
23+
#[derive(Debug, Clone, Serialize)]
24+
#[serde(rename_all = "camelCase")]
25+
pub struct WeekContractStatus {
26+
/// 本周契约(None=未立约——UI 显示设定表单)
27+
pub contract: Option<WeekContract>,
28+
/// 周界(周一零点 UTC 秒;UI 转本地日期展示)
29+
pub week_start: i64,
30+
/// 本周实际复习天数(按日去重——断签不清零)
31+
pub actual_days: usize,
32+
/// 本周实际复习卡次数
33+
pub actual_cards: usize,
34+
/// 最小可行日徽标:本周完成 ≥3 卡即成立(N9/N11 低谷生存最轻形态)
35+
pub minimal_day_met: bool,
36+
}
37+
38+
/// 校验组存在性(contracts 外键 + 前端按 id 刷新共用)。
39+
fn require_group(state: &AppState, group_id: i64) -> Result<(), String> {
40+
if group_id <= 0 {
41+
return Err("无效的组 id".to_string());
42+
}
43+
if state.db.get_group(group_id).map_err(|e| e.to_string())?.is_none() {
44+
return Err(format!("笔记组不存在: {}", group_id));
45+
}
46+
Ok(())
47+
}
48+
49+
/// 设定/覆盖本周契约(幂等——同周同组覆盖目标值;改目标即改承诺)。
50+
#[tauri::command]
51+
pub fn upsert_week_contract(
52+
state: State<'_, AppState>,
53+
group_id: i64,
54+
target_days: i64,
55+
target_cards: i64,
56+
) -> Result<WeekContract, String> {
57+
require_group(&state, group_id)?;
58+
if !TARGET_DAYS_RANGE.contains(&target_days) {
59+
return Err(format!("承诺天数需在 1..=7 之间(收到 {})", target_days));
60+
}
61+
if !(1..=TARGET_CARDS_MAX).contains(&target_cards) {
62+
return Err(format!("承诺卡数需在 1..={} 之间(收到 {})", TARGET_CARDS_MAX, target_cards));
63+
}
64+
let ws = week_start_secs(unix_seconds());
65+
state
66+
.db
67+
.upsert_week_contract(group_id, ws, target_days, target_cards)
68+
.map_err(|e| e.to_string())
69+
}
70+
71+
/// 本周契约状态(承诺 vs 实际——review_logs 周聚合纯函数)。
72+
///
73+
/// @ai-context: 取数 → 聚合两步:review_ats_in_week 拿原始时刻列表(数据层),
74+
/// aggregate_week 纯函数聚合成天数/卡数——口径可单测可追溯。
75+
#[tauri::command]
76+
pub fn week_contract_status(state: State<'_, AppState>, group_id: i64) -> Result<WeekContractStatus, String> {
77+
require_group(&state, group_id)?;
78+
let ws = week_start_secs(unix_seconds());
79+
let contract = state.db.get_week_contract(group_id, ws).map_err(|e| e.to_string())?;
80+
let ats = state.db.review_ats_in_week(group_id, ws).map_err(|e| e.to_string())?;
81+
let agg = aggregate_week(&ats);
82+
Ok(WeekContractStatus {
83+
contract,
84+
week_start: ws,
85+
actual_days: agg.review_days,
86+
actual_cards: agg.review_cards,
87+
minimal_day_met: minimal_day_met(agg.review_cards),
88+
})
89+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ mod commands_fragments;
7979
mod commands_flashcards;
8080
// v0.11.3:组结算命令层(计划/执行/核心提炼——防沼泽仪式)
8181
mod commands_settlement;
82+
// v0.11.4:周契约命令层(弹性承诺呈现层——upsert/状态读数)
83+
mod commands_contracts;
8284
// 实时会话链路依赖 Windows 捕获 API(WASAPI/DXGI/COM),非 Windows 平台不编译(TD-027 修复)
8385
#[cfg(target_os = "windows")]
8486
mod commands_live;
@@ -119,6 +121,9 @@ mod db_fragments;
119121
mod db_flashcards;
120122
// v0.11.3:结算记录数据层(settlements 表 + 归档候选判据)
121123
mod db_settlements;
124+
// v0.11.4:周契约数据层(contracts 表 + 按周取数)与周聚合纯函数
125+
mod db_contracts;
126+
mod week_contract;
122127
mod db_artifacts;
123128
// v0.7.7(REQ-183):结构图记录存储——session_structure_images 表 CRUD
124129
mod db_structures;
@@ -364,6 +369,10 @@ pub fn run() {
364369
commands_fragments::capture_fragment,
365370
commands_fragments::list_fragments,
366371
commands_fragments::list_group_fragments,
372+
// v0.11.4(REQ-201):feed 消费闭环——删除/移组/图片 resolve
373+
commands_fragments::delete_fragment,
374+
commands_fragments::update_fragment_group,
375+
commands_fragments::resolve_fragment_image,
367376
// v0.11.2:学习循环统一——组→闪卡生成/到期队列/计数/复习评分/自测
368377
commands_flashcards::generate_group_cards,
369378
commands_flashcards::list_due_cards,
@@ -374,6 +383,9 @@ pub fn run() {
374383
// v0.11.3:组结算机制——计划呈现/执行(用户可见仪式,防沼泽化)
375384
commands_settlement::settlement_plan,
376385
commands_settlement::execute_settlement,
386+
// v0.11.4(REQ-200):周契约——设定/覆盖本周目标 + 状态读数
387+
commands_contracts::upsert_week_contract,
388+
commands_contracts::week_contract_status,
377389
// 会话管理(REQ-010,ADR-004)
378390
commands_session::create_session,
379391
commands_session::finish_session,

0 commit comments

Comments
 (0)