Skip to content

Count an event once in a KPI, when several sources report it - #2472

Open
Flix6x wants to merge 4 commits into
mainfrom
fix/kpi-one-value-per-event
Open

Count an event once in a KPI, when several sources report it#2472
Flix6x wants to merge 4 commits into
mainfrom
fix/kpi-one-value-per-event

Conversation

@Flix6x

@Flix6x Flix6x commented Sep 3, 2026

Copy link
Copy Markdown
Member

Description

Two data sources reporting the same event are two claims about it, not two contributions to it —
but a KPI added them up. The total then showed a number no source ever reported, and that no point on
the chart beside it showed either.

One sensor, one day, reported by two sources:

event_start source value
2030-03-15 a forecast 100.0
2030-03-15 a later correction 80.0

A sum KPI over that day reported 180.0. It now reports 80.0, the more recent belief about
the event.

This is direction 1 from #2471, where the alternatives are written up.

Why most_recent_beliefs_only was not enough

That filter is per source, so it leaves one row per event per source. The default
use_latest_version_per_event=True does collapse sources that share a (name, type, model), but
only within one belief time, so two sources believing the same event at different moments both
survive into the values the KPI reduces.

The KPI query now also asks for one_deterministic_belief_per_event=True, which picks one value per
event: latest source version first, most recent belief within that second.

What changes for users

A KPI over a sensor that only ever has one source per event is unaffected — which is the common case,
and every existing KPI test but the new one covers it.

Where a sensor does carry several sources for one event, the KPI now reports one of their values
instead of their sum. The chart still draws every source, so it can show more points than the KPI
counted. The KPI documentation says so now, since the two no longer agree point for point in that
case — the previous behaviour agreed with the chart by summing what it drew, which is exactly what
produced the misleading total.

  • Added changelog item in documentation/changelog.rst

How to test

pytest flexmeasures/api/v3_0/tests/test_assets_api.py -k kpi

test_kpi_counts_an_event_once_when_two_sources_report_it is the example above. It fails on main
with assert 180.0 == 80.0 ± 8.0e-05.

test_kpi_reports_what_the_chart_draws still passes unchanged: its two sources report different
events, so summing across them was always the right answer, and still is.

Related items

Closes #2471. Found while reviewing #2464, which makes a scheduled sensor able to carry schedules from
several sources and so widens the window in which this bites, but the behaviour predates it and
reproduces on main.


Sign-off

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on code under GPL or another incompatible license.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Lp1bUhWjEQtyDbnvRZQgQs

Flix6x and others added 2 commits September 3, 2026 15:03
Two sources reporting one event are two claims about it, not two contributions to it, so adding them
up produced a number no source ever reported, and that no point on the chart showed. A sensor with a
forecast later corrected by an upload, or with two forecasters configured differently, totalled both.

The KPI query now asks for one deterministic belief per event, which prefers the latest source
version, and the most recent belief within it. `most_recent_beliefs_only` alone did not do this: it
is per source, and `use_latest_version_per_event` only collapses sources sharing a name, type and
model, and only within one belief time.

The chart beside the KPI still draws every source, so it can show more points than the KPI counted,
which the KPI documentation now says.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lp1bUhWjEQtyDbnvRZQgQs
Signed-off-by: F.N. Claessen <claessen@seita.nl>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lp1bUhWjEQtyDbnvRZQgQs
Signed-off-by: F.N. Claessen <claessen@seita.nl>
@read-the-docs-community

read-the-docs-community Bot commented Sep 3, 2026

Copy link
Copy Markdown

Documentation build overview

📚 flexmeasures | 🛠️ Build #34379629 | 📁 Comparing da9ebb9 against latest (a60f64e)

  🔍 Preview build  

3 files changed
± changelog.html
± api/v3_0.html
± views/asset-data.html

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.

🟡 Changes recommended

There are determinism and coverage gaps (tie-breaking behavior and a test/doc mismatch around “latest source version”) that should be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adjusts KPI calculation on the asset page so that when multiple data sources report the same event, the KPI reduces one deterministic value per event (instead of summing multiple “claims” into a misleading total). This aligns KPI semantics with “one value per event” while keeping the chart behavior (still showing all sources) explicitly documented.

Changes:

  • Update the KPI query to request one_deterministic_belief_per_event=True, so the KPI reduces a single value per event.
  • Add a regression test covering the “two sources, same event” scenario.
  • Document the new KPI-vs-chart behavior and add a bugfix changelog entry.
File summaries
File Description
flexmeasures/api/v3_0/assets.py Changes KPI belief search to select a single deterministic belief per event
flexmeasures/api/v3_0/tests/test_assets_api.py Adds a regression test for multi-source reporting of the same event
documentation/views/asset-data.rst Documents that KPIs reduce one value per event and may differ from chart point counts
documentation/changelog.rst Adds a bugfix entry describing the corrected KPI behavior
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 2107 to 2112
beliefs = sensor.search_beliefs(
event_starts_after=start,
event_ends_before=end,
most_recent_beliefs_only=True,
one_deterministic_belief_per_event=True,
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch on the ambiguity, but the fix you suggest would break a documented feature — so I have
left the tie alone and documented why.

I did implement it first: a last tie-break by highest source id, matching source_priorities, which
ranks by version and then by id. Two existing tests then failed, and the second one is the answer:

test_source_transition (flexmeasures/data/models/reporting/tests/test_aggregator.py) states

In case of encountering more than one source per event, the first source defined in the sources
array is prioritized.

An AggregatorReporter given sources=[ds1, ds2] expects ds1 to win the events both report. That
works because the remaining tie keeps the order the rows arrive in. Breaking the tie by source id
made ds2 win, since it has the higher id, and the reporter silently stopped honouring the caller's
order. test_select_latest_version_and_belief_per_event_equivalence failed too, since its reference
implementation keeps the incumbent on an exact tie.

So the order is not arbitrary noise to be tidied away — it is how precedence is expressed. What was
missing is that this is nowhere written down, so _select_latest_version_and_belief_per_event now
says it:

Beliefs that tie on both keep the order they came in, which is what lets a caller express its own
precedence by the order in which it passes its sources.

For the KPI endpoint, which passes no sources of its own, an exact tie — same version, same belief
time, two sources — does leave the value up to the order the rows come back in. That is a real
limit, and a narrow one: it needs two sources to claim one event at the very same moment under the
same version. Worth its own issue if we want KPIs to be deterministic even then; it is not something
this PR can settle without taking the reporter's precedence away.

Comment on lines +1941 to +1944
sources = list(setup_sources.values())
reported, corrected = sources[0], sources[-1]
assert reported.id != corrected.id, "this test needs two distinct sources"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Right — setup_sources creates sources without versions, so that test only ever showed the belief
time. Two changes in 097070f.

The docstring now says what the test does: these two sources are of the same version, so the one
that believed the event more recently is the one the KPI counts.

And a new test covers the version half, with two versions of one reporter, the newer one speaking
first:

# The newer version spoke first, and the older version spoke later.
...
assert total == pytest.approx(42.0), "the newer version's value, despite the older belief time"

It reports 141.0 without the fix — 42 plus 99 — so it covers both the summing and the ordering.

@Flix6x Flix6x self-assigned this Sep 3, 2026
@Flix6x Flix6x added bug Something isn't working Data UI labels Sep 3, 2026
@Flix6x Flix6x added this to the 1.1.0 milestone Sep 3, 2026
The regression test only varied the belief time, while the docstring and the documentation also
claimed a preference for the latest source version, which the fixture's unversioned sources could not
show. A second test now gives one reporter two versions and has the newer one speak first, so the
KPI answers with the newer version's value despite the older belief time.

Ties beyond that are left alone, and `_select_latest_version_and_belief_per_event` now says why:
beliefs which tie on version and belief time keep the order they came in, which is how a caller
expresses its own precedence by the order it passes its sources. `test_source_transition` documents
and relies on that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LtMZ49GH6LaiY5MdgtfNe2
Signed-off-by: F.N. Claessen <claessen@seita.nl>

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.

🟡 Changes recommended

A newly added docstring violates the repository’s “break lines only after punctuation” convention, and should be reformatted before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread flexmeasures/data/models/time_series.py Outdated
Comment on lines +905 to +907
Beliefs that tie on both keep the order they came in, which is what lets a caller
express its own precedence by the order in which it passes its sources.
See `test_source_transition`, where the first source in the list wins the events both sources report.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reflowed in da9ebb9 — the break now falls after the comma, so both lines end at punctuation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LtMZ49GH6LaiY5MdgtfNe2
Signed-off-by: F.N. Claessen <claessen@seita.nl>

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.

🟢 Approval recommended

The change is small and well-targeted, with clear documentation updates and new tests covering the previously incorrect KPI behavior and its selection rules.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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

Labels

bug Something isn't working Data UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A KPI sums across data sources, so one event reported twice is counted twice

2 participants