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

5.0 KiB
Raw Permalink Blame History

业务规则(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"两次请求)
  • ❌ 静默忽略字段缺失