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

vercelBot commented Jul 19, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 19, 2026 6:37am

Request Review

@github-actionsgithub-actionsBot 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude