Files
production-analytics/docs/bento1-fresh-bentonite.md
T

251 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.