File size: 17,173 Bytes
bf8519f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | """harness/windows.py β the DATE WINDOW vocabulary (CG-7, owner item 2, 2026-07-26).
A filter condition on a measure reads:
Where [Sales] [in the last 90 days] [>] [5,000]
The middle bracket is this module. It turns a window SPEC β a small JSON object a saved view can
persist β into a concrete `(date_from, date_to)` pair of ISO dates.
WHY IT IS ITS OWN MODULE, AND WHY IT TAKES `today` AS AN ARGUMENT
-----------------------------------------------------------------------------------------------
Two engines must agree on what "this quarter" means: this one (which compiles the SQL) and
`customer-grid/windows.ts` (which renders the label the user reads and the bounds the client
engine would use). If they disagree by one day, the grid shows a number the label denies, and
nothing errors. `aios-web/verify_windows.py` holds them in lock-step over a fixed set of probe
dates, exactly as verify_filter_engine.py does for the filter tree.
`today` is a PARAMETER, never `date.today()` read inside. Three reasons, all learned:
- a gate cannot compare two engines on "now" β the two runs are milliseconds apart and land on
different sides of midnight roughly once every few thousand runs, which is the worst kind of
flake because it looks like a real divergence
- a window resolved during a request must not shift between the count query and the row query
- the tenant's day boundary is the tenant's, not the server's
DESIGN NOTES THAT ARE EASY TO GET WRONG
-----------------------------------------------------------------------------------------------
- Bounds are INCLUSIVE at both ends and are DATES, not timestamps. `store_query` casts both
sides to TIMESTAMP and appends 23:59:59 to the upper bound, so a bare date is correct there;
the client compares the 10-char ISO prefix, so a bare date is correct there too.
- "Last N days" INCLUDES today. Airtable's "the past week" means the last 7 days up to and
including now, not the 7 days before yesterday. Off-by-one here silently drops today's
orders from every "recent" filter β invisible until someone asks why a sale they just
entered is missing.
- Week starts MONDAY (ISO 8601). Stated rather than defaulted: Python's weekday() is
Monday=0 and JavaScript's getDay() is Sunday=0, so the two engines disagree unless one of
them is explicitly corrected. That correction is the single most likely divergence in this
file, and the gate probes a Sunday and a Monday for exactly that reason.
- A `custom` window with only one bound is legal and means open-ended on the other side.
- An UNRESOLVABLE window returns None rather than a default. A window that quietly becomes
"all time" would widen a filter while still reporting an authoritative count.
"""
import datetime as _dt
#: Every window kind the UI may offer. Mirrors WINDOW_KINDS in customer-grid/windows.ts;
#: verify_windows.py asserts the two lists are identical, because a kind the client can emit and
#: the server cannot resolve is a filter that silently stops narrowing.
WINDOW_KINDS = (
"all_time",
"today",
"yesterday",
"this_week",
"last_week",
"this_month",
"last_month",
"this_quarter",
"last_quarter",
"this_year",
"last_year",
"ytd",
"ytd_last_year",
"ltm",
"past_week",
"past_month",
"past_year",
"last_n_days",
"next_n_days",
"custom",
)
#: Kinds that carry an integer `n`.
N_KINDS = frozenset({"last_n_days", "next_n_days"})
#: How each kind reads in a sentence. The UI renders these; they are here so the label and the
#: arithmetic cannot drift apart in the one place a user would never think to check.
WINDOW_LABELS = {
"all_time": "all time",
"today": "today",
"yesterday": "yesterday",
"this_week": "this week",
"last_week": "last week",
"this_month": "this month",
"last_month": "last month",
"this_quarter": "this quarter",
"last_quarter": "last quarter",
"this_year": "this year",
"last_year": "last year",
"ytd": "year to date",
# "last year to date" (LYTD), not "year to date, last year": the longer phrasing CLIPPED to
# "year to date, last ye" in the 146px window select β and this vocabulary is CLOSED, so
# the rule for it is that it stays readable rather than that it fits. Seen in a live
# screenshot of the owner's own comparison; every assertion in that run was green.
"ytd_last_year": "last year to date",
"ltm": "the last 12 months",
"past_week": "the past week",
"past_month": "the past month",
"past_year": "the past year",
"last_n_days": "the last {n} days",
"next_n_days": "the next {n} days",
"custom": "a custom range",
}
MAX_N = 3650 # ten years; a bound, not a business rule
# --- the DATE-VALUE anchors (owner item 3, 2026-07-26) ----------------------------------------
# The second half of a date CONDITION, as distinct from a measure's window:
#
# Where [Last order] [is before] [one month ago]
# ^ op ^ THIS
#
# A window answers "over what period do I sum"; an anchor answers "which single date am I
# comparing against". Both live in this module for one reason: they are the same two-engine
# contract, resolved from the same `today`, and a gate that holds one in lock-step and not the
# other would leave half the sentence free to drift.
#: Anchor modes, in the order the picker offers them. `exact` is the historical behaviour β a
#: rule with NO mode is an `exact` rule whose value is an ISO date, which is what every view
#: saved before this change carries.
ANCHOR_MODES = (
"today",
"yesterday",
"one_week_ago",
"one_month_ago",
"n_days_ago",
"exact",
)
#: Modes that carry NO value. β This set is load-bearing far outside this module: a rule whose
#: value is blank normally reads as INACTIVE, and an inactive rule is IGNORED β which WIDENS the
#: result under a count nobody would doubt. Every activeness check (`isRuleActive` in TS,
#: `filter_sql.is_rule_active`, and the validator's value handling) has to know these four are
#: active with an empty value.
ANCHOR_VALUE_FREE = frozenset({"today", "yesterday", "one_week_ago", "one_month_ago"})
ANCHOR_LABELS = {
"today": "today",
"yesterday": "yesterday",
"one_week_ago": "one week ago",
"one_month_ago": "one month ago",
"n_days_ago": "{n} days ago",
"exact": "an exact date",
}
def _iso(d):
return d.isoformat()
def _month_start(d):
return d.replace(day=1)
def _month_end(d):
return _next_month(d.replace(day=1)) - _dt.timedelta(days=1)
def _next_month(d):
return (d.replace(day=28) + _dt.timedelta(days=4)).replace(day=1)
def _quarter_start(d):
return _dt.date(d.year, 3 * ((d.month - 1) // 3) + 1, 1)
def _days_in_month(y, m):
return (_dt.date(y + (m == 12), 1 if m == 12 else m + 1, 1) - _dt.timedelta(days=1)).day
def _shift_months(d, n):
"""`d` moved `n` months, CLAMPING the day to the target month's length.
Jan 31 back one month is Dec 31, but Mar 31 back one month is Feb 28 (or 29) β there is no
Feb 31 to land on. Both engines must clamp identically or "the past month" differs by up to
three days for a third of the calendar; the gate probes a 31st and a leap day for exactly it.
"""
total = (d.year * 12 + (d.month - 1)) + n
y, m = divmod(total, 12)
m += 1
return _dt.date(y, m, min(d.day, _days_in_month(y, m)))
def _parse_date(v):
"""Accept 'YYYY-MM-DD' (and tolerate a longer ISO timestamp by taking its date part)."""
if isinstance(v, _dt.date):
return v
s = str(v or "").strip()[:10]
if not s:
return None
try:
return _dt.date.fromisoformat(s)
except ValueError:
return None
def normalize(spec):
"""Coerce an untrusted window spec to `{kind, n?, from?, to?}` or None.
Fail-closed on the KIND (an unknown kind is not a window), tolerant on the rest β the same
split `clean_filter_tree` uses, so one malformed window cannot cost a user their saved view.
"""
if not isinstance(spec, dict):
return None
kind = spec.get("kind")
if kind not in WINDOW_KINDS:
return None
out = {"kind": kind}
if kind in N_KINDS:
try:
n = int(spec.get("n"))
except (TypeError, ValueError):
return None
if n < 1 or n > MAX_N:
return None
out["n"] = n
if kind == "custom":
f, t = _parse_date(spec.get("from")), _parse_date(spec.get("to"))
if f is None and t is None:
return None # a custom range with no bounds is not a window
if f is not None and t is not None and f > t:
f, t = t, f # the builder cannot enforce order; the engine can
if f is not None:
out["from"] = _iso(f)
if t is not None:
out["to"] = _iso(t)
return out
def resolve(spec, today):
"""Window spec + the tenant's today -> `(date_from, date_to)`, both inclusive ISO dates.
Either side may be None, meaning open-ended. Returns None when the spec is not a window at
all β callers MUST treat that as "this condition cannot be evaluated" and refuse, never as
"no window", which would silently widen the result to all time.
"""
spec = normalize(spec)
if spec is None:
return None
kind = spec["kind"]
d = _parse_date(today)
if d is None:
raise ValueError("resolve() needs an explicit `today` β see this module's docstring")
if kind == "all_time":
return (None, None)
if kind == "today":
return (_iso(d), _iso(d))
if kind == "yesterday":
y = d - _dt.timedelta(days=1)
return (_iso(y), _iso(y))
# ISO 8601: the week starts MONDAY. Python's weekday() is already Monday=0; the TS mirror
# has to correct getDay(), which is Sunday=0. The gate probes both a Sunday and a Monday.
if kind == "this_week":
start = d - _dt.timedelta(days=d.weekday())
return (_iso(start), _iso(start + _dt.timedelta(days=6)))
if kind == "last_week":
start = d - _dt.timedelta(days=d.weekday() + 7)
return (_iso(start), _iso(start + _dt.timedelta(days=6)))
if kind == "this_month":
return (_iso(_month_start(d)), _iso(_month_end(d)))
if kind == "last_month":
prev = _month_start(d) - _dt.timedelta(days=1)
return (_iso(_month_start(prev)), _iso(prev))
if kind == "this_quarter":
qs = _quarter_start(d)
qe = _month_end(_dt.date(qs.year, qs.month + 2, 1))
return (_iso(qs), _iso(qe))
if kind == "last_quarter":
prev_end = _quarter_start(d) - _dt.timedelta(days=1)
qs = _quarter_start(prev_end)
return (_iso(qs), _iso(prev_end))
if kind == "this_year":
return (_iso(_dt.date(d.year, 1, 1)), _iso(_dt.date(d.year, 12, 31)))
if kind == "last_year":
return (_iso(_dt.date(d.year - 1, 1, 1)), _iso(_dt.date(d.year - 1, 12, 31)))
if kind == "ytd":
# year-to-date ENDS TODAY, unlike this_year which runs to Dec 31. The distinction is the
# whole reason both exist: comparing "this year" against last year double-counts the
# months that have not happened yet.
return (_iso(_dt.date(d.year, 1, 1)), _iso(d))
if kind == "ytd_last_year":
# SAME PERIOD last year β Jan 1 LY through today's month/day LY. This is the honest
# partner of `ytd`, and it is not a nicety: it mirrors `core.periods.ytd_last_year`
# EXACTLY (including the Feb 29 -> Feb 28 clamp), which is what `pool()` computes the
# `revenue_ly` column from. `Sales[ytd] < Sales[ytd_last_year]` therefore reproduces the
# retired `at_risk > 0` condition rather than approximating it. Comparing `ytd` against
# `last_year` instead would pit seven months against twelve.
return (_iso(_dt.date(d.year - 1, 1, 1)), _iso(_shift_months(d, -12)))
if kind == "ltm":
# the last twelve months INCLUDING today: 365 days back, both ends inclusive
return (_iso(d - _dt.timedelta(days=364)), _iso(d))
# "The past week/month/year" are ROLLING and end TODAY β Airtable's wording, and NOT the
# calendar kinds above: `last_week` is the previous Monday-Sunday, `past_week` is the seven
# days ending today. Both are offered because a user means different things by them, and
# picking one to serve both would silently answer the other question.
# past_week = the same rule as last_n_days(7) β one period back, PLUS ONE DAY, so today
# is included and the span is exactly 7 days rather than 8.
# past_year agrees with `ltm` except across a leap day, where the calendar shift keeps the
# same month/day and the 364-day subtraction cannot.
if kind == "past_week":
return (_iso(d - _dt.timedelta(days=6)), _iso(d))
if kind == "past_month":
return (_iso(_shift_months(d, -1) + _dt.timedelta(days=1)), _iso(d))
if kind == "past_year":
return (_iso(_shift_months(d, -12) + _dt.timedelta(days=1)), _iso(d))
if kind == "last_n_days":
# INCLUSIVE of today β "the last 7 days" is today and the 6 before it, not the 7 before
# yesterday. Getting this wrong drops today's orders from every recent-activity filter.
return (_iso(d - _dt.timedelta(days=spec["n"] - 1)), _iso(d))
if kind == "next_n_days":
return (_iso(d), _iso(d + _dt.timedelta(days=spec["n"] - 1)))
if kind == "custom":
return (spec.get("from"), spec.get("to"))
raise AssertionError(f"unhandled window kind {kind!r}") # unreachable; WINDOW_KINDS is closed
def label(spec):
"""How a window reads in the condition sentence ('in the last 90 days')."""
spec = normalize(spec)
if spec is None:
return "an invalid range"
return WINDOW_LABELS[spec["kind"]].format(n=spec.get("n"))
def resolve_anchor(mode, value, today):
"""A date condition's right-hand side -> ONE inclusive ISO date, or None.
`mode` is an entry of ANCHOR_MODES; None/'' means `exact`, which is what every view saved
before anchors existed carries (op + an ISO date in `value`). `value` supplies the number for
`n_days_ago` and the date for `exact`, and is ignored by the four value-free modes.
Returns None when the anchor cannot be resolved β an unknown mode, a non-numeric N, a value
that is not a date. The caller must then treat the CONDITION as unanswerable and match
nothing. It must NOT fall through to "no condition": that widens the result while the count
beside it still looks authoritative, which is the whole reason this returns None rather than
a best guess (same contract as `resolve()` above).
`n_days_ago` accepts n = 0 where a WINDOW requires n >= 1. A zero-day window is empty and
could only be a mistake; "0 days ago" is today, which is a date a person can mean.
"""
m = mode or "exact"
if m not in ANCHOR_MODES:
return None
d = _parse_date(today)
if d is None:
raise ValueError("resolve_anchor() needs an explicit `today` β see this module's "
"docstring on why the clock is never read here")
if m == "today":
return _iso(d)
if m == "yesterday":
return _iso(d - _dt.timedelta(days=1))
if m == "one_week_ago":
# A week ago is a DATE (today minus 7), not the past-week RANGE. The two read almost
# identically in English and mean different things in a comparison.
return _iso(d - _dt.timedelta(days=7))
if m == "one_month_ago":
return _iso(_shift_months(d, -1))
if m == "n_days_ago":
s = str("" if value is None else value).strip()
# `[0-9]` not `\d`: a Python \d matches every Unicode decimal digit, so a fullwidth
# 'οΌοΌ' would resolve here and be rejected by the TS mirror's Number() β the exact
# two-engine divergence filter_sql.to_num was rewritten to avoid.
if not s or not all("0" <= ch <= "9" for ch in s):
return None
n = int(s)
# No `n < 0` guard: the digit test above already excludes a sign, so it would be
# unreachable. The negative control proved that β removing it changed nothing, which is
# how dead code hides in a defensive-looking line. The CAP is the live guard.
if n > MAX_N:
return None
return _iso(d - _dt.timedelta(days=n))
parsed = _parse_date(value) # exact
return None if parsed is None else _iso(parsed)
def anchor_label(mode, value):
"""How an anchor reads in the condition sentence ('one month ago', '30 days ago')."""
m = mode or "exact"
if m not in ANCHOR_MODES:
return "an invalid date"
if m == "n_days_ago":
return ANCHOR_LABELS[m].format(n=str("" if value is None else value).strip() or "N")
if m == "exact":
return str("" if value is None else value).strip() or ANCHOR_LABELS[m]
return ANCHOR_LABELS[m]
|