251 lines
13 KiB
Markdown
251 lines
13 KiB
Markdown
# Bento 1 fresh bentonite KPIs
|
||
|
||
|
||
## Three distinct quantities
|
||
|
||
All **fresh bentonite** values exclude the third recycled/recovered-material
|
||
scatterer. Only the two actual rpm signals listed below are used; no SET signals
|
||
are introduced. The specific discharge is central calibration
|
||
data: **2.75 kg/(revolution × metre product width)**.
|
||
|
||
A) **Instantaneous process application [g/m²]**
|
||
|
||
`application_g_m2 = (rpm_left + rpm_right) × 2.75 × 1000 / line_speed_m_min`
|
||
|
||
Emitted only for observed samples with line speed **strictly greater than
|
||
0.3 m/min**. Zero, near-zero, negative and threshold-equal speeds emit no point.
|
||
Width cancels between kg/min and m²/min; neither nominal width nor ERP good area
|
||
enters this formula. For 3.739 + 3.956 rpm at 8 m/min the result is 2645.15625 g/m².
|
||
This is fresh-roll process application, not FA material efficiency.
|
||
|
||
B) **Cumulative fresh consumption [kg]**, unchanged
|
||
|
||
`kg/min = (rpm_left + rpm_right) × nominal_width_m × 2.75`
|
||
|
||
The existing previous-value integration sums `kg/min × elapsed_seconds / 60`
|
||
for eligible intervals, using the preceding sample's production gate. Width
|
||
continues to come from the ERP nominal-width parser. The integration algorithm,
|
||
gap handling, order checkpoint format and disjoint-run accumulation are unchanged.
|
||
|
||
C) **FA material efficiency [g/m²]**
|
||
|
||
`efficiency_g_m2 = cumulative_fresh_bentonite_kg × 1000 / cumulative_good_area_m2`
|
||
|
||
The generic material-efficiency service reads the latest exact-order cumulative
|
||
snapshot **at or before the ERP feedback timestamp**, then divides by that
|
||
feedback's cumulative good area. Missing/nonpositive good area emits no point.
|
||
This ratio includes losses captured by the existing consumption model (startup
|
||
material, rejects and other consumed material not represented in good area).
|
||
It does not add consumption during intervals excluded by the validated gate or
|
||
gap rules, including stopped-line periods. Disjoint runs of one FA share the
|
||
existing order total; their cumulative snapshots must not be summed again.
|
||
|
||
## Configuration, PostgreSQL and Grafana
|
||
|
||
| Calculation ID | Table | Value column | Time column |
|
||
| --- | --- | --- | --- |
|
||
| `bento1-fresh-bentonite-application` | `material_application_snapshots` | `application_g_m2` | `timestamp` |
|
||
| `bento1-fresh-bentonite-consumption` | `material_consumption_snapshots` | `consumption_kg` | `timestamp` |
|
||
| `bento1-fresh-bentonite-efficiency` | `material_efficiency_snapshots` | `material_consumption_g_per_m2` | `erp_feedback_timestamp` |
|
||
|
||
Scope Grafana queries by calculation ID and
|
||
`machine_id = '5f42a4f6-9ca0-4f6f-9786-40d50a35b230'`; optionally filter by
|
||
`production_order` (application/consumption) or `enlyze_production_order`
|
||
(efficiency). The efficiency table also stores `good_quantity_m2`,
|
||
`material_consumption_kg`, `material_consumption_kg_per_m2` and
|
||
`material_snapshot_timestamp` for auditability.
|
||
|
||
`config/bento1-material-consumption.yaml` opts into process snapshots through
|
||
`process_application_calculation_id`. This generic option requires rotational
|
||
sources and a positive speed gate. Both outputs reuse the same configured ACT
|
||
signals, discharge factor and samples. Process points use source timestamps,
|
||
including bootstrap samples from disjoint runs; no interpolation or hold-forward
|
||
points are stored for inactive periods. Configure Grafana to leave missing
|
||
periods as gaps rather than carrying the last active value forward.
|
||
|
||
The shared consumption poller still requires valid ERP width/order context to
|
||
complete a cycle, although the application formula itself has no width input.
|
||
Application rows are committed before the existing checkpoint advances; a failed
|
||
application write retries the window. Duplicate source timestamps are ignored by
|
||
the primary key. Existing checkpoints are retained, so earlier application
|
||
history is not automatically backfilled. K7 does not enable this output.
|
||
|
||
`config/bento1-material-efficiency.yaml` uses the existing generic runner.
|
||
`calculation_id` selects the cumulative input; optional `output_calculation_id`
|
||
sets the distinct persisted KPI ID. Omitting it preserves existing behavior,
|
||
including K7. ERP workplace `strip().casefold()` normalization is unchanged.
|
||
|
||
Before deploying, apply the additive, idempotent `db/schema.sql` to the existing
|
||
PostgreSQL database to create `material_application_snapshots` and its index.
|
||
The existing consumption and efficiency tables need no column migration. For
|
||
example, on the deployment host:
|
||
|
||
```sh
|
||
docker compose exec -T timescaledb psql -U production_analytics -d production_analytics \
|
||
-v ON_ERROR_STOP=1 < db/schema.sql
|
||
```
|
||
|
||
Restart the configured Bento consumption runner to enable process persistence,
|
||
and supervise a separate generic efficiency runner:
|
||
|
||
```sh
|
||
production-analytics run material-efficiency \
|
||
--config config/bento1-material-efficiency.yaml \
|
||
--secrets-file secrets/enlyze.env --erp-secrets-file secrets/erp.env
|
||
```
|
||
|
||
No cleanup/reset of validated rpm consumption state or snapshots is required for
|
||
these additions. No database migration, live service restart or historical data
|
||
cleanup was performed as part of this implementation.
|
||
|
||
## Validated cumulative model details
|
||
|
||
`config/bento1-material-consumption.yaml` defines
|
||
`bento1-fresh-bentonite-consumption`, an estimate of **fresh bentonite consumption**
|
||
from actual spreader roll speeds. Both needle rolls apply sequentially across the
|
||
full web width. The recycled/recovered third spreader is excluded.
|
||
|
||
The generic `rotational_discharge` source converts one or more rpm signals to the
|
||
existing integrator's kg/h: `sum(rpm) × nominal_width_m × specific_discharge_kg_per_rev_m × 60`.
|
||
Bento config sets the factor to **2.75 kg/(rev·m)** and uses:
|
||
|
||
- Right ACT: `6e5d2d94-98f9-4cc1-8a88-7987c6282525`
|
||
- Left ACT: `cd7385c4-337b-4759-ab32-45d65beaf190`
|
||
|
||
ENLYZE now returns physical rpm directly (scaling factor 1.0); no division by
|
||
1000 is applied. For 3.739 + 3.956 rpm and 5 m width, the rate is 105.80625 kg/min
|
||
(6348.375 kg/h), giving 17.634375 kg in ten seconds.
|
||
|
||
For cumulative consumption, transport speed
|
||
`fef41976-1103-4090-b780-eaeecc02fdfa` remains exclusively the production gate:
|
||
speed must be strictly greater than 0.3 m/min. It does not multiply the mass rate.
|
||
For instantaneous application, this same speed is also the denominator. The generic rotational mode also supports omitting
|
||
`gate_signal_ref` and `gate_threshold`, in which case all valid intervals are
|
||
active. Bento retains its gate.
|
||
|
||
The two old SET sources `19ea65d2-bd35-4d02-89de-4495583d9026` and
|
||
`d9f47615-69cf-4f97-88df-199585764491` are no longer requested by Bento.
|
||
`direct_mass_rate` remains the default for K7; `area_application` remains
|
||
available for other configurations. There is no Bento branch in the integrator.
|
||
|
||
The previous-value integration, JSON state format and order bootstrap/resume
|
||
remain unchanged. Totals accumulate across disjoint runs, with a new baseline
|
||
at each run boundary, even if the inter-run gap is under 20 seconds. Intervals
|
||
up to 20 seconds use the preceding rate and gate; longer sample gaps and
|
||
unobserved run tails are not integrated. Missing/nonfinite source values reject
|
||
the cycle. Finite negative speeds retain the existing generic signed-rate
|
||
semantics; no clamping is introduced.
|
||
|
||
Width comes from the existing ERP nominal-width parser, with workplace
|
||
`Bento 1` and order format `Bento 1-{production_order}`. Workplace comparisons
|
||
use `strip().casefold()`; order matching remains strict. Missing/mismatched ERP
|
||
context or an unparseable width rejects the cycle without saving state. The
|
||
current ERP context supplies width for all runs of the same order, assuming
|
||
constant article width. Historical orders no longer in the ERP workplace view
|
||
need a historical width source for replay.
|
||
|
||
The current factor is **2.75 kg/(rev*m)**, independently derived on
|
||
**2026-09-08** by the **gravimetric tray method**: measured application
|
||
**4068 g/m²**, line speed **2.3 m/min**, actual rotational signals **left 1.65**
|
||
and **right 1.75**. The derivation is `4.068 × 2.3 / (1.65 + 1.75) ≈ 2.75188`,
|
||
rounded to the configured 2.75. This supersedes the earlier 3.12 estimate.
|
||
See [central calibration configuration](material-calibrations.md) for updates
|
||
and persistence semantics.
|
||
|
||
Tests use realistic rpm values and synthetic samples, including disjoint-run
|
||
bootstrap and persisted resume. They are not a new replay of recorded ENLYZE data.
|
||
|
||
## Historical migration from SET to rpm: review before execution
|
||
|
||
The following records the earlier source-model migration, **not a prerequisite
|
||
cleanup for adding these KPIs**. Its deployment observations are historical.
|
||
If the validated rpm model is already deployed, retain its state and snapshots;
|
||
do not execute this historical cleanup for the KPI extension. Recheck deployment
|
||
provenance separately if that earlier migration is still outstanding.
|
||
|
||
No production state or database rows were changed during this implementation,
|
||
and no service start was requested. The operator reports Bento stopped;
|
||
sandbox systemd access could not independently confirm that state.
|
||
The deployed `/etc/systemd/system/production-analytics-bento1.service` specifies
|
||
`/opt/git-projects/production-analytics/data/state/material` as its state directory.
|
||
|
||
The existing state file for machine `5f42a4f6-9ca0-4f6f-9786-40d50a35b230`
|
||
and order `Bento 1-12026000857` is exactly:
|
||
|
||
```text
|
||
/opt/git-projects/production-analytics/data/state/material/4d6cc209e35bc58887e5b4a3816835006425429e810e0d957f651971723e8c2f.json
|
||
```
|
||
|
||
This identity was verified using the store's SHA-256 of the JSON machine/order
|
||
pair. The file exists but its contents are not readable by the sandbox user.
|
||
The other state file, `6d11088159b8e4fd21543560a54a31a0f86faf22bbf8f16873b228d1903c23f9.json`,
|
||
has not been attributed here and must be left untouched.
|
||
|
||
Old PostgreSQL snapshots are in `material_consumption_snapshots`, scoped by both
|
||
`calculation_id = 'bento1-fresh-bentonite-consumption'` and
|
||
`machine_id = '5f42a4f6-9ca0-4f6f-9786-40d50a35b230'`.
|
||
The table has no calculation-source/version provenance column. Given the stopped
|
||
service and no rpm deployment yet, existing rows in this scope belong to the old
|
||
calculation. PostgreSQL was unreachable from the sandbox, so exact row counts,
|
||
order/run membership and timestamp bounds remain unverified. Do not mistake
|
||
this predicate for a completed row inventory. Review the following output first.
|
||
|
||
Run these **read-only inventory commands** on the deployment host:
|
||
|
||
```sh
|
||
cd /opt/git-projects/production-analytics
|
||
sudo systemctl is-active production-analytics-bento1.service
|
||
sudo cat data/state/material/4d6cc209e35bc58887e5b4a3816835006425429e810e0d957f651971723e8c2f.json
|
||
sudo docker compose exec -T timescaledb psql -U production_analytics -d production_analytics -v ON_ERROR_STOP=1 <<'SQL'
|
||
BEGIN READ ONLY;
|
||
SELECT production_order, run_id, count(*) AS rows,
|
||
min(timestamp) AS first_snapshot, max(timestamp) AS last_snapshot,
|
||
min(consumption_kg), max(consumption_kg)
|
||
FROM material_consumption_snapshots
|
||
WHERE calculation_id = 'bento1-fresh-bentonite-consumption'
|
||
AND machine_id = '5f42a4f6-9ca0-4f6f-9786-40d50a35b230'
|
||
GROUP BY production_order, run_id ORDER BY first_snapshot;
|
||
SELECT * FROM material_consumption_snapshots
|
||
WHERE calculation_id = 'bento1-fresh-bentonite-consumption'
|
||
AND machine_id = '5f42a4f6-9ca0-4f6f-9786-40d50a35b230'
|
||
ORDER BY production_order, timestamp;
|
||
COMMIT;
|
||
SQL
|
||
```
|
||
|
||
After reviewing the inventory, and **only with cleanup authorization**, keep the
|
||
service stopped and execute this backup-and-delete transaction. The backup table
|
||
intentionally has no `IF NOT EXISTS`: a repeated invocation fails rather than
|
||
reusing an older backup. Only rows with the backed-up primary keys are deleted.
|
||
K7 rows are outside the predicate.
|
||
|
||
```sh
|
||
cd /opt/git-projects/production-analytics
|
||
sudo docker compose exec -T timescaledb psql -U production_analytics -d production_analytics -v ON_ERROR_STOP=1 <<'SQL'
|
||
BEGIN;
|
||
CREATE TABLE bento1_material_snapshots_before_rpm_20260907 AS
|
||
SELECT * FROM material_consumption_snapshots
|
||
WHERE calculation_id = 'bento1-fresh-bentonite-consumption'
|
||
AND machine_id = '5f42a4f6-9ca0-4f6f-9786-40d50a35b230';
|
||
DELETE FROM material_consumption_snapshots AS s
|
||
USING bento1_material_snapshots_before_rpm_20260907 AS old
|
||
WHERE s.calculation_id = old.calculation_id AND s.machine_id = old.machine_id
|
||
AND s.production_order = old.production_order AND s.timestamp = old.timestamp;
|
||
COMMIT;
|
||
SQL
|
||
sudo mv -n -- \
|
||
data/state/material/4d6cc209e35bc58887e5b4a3816835006425429e810e0d957f651971723e8c2f.json \
|
||
data/state/material/4d6cc209e35bc58887e5b4a3816835006425429e810e0d957f651971723e8c2f.json.before-rpm-20260907
|
||
```
|
||
|
||
Verify the original JSON path is absent before restarting; `mv -n` preserves an
|
||
existing backup and will not overwrite it. If inventory reveals other Bento
|
||
orders, derive their paths with `JsonMaterialStateStore._path(machine, order)`
|
||
and review any existing files before moving them too. Do not clear the shared
|
||
directory. Complete both state and snapshot cleanup before starting the new
|
||
code: the state schema deliberately does not detect a changed source model.
|
||
|
||
A later start bootstraps only the currently open order using its current ERP
|
||
width. It does not automatically rebuild snapshots for closed historical
|
||
orders. Reconstructing those requires a separately reviewed historical replay.
|