Use rotational discharge for Bento consumption
This commit is contained in:
+127
-49
@@ -1,60 +1,138 @@
|
||||
# Bento 1 fresh bentonite consumption
|
||||
|
||||
`config/bento1-material-consumption.yaml` defines
|
||||
`bento1-fresh-bentonite-consumption`. This KPI estimates **fresh bentonite
|
||||
consumption** from the two fresh-spreader setpoints in g/m². The third spreader
|
||||
uses recycled/recovered bentonite and is deliberately excluded; it is neither
|
||||
missing data nor estimated. This is not total bentonite deposited on the product,
|
||||
and setpoints are not a measurement of actual mass flow.
|
||||
`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 `area_application` source sums the configured application signals
|
||||
and converts them to kg/h using `sum(g/m²) × nominal_width_m × speed_m/min × 60 / 1000`.
|
||||
The existing previous-value integrator then accumulates kilograms only while
|
||||
speed is strictly greater than 0.3 m/min. `direct_mass_rate` remains the default
|
||||
for existing configurations, including K7. The persistent state schema and
|
||||
production-order bootstrap/resume behavior are unchanged; each disjoint run
|
||||
starts with a new sample baseline while retaining order totals.
|
||||
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 **3.12 kg/(rev·m)** and uses:
|
||||
|
||||
Width comes from the existing ERP workplace status adapter and generic nominal
|
||||
width parser. The ERP order must format to the ENLYZE order using
|
||||
`Bento 1-{production_order}`. Missing/mismatched ERP context or an unparseable
|
||||
width rejects the polling cycle without saving state. ENLYZE product metadata is
|
||||
not used for width. The current ERP context supplies width for all runs of that
|
||||
same order; this assumes the order's article width stays constant. Historical
|
||||
orders no longer present in current ERP workplace status require a separate
|
||||
historical context source for offline replay.
|
||||
- Right ACT: `6e5d2d94-98f9-4cc1-8a88-7987c6282525`
|
||||
- Left ACT: `cd7385c4-337b-4759-ab32-45d65beaf190`
|
||||
|
||||
Run with:
|
||||
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 120.042 kg/min
|
||||
(7202.52 kg/h), giving 20.007 kg in ten seconds.
|
||||
|
||||
```sh
|
||||
production-analytics run material-poll \
|
||||
--config config/bento1-material-consumption.yaml \
|
||||
--erp-secrets-file secrets/erp.env
|
||||
Transport speed `fef41976-1103-4090-b780-eaeecc02fdfa` is exclusively the
|
||||
production gate: speed must be strictly greater than 0.3 m/min. It does not
|
||||
multiply the mass rate. 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 configured factor is based on the supplied independent historical checks:
|
||||
3.1175 at 5.00 m (article 180305), 3.1193 and 3.1187 at 4.85 m (article 173000),
|
||||
and median 3.1208 at 5.00 m (article 180005; p05–p95 3.0703–3.1558).
|
||||
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.
|
||||
|
||||
## Old calculation cleanup: review before execution
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
`erp_workplace: Bento 1` assumes that exact ERP workplace identifier; verify it
|
||||
against the deployment's workplace view. The 20-second maximum sample gap is a
|
||||
generic policy shared with K7, intended for a nominal 10-second sampling cadence.
|
||||
One missing sample is tolerated: the resulting 20-second interval is integrated
|
||||
using the preceding rate and gate. Gaps greater than 20 seconds are not
|
||||
integrated, so prolonged or repeated gaps can undercount consumption.
|
||||
Missing/nonfinite values from either configured fresh spreader reject the cycle.
|
||||
Intervals exceeding the maximum gap and unobserved run tails are not inferred.
|
||||
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.
|
||||
|
||||
Before implementation, the calculation was independently validated manually
|
||||
against the same three real ENLYZE signals over the two runs below, yielding
|
||||
approximately **147021.9 kg** of fresh bentonite for `Bento 1-12026000814`.
|
||||
The post-implementation live replay could not be repeated because ENLYZE
|
||||
connectivity was temporarily unavailable (DNS resolution failure), not because
|
||||
of a known implementation issue. Exact production-path replay remains a
|
||||
follow-up verification rather than a blocker for this milestone.
|
||||
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.
|
||||
|
||||
The regression uses article 180305, description `Bfix NSP 5300, 5,00 x 40 m`
|
||||
(width 5.00 m), and the supplied boundaries of runs
|
||||
`b675818c-4636-4976-be6c-3e0e24da9e8a` and
|
||||
`25d67424-8346-42d4-b844-75b4097382bd` for order `Bento 1-12026000814`.
|
||||
It constructs synthetic samples from the supplied aggregates: 1256.0 active
|
||||
minutes, 31294.6 m² and 4698.0 g/m², yielding approximately 147021.9 kg.
|
||||
It verifies bootstrap across both runs and persisted resume without counting the
|
||||
inter-run gap. It is an aggregate plausibility regression, not an independent
|
||||
replay of recorded signals; raw reference samples were not supplied.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user