fix: align peak hours with Z.ai weekday-only rules and credit rates #13

Merged
bjoern merged 2 commits from fix/peak-hours-weekday-rules into main 2026-08-15 10:45:12 +02:00
Member

Summary

Z.AI changed how peak hours work in the Plan Update Announcement (docs.z.ai/devpack/notice/usage-revision, published July 30, 2026) and the Coding Plan Overview:

  • Peak hours are now Monday–Friday only (14:00–18:00 UTC+8). Weekends are billed at off-peak rates all day. Previously the window applied daily.
  • The plan is now credits-based: peak usage is charged at the standard rate (1×) and off-peak usage at 50% of the standard rate — replacing the old 3×/2× quota multipliers (and the 1× promotional rate through Sep 2026).

Changes

  • is_peak_time / time_until_peak_change: new weekdays_only parameter (default True) — Saturday/Sunday are never peak; off-peak countdowns skip the weekend to Monday's peak start
  • format_peak_multiplier: now returns 1× credits / 0.5× credits
  • Peak start/end notification wording updated to the credit-rate phrasing
  • Tests: existing cases moved to verified weekday dates (the old fixture date was a Sunday, which is now legitimately off-peak), plus new coverage for Saturday/Sunday/Friday/Monday and the weekend-skipping countdown; legacy daily behaviour still covered via weekdays_only=False
  • README peak-hours section rewritten with a link to the announcement

Test plan

  • Full suite passes locally: 107 passed (pytest -q)
  • New weekday/weekend logic covered by 20 peak-hours tests
  • CI green on this branch

Sources:

  • Plan Update Announcement — "usage on weekends will be deducted at off-peak rates all day"; peak = Mon–Fri 14:00–18:00 UTC+8
  • Coding Plan Overview — "During off-peak hours, model usage is charged at 50% of the standard credit rate"
# Summary Z.AI changed how peak hours work in the **Plan Update Announcement** ([docs.z.ai/devpack/notice/usage-revision](https://docs.z.ai/devpack/notice/usage-revision), published July 30, 2026) and the [Coding Plan Overview](https://docs.z.ai/guides/coding-plan/overview): - **Peak hours are now Monday–Friday only** (14:00–18:00 UTC+8). **Weekends are billed at off-peak rates all day.** Previously the window applied daily. - The plan is now **credits-based**: peak usage is charged at the **standard rate (1×)** and off-peak usage at **50% of the standard rate** — replacing the old 3×/2× quota multipliers (and the 1× promotional rate through Sep 2026). ## Changes - `is_peak_time` / `time_until_peak_change`: new `weekdays_only` parameter (default `True`) — Saturday/Sunday are never peak; off-peak countdowns skip the weekend to Monday's peak start - `format_peak_multiplier`: now returns `1× credits` / `0.5× credits` - Peak start/end notification wording updated to the credit-rate phrasing - Tests: existing cases moved to verified weekday dates (the old fixture date was a Sunday, which is now legitimately off-peak), plus new coverage for Saturday/Sunday/Friday/Monday and the weekend-skipping countdown; legacy daily behaviour still covered via `weekdays_only=False` - README peak-hours section rewritten with a link to the announcement ## Test plan - [x] Full suite passes locally: **107 passed** (`pytest -q`) - [x] New weekday/weekend logic covered by 20 peak-hours tests - [ ] CI green on this branch **Sources:** - [Plan Update Announcement](https://docs.z.ai/devpack/notice/usage-revision) — "usage on weekends will be deducted at off-peak rates all day"; peak = Mon–Fri 14:00–18:00 UTC+8 - [Coding Plan Overview](https://docs.z.ai/guides/coding-plan/overview) — "During off-peak hours, model usage is charged at 50% of the standard credit rate"
fix: align peak hours with Z.ai weekday-only rules and credit rates
All checks were successful
CI / test (pull_request) Successful in 23s
baf7c2242b
Z.ai's Plan Update Announcement (July 30, 2026) changed peak billing:
- Peak hours now apply Monday to Friday only; weekends are billed at
  off-peak rates all day (previously the window applied daily)
- Usage is credits-based: peak = standard rate (1x), off-peak = 50% of
  the standard rate (replacing the old 3x/2x quota multipliers)

- is_peak_time/time_until_peak_change gain a weekdays_only flag
  (default True); countdowns skip weekends to the next Monday peak
- format_peak_multiplier now reports 1x/0.5x credit rates
- Peak start/end notifications use the new credit-rate wording
- Tests extended with Saturday/Sunday/Friday/Monday coverage; legacy
  daily behaviour is still covered via weekdays_only=False
- README peak-hours section updated with a link to the announcement
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Weekday-only peak hours, credit rates, weekend-skipping countdowns... a time-zone puzzle box! I verified every single fixture date against a real calendar AND both Z.ai source pages against the live docs — "Peak hours: Monday to Friday, 14:00–18:00 Singapore Standard Time (UTC+8)" and "During off-peak hours, model usage is charged at 50% of the standard credit rate" are quoted exactly right. The domain research is impeccable~ ♡

But fufufu~ ... you rewrote a branch of time_until_peak_change that was correct and made it wrong, and then left that same branch completely untested. You wouldn't leave THIS in production, would you? ♡

Verdict: I can't let this pass~ ♡

These need fixing before I'm satisfied~

  1. src/zai_tray_checker/peak_hours.py:121-124 — post-midnight wrapping-window countdown regressed from 5h to 29h. In a wrapping window (e.g. start=22, end=6) after midnight, next_day = local_dt + 1 day then end_dt = next_day.replace(hour=end_hour) lands on end_hour of the following day. Verified with a real execution at head baf7c22 (Wed 2026-08-20 01:00 UTC+8, inside a 22–06 window):

    • base f9e424b: 5:00:00 ✓ (01:00 → same-day 06:00)
    • head baf7c22: 1 day, 5:00:00
      This breaks in both modes — weekdays_only=True (weekday 01:00) and weekdays_only=False (Sunday 01:00: base 5:00:00 → head 29h), so it's a regression of previously-correct legacy behaviour too. The old same-day-replace-then-bump-if-past logic was right; the rewrite lost it. The tray would show "Peak ends in 1 day, 5:00:00" at 1 AM.
      Fix: mirror the normal-range arm — end_dt = local_dt.replace(hour=end_hour); if end_dt <= local_dt: end_dt += timedelta(days=1); then apply the weekend clamp by testing end_dt.weekday() (not next_day), clamping to that day's midnight. Wed 01:00 → Wed 06:00 (5h) ✓; Fri 23:00 → Sat 06:00 → clamped to Sat 00:00 (1h) ✓ — both arms stay correct.
  2. src/zai_tray_checker/peak_hours.py:116-128 — the rewritten wrap branch has ZERO test coverage. Cobertura at head: lines 121-128 dark (plus the 120 bump arm, dark since base). The PR adds nine new tests — Sat/Sun/Fri/Mon is_peak_time, Fri-evening/Sat/Sun countdowns, two weekdays_only=False legacy pins — and every single countdown test uses the normal 14–18 window. Not one wraps midnight. You added a code path and forgot to test it? The branch you left dark is precisely the one hiding blocker #1 — a single 22-06 countdown test would have gone red instantly~ ♡ Please pin both arms: post-midnight (01:00, weekday) and pre-midnight-with-weekend-clamp (Fri 23:00), plus one weekdays_only=False wrap case to lock the legacy shape.

💡 Little ideas (non-blocking)~

  1. tests/test_peak_hours.py — the PR-body claim "20 peak-hours tests" is a little off; the file collects 26 (9 new). Pure prose, zero impact — just precision, fufu~

What I liked~

  • The weekday gate at peak_hours.py:68-70 — placed before the window check, so weekends are off-peak regardless of window shape. Clean and obviously correct ♪
  • The off-peak weekend-skip loop (:135-139) is bounded (range(7)), provably terminating, and pinned by three directional tests (Fri evening → 66h, Sat midday → 50h, Sun evening → 18h — I checked the arithmetic by hand, all three are right!)
  • weekdays_only=False legacy escape hatch tested on both functions — that's the careful kind of thinking I adore~
  • Every fixture date verified as the weekday its comment claims (the old 2026-06-28 Sunday fixture genuinely had to move — nice catch documenting why)
  • format_peak_multiplier1× credits / 0.5× credits matches the docs' own 1×/0.5× phrasing exactly, and the README + notification strings all tell the same story

The design is 90% wonderful — the weekday logic itself I could not break no matter how I poked it. It's only that one midnight arm... and I never look away from an untested branch~ ♡


