Files
tennis-book/skills/tennis-booking/references/current-logic.md
2026-09-23 19:49:59 +08:00

109 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 业务规则(agent 自行落地的稳定约束)
不依赖 Electron、不依赖任何 daemon。下列规则从 `src/main/adapters/fansibote-fuzhong/adapter.ts` 提炼,agent 在做任何写操作前必须自己执行这些校验。
## 时间块归一
slot 的 `endDatetime` 形如 `HH:59:00`,代表该 slot 占据"开始时间所在整点"整块。
- `08:00-08:59` = 8 点这一小时块
- `19:00-19:59` = 19 点这一小时块
- 连续组合判定时把 slot 投影到 `[beginHour, endHour)`,按整点对齐
- `helper find-continuous` 内部已实现该归一,agent 不必手算
## 连续组合判定
判定"19-21 点完整连续组合"的步骤(agent 也可以直接调 `pospal.mjs find-continuous`):
1. 过滤出 `status in {available, limited}` 且无 enrollment 覆盖的 slots
2. 按 `txtClassroomUid` 分组
3. 在每个分组内按 `beginDatetime` 升序排序
4. 在每个分组内滑窗 `[targetStartHour, targetEndHour)`:
- 窗口第一个 slot 的 `beginHour == targetStartHour`
- 窗口最后一个 slot 的 `endHour == targetEndHour`
- 相邻 slot 整点连续(`prev.endHour == cur.beginHour`,无空缺)
- 全部同一 `txtClassroomUid`
- 全部不在 enrollment 覆盖区间
5. 任一不满足即视为无完整连续组合
**整段"19-21 点" ≠ "19-20 和 20-21 分别可订"**——必须是同一场地、连续、无活动、可订。
## 写操作前实时复核
锁场前必须**重新查询**一次目标日期的列表,不信任之前缓存的:
- 全部目标 slot 仍 `available` / `limited`
- 全部目标 slot 无 enrollment 覆盖
- 全部 slot 仍同一 `txtClassroomUid`
- 全部 slot 仍按目标时间整点连续
- 总价仍等于 `sum(cost)`,单个 cost 未变
- 日期未变
任一不符则放弃本次锁场,**不重试**。
## 错误分类与重试边界
| 现象 | 分类 | 处置 |
|---|---|---|
| HTTP 2xx + `successed: true` | success | 一次性操作,session 结束本次写 |
| HTTP 2xx + `successed: false` | rejected | 可换时段重试(同一会话同一时段最多 1 次) |
| HTTP 4xx | rejected | 同上 |
| HTTP 5xx | uncertain | **不重试**,告诉用户去小程序核实 |
| 网络错误 / 超时 | uncertain | 同上 |
| JSON 解析失败 | uncertain | 同上 |
**绝不在结果不确定时重试。** 已经发起的锁场,agent 必须假设服务端可能已经接受,必须提示用户去小程序核对。
## 写操作幂等与熔断
- 同一 session 中:
- 同一时段只允许一次"明确失败 → 换时段重试"
- 任何 uncertain 后立即停止本 session 的写操作,等用户去小程序确认
- 锁场成功后,session 内禁止再对该用户发起任何写操作;告诉用户去小程序支付
- 锁场成功后 `script.expireTime` 已过:锁场已失效,告知用户
## 脱敏与日志
- 日志中**禁止**出现:`PSPLVISITORID` 原值、完整请求头、完整响应、支付签名、Cookie、预约人姓名/手机号
- 访客凭证指纹:`pospal.mjs fingerprint <visitor-id>` → 12 字符 hex,仅用于日志关联
- 响应脱敏:`pospal.mjs redact < raw.json` → 移除 `script.paySign`、所有 `PSPL*` 头、任何 `customerName/customerTel`,输出可记录版本
- 待支付摘要只保存:`apptUid`、`venueAppointUids`、`expireTime`、金额、场地名、时间;**绝不**保存 `script` 对象
## 重复通知与去重
agent 自己实现轮询时的去重:
- 维护"已知完整连续组合"集合,key = `txtClassroomUid + 日期 + 起止整点 + 总价`
- 只对"本轮新出现"且"相对上一轮状态变化"的组合产生通知
- 持续可订不重复通知
- 消失后重现可以再次通知
- 同一组合的多次出现合并为一条通知
## 轮询节奏
- 默认每 10 秒一轮,由 agent 自行 sleep 实现
- 整点(如 `19:00:00.000`)到达时绕过冷却立即查一次,但仍要遵守"同一 date 的并发查询只允许一个在飞"
- 同一球场的写操作天然串行(锁场 / 释放 不并发);同一 session 不允许同时发起两笔锁场
## PSPLVISITORID 处理
- agent 在首次需要写操作时向用户索取,使用隐藏式输入:
- bash: `read -rs PSPAL_VISITOR_ID`
- Node: `readline` 关闭回显
- 任何等价手段
- 输入后只在 session 内存保存为 `process.env.POSPAL_VISITOR_ID`,绝不落盘
- session 结束或被新 session 替换时立即 `unset POSPAL_VISITOR_ID`
- helper 子命令只从 env 或 `--visitor` 参数读取;**优先用 env**避免值出现在进程列表
- 任何情况下都不得在日志、对话、git、临时文件中出现 `PSPLVISITORID` 原值
- 调试用指纹:`pospal.mjs fingerprint "$POSPAL_VISITOR_ID"` → 12 字符 hex
## 不允许做的事
- ❌ 用任何形式的"自动支付"或"自动点确认"
- ❌ 缓存 `script` 对象 / `paySign`
- ❌ 把 `PSPLVISITORID` 写入 Skill 文件、`.env`、配置、日志、对话
- ❌ 改写 `STOREID` / `userId` / `PROJECT_UID`(除非接口契约明确变更)
- ❌ 在结果不确定时重试
- ❌ 拆单(把"19-21"拆成"19-20"和"20-21"两次请求)
- ❌ 静默忽略字段缺失