Skip to content

feat(analytics): date-range drill scope for granularity-bucketed date dimensions (#1752)#3256

Merged
os-zhuang merged 1 commit into
mainfrom
claude/report-drill-down-range-filter-lxt859
Jul 19, 2026
Merged

feat(analytics): date-range drill scope for granularity-bucketed date dimensions (#1752)#3256
os-zhuang merged 1 commit into
mainfrom
claude/report-drill-down-range-filter-lxt859

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Why

Closes the framework half of #1752. A report/dashboard cell grouped by a dateGranularity date dimension ("2026-Q2") covers a span of records, so drilling it needs a range (>= start AND < nextStart) — which the equality drill contract (drillRawRows) can't express. Date dims were therefore excluded from drill metadata entirely, so a drill landed on an unscoped superset (all time buckets).

What

  • @objectstack/core — add bucketKeyToCalendarRange(key, granularity), the inverse of bucketDateValue: a canonical bucket key → its half-open [start, end) calendar span as YYYY-MM-DD (end exclusive, i.e. the next bucket's first day). Pure, timezone-naive calendar arithmetic; returns null for the null bucket or a shape-valid-but-out-of-range key (2026-13, 2025-W53, 2026-02-30) so callers fall back to a superset drill rather than emit a wrong bound.
  • @objectstack/service-analytics — emit a drillRanges sidecar (row-aligned; the range companion to drillRawRows) for date + dateGranularity dimensions, computed from the canonical bucket key in the pre-label-resolution snapshot pass (before labels overwrite the raw grouped value). Also sets object for time-only reports so the host can open the list.

Design decisions

  • Range derived from the canonical bucket KEY, not a raw instant. The key is parity-guaranteed identical across the in-memory (bucketDateValue) and native-SQL (buildDateBucketExpr) paths and is always available at output time — no raw-instant retention, works uniformly for both strategies. A round-trip test pins the inverse to the forward function.
  • Half-open [gte, lt) enforced by the util; callers never choose the operator (avoids the classic <= boundary double-count).
  • date vs datetime (ADR-0053). A date is a tz-naive calendar day → YYYY-MM-DD bounds are exact under any reference timezone. A datetime buckets on the reference tz's calendar; its boundaries are that tz's midnight instants, which YYYY-MM-DD bounds capture only under UTC — so a datetime field under a non-UTC reference tz is omitted (host drills a superset) until instant-boundary support lands. This never emits a half-right range (a regression vs. the honest superset).

Testing

  • bucketKeyToCalendarRange round-trip/parity + exact-boundary + rejection tests (49 cases, incl. ISO-week 2024-12-30 → 2025-W01, leap 2024-02-29, quarter/year rollover).
  • drillRanges emission: quarter range, matrix "X by time" (equality dim + range side by side), datetime-non-UTC omission, date-non-UTC still emitted.
  • Full suites green: @objectstack/service-analytics 170/170, @objectstack/objectql 1001/1001. Source type-checks clean (DTS build).

Follow-up

Consumed by the objectui report drill-through (companion PR on objectstack-ai/objectui, same branch) which forwards drillRanges into the drill drawer and the /data bare-data-surface URL contract.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RCXBnmMQTwjmFkjtpEe11f


Generated by Claude Code

… dimensions (#1752)

A report/dashboard cell grouped by a `dateGranularity` date dimension ("2026-Q2")
covers a SPAN of records, so drilling it needs a range (>= start AND < nextStart),
which the equality drill contract (drillRawRows) can't express — date dims were
excluded from drill metadata, so a drill landed on an unscoped superset.

- core: add `bucketKeyToCalendarRange(key, granularity)`, the inverse of
  `bucketDateValue` — canonical bucket key → half-open [start, end) YYYY-MM-DD span.
  Pure, tz-naive calendar math; null for unbucketable/out-of-range keys.
- service-analytics: emit a `drillRanges` sidecar (row-aligned, the range companion
  to drillRawRows) for date+granularity dims from the canonical key in the pre-label
  snapshot pass. datetime under a non-UTC tz is omitted (superset) until instant
  bounds land; a tz-naive date field is exact under any tz (ADR-0053).

Round-trip parity test pins bucketKeyToCalendarRange to bucketDateValue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RCXBnmMQTwjmFkjtpEe11f
@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 19, 2026 6:37am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/core, @objectstack/objectql, packages/services.

31 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/actions-as-tools.mdx (via @objectstack/core)
  • content/docs/ai/knowledge-rag.mdx (via @objectstack/core)
  • content/docs/ai/natural-language-queries.mdx (via @objectstack/core)
  • content/docs/automation/webhooks.mdx (via @objectstack/core, packages/services)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/concepts/north-star.mdx (via packages/core)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/core, @objectstack/objectql)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/core)
  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/core)
  • content/docs/kernel/runtime-services/index.mdx (via packages/services)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/core, @objectstack/objectql)
  • content/docs/kernel/services.mdx (via @objectstack/core, @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/core, @objectstack/objectql)
  • content/docs/permissions/authorization.mdx (via packages/core)
  • content/docs/plugins/anatomy.mdx (via @objectstack/core)
  • content/docs/plugins/development.mdx (via @objectstack/core)
  • content/docs/plugins/index.mdx (via @objectstack/core, @objectstack/objectql)
  • content/docs/plugins/packages.mdx (via @objectstack/core, @objectstack/objectql, packages/services)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/core)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/services)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/core, @objectstack/objectql)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/core)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/core)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/core, @objectstack/objectql)
  • content/docs/releases/v15.mdx (via @objectstack/core)
  • content/docs/releases/v9.mdx (via @objectstack/objectql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants