Skip to content

UI: Monitoring pages treat the baseline as a window, and its overlay uses mismatched bins #7054

Description

@Nitant28

Expected Behavior

The monitoring UI shows the newest monitoring window, compares it with the baseline on the same
value ranges and at a comparable scale, and plots history one window length at a time.

Current Behavior

/monitoring/metrics/features returns one row per granularity plus the baseline row. The
baseline row has granularity: "baseline" and is dated by the day it was computed
(metric_date=date.today(), monitoring_service.py:182, :462), while windows are dated by their
start (:202, :1028). Both pages treat every returned row as an interchangeable window.

1. Feature → Monitoring tab: the baseline is compared with itself. FeatureMonitoringTab
picks the newest metric_date among all rows, so right after feast monitor run it shows
Granularity "baseline" and Row Count "1,807 (baseline: 1,807)".

2. Monitoring → feature detail page: opens on the baseline, lists it twice. FeatureMetricsDetail
builds the View options from every row's granularity and sorts them alphabetically. The options are
"baseline", "Biweekly", "Daily", "Monthly", "Quarterly", "Weekly", "Baseline (all data)", and the
page opens on the first one, the baseline.

3. Detail page: the Time-Series Analysis mixes window lengths. It receives every row and plots
them by metric_date as one line. On the demo, the points are the quarterly window (07-21), monthly
(09-19), biweekly (10-05), weekly (10-12), daily (10-18) and the baseline (10-20). Each point
summarizes a different span of data, so the "drift" line has no meaning.

4. Monitoring tab: the baseline overlay doesn't line up with the window. renderNumericSvg
(HistogramChart.tsx) draws baseline bin i behind window bin i, and scales both by the largest
raw count.

  • Each stored histogram covers its own row's min and max, so bin i of each covers different values.
  • On the demo, the 125-row daily window's bars reach about a tenth of the 1,807-row baseline's height.
  • With the last day of conv_rate multiplied by 100, the bars look exactly like a clean day's. Only
    the axis labels move, from 0.004–1.00 to 0.43–99.6, and the baseline is drawn across the whole axis
    although its values lie in 0.0002–1.0. The stats panel shows the shift (mean 50.94 vs. 0.5237); the
    chart doesn't.
  • If a feature's baseline is categorical and its newest window numeric (the feature changed type),
    the chart throws TypeError: undefined is not iterable and the tab fails to render.

Screenshots (Feast 0.67.0, feast init demo): the tab after a fresh run, on a clean day and on a ×100
day; the detail page after a fresh run, and its time series.

Steps to reproduce

  1. feast init demo && cd demo/feature_repo, add data_quality_monitoring: {auto_baseline: true}
    to feature_store.yaml, and run feast apply && feast monitor run && feast ui.
    • Open Feature views → driver_hourly_stats → conv_rate → Monitoring (case 1).
    • Open Monitoring → conv_rate (cases 2 and 3).
  2. Case 4 is the normal state from the day after the baseline was computed. To get there without
    waiting, run this in feature_repo, then feast monitor run, and reload the Monitoring tab:
import datetime as dt
import pandas as pd

# Date the baseline three days back, as it would be after a few daily runs.
p = "feast_monitoring/feast_monitoring_feature_metrics.parquet"
m = pd.read_parquet(p)
m.loc[m["is_baseline"].astype(bool), "metric_date"] = dt.date.today() - dt.timedelta(days=3)
m.to_parquet(p, index=False)

# Simulate a x100 unit bug in the last day of conv_rate.
s = pd.read_parquet("data/driver_stats.parquet")
last = s["event_timestamp"] >= s["event_timestamp"].max() - pd.Timedelta(days=1)
s.loc[last, "conv_rate"] *= 100
s.to_parquet("data/driver_stats.parquet", index=False)

Specifications

  • Version: 0.67.0 (the UI files involved are unchanged on master at e304a84)
  • Platform: Windows 11, Chromium-based browser
  • Subsystem: UI, pages/features/FeatureMonitoringTab.tsx, pages/monitoring/FeatureMetricsDetail.tsx,
    pages/monitoring/components/HistogramChart.tsx

Possible Solution

A UI-only fix, which I have ready with tests, in two commits:

  1. Keep the baseline out of window views (cases 1–3). One small helper module picks windows,
    orders granularities shortest first, and builds a single-granularity time series. The baseline
    stays reachable through the detail page's "Baseline (all data)" option, and the tab shows it on its
    own only when no window exists yet.
  2. Align the overlay (case 4). Rebin the baseline onto the window's edges (uniform within each
    baseline bin; a SQL-style zero-width bin as a point) and scale it to the window's row count. The
    legend states how much of the baseline lies outside the window's range: in the ×100 case above,
    39%, with the rest in the first bin. Don't overlay a baseline that isn't numeric.

The fuller alternative, storing window histograms on the baseline's edges, would change the stored
data and is close to drift detection (#6341), so I've kept this to the UI. @jyejare, would you prefer
a different approach, or these as two separate PRs?

Image Image Image Image Image

Activity

  1. Mrun25 commented on Oct 21, 2026

    @Mrun25
    Contributor

    I'd like to work on this. I reproduced the root cause on current main (e304a84): the monitoring service saves the baseline with granularity="baseline" and metric_date=date.today(), /monitoring/metrics/features returns it unfiltered alongside real windows, and both pages then treat it as the newest window. I have a fix in progress with tests.

  2. added a commit that references this issue on Oct 21, 2026
    4a8285f
  3. Mrun25 commented on Oct 21, 2026

    @Mrun25
    Contributor

    Opened #7056 for this.

  4. Nitant28 commented on Oct 21, 2026

    @Nitant28
    Author

    I've opened #7057 with the fix I mentioned above: 19 tests, plus before/after screenshots. I saw #7056 also targets this; the PR description lists the differences, and I'm happy to go with whatever maintainers prefer.

  5. added 2 commits that reference this issue on Nov 11, 2026
    216d2f3
    5b94c9e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions