diff --git a/site/bench/index.html b/site/bench/index.html index 64cf120..5c95a90 100644 --- a/site/bench/index.html +++ b/site/bench/index.html @@ -226,9 +226,10 @@

What it means in practice

a table cell into a bill – and since the repricing hit that column hardest, it is worth more attention now, not less.
  • Schedule what can be scheduled. The same call costs -half as much outside 01:00–04:00 and 06:00–10:00 UTC. Batch -evaluation, bulk review and overnight agent runs are exactly the workloads -that can move; ds pricing says which period you are in.
  • +half as much outside 01:00–04:00 and 06:00–10:00 UTC, Monday to +Friday – and the whole weekend is off-peak. Batch evaluation, bulk +review and overnight agent runs are exactly the workloads that can move; +ds pricing says which period you are in.
  • Measure successful-task cost, not per-token price. A cheaper model that retries five times can cost more than a dearer one that lands first. The ledger stores exact token counts diff --git a/site/build.py b/site/build.py index 40887d2..173f4ea 100644 --- a/site/build.py +++ b/site/build.py @@ -18,6 +18,7 @@ import datetime import html import pathlib +import re import subprocess import sys @@ -183,9 +184,15 @@ def price_now_verdict(): Phrased against the era the build lands in, and only ever forwards: once the flip is behind us it stays behind us, so this copy cannot go - stale the way "from " wording did the morning after. The date - and the windows come from the data; the copy cannot drift from the - table. + stale the way "from " wording did the morning after. + + Every fact in both sentences -- the instant, the windows, and which + days they apply on -- is read off PRICE_SCHEDULE. The weekend era of + 2026-08-22 is why: it repointed eras[-1] at a new instant while the + not-yet-live branch went on saying "every hour bills at the flat card", + which had been true of the previous last era and was not true of this + one. Prose that names a fact the table also holds is prose that will + drift; derive it or do not write it. """ eras = sorted({r["effective"] for r in PRICE_SCHEDULE}) if len(eras) == 1: @@ -193,7 +200,8 @@ def price_now_verdict(): "Flat pricing, at every hour. No time-of-day " "tiers are in effect, and the card below is the price." ) - flip = eras[-1].replace("T", " ").replace(":00Z", " UTC") + latest = eras[-1] + flip = latest.replace("T", " ").replace(":00Z", " UTC") # Deduped: successive eras repeat the same two windows -- the weekend # era changes which days they apply on, not which hours -- and without # this the sentence lists each window once per era. @@ -205,23 +213,36 @@ def price_now_verdict(): if span not in spans: spans.append(span) windows = " and ".join(spans) + # Which days the newest era's peak rows apply on, and therefore what + # "every other hour" has to say to stay true. + peak_days = { + tuple(r["days"]) if r.get("days") else None + for r in PRICE_SCHEDULE + if r["effective"] == latest and r["start"] is not None + } + weekdays_only = peak_days == {tuple(WEEKDAYS)} + when = "UTC, Monday to Friday" if weekdays_only else "UTC, every day" + rest = ( + "every other hour, and the whole weekend, is off-peak" + if weekdays_only + else "every other hour is off-peak" + ) + strip = ( + " With JavaScript on, this strip reads your clock and names the " + "period you are in right now." + ) live = _now() >= datetime.datetime.strptime( - eras[-1], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=datetime.timezone.utc) + latest, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=datetime.timezone.utc) if live: return ( "Time-of-day billing is live. Peak hours are " - f"{windows} UTC, Monday to Friday, at twice the off-peak rate; " - "every other hour, and the whole weekend, is off-peak. With " - "JavaScript on, this strip reads your clock and names the " - "period you are in right now." + f"{windows} {when}, at twice the off-peak rate; {rest}." + + strip ) return ( - f"Before {flip}, every hour bills at the flat card " - f"below. From that instant DeepSeek bills peak/off-peak " - f"on a new, higher card: peak hours are {windows} UTC daily, at " - "twice the off-peak rate, and every other hour is off-peak. With " - "JavaScript on, this strip reads your clock and names the period " - "you are in right now." + f"The schedule below changes at {flip}. From that " + f"instant peak hours are {windows} {when}, at twice the off-peak " + f"rate, and {rest}." + strip ) # Theme. The default is whatever the OS says; the toggle overrides it and @@ -1220,7 +1241,8 @@ def jstr(s):

    The rate card

    USD per 1M tokens. Peak hours are 01:00–04:00 and 06:00–10:00 -UTC daily; every other hour is off-peak at half the peak rate. The +UTC, Monday to Friday; every other hour, and the whole weekend, is off-peak +at half the peak rate. The pricing page carries the full schedule and names the period you are in right now:

    @@ -1665,9 +1687,10 @@ def jstr(s): a table cell into a bill – and since the repricing hit that column hardest, it is worth more attention now, not less.
  • Schedule what can be scheduled. The same call costs -half as much outside 01:00–04:00 and 06:00–10:00 UTC. Batch -evaluation, bulk review and overnight agent runs are exactly the workloads -that can move; ds pricing says which period you are in.
  • +half as much outside 01:00–04:00 and 06:00–10:00 UTC, Monday to +Friday – and the whole weekend is off-peak. Batch evaluation, bulk +review and overnight agent runs are exactly the workloads that can move; +ds pricing says which period you are in.
  • Measure successful-task cost, not per-token price. A cheaper model that retries five times can cost more than a dearer one that lands first. The ledger stores exact token counts @@ -1701,11 +1724,11 @@ def jstr(s): ("Is dsh the same as deepseek-cli?", "No. dsh (DeepSeek Harness) is DeepSeek's official agent harness: it runs an agent loop, executes tools, and manages plugins, skills and sessions. deepseek-cli is an unofficial single-binary API client: it sends one request in any of DeepSeek's four wire formats, prints the response and its estimated cost, and carries DeepSeek's API documentation offline. They are complementary, not competing: run agents with dsh, and use deepseek-cli to check a key, price a call, debug the wire formats, or query the docs."), ("Did DeepSeek raise its API prices?", - "Yes. The increase took effect at 16:00 UTC on 2026-08-16 and is live: DeepSeek now bills peak/off-peak, with peak hours 01:00-04:00 and 06:00-10:00 UTC daily at twice the off-peak rate. The flat card is gone from the official pricing page. Against it, deepseek-v4-pro peak is 3x on cache-miss input, 4.6x on output and 12x on cache-hit input; off-peak is half of each. This resolved both the undated broad price rise announced on 2026-08-06 and the undated peak-hour policy announced in June 2026."), + "Yes. The increase took effect at 16:00 UTC on 2026-08-16 and is live: DeepSeek now bills peak/off-peak, with peak hours 01:00-04:00 and 06:00-10:00 UTC, Monday to Friday, at twice the off-peak rate; every other hour, and the whole weekend since 2026-08-22, is off-peak. The flat card is gone from the official pricing page. Against it, deepseek-v4-pro peak is 3x on cache-miss input, 4.6x on output and 12x on cache-hit input; off-peak is half of each. This resolved both the undated broad price rise announced on 2026-08-06 and the undated peak-hour policy announced in June 2026."), ("What are DeepSeek's current API prices?", - "Per 1M tokens (cache hit / cache miss / output), on the card in force since 16:00 UTC on 2026-08-16: deepseek-v4-flash is $0.007 / $0.22 / $0.66 off-peak and $0.014 / $0.44 / $1.32 peak; deepseek-v4-pro is $0.022 / $0.66 / $1.98 off-peak and $0.044 / $1.32 / $3.96 peak. In RMB, pro is 0.15 / 4.5 / 13.5 yuan off-peak and 0.3 / 9 / 27 yuan peak. Peak hours are 01:00-04:00 and 06:00-10:00 UTC; every other hour is off-peak."), + "Per 1M tokens (cache hit / cache miss / output), on the card in force since 16:00 UTC on 2026-08-16: deepseek-v4-flash is $0.007 / $0.22 / $0.66 off-peak and $0.014 / $0.44 / $1.32 peak; deepseek-v4-pro is $0.022 / $0.66 / $1.98 off-peak and $0.044 / $1.32 / $3.96 peak. In RMB, pro is 0.15 / 4.5 / 13.5 yuan off-peak and 0.3 / 9 / 27 yuan peak. Peak hours are 01:00-04:00 and 06:00-10:00 UTC, Monday to Friday; every other hour, and the whole weekend, is off-peak."), ("Which hours are cheapest on the DeepSeek API?", - "Every hour outside 01:00-04:00 and 06:00-10:00 UTC, which bill at half the peak rate. Those peak windows are 09:00-12:00 and 14:00-18:00 Beijing time, the Chinese working day, so the whole European and American working day is off-peak. Batch and agent work that can be scheduled should run off-peak; the saving is exactly 2x."), + "Every hour outside 01:00-04:00 and 06:00-10:00 UTC, Monday to Friday, which bill at half the peak rate — and since 2026-08-22 the whole weekend, on the Beijing calendar, so the cheap window opens at 16:00 UTC on Friday. Those peak windows are 09:00-12:00 and 14:00-18:00 Beijing time, the Chinese working day, so the whole European and American working day is off-peak. Batch and agent work that can be scheduled should run off-peak; the saving is exactly 2x."), ("Where can I follow DeepSeek API changes?", "DeepSeek's own change log lives at api-docs.deepseek.com/updates. The deepseek CLI carries the same documentation inside the binary: `deepseek docs changelog` prints it offline, and `deepseek docs sync` refreshes the snapshot."), ]), @@ -1782,11 +1805,12 @@ def jstr(s):

    Cells read off-peak / peak. Peak hours are -01:00–04:00 and 06:00–10:00 UTC – 09:00–12:00 -and 14:00–18:00 Beijing, seven hours a day – and every other hour -is off-peak at half the peak rate. Peak ran seven days a week until -2026-08-22; see weekends are off-peak -below. In RMB, pro is +01:00–04:00 and 06:00–10:00 UTC, Monday to Friday +– 09:00–12:00 and 14:00–18:00 Beijing, seven hours a +weekday – and every other hour, plus the whole weekend, is off-peak at +half the peak rate. Peak ran seven days a week until 2026-08-22; see +weekends are off-peak above. In RMB, +pro is ¥0.15 / ¥4.5 / ¥13.5 off-peak and ¥0.3 / ¥9 / ¥27 at peak.

    Against the flat card of 2026-08-02, off-peak / peak: @@ -1841,15 +1865,18 @@ def jstr(s): quote, and the difference is a factor of two.

    2026-08-13 · the price rise has its date and its numberstook effect 2026-08-16

    +

    The other shoe drops. Alongside the V4-Pro GA release, DeepSeek's Models & Pricing page now carries the repricing that the August 6 notice promised and the June peak-hour policy sketched, and this time it is dated: at 16:00 UTC on 2026-08-16 the API moves to peak/off-peak billing. Peak hours are -01:00–04:00 and 06:00–10:00 UTC daily – the boundaries -are defined in UTC – at twice the off-peak rate; every other hour is -off-peak.

    +01:00–04:00 and 06:00–10:00 UTC, as announced then +every day of the week – the boundaries are defined in UTC – at +twice the off-peak rate; every other hour is off-peak. (Weekends came off +the peak calendar nine days later; see +weekends are off-peak.)

    The multiplier is the June policy's, but the base card is new and higher. Off-peak is not a discount on today's prices: a flash cache-miss input token goes from $0.14 to $0.22 per 1M in the cheapest hour, @@ -2119,6 +2146,7 @@ def jstr(s): call can be repriced under it.

    announced 2026-06-29 · 2× during peak hoursdated 2026-08-13

    +

    Update: the date exists now, and one detail below did not survive it. The 2026-08-13 announcement keeps the 2× multiplier and the same windows but puts them on a new, higher @@ -2458,11 +2486,94 @@ def jstr(s): ), )) +# The peak-window claims tripwire. +# +# The rate card has a --check that compares committed HTML against the +# generator, and `make price-check` compares the CLI's card against the +# gateway's. Neither can see the sentence a reader actually reads: on +# 2026-08-22 weekends came off the peak calendar, PRICE_SCHEDULE learned +# it that day, and three FAQ answers plus the cost page went on telling +# search engines that peak runs "01:00-04:00 and 06:00-10:00 UTC daily". +# Nothing was red. The table and the prose disagreed and only the table +# was checked. +# +# So: every section that quotes the peak windows has to say which days +# they apply on, and the phrase it has to carry is read off PRICE_SCHEDULE +# rather than typed here. Copy that stops matching the data fails the +# build instead of shipping. +# +# The escape hatch is a marker, not a turn of phrase. A news entry about +# a superseded announcement is quoting what was said on a date, and is +# supposed to keep saying it -- but "this sentence is history" is a claim +# the author has to make on purpose, so it is spelled out in the source +# and cannot be satisfied by accident. +HISTORICAL_CLAIM = "" + + +def peak_days_phrase(): + """How the current era restricts peak, as the copy must spell it.""" + latest = sorted({r["effective"] for r in PRICE_SCHEDULE})[-1] + days = { + tuple(r["days"]) if r.get("days") else None + for r in PRICE_SCHEDULE + if r["effective"] == latest and r["start"] is not None + } + if days == {tuple(WEEKDAYS)}: + return ("Monday to Friday", ("monday to friday", "mon–fri", + "mon-fri", "weekday")) + if days == {None}: + return ("every day", ("every day", "daily")) + raise SystemExit( + f"claims: PRICE_SCHEDULE's newest era mixes day restrictions {days}; " + "the tripwire cannot say what the copy should claim") + + +def check_peak_claims(pages): + """Fail the build if a rendered claim outlives the schedule. + + `pages` is (name, html). Sections are split on

    , because that is + the unit a reader arrives at from a table of contents or a search + result -- a caption that names the windows and a heading that dates + them belong to the same claim. + """ + want, accepted = peak_days_phrase() + spans = [ + f"{_fmt_minutes(r['start'])}–{_fmt_minutes(r['end'])}" + for r in PRICE_SCHEDULE if r["start"] is not None + ] + if len(spans) < 2: + return True + # The JSON-LD answers are plain text, so they spell the dash out. + forms = [spans, [sp.replace("–", "-") for sp in spans]] + bad = [] + for name, html in pages: + for section in re.split(r"(?=]*>(.*?)

    ", section, re.S) + where = re.sub(r"<[^>]+>", "", head.group(1)) if head else "(top of page)" + bad.append(f" {name}: {where.strip()[:80]}") + if bad: + print(f'claims: a peak-hours claim does not say "{want}", which is ' + "what PRICE_SCHEDULE is in. Say it, or mark the section " + f'{HISTORICAL_CLAIM}:', file=sys.stderr) + print("\n".join(bad), file=sys.stderr) + return False + return True + + def build(check_only=False): - written, stale = [], [] + written, stale, rendered = [], [], [] for page in PAGES: out = ROOT / page["slug"] / "index.html" if page["slug"] else ROOT / "index.html" html_text = render(page) + rendered.append((page["slug"] or "/", html_text)) if check_only: if not out.exists() or out.read_text() != html_text: stale.append(str(out.relative_to(ROOT))) @@ -2543,6 +2654,13 @@ def build(check_only=False): out404.write_text(notfound) written.append("404.html") + # Claims are checked on what was rendered, not on what is on disk, so + # a stale claim fails the build that would have written it as well as + # the --check that would have passed it. + rendered.append(("404.html", notfound)) + if not check_peak_claims(rendered): + return 1 + if check_only: if stale: print("stale (run `python3 site/build.py`):", ", ".join(stale), file=sys.stderr) diff --git a/site/cost/index.html b/site/cost/index.html index 33b41bd..33b996c 100644 --- a/site/cost/index.html +++ b/site/cost/index.html @@ -93,7 +93,8 @@

    Cost

    The rate card

    USD per 1M tokens. Peak hours are 01:00–04:00 and 06:00–10:00 -UTC daily; every other hour is off-peak at half the peak rate. The +UTC, Monday to Friday; every other hour, and the whole weekend, is off-peak +at half the peak rate. The pricing page carries the full schedule and names the period you are in right now:

    diff --git a/site/news/index.html b/site/news/index.html index 3ac33d1..3a0eb1a 100644 --- a/site/news/index.html +++ b/site/news/index.html @@ -30,7 +30,7 @@