feat(fusion_iot): add setpoint/optimum + deviation to sensor schema
Sensors previously only tracked alarm thresholds (alert_min/alert_max). Missing the third piece of standard process control: the SETPOINT — what the heater/chiller controls toward and what dashboards compare against. Without it an operator can't tell whether 89°C is "on target" or "barely still in spec". Schema changes: **fusion.plating.bath.parameter** (shop-wide default) - New `target_value` field — the default setpoint for this parameter across the shop (e.g. 87°C for ENP bath). Parallel to existing target_min / target_max. **fp.tank.sensor** (per-sensor override) - New `target_value_override` — per-sensor override, zero = inherit from parameter. Matches the existing override pattern for alert thresholds so users can fine-tune per-tank without touching the shop-wide spec. - New `effective_target` / `effective_target_unit` computed — resolves override → parameter default, converts to company-preferred unit. - New `_get_setpoint()` helper for internal use. **fp.tank.reading** - New `deviation_from_target` — signed Δ from the sensor's effective setpoint, in the company's preferred unit. Positive = above, negative = below, zero if no setpoint defined. - New `deviation_band` (selection: on/near/far/out/none) — coarse band for fast visual scanning. `on` = within ±1° of target, `near` = ±3°, `far` = beyond, `out` = actually out of the alarm band. **Views** - Sensor form: split the alerting panel into two groups — "Target (setpoint)" on the left, "Alarm band" on the right. Makes the distinction between "where we want to be" and "where we'd panic" visually obvious. - Reading list: new Δ + band columns, with decoration classes (success/info/warning/danger) so the list reads at a glance. - Tank form Sensors tab: inline setpoint + unit column. Seeded: parameter "Bath Temperature (Hot Process)" now carries target_value=87°C as a realistic ENP shop default. Sensors inherit unless they set their own override. Design decisions kept simple: - Did NOT add a warning band (warn_min/warn_max). Two-tier model (setpoint + alarm band) is enough for the pilot. Can add soft warnings later as a separate commit if ops wants them. - Did NOT auto-control heaters. Setpoint is stored as metadata only; actual heater actuation via IoT is a future phase C project. Verified: setpoint 87°C stored → displays 188.60°F on the live pilot sensor (company pref = F). Each incoming reading correctly computes signed deviation; bands colour the reading list appropriately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,66 @@ class FpTankReading(models.Model):
|
||||
r.display_value = r.value
|
||||
r.display_unit = r.parameter_id.uom or ''
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Deviation from setpoint — signed Δ from the sensor's effective target
|
||||
# in the company-preferred unit. Zero if no setpoint defined.
|
||||
# ------------------------------------------------------------------
|
||||
deviation_from_target = fields.Float(
|
||||
string='Δ from Setpoint',
|
||||
compute='_compute_deviation',
|
||||
digits=(12, 2),
|
||||
help='Signed difference between this reading and the sensor\'s '
|
||||
'setpoint, in the company-preferred unit. Positive = above '
|
||||
'setpoint, negative = below. Zero if no setpoint is defined.',
|
||||
)
|
||||
deviation_band = fields.Selection(
|
||||
[
|
||||
('none', 'No setpoint'),
|
||||
('on', 'On target (±1°)'),
|
||||
('near', 'Near target (±3°)'),
|
||||
('far', 'Far from target (>3°)'),
|
||||
('out', 'Out of spec'),
|
||||
],
|
||||
string='Band',
|
||||
compute='_compute_deviation',
|
||||
help='Coarse band for quick visual scanning in lists.',
|
||||
)
|
||||
|
||||
@api.depends('value', 'display_value', 'in_spec',
|
||||
'sensor_id', 'sensor_id.target_value_override',
|
||||
'sensor_id.parameter_id.target_value',
|
||||
'sensor_id.parameter_id.parameter_type')
|
||||
def _compute_deviation(self):
|
||||
pref = self.env.company.x_fc_default_temp_uom or 'C'
|
||||
for r in self:
|
||||
sensor = r.sensor_id
|
||||
if not sensor:
|
||||
r.deviation_from_target = 0.0
|
||||
r.deviation_band = 'none'
|
||||
continue
|
||||
raw_sp = sensor._get_setpoint()
|
||||
if not raw_sp:
|
||||
r.deviation_from_target = 0.0
|
||||
r.deviation_band = 'none'
|
||||
continue
|
||||
# Convert setpoint + value to display unit for the delta so the
|
||||
# number shown matches what operators expect (°F if pref=F).
|
||||
ptype = (sensor.parameter_id.parameter_type or '').lower()
|
||||
if ptype == 'temperature' and pref == 'F':
|
||||
sp_disp = raw_sp * 9.0 / 5.0 + 32.0
|
||||
else:
|
||||
sp_disp = raw_sp
|
||||
delta = r.display_value - sp_disp
|
||||
r.deviation_from_target = round(delta, 2)
|
||||
if not r.in_spec:
|
||||
r.deviation_band = 'out'
|
||||
elif abs(delta) <= 1.0:
|
||||
r.deviation_band = 'on'
|
||||
elif abs(delta) <= 3.0:
|
||||
r.deviation_band = 'near'
|
||||
else:
|
||||
r.deviation_band = 'far'
|
||||
|
||||
source = fields.Selection(
|
||||
[
|
||||
('iot_proxy', 'IoT Proxy (Pi)'),
|
||||
|
||||
Reference in New Issue
Block a user