Skip to content

fix(forecast): resolve time grain robustly for Prophet forecasting#42145

Open
yousoph wants to merge 1 commit into
apache:masterfrom
preset-io:sc-113749-prophet-missing-time-grain
Open

fix(forecast): resolve time grain robustly for Prophet forecasting#42145
yousoph wants to merge 1 commit into
apache:masterfrom
preset-io:sc-113749-prophet-missing-time-grain

Conversation

@yousoph

@yousoph yousoph commented Jul 17, 2026

Copy link
Copy Markdown
Member

SUMMARY

Enabling Forecast (Advanced Analytics → Predictive Analytics) on a time-series chart could fail with an opaque 500:

prophet() missing 1 required positional argument: 'time_grain'

Root cause: the prophet post-processing op is dispatched via getattr(pandas_postprocessing, operation)(df, **options) in QueryObject.exec_post_processing. The frontend prophetOperator set time_grain: formData.time_grain_sqla; when that value is undefined — e.g. a saved/dashboard chart whose Time Grain control was cleared to "None", or a chart using the generic x-axis where the grain lives on the adhoc column's timeGrain popover instead of the panel control — JSON.stringify drops the key. The backend then calls prophet() without time_grain and Python raises a raw TypeError at call time, before the graceful if not time_grain: raise InvalidPostProcessingError(_("Time grain missing")) guard inside prophet() can run.

Fix (both ends):

  • Frontend (prophetOperator.ts): resolve the effective time grain in priority order — adhoc x-axis column timeGrainqueryObject.extras.time_grain_sqla (picks up dashboard-applied grains) → formData.time_grain_sqlaP1D fallback (matching the time_grain_sqla control default in sharedControls), so forecasting works instead of erroring.
  • Backend (prophet.py): make time_grain an optional kwarg (Optional[str] = None), so any remaining path that can't determine a grain surfaces the existing readable "Time grain missing" InvalidPostProcessingError (400) instead of an unhandled TypeError (500). No positional callers exist — the op is always dispatched with keyword options.

BEFORE/AFTER

Before: forecast on a chart without time_grain_sqla in its form data → HTTP 500, raw TypeError message shown in the chart.

After: the grain is resolved from the x-axis column / query extras / form data (falling back to P1D) and the forecast renders; if a grain still can't be determined server-side, the user gets a readable "Time grain missing" error (400).

TESTING INSTRUCTIONS

pytest tests/unit_tests/pandas_postprocessing/test_prophet.py -v
cd superset-frontend && npx jest packages/superset-ui-chart-controls/test/operators/prophetOperator.test.ts

New tests:

  • Backend: dispatch-path call with no time_grain and explicit time_grain=None both raise InvalidPostProcessingError ("Time grain missing") instead of TypeError.
  • Frontend: grain resolution from adhoc x-axis column, from queryObject.extras, and the P1D fallback when no grain is present anywhere.

To reproduce manually: save a Time-series Line Chart, clear Time Grain to "None", enable Forecast under Advanced Analytics, run the chart.

ADDITIONAL INFORMATION

🤖 Generated with Claude Code

@dosubot dosubot Bot added change:backend Requires changing the backend change:frontend Requires changing the frontend explore:advanced-analysis Related to Advanced Analysis in Explore labels Jul 17, 2026
@bito-code-review

bito-code-review Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Bito Review Skipped - Source Branch Not Found

Bito didn’t review this change because the pull request is no longer valid. It may have been merged, or the source/target branch may no longer exist.

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.97%. Comparing base (9f230bc) to head (0338d53).
⚠️ Report is 115 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #42145      +/-   ##
==========================================
- Coverage   65.17%   64.97%   -0.20%     
==========================================
  Files        2753     2784      +31     
  Lines      154657   157312    +2655     
  Branches    35484    35841     +357     
==========================================
+ Hits       100801   102221    +1420     
- Misses      51940    53122    +1182     
- Partials     1916     1969      +53     
Flag Coverage Δ
hive 38.74% <ø> (-0.25%) ⬇️
javascript 70.74% <100.00%> (+<0.01%) ⬆️
mysql 57.65% <ø> (-0.15%) ⬇️
postgres 57.70% <ø> (-0.16%) ⬇️
presto 40.65% <ø> (-0.31%) ⬇️
python 59.09% <ø> (-0.21%) ⬇️
sqlite 57.32% <ø> (-0.15%) ⬇️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yousoph
yousoph requested a review from Copilot July 17, 2026 20:31
@yousoph yousoph added the 🎪 ⚡ showtime-trigger-start Create new ephemeral environment for this PR label Jul 17, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a forecasting failure mode where the Prophet post-processing step could be invoked without a time_grain, causing an unhandled backend TypeError (500) instead of a readable validation error. It makes time grain resolution more robust on the frontend and makes the backend Prophet post-processing function resilient to missing time_grain in the dispatched options.

Changes:

  • Frontend: resolve the effective time_grain from (1) adhoc x-axis column timeGrain, (2) queryObject.extras.time_grain_sqla, (3) formData.time_grain_sqla, falling back to daily (P1D).
  • Backend: make time_grain optional in prophet() so missing/None hits the existing "Time grain missing" InvalidPostProcessingError path rather than raising a raw TypeError.
  • Tests: add regression coverage for missing/None time_grain on the backend, and for the new frontend resolution/fallback behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
tests/unit_tests/pandas_postprocessing/test_prophet.py Adds regression tests ensuring missing/None time_grain yields a readable InvalidPostProcessingError.
superset/utils/pandas_postprocessing/prophet.py Updates Prophet post-processing signature to accept optional time_grain and preserve graceful error behavior.
superset-frontend/packages/superset-ui-chart-controls/src/operators/prophetOperator.ts Resolves effective time_grain from x-axis/query extras/form data with a P1D fallback to avoid dropped undefined options.
superset-frontend/packages/superset-ui-chart-controls/test/operators/prophetOperator.test.ts Adds tests for time grain resolution sources and the daily fallback when no grain is provided.

@github-actions github-actions Bot added 🎪 6c376b7 🚦 building 🎪 ⌛ 48h Environment expires after 48 hours (default) and removed 🎪 ⚡ showtime-trigger-start Create new ephemeral environment for this PR labels Jul 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🎪 Showtime is building environment on GHA for 6c376b7

@github-actions

Copy link
Copy Markdown
Contributor

🎪 Showtime deployed environment on GHA for 6c376b7

Environment: http://34.214.238.14:8080 (admin/admin)
Lifetime: 48h auto-cleanup
Updates: New commits create fresh environments automatically

@yousoph
yousoph requested review from rusackas and sadpandajoe July 18, 2026 00:51

@rusackas rusackas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @yousoph, LGTM.

Curious about the P1D fallback: for a chart where Time Grain was deliberately cleared to None, assuming daily could put the forecasted points at the wrong cadence versus the actual data. Not blocking, just wondering if that's worth a follow-up.

@yousoph

yousoph commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review @rusackas! Fair point on the cadence. Some notes on the blast radius as shipped: the P1D fallback only engages when no grain is resolvable anywhere (panel control, x-axis column grain, or dashboard-applied extras), and the frequency only affects the appended future points from make_future_dataframe — the historical fit stays anchored to the actual data timestamps. So the worst case today is the forecast horizon extending at daily spacing over non-daily data (vs. the previous behavior for this state, which was the unhandled 500).

Agreed it's worth a follow-up. The natural improvement is to infer the frequency from the data when the grain is unset — pd.infer_freq over the temporal index, falling back to the median timestamp delta, then P1D as a last resort — which would keep the forecast cadence aligned with non-daily/irregular series and also improve raw API callers that omit time_grain.

