Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
R
rentos
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
accounting
rentos
Commits
19280667
Commit
19280667
authored
Oct 14, 2026
by
wenwen.tang
😕
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
docs(memory): 记录提交分 3 层、草稿落盘、14 张残留清理与部署验证
parent
9b014b66
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
200 additions
and
2 deletions
+200
-2
2026-10-14.md
.workbuddy/memory/2026-10-14.md
+166
-1
MEMORY.md
.workbuddy/memory/MEMORY.md
+34
-1
No files found.
.workbuddy/memory/2026-10-14.md
View file @
19280667
...
...
@@ -73,3 +73,168 @@ test_round5 54 · test_round6 39 · test_masters 19 = **303 项全绿**。
## 待办
-
9 月电费批次错计(多计 10 行 ¥167,947.41 / 少计 601B ¥299.67 / 出租方错记精锐)仍待处置
## 第十五轮:核对中可直接改数据(「保存修改」)
巴特菲:「核对时也要可以编辑,一并操作了。不用回归测试,先直接改」
### 设计要点
核对是
**账期级**
(一个账期 N 张批次),而
`reingest`
是
**单批次 PUT 且一律重置核对**
。
解法 =
**按 `_bid` 分组逐张保存**
+ 后端新增
`keep_verify=True`
:
只让
**改动过的行**
退回待核对,没改的行照旧算已核,不用整张账期重新核。
后端
`rentos/service.py`
:
-
`_FP_FIELDS = (meter, prev, cur, price, factor, company, owner)`
、
`_NUM_FP`
数值归一
-
`_detail_fp(d)`
行内容指纹(字符串 "100" 与 100 视为相同)
-
`_PLACEHOLDER = ("(空置)",)`
+
`_same_detail(old_fp, new)`
:
**任一侧为空/占位符的字段一律跳过**
—— 否则前端只提交 unit/类目/抄表数时,服务端补全的 company/price 会被误判成改动,
**把整张账期的核对无辜清空**
(这个坑真踩到过,第一次冒烟就红了)
-
`_kept_verify(old_batch, new_details)`
:行键 →
**指纹多重集**
(不能只存一条!)
逐行消费匹配,
`all_matched`
才保留
`done`
-
⚠️
**`meter` 必须在指纹里,且要逐行判定不能只看行键集合**
:
行键是「单元|类目」,把「302室|空调」改成「照明」会与原有照明行
**撞同一个行键**
,
按集合判会把「两行变一行」误当成没变 → 确认状态错误地继续有效
-
`reingest(..., keep_verify=False)`
,不传时行为完全不变(老调用方零影响)
前端
`web/app.js`
:
-
`renderElecRows()`
去掉 verify 的
`disabled`
;核对模式也能加行(
`#e-vf-add`
)、删行(带确认)
-
新增
`#e-vf-save`
「保存修改(N)」,
`saveVerifyEdits()`
按
`_bid`
分组逐张 PUT
-
脏数据基线:
`ELEC_CMP_FIELDS`
+
`ESNAP`
/
`takeVerifySnapshot()`
/
`verifyDirty()`
/
`markVerifyDirty()`
-
⚠️
**基线键必须用稳定的 `_rid`**
,不能用数组下标 —— 删中间行会让后面所有行的基线错位
-
⚠️
**`markVerifyDirty()` 只改文字、绝不重渲染模式条**
(重渲染会让输入框失焦);
按键事件里也不能调
`renderEModeBar()`
,改用 300ms 节流 + 只更新
`#e-vf-dirty`
/
`#e-vf-save`
-
顺手修:
`tr.children[4]/[10]`
改为
`querySelector('[data-col="qty"]/[data-col="amt"]')`
(项目规矩:表格按 data-col 定位,不按第 N 列)
### 验证
-
新增单测
`TestKeepVerify`
9 项 + 沙箱冒烟
`tools/smoke_verify_edit.py`
(11 断言)
-
**单测 312 项全绿**
(303 + 9)、冒烟全通过、真实
`data/batches`
7→7 零污染
-
前端
`node --check`
通过
### 踩坑记录(三次失败的教训)
1.
`_detail_fp`
三元里字段名硬编码成
`d.get("prev")`
→ 指纹全返回 prev 的值,
**改了 cur 也检测不到**
。写映射表时务必
`d.get(f)`
2.
提交字段不全 → 勾选被全清(见上,
`_same_detail`
宽容规则)
3.
类目合并撞行键 → 确认错误地保留(见上,逐行判定)
---
## 核对口径再改 + 录入临时保存(13:09 起 · 当天第二个需求)
巴特菲原话:
> 「核对时直接修改数据,不影响核对状态,点击确认核对直接执行数据保存,去掉保存修改按钮。
> 录入本期电费时增加临时保存按钮,将数据落盘存储。下次可直接编辑。」
### 一、核对口径反转:从「改过就退回」改成「改数据不影响核对状态」
上午那版是「点保存修改 → 只让改动过的行退回待核对」。现在
**去掉「保存修改」按钮**
,
改成:表里随便改,
**改数据不动核对状态**
;点「确认核对」时
**先存数据、再写核对**
。
前端
`confirmVerify()`
两步走(这也顺手修掉一个隐藏 bug):
1.
`saveVerifyData()`
—— 按
`_bid`
分组逐张
`PUT /api/batches/{id}`
。
**有改动才发请求**
:
`editSummary().any === false`
时一个包都不发,不白白重出单据。
2.
`POST /api/periods/verify {period, rows, done:true}`
—— 把整期勾选写成「已核对」。
于是「改过的行要不要重新核」由
**用户勾没勾**
决定,服务端不猜。
**删掉的东西**
(旧口径整套):
-
`service._kept_verify`
/
`_detail_fp`
/
`_same_detail`
/
`_FP_FIELDS`
/
`_NUM_FP`
-
`reingest(..., keep_verify=)`
参数 +
`server.py`
透传 + 文档行
-
`web/app.js`
的
`saveVerifyEdits()`
与
`#e-vf-save`
按钮
-
`test_round6.TestKeepVerify`
(9 项)
> 留着会与新口径**直接矛盾**,不如删干净。`reingest` 恢复为「核对一律重置」。
⚠️
**顺手发现一个真 bug**
:
`saveVerifyEdits()`
里调了
`await enterVerify(EPERIOD)`
,
但
**`enterVerify` 这个函数从来不存在**
(只有
`enterPeriodVerify`
)——一调用就 ReferenceError。
因为 UI 回归只测了
`confirmVerify`
这条路径,这个函数从没被真正跑到过。删掉它正好。
⚠️
**PUT 是全量覆盖**
:所以「按批次筛要不要发」只能在
**发不发**
这一层做,
每张要发的批次必须带上
**它的全部行**
(不能只发改过的那几行,否则其余行会被覆盖没了)。
⚠️
**退出核对必须警告**
:数据只在「确认核对」时落盘,改完直接「退出核对」改动就没了。
`cancelElecMode()`
现在检测到
`editSummary().any`
会明确说「改过的还没保存,退出就丢了」。
前端辅助函数:
`dirtyRows()`
(返回行对象)/
`editSummary()`
(
`{n, structural, any}`
)/
`dirtyTip(ed)`
/
`doneLabel(ed)`
;
`verifyDirty()`
保留为兼容旧名。
`markVerifyDirty()`
改成刷新
`#e-vf-done`
文案 +
`#e-vf-dirty`
提示(依旧只改文字不重渲染)。
`structural`
用
`ELEC.length !== ESNAP.size`
判增删行(基线一行一条,键是
`_rid`
)。
### 二、新增:录入「临时保存」草稿(落盘,下次直接编辑)
-
`config.DRAFT_DIR = data/drafts/`
(已加进
`ensure_dirs`
)
-
`store.draft_path/save_draft/load_draft/delete_draft`
;账号名净化
`[^0-9A-Za-z_\-] → _`
-
接口:
`GET /api/drafts/elec`
(读,无则
`draft: null`
)、
`PUT`
(存)、
`DELETE`
(清)
-
`_write_cap`
:PUT/DELETE 记
`CAP_ELEC`
→
**访客(guest)也能临时保存**
-
草稿按
**账号**
隔离:
`_draft_key(user) = "elec__" + username`
-
前端:
`saveElecDraft()`
/
`clearElecDraft()`
/
`restoreElecDraft()`
/
`elecBootFill()`
-
`applyNav`
里电费页由
`autoFillFromPrev()`
改为
`elecBootFill()`
:
**有草稿恢复草稿(并 `PREV_DONE=true`),没草稿才自动带入上一期**
—— 否则两者互相覆盖
-
恢复后
`#uploadResult`
显示「已恢复 X 临时保存的录入:N 行」+「丢弃这份草稿」按钮
-
生成通知书成功后
`clearElecDraft()`
;「清空」按钮也一并清草稿
-
`#btn-e-draft`
只在
`EMODE === 'new'`
显示(
`#eDraftWrap`
在重编模式隐藏)
### ⚠️ 最大的坑:沙箱白名单漏了 `DRAFT_DIR`
`ui_verify_runner`
与自带沙箱的
`ui_verify7/8/9/10/11`
、
`check_scheduler`
都是
**枚举式改写 config 路径常量**
。新增
`DRAFT_DIR`
时若不同步补进这些列表,
草稿就会写到
**真实**
`data/drafts/`
—— 和上次 17 个测试批次泄漏到真实 data/ 是
**同一类**
坑。
已给 7 个脚本全部补上(各 +1 行,
`git diff --stat`
已核对无行尾变动)。
### 验证(按规矩:没跑 UI 回归,等巴特菲发话)
-
**单测 308 项全绿**
:test_rules 45 / test_rent 53 / test_directory 33 / test_accounts 60 /
test_round5 54 /
**test_round6 44**
(删 9 项 keep_verify + 加 5 项
`TestElecDraft`
)/ test_masters 19
-
沙箱冒烟
`tools/smoke_verify_edit.py`
**重写**
为 16 断言,全过;真实
`data/`
17→17 零污染
-
A 段:PUT 重编后核对清空 → 紧接账期确认 →
**改动落盘(303 本期=150)+ 仍 3/3 ok**
-
A5:只勾 2 行 →
`ok=True`
`partial=True`
2/3(部分核对可确认)
-
B 段:草稿存/读/按账号隔离(guest 读不到 admin 的、guest 清不影响 admin)/删后为 null
-
`node --check web/app.js`
、4 个 .py
`ast.parse`
均通过
-
**本机预览已重启**
(旧进程 PID 65840 已 kill 再用新代码起,safe 模式无定时器):
`GET /api/drafts/elec`
→
`{"ok":true,"draft":null}`
;PUT/DELETE 实测通
(⚠️
**改完后端必须重启**
:静态资源是每次请求读盘所以前端立即生效,
但 Python 进程不重启就没有新接口,临时保存会 404)
### 第四轮:账期口径答疑 + 再次清理测试残留(14 张)
**巴特菲问「电费账期里的批次是根据什么来的」**
,答(口径已记进 MEMORY.md):
批次 = 一次「生成通知书」;账期
`period_label`
是批次上的文本字段,不是实体;
`list_periods()`
按它归组 = 页面「账期汇总」。来源三处:录入页「所属期间」(默认今天年月 /
带入上期时 +1 月)、Excel 标题或抄表日期、后端兜底用通知日期年月。
⚠️ 自由文本无归一化,「2026-11」与「2026年11月」会分成两个账期。
**顺带查出真实 `data/batches/` 又被写了 14 张测试残留**
(账期
`2026年11月`
、302/303室仅照明、
公司「科博(上海)仓储有限公司」、成对出现在 10:20/12:49/12:52/12:53/12:54/12:55/13:15/13:17):
-
**硬证据**
:批次
`documents[].abs_path`
=
`D:\AI\rentos\output\elec\{id}\...`
(真实仓库路径)。
若走 runner 沙箱,
`OUTPUT_DIR`
会被改写成
`tmp/output/elec`
→ 证明写入方是
**未改写 config 的真实服务**
-
**受控实验**
:
`RENTOS_VERIFY_PORT=8931 runner tools/smoke_verify_edit.py`
→ 真实 data
**17→17 零增量**
,
冒烟 16 项全过(沙箱批次
`20261014-133456`
已自动删除)→
**runner 隔离本身有效**
-
结论:泄漏发生在
**某几轮没走 runner / 早期版冒烟脚本**
上,机制现已收口(1~6 号脚本硬闸门 + runner 覆盖
`BATCH_DIR`
/
`OUTPUT_DIR`
/
`DRAFT_DIR`
)
-
**处置**
:14 张批次 JSON 移入
`data/batches/_trash/20261014/`
,对应
`output/elec/{id}/`
同步移入
`output/elec/_trash/20261014/`
,附 README。清理后顶层只剩 3 张真实批次:
`20261009-115501`
(电费 2026年11月)、
`20261009-175756`
(房租 2026年10月)、
`20261013-134828`
(电费 2026年9月补单)
-
台账未被污染(
`git status data/tables/rent_parking.json`
为空)
### 第五轮:提交 + 部署上线(13:44 起)
**提交分 3 层**
(
`7709e42..9b014b6`
,推送 origin/main 成功):
1.
`9f0dd9e`
feat(elec) 后端:DRAFT_DIR + store 草稿读写 +
`/api/drafts/elec`
+ 删 keep_verify 那套 +
`.gitignore`
加
`data/drafts/`
2.
`fff9b2a`
feat(web) 前端:核对态可编辑 +
`confirmVerify`
两步存盘 + 稳定
`_rid`
基线 + 临时保存按钮
3.
`9b014b6`
test:沙箱补
`DRAFT_DIR`
、test_round6 换草稿用例、新增
`tools/smoke_verify_edit.py`
**提交前处置**
:本机测试草稿
`data/drafts/elec__admin.json`
(302/303室 4 行、账期 2026年11月)
移入
`data/_trash/drafts-20261014/`
,避免下次打开录入页自动灌入测试数据;
`data/drafts/`
已进
`.gitignore`
(运行时状态,同
`data/batches/`
)。台账文件未出现在改动里(未被污染)。
**单测**
:7 个脚本全 OK(rules/round6/directory/rent/round5/accounts/masters)。未跑 UI 回归(巴特菲规矩)。
**部署**
:
`RENTOS_SSH_PASSWORD=... venv-python tools/deploy_git.py sync`
rc=0;
容器 Up、容器内/宿主机 HTTP 200、台账 85×31、名册 33 公司 + 4 出租方、
wecom-cli 1.3.4 authorized(Bot 唐文文)、scheduler enabled。
线上 6b 自检:字体
`/app/assets/fonts/simhei.ttf`
、AJ 有值 64 行。
**线上接口验证**
:
`/api/drafts/elec`
、
`/api/periods`
、
`/api/batches?kind=elec`
均返回
**401**
(未登录),
证明路由已挂上(404 才是没生效);容器内
`DRAFT_DIR=/app/data/drafts`
与新代码一致。
无列模型变更 →
**无需跑数据迁移**
。
### 待办
-
9 月电费批次错计(10 行 ¥167,947.41 / 少计 601B ¥299.67 / 出租方错记精锐)仍未处置
-
`ui_verify11`
里「保存修改」按钮的断言需同步改写(本轮删了该按钮,用户未点头跑回归)
.workbuddy/memory/MEMORY.md
View file @
19280667
...
...
@@ -14,7 +14,17 @@
-
**AD~AI 房产/房产税六列已删**
(
`RETIRED_TAX_COLS`
,守护
`test_retired_cols_not_used_by_billing`
)
-
默认锁定只读;改单元格/增行/删行必须
`confirm=true`
(否则 428);备份
`data/tables/.backup/`
-
**电费批次**
`data/batches/{id}.json`
:只留原始数据(details/totals/documents/发送状态),按账期月份留档;
测试残留移入
`data/batches/_trash/`
(store 只 listdir 顶层
`*.json`
,子目录隐身、可恢复)
测试残留移入
`data/batches/_trash/YYYYMMDD/`
(store 只 listdir 顶层
`*.json`
,子目录隐身、可恢复)
-
已有回收站:
`_trash/20261013/`
(17 张)、
`_trash/20261014/`
(14 张,output 同批移入
`output/elec/_trash/`
)
-
**账期口径**
(2026-10-14 确认):
**批次 = 点一次「生成通知书」的产物**
(号
`YYYYMMDD-HHMMSS`
·
`make_batch_id`
纯 ASCII 时间戳);
**账期 `period_label` 只是批次上的一个文本字段,不是独立实体**
,
`list_periods()`
按它归组即为页面上的「账期汇总」(组内按
`created_at`
倒序)
-
来源:① 录入页「所属期间」(默认今天年月;点「带入上一期」时
`nextPeriod()`
+1 月)—— 主要
② Excel 解析
`elec_parser._extract_period`
(先正则抓 A1 标题「YYYY年M月」,再退抄表日期年月)
③ 后端兜底
`ingest_rows`
:未传时用
**通知日期**
的年月
-
⚠️
**自由文本、无格式归一化**
:匹配是
`strip()`
后精确比较 → 「2026-11」与「2026年11月」会分成两组;
空值归「(未标账期)」
-
同账期多张批次是正常的(分批录 / 按公司分批 / 补录),故台账按账期汇总并默认折叠
## 免租(`rentos/rentfree.py`,2026-10-13 重写为两种类型)
-
`首N月免租`
:窗口
`[I, I+L月)`
,含起始不含结束;0.5 月 = 15 天
...
...
@@ -55,6 +65,29 @@
-
⚠️
**会 `innerHTML` 重渲染的绑定函数里不要挂 document 级监听**
:每渲染一次堆一个,
旧闭包持有已移除的节点 → 用一次性模块标志(
`VCOLS_DOC_BOUND`
)或事件委托
## 电费「核对中改数据」(2026-10-14 · 当日晚改口径)
核对表
**可直接编辑**
,
**改数据不影响核对状态**
;点「确认核对」才把改动
**一并存盘**
。
前端
`confirmVerify()`
两步:①
`saveVerifyData()`
按
`_bid`
分组逐张
`PUT /api/batches/{id}`
(有改动才发请求)→ ②
`POST /api/periods/verify {rows, done:true}`
把勾选整期写成「已核对」。
于是「改过的行要不要重新核」由
**用户勾了没勾**
决定,服务端不猜。
-
**已删**
`keep_verify`
/
`_kept_verify`
/
`_detail_fp`
/
`_same_detail`
(旧「改过的行退回待核对」
口径被上面两步取代,留着会与新口径矛盾);
`reingest`
恢复为
**一律重置核对**
-
前端脏数据基线:
`ELEC_CMP_FIELDS`
+
`ESNAP`
/
`takeVerifySnapshot()`
/
`dirtyRows()`
/
`editSummary()`
-
⚠️ 基线键必须用稳定
`_rid`
,
**不能用数组下标**
(删中间行会让基线整体错位)
-
⚠️ 改动提示
**只改文字、绝不重渲染模式条**
(
`markVerifyDirty`
300ms 节流),否则输入框失焦
-
⚠️
`PUT`
是
**全量覆盖**
:每组必须带上该批次的
**全部行**
,只是「哪些批次要发」按有无改动筛
-
⚠️ 退出核对若有未保存改动要
**明确警告会丢失**
(数据只在确认时落盘)
## 电费录入「临时保存」草稿(2026-10-14)
录入页「临时保存」把当前明细整份落盘
`data/drafts/elec__{账号}.json`
(
**按账号隔离**
),
下次进电费页自动恢复、直接接着编辑;生成通知书 / 清空 / 手动丢弃时删除草稿。
-
接口
`GET|PUT|DELETE /api/drafts/elec`
;
`_write_cap`
里 PUT/DELETE 记
`CAP_ELEC`
(访客可用)
-
`store.draft_path/save_draft/load_draft/delete_draft`
;账号名净化
`[^0-9A-Za-z_\-] → _`
-
前端
`saveElecDraft/clearElecDraft/restoreElecDraft/elecBootFill`
;
`applyNav`
里电费页改调
`elecBootFill()`
——
**有草稿就不「带入上期」**
(否则互相覆盖)
-
⚠️
**沙箱白名单必须加 `DRAFT_DIR`**
:
`ui_verify_runner`
与自带沙箱的
`7/8/9/10/11`
、
`check_scheduler`
都逐一补过 —— 漏了就会把草稿写进
**真实**
`data/drafts/`
(与批次泄漏同类坑)
## 房租车位费出单
链路:
`rent_parser.py`
→
`rent_render.py`
(→
`notice_pdf`
)→
`service.ingest_rent`
→ 批次
-
计费:期间起 =
**AC**
,止 = 起 + N 个自然月 − 1 天;
`房屋租赁费 = P × N`
、
`物业费 = O × N`
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment