Skip to content

feat(mobile): OCR 相册来源选择 + fix 账单日期 NaN 与 sync 422 - #59

Merged
hinson0 merged 2 commits into
mainfrom
worktree-fix-ocr
Apr 20, 2026
Merged

hinson0 merged 2 commits into
mainfrom
worktree-fix-ocr

Conversation

@hinson0

@hinson0 hinson0 commented Apr 20, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • feat: OCR 📷 按钮新增「拍照 / 相册」来源选择面板(输入框上方悬浮卡片)
  • fix: 账单卡片日期显示 NaN年NaN月NaN日 — 根因是 LLM 无日期返回 ""、前端 ?? 不拦空串
  • fix: sync push 被历史坏数据拖累整批 422 — 单条 occurred_at="" 让 Pydantic 拒掉整次推送

主要改动

功能(commit 562deae)

  • ImageSourceSheet:2 列 Grid 悬浮卡片,参考 QuickActions 的条件渲染模式,自带 onClose 职责下沉
  • useCamera.pickFromLibrary:launchImageLibraryAsync,权限/base64 行为与 pickImage 对齐
  • ChatInputBar:三态互斥(plusExpanded / sourceSheetOpen / voiceMode)
  • app.json:补 photosPermission,避免 iOS 14+ 首次访问相册因缺 NSPhotoLibraryUsageDescription 闪退
  • theme.sizes."7xl": 32:emoji 字号进设计系统

修复(commit 3b12a97)

三层防御根治 NaN + 422:

层 文件 做什么
后端 Router routers/ocr.py datetime.now(UTC).isoformat().replace("+00:00","Z") 兜底
后端 Schema schemas/ocr.py Field(min_length=1) 合约层明确非空
后端 Sync Schema schemas/sync.py @field_validator(mode="before") 空值 → 当前时间,防单条坏数据拖累整批 push
前端 useChat hooks/useChat.ts ?? → || 拦截空串写入
前端 展示 lib/format.ts formatDate 加 isNaN(d.getTime()) 兜底救旧数据
前端 Sync lib/sync/sync-service.ts sanitizeTransactions push 前用 created_at 兜底

Test plan

  • 后端:pytest tests/ 49 通过(新增 test_ocr_empty_occurred_at_falls_back_to_now + test_ocr_null_occurred_at_falls_back_to_now)
  • 前端:just cicd-fe 18 套件 177 测试通过(新增 4 条 formatDate 用例)
  • 真机验证:点 📷 弹面板,选「拍照」/「相册」均能正常完成 OCR
  • 真机验证:旧账单卡片显示为当天日期而非 NaN
  • 真机验证:sync push 成功推上去(不再 60s 重试 422)

注意事项

  • photosPermission 自定义中文文案需 EAS build 后才能在 iOS 系统弹框生效;Expo Go 下显示 Expo Go 的默认文案,功能正常
  • 本地 SQLite 的坏 occurred_at="" 行仍在数据库里,但 sanitize + 后端 validator 双层防御保证 push 时被修正到 PostgreSQL(用户下次 pull 回来就是合法值)

Co-Authored-By: Claude noreply@anthropic.com

hinson0 and others added 2 commits April 20, 2026 16:05
点击 OCR 📷 按钮不再直接进入拍照,而是弹出「拍照 / 相册」来源选择面板,
满足用户已在相册里(朋友发的截图、微信保存的图)直接选图识别的场景。

- 新增 ImageSourceSheet 悬浮卡片(输入框上方 2 列 Grid)
- useCamera 扩展 pickFromLibrary,权限/base64 行为与 pickImage 对齐
- ChatInputBar props 从 onCamera 拆成 onPickCamera/onPickLibrary,与 voiceMode/plusExpanded 互斥
- app.json 补 photosPermission 文案,避免 iOS 14+ 首次访问相册因缺 NSPhotoLibraryUsageDescription 崩溃
- theme.sizes 新增 7xl: 32 供 emoji 字号复用

Co-Authored-By: Claude <noreply@anthropic.com>
根因:后端 LLM prompt 让模型在识别不到日期时返回空字符串,前端用
?? 做兜底但空串是 falsy 不 nullish,`new Date("")` 得到 Invalid Date,
最终卡片显示 "NaN年NaN月NaN日";同时本地 SQLite 里的坏数据被
sync push 时 Pydantic 判定为非法 datetime,整批返回 422 拖累其他记录。

三层防御:
- 后端 routers/ocr.py: occurred_at 为空/None 时用 datetime.now(UTC) 兜底,
  统一 Z 后缀(与项目其他 ISO 格式一致)
- 后端 schemas/sync.py: TransactionRow.occurred_at 加 field_validator,
  拦空串/None 防止单条坏数据阻塞整批 push
- 后端 schemas/ocr.py: occurred_at 加 Field(min_length=1),合约层明确非空
- 前端 useChat.ts: ?? 改 ||,拦截空字符串
- 前端 lib/format.ts: formatDate 提取 + isNaN 兜底,救旧数据的展示
- 前端 lib/sync/sync-service.ts: push 前 sanitizeTransactions 用 created_at 兜底

测试:后端新增 2 条 ocr 兜底测试,前端新增 4 条 formatDate 测试。

Co-Authored-By: Claude <noreply@anthropic.com>
@hinson0
hinson0 merged commit af90a08 into main Apr 20, 2026
2 of 3 checks passed
@hinson0
hinson0 deleted the worktree-fix-ocr branch April 20, 2026 08:13
@hinson0
hinson0 restored the worktree-fix-ocr branch April 20, 2026 08:13
hinson0 added a commit that referenced this pull request Apr 20, 2026
- docs/cc/plans/todo-ocr-indexed-volcano.md: feature-dev 流程产出的
  实施计划(Context / UI 设计 / 5 步实施 / 验证清单)
- apps/mobile/docs/playground/ocr-source-sheet.html: 单文件可交互的
  UI/UX 原型,左控制面板调参、右实时预览、输出 RN StyleSheet 片段

两份均为 PR #59 功能落地前的设计产物,便于后续回溯设计决策。

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant