Repository navigation
Description
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_ratemultiplied 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 throwsTypeError: undefined is not iterableand 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
feast init demo && cd demo/feature_repo, adddata_quality_monitoring: {auto_baseline: true}
tofeature_store.yaml, and runfeast 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).
- Open Feature views →
- Case 4 is the normal state from the day after the baseline was computed. To get there without
waiting, run this infeature_repo, thenfeast 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:
- 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. - 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?

Activity
Mrun25 commentedon Oct 21, 2026
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"andmetric_date=date.today(),/monitoring/metrics/featuresreturns it unfiltered alongside real windows, and both pages then treat it as the newest window. I have a fix in progress with tests.fix(ui): stop treating the monitoring baseline as the latest window
Mrun25 commentedon Oct 21, 2026
Opened #7056 for this.
Nitant28 commentedon Oct 21, 2026
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.
chore(release): release 0.68.0
fix(ui): stop treating the monitoring baseline as the latest window (f…
chore(release): release 0.68.0