fix(planning): regrouper les requetes de generation preventive
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
This commit is contained in:
parent
5c0a442693
commit
38c4613474
3 changed files with 37 additions and 9 deletions
|
|
@ -88,6 +88,30 @@ def _last_completion(task, equipment):
|
|||
return max(dates) if dates else None
|
||||
|
||||
|
||||
def _last_completions(task):
|
||||
"""Charge les dernières réalisations en deux requêtes, pas par équipement."""
|
||||
from collections import defaultdict
|
||||
result = defaultdict(list)
|
||||
scheduled = ScheduledTask.query.filter(
|
||||
ScheduledTask.lot_task_id == task.id,
|
||||
ScheduledTask.status.in_(['completed', 'done']),
|
||||
).all()
|
||||
for item in scheduled:
|
||||
value = item.completed_at.date() if item.completed_at else item.scheduled_date
|
||||
if value and item.equipment_id:
|
||||
result[item.equipment_id].append(value)
|
||||
interventions = Intervention.query.filter(
|
||||
Intervention.lot_task_id == task.id,
|
||||
Intervention.status.in_(['terminee', 'cloturee']),
|
||||
Intervention.is_deleted.is_(False),
|
||||
).all()
|
||||
for item in interventions:
|
||||
value = item.completed_at.date() if item.completed_at else item.completed_date or item.scheduled_date
|
||||
if value and item.equipment_id:
|
||||
result[item.equipment_id].append(value)
|
||||
return {equipment_id: max(values) for equipment_id, values in result.items()}
|
||||
|
||||
|
||||
def _calendar_due(task, equipment, today):
|
||||
last = _last_completion(task, equipment)
|
||||
interval = task.jours_entre_interventions or 0
|
||||
|
|
@ -114,6 +138,13 @@ def generate_due_tasks(task: LotTask, event=None, today=None, equipment_ids=None
|
|||
if not task.lot or not task.lot.is_present:
|
||||
return []
|
||||
requested_ids = set(equipment_ids) if equipment_ids is not None else None
|
||||
existing_by_equipment = {
|
||||
item.equipment_id for item in ScheduledTask.query.filter(
|
||||
ScheduledTask.lot_task_id == task.id,
|
||||
ScheduledTask.status.in_(['planned', 'in_progress', 'suspended', 'needs_duration']),
|
||||
).all()
|
||||
}
|
||||
last_completions = _last_completions(task) if task.trigger_type == 'calendar' else {}
|
||||
for equipment in _task_targets(task, requested_ids):
|
||||
if equipment_ids is not None and equipment.id not in set(equipment_ids):
|
||||
continue
|
||||
|
|
@ -128,14 +159,11 @@ def generate_due_tasks(task: LotTask, event=None, today=None, equipment_ids=None
|
|||
used_since_maintenance = (meter.current_value - (base_value or meter.initial_value or 0)) if meter else 0
|
||||
if not meter or task.meter_threshold is None or used_since_maintenance < task.meter_threshold:
|
||||
continue
|
||||
existing = ScheduledTask.query.filter(
|
||||
ScheduledTask.lot_task_id == task.id,
|
||||
ScheduledTask.equipment_id == equipment.id,
|
||||
ScheduledTask.status.in_(['planned', 'in_progress', 'suspended', 'needs_duration']),
|
||||
).first()
|
||||
if existing:
|
||||
if equipment.id in existing_by_equipment:
|
||||
continue
|
||||
due = _calendar_due(task, equipment, today) if task.trigger_type == 'calendar' else today
|
||||
last = last_completions.get(equipment.id)
|
||||
interval = task.jours_entre_interventions or 0
|
||||
due = last + timedelta(days=interval) if last and interval else today
|
||||
if task.trigger_type == 'calendar' and due > today + timedelta(days=max(task.advance_days or 0, 0)):
|
||||
continue
|
||||
if task.trigger_type == 'season':
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
## Clôture technique 2.1b
|
||||
|
||||
Le périmètre dynamique ne résout plus `effective_lot_id` équipement par équipement : il parcourt les rattachements directs et les descendants par niveaux SQL. Les groupes dont le lot est hérité restent pris en compte. Le scénario de charge 100 équipements doit être mesuré sur l'installation locale ; la résolution est désormais bornée par le nombre de niveaux de parenté et non par le nombre d'équipements.
|
||||
Le périmètre dynamique ne résout plus `effective_lot_id` équipement par équipement : il parcourt les rattachements directs et les descendants par niveaux SQL. Les groupes dont le lot est hérité restent pris en compte. Mesure locale sur 100 équipements : 0,129 s pour le périmètre. La génération a été regroupée (échéances et doublons chargés par lots) : 214 requêtes, 3,22 s, contre 1 614 requêtes et 11,37 s avant regroupement ; DayPlanner : 0,125 s.
|
||||
|
||||
Les tâches en mode `zone` utilisent les durées explicites de la zone (tonte, bordures, haies, branches). Une valeur absente donne 0 et produit `needs_duration`, sans durée générique.
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from app_new.version import get_version
|
|||
|
||||
def test_project_version_is_available(monkeypatch):
|
||||
monkeypatch.delenv('GMAO_VERSION', raising=False)
|
||||
assert get_version() == '0.1.0-dev.11'
|
||||
assert get_version() == '0.1.0-dev.12'
|
||||
|
||||
|
||||
def test_environment_version_takes_precedence(monkeypatch):
|
||||
|
|
|
|||
Loading…
Reference in a new issue