Enabling Forecast (Advanced Analytics) on a time-series chart could fail
with the opaque error `prophet() missing 1 required positional argument:
'time_grain'` (follow-up to apache#41180).

Root cause
----------
The `prophet` post-processing op is dispatched in
`QueryObject.exec_post_processing` via
`getattr(pandas_postprocessing, operation)(df, **options)`. The frontend
`prophetOperator` set `time_grain: formData.time_grain_sqla`; when that is
`undefined` (a saved/dashboard chart whose Time Grain was cleared to
"None", or a chart using the generic x-axis where the grain lives on the
adhoc column's `timeGrain` popover instead of the panel control),
`JSON.stringify` drops the key. The options dict then reaches the backend
without `time_grain`, so Python raises a raw `TypeError` at call time —
before the graceful `if not time_grain: raise InvalidPostProcessingError`
guard inside `prophet()` can run (yielding a 500 instead of a readable
4xx).

Reproduced
----------
- Backend: `prophet(df, periods=3, confidence_interval=0.9,
  index="__timestamp")` (no `time_grain`) raised the exact customer
  `TypeError`.
- Frontend: `prophetOperator` with `time_grain_sqla` absent from formData
  emitted `time_grain: undefined`.

Fix
---
- Backend (robustness): make `time_grain` an optional kwarg
  (`Optional[str] = None`, reordered after the required positional args)
  so any code path missing a grain now surfaces the readable
  "Time grain missing" `InvalidPostProcessingError` instead of a
  `TypeError`.
- Frontend (make the feature work): resolve the effective grain in order
  — adhoc x-axis column `timeGrain`, `queryObject.extras.time_grain_sqla`
  (picks up dashboard-applied grains), then `formData.time_grain_sqla`,
  with a final `P1D` fallback matching the `time_grain_sqla` control
  default so a grain-less chart still forecasts.

Tests
-----
- Backend: added dispatch-path and explicit-`None` cases to
  `tests/unit_tests/pandas_postprocessing/test_prophet.py`.
- Frontend: added fallback, adhoc-column, and extras resolution cases to
  `prophetOperator.test.ts`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@yousoph
yousoph force-pushed the sc-113749-prophet-missing-time-grain branch from 6c376b7 to 0338d53 Compare July 21, 2026 23:15
@github-actions github-actions Bot added 🎪 0338d53 🚦 building 🎪 0338d53 📅 2026-07-21T23-16 Environment 0338d53 created at 2026-07-21T23-16 🎪 0338d53 🤡 yousoph Environment 0338d53 requested by yousoph labels Jul 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🎪 Showtime is building environment on GHA for 0338d53

@netlify

netlify Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 0338d53
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a5ffd9643197a0008d6befc
😎 Deploy Preview https://deploy-preview-42145--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added 🎪 0338d53 🚦 deploying 🎪 0338d53 🚦 running Environment 0338d53 status: running 🎪 0338d53 🌐 54.149.38.146:8080 Environment 0338d53 URL: http://54.149.38.146:8080 (click to visit) and removed 🎪 0338d53 🚦 building 🎪 0338d53 🚦 running Environment 0338d53 status: running labels Jul 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🎪 Showtime deployed environment on GHA for 0338d53

Environment: http://54.149.38.146:8080 (admin/admin)
Lifetime: 48h auto-cleanup
Updates: New commits create fresh environments automatically

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

Labels

change:backend Requires changing the backend change:frontend Requires changing the frontend explore:advanced-analysis Related to Advanced Analysis in Explore packages size/L 🎪 ⌛ 48h Environment expires after 48 hours (default) 🎪 0338d53 🚦 running Environment 0338d53 status: running 🎪 0338d53 🤡 yousoph Environment 0338d53 requested by yousoph 🎪 0338d53 🌐 54.149.38.146:8080 Environment 0338d53 URL: http://54.149.38.146:8080 (click to visit) 🎪 0338d53 📅 2026-07-21T23-16 Environment 0338d53 created at 2026-07-21T23-16

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants