Persist material efficiency snapshots

This commit is contained in:
2026-09-06 08:23:28 +02:00
parent c411581a1a
commit ed0e20a04b
10 changed files with 851 additions and 32 deletions
+61 -9
View File
@@ -318,7 +318,7 @@ across the order's runs. The service neither reintegrates nor sums snapshots and
does not bridge run boundaries.
The result includes workplace/machine/calculation identifiers, both order identifiers,
article context, nominal width, both original source timestamps, good area, aligned
article context, nominal width, the UTC ERP feedback instant and selected material timestamp, good area, aligned
consumption, `material_consumption_kg_per_m2 = consumption_kg / good_quantity_m2`,
and `material_consumption_g_per_m2 = material_consumption_kg_per_m2 * 1000`.
Nominal width comes from generic ERP description parsing and is context only;
@@ -352,9 +352,9 @@ result = service.evaluate_current()
Aware ERP timestamps are compared as UTC instants. Naive ERP timestamps require
explicit `erp_timezone`; there is no inferred system/database timezone. Ambiguous
or nonexistent local times during DST transitions are rejected. The original ERP
timestamp (including naivety) and selected material timestamp are preserved in the
result; retain the source timezone configuration alongside it for interpretation.
or nonexistent local times during DST transitions are rejected. The result carries the
ERP feedback instant normalized to UTC and the selected aware material timestamp.
The original ERP status object remains unchanged.
This alignment avoids knowingly including future material consumption, but does
not eliminate ERP roll-feedback timing uncertainty (feedback may be roughly one
@@ -366,11 +366,10 @@ stored timestamps satisfy the cutoff; a later cumulative total cannot reconstruc
an earlier value. Reliable final evaluation requires retaining final ERP feedback;
the current-workplace adapter alone does not provide historical completed orders.
Call on changed ERP feedback; no new runner or polling loop is provided. Results
are returned in memory without modifying earlier results or material snapshots.
No schema, Grafana, or timeseries changes are needed. Daily per-machine 24h reporting
and aggregation remain future work. Repository SQL selection tests use an in-memory
SQLite fixture with driver transport adaptation; they require no live PostgreSQL.
The service itself returns results in memory without modifying material snapshots.
The standalone persistence runner is described below. Daily per-machine 24h reporting
and aggregation remain future work. Repository SQL tests use an in-memory SQLite
fixture with driver transport adaptation; they require no live PostgreSQL.
## Peak-cycle detection
@@ -412,3 +411,56 @@ The normal unit test suite mocks PostgreSQL and requires no live database.
See [PROJECT_KNOWLEDGE.md](PROJECT_KNOWLEDGE.md) for durable project context
and [docs/roadmap.md](docs/roadmap.md) for the implementation sequence.
### Feedback-driven material-efficiency persistence
Apply the updated `db/schema.sql` using the PostgreSQL setup command above, then run
this independent foreground process:
```bash
production-analytics run material-efficiency --config config/k7-material-efficiency.yaml
```
`poll_interval_seconds` in YAML defaults to 60 seconds (fixed delay after each
cycle). Workplace, machine, calculation, order format and `erp_timezone` are also
configured in YAML; K7 uses `Europe/Berlin`. PostgreSQL settings use the existing
exported `POSTGRES_*` variables and `--secrets-file` (default `secrets/enlyze.env`);
ERP uses `--erp-secrets-file` (default `secrets/erp.env`). No additional secrets are
needed, and `.env` is not loaded automatically.
`PostgresMaterialEfficiencyWriter.write(snapshot) -> bool` validates aware
timestamps and finite numbers, then inserts all KPI fields in a short transaction.
`material_efficiency_snapshots` retains one immutable point per
`(calculation_id, machine_id, enlyze_production_order, erp_feedback_timestamp)`.
The writer uses `RETURNING 1` to return `True` for a new insert and `False` for a
duplicate. Only new inserts produce a flushed stdout line with feedback time,
workplace, order, good m², material kg and g/m². Unavailable evaluations and
duplicates remain silent.
Repeated evaluations attempt `ON CONFLICT DO NOTHING`; they never update history,
including after a restart. Missing/invalid KPI inputs produce no row and can be
retried on a later cycle. Nullable article context remains SQL NULL.
Persistence frequency follows ERP feedback changes, not ENLYZE sample frequency.
This runner is independent of the 10-second material polling process. Live values
are plausibility indicators; final production-order (FA) values are more meaningful.
Grafana can read PostgreSQL alone:
```sql
SELECT erp_feedback_timestamp AS "time", material_consumption_g_per_m2
FROM material_efficiency_snapshots
WHERE $__timeFilter(erp_feedback_timestamp)
AND calculation_id = 'k7-fiber-consumption'
AND machine_id = 'c220f95c-a65e-4cb7-99b7-0626d6c7508c'
ORDER BY erp_feedback_timestamp;
```
ERP read errors and transient PostgreSQL connection/operational errors are reported
using error classes only and retried after the configured delay. Each database
operation opens a fresh connection. Configuration, schema/programming and invalid
timestamp contract errors terminate with a nonzero CLI exit; ambiguous/nonexistent
DST feedback remains rejected. Ctrl-C stops the foreground runner cleanly.
The current-status ERP source cannot backfill feedback events missed between polls
or during outages, nor guarantee observation of final FA feedback before the order
changes. Daily 24h reports and final order summary tables remain future work.
No dashboard, aggregation or lag correction is added here.