Your OEE Tracker file already contains every day's results, worked out by the tracker. Power BI only reads them: it never changes your file, and you keep entering days in the tracker as usual.
A ready-made Power BI template that does all of this for you is coming soon. Until then, the steps below take about ten minutes.
The tracker file (.json) has a table called values: one row for every entered day,
line, shift and component, with these columns:
breakdown, and
value: the numberValues are in base units: times in minutes, quantities in your own units, rates per minute.
Availability, Performance, Quality and Loading are always stored under the same fixed names, whatever you called
their parts: A.num and A.den (Availability's top and bottom), P.num,
P.den, Q.num, Q.den, L.num, L.den.
If you use tags (such as SKU or crop), the file also has a table called entryTags: one row per
day, line and shift for each tag, with the tag's name and the value picked that day. Load it with
Table.FromRecords(Source[entryTags]) and relate it to your values on date, line and shift to filter by
tag.
let
Source = Json.Document(File.Contents("C:\Users\you\Documents\My plant 2026.json")),
Values = Table.FromRecords(Source[values],
{"date", "line", "shift", "version", "component", "value"}, MissingField.UseNull),
Components = Table.FromRecords(Source[valueComponents],
{"id", "name", "unit"}, MissingField.UseNull),
Lines = Table.FromRecords(Source[lines], {"id", "name"}, MissingField.UseNull),
Shifts = if List.IsEmpty(Source[shifts]) then #table({"id", "name"}, {})
else Table.FromRecords(Source[shifts], {"id", "name"}, MissingField.UseNull),
AddComponent = Table.ExpandTableColumn(
Table.NestedJoin(Values, {"component"}, Components, {"id"}, "c", JoinKind.LeftOuter),
"c", {"name", "unit"}, {"component name", "unit"}),
AddLine = Table.ExpandTableColumn(
Table.NestedJoin(AddComponent, {"line"}, Lines, {"id"}, "l", JoinKind.LeftOuter),
"l", {"name"}, {"line name"}),
AddShift = Table.ExpandTableColumn(
Table.NestedJoin(AddLine, {"shift"}, Shifts, {"id"}, "s", JoinKind.LeftOuter),
"s", {"name"}, {"shift name"}),
Typed = Table.TransformColumnTypes(AddShift, {
{"date", type date}, {"value", type number}, {"line", type text}, {"shift", type text},
{"version", type text}, {"component", type text}, {"component name", type text},
{"unit", type text}, {"line name", type text}, {"shift name", type text}})
in
Typed
Choose Modeling → New measure and add each of these. They divide the totals for whatever you've filtered (a month, a line, a shift), which is how the tracker works out its own figures. Never average daily percentages: a short day would count as much as a full one.
Availability =
DIVIDE (
CALCULATE ( SUM ( 'OEE values'[value] ), 'OEE values'[component] = "A.num" ),
CALCULATE ( SUM ( 'OEE values'[value] ), 'OEE values'[component] = "A.den" )
)
Performance =
DIVIDE (
CALCULATE ( SUM ( 'OEE values'[value] ), 'OEE values'[component] = "P.num" ),
CALCULATE ( SUM ( 'OEE values'[value] ), 'OEE values'[component] = "P.den" )
)
Quality =
DIVIDE (
CALCULATE ( SUM ( 'OEE values'[value] ), 'OEE values'[component] = "Q.num" ),
CALCULATE ( SUM ( 'OEE values'[value] ), 'OEE values'[component] = "Q.den" )
)
Loading =
DIVIDE (
CALCULATE ( SUM ( 'OEE values'[value] ), 'OEE values'[component] = "L.num" ),
CALCULATE ( SUM ( 'OEE values'[value] ), 'OEE values'[component] = "L.den" )
)
OEE = [Availability] * [Performance] * [Quality]
TEEP = [OEE] * [Loading]
Format the six measures as percentages. To chart one of your own inputs, for example breakdown minutes by month, use a measure such as:
Breakdown minutes =
CALCULATE ( SUM ( 'OEE values'[value] ), 'OEE values'[component name] = "Breakdown" )
Only add up minutes and quantities this way. Rates (such as pieces per minute) and percentages can't be added across days; work them out from totals, as the measures above do.