Skip to content

feat(tools/infrastructure): utilisation, intervention rate and MTBF/MTTR KPI views - #369

Merged
Minipada merged 1 commit into
jazzyfrom
feature/363-extend-the-kpi-views-utilisation-interve
Aug 18, 2026
Merged

feat(tools/infrastructure): utilisation, intervention rate and MTBF/MTTR KPI views#369
Minipada merged 1 commit into
jazzyfrom
feature/363-extend-the-kpi-views-utilisation-interve

Conversation

@Minipada

Copy link
Copy Markdown
Owner

Closes #363

#304 established where KPI definitions live, how they reach a database, how they are tested and
how they are charted, and shipped one metric through it. This finishes the starter set except
for the one whose contract nobody has agreed yet.

Utilisation

dc_kpi_driving_samples narrows dc to driving_type Records, each carrying the speed the
robot last reported at or before it — a lookup bounded by dc_kpi_max_gap(), so a stale speed
Record vouches for one grace period and no longer.

dc_kpi_utilisation(from, to [, max_gap [, min_speed]]) credits time exactly the way
dc_kpi_availability() does and splits it four ways: autonomous_seconds, manual_seconds,
teleop_seconds, unknown_seconds, which add up to reported_seconds so the ratio can always
be checked against its parts. Productive is moving under a known modemanual and teleop
count, since a human driving the robot is still the robot being used.

What it deliberately does not claim:

  • No speed Record in the window makes utilisation NULL, not 0 %. A deployment collecting
    driving_type but not speed has unreported movement, not zero movement; speed_samples is
    in the output so a NULL can be told from an empty range.
  • Not "useful work". A robot standing still to inspect something reads as idle. That
    distinction needs the Mission Measurement.

Intervention rate

dc_kpi_intervention_events derives is_start (leaving autonomous for manual/teleop) and
is_end (returning to it) from the modes a Record names, so the shape of the metric lives in
one place. dc_kpi_intervention_rate() reports both denominators — per_autonomous_hour
off dc_kpi_utilisation()'s autonomous_seconds, per_km off summed distance_traveled
because they fail differently: a robot parked all shift has no autonomous hours, a robot doing
tight manoeuvring has hours and few kilometres.

  • A denominator nothing reported gives a NULL rate, never a rate over zero.
  • Only an end Record carries a duration, so a takeover still running is counted in
    interventions and absent from mean_intervention_seconds rather than a zero in it.

MTBF and MTTR

dc_kpi_reliability(from, to [, failure_levels]) averages the fault Records' own
previous_duration: healthy time before a raise is MTBF, time in a failure level before a clear
is MTTR. failure_levels defaults to ARRAY['ERROR','STALE'] and is a query parameter, so a
deployment treating a silent component as reportable-but-not-broken passes ARRAY['ERROR'].

  • ERROR → STALE is neither a raise nor a clear: a component that goes quiet while already
    broken is still one fault.
  • WARN is not a failure by default, so OK → WARN → ERROR is one failure timed from the last
    healthy state.
  • A fault never cleared counts in failures and open_faults and is left out of mttr_seconds.
  • Grouped by component, never rolled up per robot — "the robot is down" is a policy over
    components that nothing here knows, and averaging independent components reports a number for
    a failure mode no component has.

Mission success rate is deliberately not in this PR

#305 — how far a nav2 adapter infers, what the escape-hatch message in dc_interfaces looks
like, how a mission still running at shutdown is represented — is an open design decision that
says on its face it needs a human. A view written before it would pin the contract from the
reporting end, which is the wrong end. doc/src/dc/kpi_views.md has a section saying so rather
than leaving a gap to rediscover; that acceptance criterion is the one thing left open here.

The Records the views read

intervention (#362) and fault (#365) do not exist yet, so their views return no rows until
those Measurements land. That is what the fixture test is for: it seeds the Records directly, so
a definition change breaks a test here rather than a dashboard later.

Both Measurements are StateTransitionDetector projections and share a column vocabulary, added
to init.sql now because Vector's postgres sink maps a Record's keys onto existing columns
and silently drops the rest: previous_duration, sequence, open, plus mode,
from_mode/to_mode and component/from_level/to_level/reason. open is reported but
never load-bearing — correctness comes from only end/clear Records carrying a duration.

Demo and dashboard

driving_type is enabled in tb3_simulation_pgsql_minio.yaml off Nav2's /cmd_vel (the only
command source the simulation has, so the mode is autonomous while Nav2 publishes and
unknown otherwise) and routed to the pgsql Destination, so the utilisation panels populate
on the existing demo. Nine panels are added to the provisioned dc-kpi dashboard; the
intervention and fault ones stay empty until #362/#365, for the same reason a battery panel
would.

Verification

  • tools/infrastructure/test/test_kpi_views.py goes from 10 to 29 cases. New coverage: the
    missing-speed NULL, a speed Record older than the grace period, another robot's speed, an open
    takeover, a missing denominator, ERROR → STALE, WARN, a never-cleared fault, the
    failure_levels parameter, and each bucketed view.
  • ./tools/infrastructure/scripts/test_kpi_views.sh29 passed.
  • All 16 dashboard panel queries were run against a live PostgreSQL with the Grafana macros
    expanded, not eyeballed.
  • prek run --all-files --skip build-doc is green.

🤖 Generated with Claude Code

https://claude.ai/code/session_018dWQxJJEaDcsD5szRu2LEG

…TTR KPI views

#304 shipped availability and uptime through the whole path; this adds three more
metrics on the same conventions and says plainly why the fourth is not here.

- dc_kpi_utilisation(): moving time under a known driving mode over reported time,
  with the per-mode seconds broken out. No speed Record in the window makes the ratio
  NULL, not 0 % — unreported movement is not zero movement.
- dc_kpi_intervention_rate(): takeovers per autonomous hour and per kilometre. Both
  denominators, because they fail differently; a denominator nothing reported gives
  NULL, never a rate over zero. Only an end Record carries a duration, so a takeover
  still running is counted and never timed.
- dc_kpi_reliability(): MTBF and MTTR per component, over the fault Measurement's own
  durations. ERROR to STALE is neither a raise nor a clear, WARN is not a failure by
  default, and the failure levels are a query parameter.

Mission success rate is deferred: #305 has not agreed what a Mission Measurement
consumes, and a view written first would pin that contract from the reporting end.

intervention (#362) and fault (#365) do not exist yet, so their views return no rows
until they land — the fixture test seeds the Records directly, which is where a
definition change has to break. Their columns are added to init.sql now because
Vector's postgres sink drops keys with no column to land in.

The demo enables driving_type off Nav2's /cmd_vel so the utilisation panels populate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dWQxJJEaDcsD5szRu2LEG
Signed-off-by: David Bensoussan <d.bensoussan@proton.me>
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.55%. Comparing base (7d8c9d7) to head (6f359fc).

Additional details and impacted files
@@           Coverage Diff           @@
##            jazzy     #369   +/-   ##
=======================================
  Coverage   67.55%   67.55%           
=======================================
  Files          98       98           
  Lines        6045     6045           
=======================================
  Hits         4083     4083           
  Misses       1962     1962           
Flag Coverage Δ
cpp-jazzy 67.55% <ø> (ø)

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.

@Minipada
Minipada merged commit 8912018 into jazzy Aug 18, 2026
10 checks passed
@Minipada
Minipada deleted the feature/363-extend-the-kpi-views-utilisation-interve branch September 2, 2026 12:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant