- 项目名称:Pace
- 产品定位:原生 Android + 原生 iOS 的每日预算记账 App。
- 核心价值:让用户随时知道今天还可以花多少钱,并用尽量少的操作记录账单。
- 产品口号:Know what you can spend today.
- 隐私定位:账本数据只保存在用户设备本地;用户主动导出后,导出文件由用户自行管理。
- AI 定位:AI 是可选的输入和分析辅助,不是账本事实来源,也不能绕过用户确认直接写入账本。
- 主仓库:https://github.com/CarlOpenLab/pace.git
- 当前分支:
main - 协作方式(2026-10-26 改:不再走 Pull Request):
- 有仓库写权限时,做完的改动直接提交并推送
main,不另开分支、不提交 Pull Request。 - 没有仓库写权限时,Fork
CarlOpenLab/pace,在 Fork 中开发,再向上游main推送。 - 不绕 PR 不等于不用验:谁推送谁负责把该截的图、该量的数放进频道,出了问题是当场能说清哪一步漏的。半成品不推。
- 发布:版本号在三处一起改(
iosApp/project.yml的MARKETING_VERSION、README 徽标、CHANGELOG),打完 tag 再推。
- 有仓库写权限时,做完的改动直接提交并推送
- 当前仓库状态:现有代码是 React + TypeScript + Vite 的离线优先 Web/PWA 原型,包含本地 IndexedDB、账单导入、分类和统计能力。目标产品需要逐步演进为原生 Android 与原生 iOS App;现有 Web 原型可作为业务规则、数据模型和交互验证参考,不能视为最终移动端实现。
- Kotlin + Jetpack Compose。
- Room/SQLite 保存本地账本。
- Android Keystore 保护本地加密密钥。
- WorkManager 处理可延迟的本地维护任务。
- Android Notifications、App Widget、快捷方式和系统分享入口使用原生能力。
- Swift + SwiftUI。
- SwiftData/Core Data/SQLite 保存本地账本,最终只选一种实现。
- Keychain 与系统 Data Protection 保护敏感数据。
- BackgroundTasks 处理可延迟的本地维护任务。
- WidgetKit、App Intents、Share Extension 使用原生能力。
两个客户端不共享 UI 运行时,但必须共享以下产品契约:
- 数据字段和枚举定义。
- 金额、日期、时区和货币规则。
- 预算计算规则。
- AI 草稿输入输出格式。
- 导出 JSON 格式及版本迁移规则。
- 重复账单检测规则。
- 错误状态和验收案例。
第一版采用固定每日预算:
今日剩余 = 今日预算 - 今日计入预算的净支出
规则:
- 当日结余默认不结转到下一天。
- 当日超支默认不自动扣减明日预算。
- 修改默认预算从指定生效日开始,不重写历史预算。
- 每笔支出可选择是否计入每日预算。
- 不计入每日预算的支出仍计入总支出统计。
- 首页必须展示预算、已花、剩余/超支及计算说明。
- 预算引擎不依赖网络和 AI。
后续再评估动态周期预算:
周期剩余预算 / 周期剩余天数(含今天)
固定预算和动态预算必须作为明确的模式选择,不能同时展示含义不清的多个“剩余”数字。
第一版支持:
- 支出、收入、退款、转账类型。
- 金额、货币、分类、发生时间、归属日期、备注。
- 是否计入每日预算。
- 新增、编辑、删除、撤销。
- 补记历史账单。
- 待分类账单。
- 分类管理和最近使用分类。
- 退款关联原账单,避免重复计算。
- 转账和信用卡还款与消费分开,避免重复计入。
金额在内部使用最小货币单位保存,例如分;展示层再格式化金额,避免浮点误差。
第一版支持:
- 今日账单。
- 按日期查看账单。
- 本周和本月总支出。
- 预算内支出与全部支出分开统计。
- 分类排行。
- 每日预算和实际支出对比。
- 超支记录。
统计数字由本地确定性程序计算。AI 只能解释已经计算出的结果,不能自行替代账本查询和金额计算。
- 不建立云端账本数据库。
- 不要求注册账号才能记账。
- 断网时完整支持新增、编辑、删除、预算计算和统计。
- 正式账本只以设备本地数据库为事实来源。
- 服务端(如未来存在 AI 网关)不得持久化完整账本。
- 日志不得写入完整账单、备注、票据内容或 API 密钥。
- App 删除、设备损坏或未导出时,用户需要承担本地数据丢失风险;产品必须在设置和导出流程中明确说明。
第一版必须支持:
- CSV:便于 Excel、Numbers 等工具查看和分析。
- JSON:包含完整账单、分类、预算规则、历史预算版本、货币、时区、退款关联和格式版本号,用于恢复。
导出流程:
- 用户主动选择导出格式。
- App 在本地生成文件。
- 通过 Android 文件选择器/分享面板或 iOS Files/分享面板交给用户保存。
- App 清理临时文件,不自动上传。
必须提示:用户若将文件保存到 iCloud Drive、Google Drive、微信或其他服务,导出副本将由对应服务管理,不再属于 App 的本地存储范围。
虽然产品定位是本地账本,但 JSON 导入必须纳入第一版,否则导出不能承担真正的备份作用:
- 支持从 JSON 或加密备份恢复。
- 导入前预览账单数量、日期范围和预算设置。
- 先校验整个文件,校验通过后再写入,不能因部分失败破坏现有账本。
- 通过稳定 ID 和指纹检测重复记录。
- 默认提供“合并到现有账本”和“替换当前账本”两个明确操作;替换前必须再次确认。
- CSV 第一版主要用于查看和交换,不承诺能恢复所有预算关联信息。
- 后续增加用户密码保护的加密备份文件。
示例输入:
昨天午饭 28 元,晚上打车 16 元。
AI 应返回结构化草稿,至少包含:金额、类型、日期、分类、备注、是否计入预算、需要补充的信息。
流程固定为:
输入 → 解析 → 本地业务校验 → 待确认草稿 → 用户确认 → 本地正式入账
规则:
- AI 不能直接新增、修改或删除正式账单。
- 金额不明确、日期不确定或个人分摊不清楚时,必须要求用户补充。
- 用户可逐笔修改草稿。
- AI 失败、超时或无网络时必须回退到手动记账。
- 重复点击确认不能产生重复账单。
提供两个明确模式:
- 本地隐私模式:账本内容不离开设备;使用手动记账,后续可评估设备端模型。
- AI 辅助模式:用户主动开启后,仅发送完成当前任务所需的输入或用户选定内容;正式账本仍只保存在本地。
默认采用本地隐私模式。开启云端 AI 前必须说明:当前输入可能离开设备,且第三方模型服务的处理规则由其服务条款决定。
现有 Web 原型中的 AI 分类接口需要按此规则重构:不能把完整账单历史默认发送给外部接口,也不能把用户 API Key 作为未来原生 App 的默认方案长期保存在客户端。
按优先级推进:
- 一句话记账。
- 分类建议。
- 用户主动上传的截图/小票识别。
- 自然语言查询本地统计。
- 基于程序统计结果的消费复盘。
- 预算调整建议,仅生成方案,不自动修改设置。
两个平台核心页面保持同一业务信息和计算结果,但交互遵循各自平台规范:
- 首页:今日剩余/超支、预算、已花、记一笔、今日账单。
- 记账页:金额、分类、日期、备注、预算归属;支持 AI 输入。
- 账单页:按日期浏览、搜索、筛选、编辑和删除。
- 统计页:周/月、分类、预算对比。
- 预算设置:额度、模式、生效日期、预算归属默认值。
- 分类设置:新增、编辑、排序、默认归属。
- 导出与恢复:CSV、JSON、导入预览、数据删除。
- 隐私与 AI 设置:AI 模式、授权说明、日志与数据清理说明。
平台增强能力:
- Android:桌面小组件、快捷方式、通知栏快速记账、系统分享入口。
- iOS:主屏幕/锁屏小组件、Siri/App Intents、Share Extension、通知和后台刷新。
产出:
- 预算规则文档。
- 数据模型和枚举。
- 跨端 JSON 导出格式。
- AI 草稿格式。
- Android/iOS 页面流程和设计稿。
- 边界案例清单。
验收:任何账单变更都能说明如何影响预算、统计和导出文件。
- Android 与 iOS 原生工程。
- 本地数据库和数据迁移。
- 预算引擎。
- 手动记账。
- 编辑、删除、撤销。
- 首页、账单页、统计页。
- 断网可用。
验收:重启 App、补记、编辑、删除、退款和跨日期后,两个平台的计算结果一致。
- CSV 导出。
- 完整 JSON 导出。
- JSON 导入预览和恢复。
- 重复检测和事务性写入。
- 数据删除和清空流程。
验收:导出后在全新安装环境恢复,账单、预算和统计结果保持一致。
- 文本解析。
- 多笔草稿。
- 字段校验和追问。
- 用户确认入账。
- AI 关闭和失败回退。
- 隐私提示。
验收:AI 只生成草稿;不明确的金额/日期不会被擅自写入;网络失败不影响手动记账。
- Android/iOS 小组件。
- 系统分享入口。
- 快捷记账。
- 本地提醒。
- 深色模式和无障碍适配。
- 真机测试和弱网测试。
- 时区、午夜、跨年和货币测试。
- 导出文件兼容性测试。
- 隐私政策、权限说明和数据删除说明。
- App Store 与 Google Play 发布资料。
- 云端账本同步。
- 银行、微信、支付宝自动读取流水。
- 自动读取短信。
- 家庭共享和多人账本。
- 投资、借贷、资产负债管理。
- 多币种汇率核算。
- AI 自动修改或删除账单。
- 复杂的周期预算和自动省钱承诺。
- 飞行模式下可以完整手动记账和查看今日剩余。
- Android 与 iOS 对同一组案例得出相同预算结果。
- 账本不会因 AI 不可用而无法使用。
- CSV 导出可被常用表格工具打开。
- JSON 导出可以在新安装环境恢复完整账本。
- 导入失败不会破坏已有数据。
- App 不保存云端账本副本。
- AI 默认关闭,开启前有明确的数据流向说明。
- 用户确认前的 AI 结果永远只是草稿。
- 删除、导出和恢复操作都有清晰的用户确认与错误提示。
- 以本文件冻结 Pace 的产品边界和本地-only 原则。
- 从现有 Web 原型提取可复用的分类、解析、去重和统计业务规则。
- 先实现原生 Android/iOS 的本地账本闭环,不先做云服务。
- 再实现 CSV/JSON 导出和恢复。
- 最后接入可选 AI 草稿流程与平台专属能力。
Pace 采用“共享业务核心 + 双端原生 UI”的方案,满足 Android 和 iOS 都是原生客户端的要求,同时避免预算计算、导出恢复和日期规则在两端重复实现后产生差异。
- Android:Kotlin + Jetpack Compose。
- iOS:Swift + SwiftUI。
- Android 状态与异步:ViewModel、Kotlin Coroutines、Flow、Navigation Compose;依赖注入优先使用 Hilt。
- iOS 状态与异步:Observation/ObservableObject、Swift Concurrency、NavigationStack。
- 本地数据库:SQLDelight + SQLite,Android 和 iOS 共用数据库结构与迁移版本;不同时引入 Room、SwiftData 和 Core Data。
- 序列化:Kotlin Serialization,负责共享数据模型、JSON 导出格式和 AI 草稿协议。
- 系统能力:Android 使用 Notifications、WorkManager、App Widget、快捷方式和 Storage Access Framework;iOS 使用 UserNotifications、BackgroundTasks、WidgetKit、App Intents、Share Extension 和 Files/Share Sheet。
使用 Kotlin Multiplatform 共享以下模块,UI 和平台系统能力不共享:
shared/
├── money/ # 最小货币单位、金额计算与格式化规则
├── budget/ # 每日预算和后续动态预算
├── transaction/ # 账单模型、类型、退款和转账关联
├── statistics/ # 本地确定性统计
├── export/ # CSV/JSON 导出与版本迁移
├── import/ # 导入校验、预览、去重和事务性恢复
├── date-time/ # 时区、归属日和午夜边界
└── validation/ # 账单和预算业务校验
共享核心必须提供稳定、容易被 Swift 调用的 API;避免把复杂 Kotlin 内部实现类型直接暴露给 iOS。Android 与 iOS 对同一组预算案例必须得到相同结果。
- 金额禁止使用
Double作为账本计算类型。 - 账单金额保存为最小货币单位的
Long,同时保存currency。 - 账单至少保存
occurredAt、localDate、timezone、createdAt和updatedAt。 - 预算按用户时区的
localDate计算,不能依赖服务器时间。 - 导入、导出和 AI 解析都必须经过共享核心校验。
- Android 使用 Android Keystore 保护本地加密密钥。
- iOS 使用 Keychain 和系统 Data Protection 保护敏感数据。
- 正式账本只写入设备本地数据库,不建立云端账本同步服务。
- 导出文件由用户主动通过系统文件选择器或分享面板保存;App 不自动上传。
- 数据库迁移、备份恢复和加密导出必须可测试、可回滚或在失败时保持原数据不变。
后续使用 GLM 协助实现时,必须先以本文件及预算规则、数据模型、导出格式、AI 隐私规范为约束,不以生成代码替代产品规则确认。
- AI 默认关闭,手动记账和本地统计不能依赖 AI。
- AI 首发能力是“自然语言生成待确认账单草稿”。
- AI 只能返回结构化草稿,不能直接写入、修改或删除正式账单。
- 本地共享核心负责字段校验、金额计算、日期归属、重复检测和最终入账。
- 金额、日期、分摊或账单类型不明确时,必须要求用户补充,禁止猜测。
- AI 超时、网络失败、模型返回格式错误时,必须回退到手动记账。
- GLM 通过可替换的 AI Provider 接口接入,不把模型调用散落在页面或数据库代码中。
- 模型密钥、请求地址和模型名称不能硬编码进原生客户端;如果使用远端 GLM 服务,密钥由受控服务端或用户明确配置的本地代理管理。
- GLM 请求只携带完成当前任务所需的用户主动提交内容;默认不发送完整账本历史。
- GLM 返回后先在本地解析为版本化草稿协议,再经过共享核心校验,最后进入用户确认页面。
- 需要记录模型版本、协议版本和解析失败原因,但日志不得记录完整消费内容、备注、票据或密钥。
- 如果要求“任何账本内容都不能离开设备”,则不得调用远端 GLM;只能使用设备端模型或本地规则解析。
- 先完成纯手动、本地离线记账闭环。
- 定义 AI 草稿 JSON Schema 和错误码。
- 用本地假数据实现 Provider 接口和确认流程,不连接真实模型。
- 接入 GLM,并验证结构化输出、拒答、不确定字段和超时回退。
- 用固定测试样例比较 Android/iOS 的解析和预算结果。
- 通过用户主动开启的隐私模式上线,不默认开启远端 AI。
每个实现任务应明确:
- 修改范围和不应修改的目录。
- 输入输出契约。
- 离线行为和错误处理。
- 数据是否允许离开设备。
- 验收案例。
- 需要运行的测试或真机验证。
GLM 生成代码后必须人工检查数据库迁移、金额精度、时区边界、导入事务、重复入账和隐私数据流;不能仅以“能编译”作为完成标准。
pace/
├── shared/ # Kotlin Multiplatform 共享业务核心
├── androidApp/ # Kotlin + Jetpack Compose 原生 Android 客户端
├── iosApp/ # Swift + SwiftUI 原生 iOS 客户端
├── web-prototype/ # 现有 Web/PWA 原型,迁移期间仅作参考
├── docs/
│ ├── BUDGET_RULES.md
│ ├── DATA_MODEL.md
│ ├── EXPORT_FORMAT.md
│ └── AI_PRIVACY.md
├── PLAN.md
└── README.md
当前 Web 原型尚未按该目录重组。重组前必须先保留可运行基线,并分阶段迁移,避免为了目录形式一次性破坏现有验证能力。
- Android 使用 Jetpack Compose,iOS 使用 SwiftUI;不使用 Flutter 或 React Native 作为最终客户端 UI。
- 预算、金额、日期、统计、导入和导出核心逻辑由 Kotlin Multiplatform 共享。
- Android 和 iOS 对同一组固定案例输出一致。
- 断网时手动记账、预算和统计完整可用。
- CSV/JSON 导出和 JSON 恢复不依赖服务器。
- GLM 不可用时 App 仍完整可用。
- GLM 结果在用户确认前永远是草稿。
- 远端 GLM 默认关闭,开启前显示明确的数据流向和隐私说明。