diff --git a/fusion_plating/fusion_plating/models/fp_work_centre.py b/fusion_plating/fusion_plating/models/fp_work_centre.py index 61b3f636..8b8dcb5d 100644 --- a/fusion_plating/fusion_plating/models/fp_work_centre.py +++ b/fusion_plating/fusion_plating/models/fp_work_centre.py @@ -65,6 +65,51 @@ class FpWorkCentre(models.Model): # field via _inherit if/when the bake-oven coupling is needed. active = fields.Boolean(default=True) + # Phase 4 tablet redesign — Manager At-Risk heatmap inputs. + # Non-stored (recomputed on every read by /fp/manager/at_risk; the + # endpoint caches the payload for 60s anyway so the cost is bounded). + bottleneck_score = fields.Float( + compute='_compute_bottleneck', + string='Bottleneck Score', + help='active_step_count * avg_wait_minutes (rolling 7-day). ' + 'Drives the Manager At-Risk heatmap — work centres with ' + 'high score have queue + wait pressure.', + ) + avg_wait_minutes = fields.Float( + compute='_compute_bottleneck', + string='Avg Wait (min)', + help='Average minutes that steps at this work centre waited in ' + 'ready state before starting, over the last 7 days.', + ) + + def _compute_bottleneck(self): + from datetime import timedelta + Step = self.env['fp.job.step'] + now = fields.Datetime.now() + seven_days_ago = now - timedelta(days=7) + for wc in self: + active_n = Step.search_count([ + ('work_centre_id', '=', wc.id), + ('state', 'in', ('ready', 'in_progress')), + ]) + # Avg wait: recent steps where date_started is set; approximate + # "ready since" as create_date when no explicit ready timestamp + # is recorded. Bounded set (last 7 days) keeps the search cheap. + recent = Step.search([ + ('work_centre_id', '=', wc.id), + ('date_started', '>=', seven_days_ago), + ('date_started', '!=', False), + ]) + waits = [] + for s in recent: + if s.create_date and s.date_started: + waits.append( + (s.date_started - s.create_date).total_seconds() / 60.0 + ) + avg = (sum(waits) / len(waits)) if waits else 0.0 + wc.avg_wait_minutes = avg + wc.bottleneck_score = active_n * avg + _sql_constraints = [ ('unique_code', 'UNIQUE(code)', 'Work centre code must be unique.'), ]