feat(fusion_plating): fp.work.centre bottleneck_score + avg_wait_minutes (P4.1)
Computes for the Manager At-Risk heatmap (Phase 4 tablet redesign). Non-stored — recomputed on /fp/manager/at_risk read; that endpoint caches its full payload for 60s so the cost is bounded. bottleneck_score = active_step_count * avg_wait_minutes avg_wait_minutes = rolling-7-day avg of (date_started - create_date) Work centres with high score show red in the heatmap — combination of queue length AND average wait time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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.'),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user