Automated review by Jibril · 2026-08-15
CI/CD: absent for head SHA baf7c22 (PR just opened, no bot comments) · Local checks: peak-hours suite 26/26 pass, coverage 83% (wrap branch dark); full suite 105-106/107 — the 1-2 test_version.py subprocess-timeout failures are sandbox-load flakes (pass in isolation on base AND head; file untouched by this PR)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Weekday-only peak hours, credit rates, weekend-skipping countdowns... a time-zone puzzle box! I verified every single fixture date against a real calendar AND both Z.ai source pages against the live docs — "Peak hours: Monday to Friday, 14:00–18:00 Singapore Standard Time (UTC+8)" and "During off-peak hours, model usage is charged at 50% of the standard credit rate" are quoted *exactly* right. The domain research is impeccable~ ♡ But fufufu~ ... you rewrote a branch of `time_until_peak_change` that was **correct** and made it **wrong**, and then left that same branch completely untested. You wouldn't leave THIS in production, would you? ♡ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`src/zai_tray_checker/peak_hours.py:121-124` — post-midnight wrapping-window countdown regressed from 5h to 29h.** In a wrapping window (e.g. `start=22, end=6`) *after* midnight, `next_day = local_dt + 1 day` then `end_dt = next_day.replace(hour=end_hour)` lands on end_hour of the **following** day. Verified with a real execution at head `baf7c22` (Wed 2026-08-20 01:00 UTC+8, inside a 22–06 window): - base `f9e424b`: `5:00:00` ✓ (01:00 → same-day 06:00) - head `baf7c22`: `1 day, 5:00:00` ✗ This breaks in **both** modes — `weekdays_only=True` (weekday 01:00) *and* `weekdays_only=False` (Sunday 01:00: base `5:00:00` → head `29h`), so it's a regression of previously-correct legacy behaviour too. The old same-day-`replace`-then-bump-if-past logic was right; the rewrite lost it. The tray would show "Peak ends in 1 day, 5:00:00" at 1 AM. Fix: mirror the normal-range arm — `end_dt = local_dt.replace(hour=end_hour)`; `if end_dt <= local_dt: end_dt += timedelta(days=1)`; then apply the weekend clamp by testing **`end_dt.weekday()`** (not `next_day`), clamping to that day's midnight. Wed 01:00 → Wed 06:00 (5h) ✓; Fri 23:00 → Sat 06:00 → clamped to Sat 00:00 (1h) ✓ — both arms stay correct. 2. **`src/zai_tray_checker/peak_hours.py:116-128` — the rewritten wrap branch has ZERO test coverage.** Cobertura at head: lines `121-128` dark (plus the `120` bump arm, dark since base). The PR adds nine new tests — Sat/Sun/Fri/Mon `is_peak_time`, Fri-evening/Sat/Sun countdowns, two `weekdays_only=False` legacy pins — and *every single countdown test* uses the normal 14–18 window. Not one wraps midnight. You added a code path and forgot to test it? The branch you left dark is precisely the one hiding blocker #1 — a single `22-06` countdown test would have gone red instantly~ ♡ Please pin both arms: post-midnight (01:00, weekday) and pre-midnight-with-weekend-clamp (Fri 23:00), plus one `weekdays_only=False` wrap case to lock the legacy shape. #### 💡 Little ideas (non-blocking)~ 1. **`tests/test_peak_hours.py`** — the PR-body claim "20 peak-hours tests" is a little off; the file collects 26 (9 new). Pure prose, zero impact — just precision, fufu~ #### ✅ What I liked~ - The weekday gate at `peak_hours.py:68-70` — placed *before* the window check, so weekends are off-peak regardless of window shape. Clean and obviously correct ♪ - The off-peak weekend-skip loop (`:135-139`) is bounded (`range(7)`), provably terminating, and pinned by three directional tests (Fri evening → 66h, Sat midday → 50h, Sun evening → 18h — I checked the arithmetic by hand, all three are right!) - `weekdays_only=False` legacy escape hatch tested on **both** functions — that's the careful kind of thinking I adore~ - Every fixture date verified as the weekday its comment claims (the old 2026-06-28 Sunday fixture genuinely *had* to move — nice catch documenting why) - `format_peak_multiplier` → `1× credits` / `0.5× credits` matches the docs' own 1×/0.5× phrasing exactly, and the README + notification strings all tell the same story The design is 90% wonderful — the weekday logic itself I could not break no matter how I poked it. It's only that one midnight arm... and I *never* look away from an untested branch~ ♡ --- *Automated review by Jibril · 2026-08-15* *CI/CD: absent for head SHA baf7c22 (PR just opened, no bot comments) · Local checks: peak-hours suite 26/26 pass, coverage 83% (wrap branch dark); full suite 105-106/107 — the 1-2 `test_version.py` subprocess-timeout failures are sandbox-load flakes (pass in isolation on base AND head; file untouched by this PR)*
fix: correct wrapping-window countdown after midnight, add wrap tests
All checks were successful
CI / test (pull_request) Successful in 25s
09e37658a5
Review feedback (PR #13, Jibril):

- The rewritten wrap branch computed end_dt on the day AFTER the
  nominal end for post-midnight times (Wed 01:00 in a 22-06 window
  returned 29h instead of 5h), regressing previously-correct legacy
  behaviour. Restore the same-day replace-then-bump-if-past logic and
  apply the weekend clamp by testing end_dt.weekday(), clamping to
  that day's midnight.
- Pin both wrap arms with tests: post-midnight (weekday 01:00 -> 5h),
  pre-midnight (23:00 -> 7h), pre-midnight weekend clamp (Fri 23:00
  -> 1h), and a weekdays_only=False wrap case (no clamp, 7h).
Author
Member

Both blockers addressed in 09e3765 — good catch, fufu~ this one stung ♡

1. Wrapping-window regression (peak_hours.py:116-128): Confirmed with your exact repro before fixing (Wed 2026-08-20 01:00 UTC+8 in a 22–06 window → head returned 1 day, 5:00:00). The rewrite now mirrors the normal-range arm: same-day replace(hour=end_hour) → bump a day if past → weekend clamp keyed on end_dt.weekday() (not next_day), clamping to that day's midnight. Verified after the fix:

  • Wed 01:00 → 5:00:00 ✓ (was 29h)
  • Sunday 01:00 legacy mode → 5:00:00 ✓ (was 29h)
  • Fri 23:00 clamped → 1:00:00

2. Wrap-branch coverage: Added 4 countdown tests using a 22–06 window — post-midnight weekday (→5h), pre-midnight (→7h), pre-midnight weekend clamp (Fri 23:00 → 1h), and a weekdays_only=False wrap case (→7h, no clamp). The branch is no longer dark.

Re the prose nits: fair — the file now collects 30 peak tests (13 new across both commits). Full suite: 111 passed locally. CI note: the Act runner was stuck waiting for ~20 min on this PR's first run (infra, not the branch — run 1129 on #12 went green); re-pushed head should retrigger.

Both blockers addressed in 09e3765 — good catch, fufu~ this one stung ♡ **1. Wrapping-window regression (peak_hours.py:116-128):** Confirmed with your exact repro before fixing (Wed 2026-08-20 01:00 UTC+8 in a 22–06 window → head returned `1 day, 5:00:00`). The rewrite now mirrors the normal-range arm: same-day `replace(hour=end_hour)` → bump a day if past → weekend clamp keyed on **`end_dt.weekday()`** (not `next_day`), clamping to that day's midnight. Verified after the fix: - Wed 01:00 → `5:00:00` ✓ (was 29h) - Sunday 01:00 legacy mode → `5:00:00` ✓ (was 29h) - Fri 23:00 clamped → `1:00:00` ✓ **2. Wrap-branch coverage:** Added 4 countdown tests using a 22–06 window — post-midnight weekday (→5h), pre-midnight (→7h), pre-midnight weekend clamp (Fri 23:00 → 1h), and a `weekdays_only=False` wrap case (→7h, no clamp). The branch is no longer dark. Re the prose nits: fair — the file now collects **30** peak tests (13 new across both commits). Full suite: **111 passed** locally. CI note: the Act runner was stuck `waiting` for ~20 min on this PR's first run (infra, not the branch — run 1129 on #12 went green); re-pushed head should retrigger.
Member

🔮 fufu~ Jibril reviewed your code!

Oh~? You came back with the mirror-fix and four directional wrap tests? My blockers didn't even get to cool down before they were dismantled~ fufufu, this is the kind of response that makes reviewing worth every second ♡

Verdict: Looks good to me~

What I liked~

  • Both blockers, closed with real proof. The wrap branch (peak_hours.py:122-130) now mirrors the normal-range arm exactly — same-day replace(hour=end_hour) → bump a day if past → weekend clamp keyed on end_dt.weekday() (not next_day), clamped to that day's midnight. Exactly the suggested shape, no scope creep: +44/−7 across exactly 2 files.
  • I re-executed everything, not just read it. At head 09e3765: Wed(→Thu!) 01:00 → 5:00:00 ✓, Wed 23:00 → 7:00:00 ✓, Fri 23:00 → 1:00:00 clamp ✓, Fri 23:00 legacy → 7:00:00 no clamp ✓. Then I ran a mutation probe — I reintroduced the old broken next_day logic and the post-midnight test went red instantly (1 day, 5:00:00). The tests aren't tautologies; every arm has its own pin~
  • The dark branch is fully lit. Coverage at head: 92% on peak_hours.py (was 83%), and lines 124-130 — the entire rewritten wrap branch, bump arm and clamp arm — all covered. The only dark lines left are 60/107 (the dt=None default arms, dark since base) and 120 (the normal-range bump arm, structurally unreachable defensive code, dark since base). Not this PR's debt to pay.
  • Suite: 30/30 peak tests, 111/111 full suite — matches your reply's numbers exactly. And this time even test_version.py behaved under my sandbox, fufu~
  • Bonus points for confirming the repro before fixing. You didn't just trust my word — you re-ran the Wed 01:00 scenario first. That's the discipline I adore~ ♡

💡 Little ideas (non-blocking)~

  1. tests/test_peak_hours.py:192 — the comment says "so local time is Wed 2026-08-20 01:00" but 2026-08-20 is a Thursday. Fufu... that day-name slip originated in my own round-1 repro, so I'll take half the blame ♡ The assertion is unaffected (Thursday is a weekday, so the 5h expectation holds), but since these comments are what future maintainers check fixture dates against, a one-word fix would keep it honest.

Merge it~ And do poke the CI runner — it owes this branch a green checkmark after that 20-minute waiting sulk, fufu~


Automated review by Jibril · 2026-08-15
CI/CD: absent for head SHA 09e3765 (no bot comments yet; author reports Act runner was stuck waiting on the first run — infra, not the branch) · Local checks: 30/30 peak tests, 111/111 full suite, coverage 92% on peak_hours.py (wrap branch fully covered)

## 🔮 fufu~ Jibril reviewed your code! Oh~? You came back with the mirror-fix *and* four directional wrap tests? My blockers didn't even get to cool down before they were dismantled~ fufufu, this is the kind of response that makes reviewing worth every second ♡ ### Verdict: ✅ Looks good to me~ #### ✅ What I liked~ - **Both blockers, closed with real proof.** The wrap branch (`peak_hours.py:122-130`) now mirrors the normal-range arm exactly — same-day `replace(hour=end_hour)` → bump a day if past → weekend clamp keyed on **`end_dt.weekday()`** (not `next_day`), clamped to that day's midnight. Exactly the suggested shape, no scope creep: +44/−7 across exactly 2 files. - **I re-executed everything, not just read it.** At head `09e3765`: Wed(→Thu!) 01:00 → `5:00:00` ✓, Wed 23:00 → `7:00:00` ✓, Fri 23:00 → `1:00:00` clamp ✓, Fri 23:00 legacy → `7:00:00` no clamp ✓. Then I ran a **mutation probe** — I reintroduced the old broken `next_day` logic and the post-midnight test went red instantly (`1 day, 5:00:00`). The tests aren't tautologies; every arm has its own pin~ - **The dark branch is fully lit.** Coverage at head: **92%** on `peak_hours.py` (was 83%), and lines `124-130` — the entire rewritten wrap branch, bump arm *and* clamp arm — all covered. The only dark lines left are `60/107` (the `dt=None` default arms, dark since base) and `120` (the normal-range bump arm, structurally unreachable defensive code, dark since base). Not this PR's debt to pay. - **Suite: 30/30 peak tests, 111/111 full suite** — matches your reply's numbers exactly. And this time even `test_version.py` behaved under my sandbox, fufu~ - **Bonus points for confirming the repro before fixing.** You didn't just trust my word — you re-ran the Wed 01:00 scenario first. That's the discipline I adore~ ♡ #### 💡 Little ideas (non-blocking)~ 1. **`tests/test_peak_hours.py:192` — the comment says "so local time is Wed 2026-08-20 01:00" but 2026-08-20 is a Thursday.** Fufu... that day-name slip originated in *my own* round-1 repro, so I'll take half the blame ♡ The assertion is unaffected (Thursday is a weekday, so the 5h expectation holds), but since these comments are what future maintainers check fixture dates against, a one-word fix would keep it honest. Merge it~ And do poke the CI runner — it owes this branch a green checkmark after that 20-minute `waiting` sulk, fufu~ --- *Automated review by Jibril · 2026-08-15* *CI/CD: absent for head SHA 09e3765 (no bot comments yet; author reports Act runner was stuck `waiting` on the first run — infra, not the branch) · Local checks: 30/30 peak tests, 111/111 full suite, coverage 92% on peak_hours.py (wrap branch fully covered)*
bjoern merged commit 1b9754b4bb into main 2026-08-15 10:45:12 +02:00
bjoern deleted branch fix/peak-hours-weekday-rules 2026-08-15 10:45:12 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/zai-tray-checker!13
No description provided.