Operating Hours Schema

Source of truth: callytics-common/src/db/schema/operating-schedules.ts · operating-overrides.ts · blackout-periods.ts (verified 2026-06-15) ⚠️ 数据已建模,SLA 计算待接入 — 三张表已存在,但当前 computeDueAt() 用硬编码 8 AM–9 PM ET,per-store 时区 / 营业时间 / blackout override 未接线。函数签名支持传 per-store businessHours / timezone,但 lead 和 contacts 两条管道的调用处都没传。本页是"schema 占位,SLA 计算尚未读取"状态。


1. 表说明

这三张表合起来定义 Lead SLA 的"营业时段内才计时"逻辑:Lead SLA 倒计时只在营业时段内走,落在闭店时段或 blackout 时段被排除/推迟。三表都从 DynamoDB 旧记录迁来,主键都以 store_id 打头。

职责关键约束
operating_schedules每周营业时间模板 + 生效日期(可堆历史版本)PK (store_id, sk),sk = SCHEDULE#{effectiveDate}#{createdAt}。给定某天取 effective_date <= 目标日 中最新一条
operating_overrides单日营业例外(提前关门 / 当天歇业 / 临时改时间)PK (store_id, sk),sk = OVERRIDE#{date}。同日再写会 ON CONFLICT DO UPDATE 替换
blackout_periodsLead SLA 排除时段(支持每周重复)PK (store_id, blackout_id)expandBlackouts() 把 recurring 规则解析成单日实例

写入者:studio-api 的 operating-hours / blackouts 路由(create / update / delete)。 读取者:同上路由(list / effective / expanded)+ SLA 计算(待接入)


2. Schema 全字段

2.1 operating_schedules — 每周营业时间模板

替代 DDB OperatingHours 里 SK prefix SCHEDULE# 的记录。

#Field (TS)SQL 列TypeNullable用途
1storeIdstore_idtextNOT NULL (PK)门店隔离键
2sksktextNOT NULL (PK)排序键,格式 SCHEDULE#{effectiveDate}#{createdAt},保留 DDB 排序语义
3effectiveDateeffective_datedateNOT NULL生效日期。给定某天,取 effective_date <= 目标日 中最新的那条 schedule
4scheduleschedulejsonbNOT NULL每日开关门时间。例:{ "monday": [{ "openTime": "09:00", "closeTime": "17:00" }], ... }
5createdAtcreated_attimestamptzNOT NULL DEFAULT NOW()创建时间
6createdBycreated_bytextNOT NULL创建者(staff ID / 系统标识)
  • 主键: (store_id, sk)
  • 索引: idx_op_schedules_effective on (store_id, effective_date) — 按生效日查当前 active 模板

2.2 operating_overrides — 单日营业例外

替代 DDB OperatingHours 里 SK prefix OVERRIDE# 的记录。

#Field (TS)SQL 列TypeNullable用途
1storeIdstore_idtextNOT NULL (PK)门店隔离键
2sksktextNOT NULL (PK)排序键,格式 OVERRIDE#{date}
3datedatedateNOT NULL例外日期
4overrideDataoverride_datajsonbNOT NULL该日的 override 配置。例:{ "isClosed": true }{ "openTime": "10:00", "closeTime": "14:00" }
5createdAtcreated_attimestamptzNOT NULL DEFAULT NOW()创建时间
6updatedAtupdated_attimestamptzNULL更新时间(首次创建为 NULL,upsert 时写入)
7createdBycreated_bytextNOT NULL创建者
  • 主键: (store_id, sk)
  • Upsert 语义: 同一日期再建 override 会替换前一条(service 层 ON CONFLICT DO UPDATE)
  • 索引: idx_op_overrides_date_range on (store_id, date)

2.3 blackout_periods — Lead SLA 排除时段

替代 DDB studio-BlackoutPeriods-{env}。支持 one-time 和 recurring(每周重复)两种。

#Field (TS)SQL 列TypeNullable用途
1storeIdstore_idtextNOT NULL (PK)门店隔离键
2blackoutIdblackout_idtextNOT NULL (PK)blackout 唯一标识
3startTimestart_timetextNOT NULL起始时间(ISO 字符串或时段表示)
4endTimeend_timetextNOT NULL结束时间
5reasonreasontextNULL原因说明(自由文本,UI 展示用)
6excludeFromSlaexclude_from_slabooleanNOT NULL DEFAULT true是否从 SLA 倒计时排除(默认排除)
7recurrencerecurrencejsonbNULL重复规则。例:{ "type": "weekly", "daysOfWeek": [0..6], "startDate": "...", "endDate"?: "..." }。NULL = one-time
8createdBycreated_bytextNOT NULL创建者
9createdAtcreated_attimestamptzNOT NULL DEFAULT NOW()创建时间
10updatedAtupdated_attimestamptzNOT NULL DEFAULT NOW()更新时间
  • 主键: (store_id, blackout_id)
  • 重复规则展开: studio-api 的 expandBlackouts() 把 recurring 规则解析成单个日期实例,用于日历展示和 SLA 计算

3. Enum 清单

三张表没有 text-as-enum 字段(schedule / override_data / recurrence 是结构化 JSONB,不是枚举)。 唯一的枚举状态是 JSONB payload 内的约定值,无 DB CHECK,合法值靠写入方(studio-api 路由)遵守:

出现位置约定值含义
operating_overrides.override_data.isClosedtrue当天歇业(此时 openTime / closeTime 可省)
blackout_periods.recurrence.typeweekly当前仅支持每周重复;null = one-time
blackout_periods.recurrence.daysOfWeek[]0..6周日 = 0,周六 = 6(JS Date 风格)

4. Prompt Output ↔ Schema 字段对照

N/A — 7 个 prompt 都不直接读写这三张表。 Operating-hours 数据由 studio-api UI 维护(店主/管理员手动配置),AI pipeline 不产出 schedule / override / blackout 数据。

Prompt与本 schema 关系
01 Triagen/a
02 Classifyn/a
03 Verifyn/a
04 Coachingn/a
05 Contact Profilen/a
06 Task Decisionn/a — 但 prompt 输出的 nextDueAt / dueAt 建议,最终由代码 side 的 computeDueAt() 计算,而 computeDueAt() 理论上应该读这三张表(目前未接线,见页首警告)
07 Task Playbookn/a

给 SLA 接入工程师的注意: 当 computeDueAt() 接入 per-store operating-hours 后,Lead Outreach / Follow-up 两类 task 的 dueAt 计算路径会变成: createdAt → 取当前 operating_schedule(by effective_date) → 应用 operating_override(if 同日有) → 跳过 blackout_periods(if excludeFromSla=true) → 累计 SLA 时长 → dueAt


5. Appendix A — Re-verification commands

2026-06-01 verified。文档 stale(>30 天)时重跑下方命令并比对。

# 1. 三张表所有 column
for f in operating-schedules operating-overrides blackout-periods; do
  echo "=== $f ==="
  grep -E "^\s*[a-zA-Z]+:\s*(text|integer|timestamp|boolean|numeric|jsonb|date)\(" \
    ../../../../../callytics-common/src/db/schema/$f.ts
done

# 2. 三表的 PK / index
grep -E "primaryKey|index\(" \
  ../../../../../callytics-common/src/db/schema/operating-schedules.ts \
  ../../../../../callytics-common/src/db/schema/operating-overrides.ts \
  ../../../../../callytics-common/src/db/schema/blackout-periods.ts

# 3. Task pipeline design 是否动这三张表(期望:无 match)
grep -iE "operating_schedules|operating_overrides|blackout_periods" \
  ../../tasks-feature/design/task-pipeline-deliverable-codex.md

# 4. Unified pipeline 是否动这三张表(期望:无 match)
grep -iE "operating_schedules|operating_overrides|blackout_periods" \
  ../../unified-pipeline/unified-pipeline-final.md

# 5. computeDueAt 当前是否已接入 per-store hours(grep "businessHours" 调用处)
grep -rn "computeDueAt\|businessHours" ../../../../../callytics-infrastructure/lambda/ \
  | grep -v node_modules | head -20

6. Cross-References