feat(preventive): embed historical lot task catalogue
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
b32d1a989e
commit
0f6770611f
15 changed files with 359 additions and 21 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.1.0-dev.9
|
||||
0.1.0-dev.10
|
||||
|
|
|
|||
160
app_new/core/preventive_catalog.py
Normal file
160
app_new/core/preventive_catalog.py
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
"""Catalogue préventif historique embarqué.
|
||||
|
||||
Le blob est une extraction figée du dump de référence (330 lignes de
|
||||
``lot_tasks``). Le dump n'est jamais lu à l'exécution : cette représentation
|
||||
compacte permet de garder une seule source embarquée et réutilisable par le
|
||||
setup wizard et les tests de comparaison.
|
||||
"""
|
||||
import base64
|
||||
import json
|
||||
import zlib
|
||||
|
||||
_BLOB = (
|
||||
"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"
|
||||
)
|
||||
_FIELDS = ("lot", "category", "num_tache", "tache", "periodicite",
|
||||
"contrat", "gestionnaire", "jours_entre_interventions",
|
||||
"type_maintenance", "duree_minutes", "trigger_type", "trigger_event",
|
||||
"meter_threshold", "season_start_month", "season_end_month",
|
||||
"advance_days", "is_active", "application_scope",
|
||||
"unavailable_policy", "after_repair_policy", "is_safety_critical")
|
||||
|
||||
|
||||
def load_preventive_catalog():
|
||||
raw = json.loads(zlib.decompress(base64.b64decode(_BLOB)))
|
||||
return [dict(zip(_FIELDS, row)) for row in raw]
|
||||
|
||||
|
||||
LOT_TASKS = tuple(load_preventive_catalog())
|
||||
|
||||
|
|
@ -13,12 +13,12 @@ from ...extensions import db, csrf
|
|||
from ..models.user import User
|
||||
from ..models.college import College, Building, Zone, Room
|
||||
from ..models.equipment import EquipmentCategory
|
||||
from ..models.maintenance import Lot
|
||||
from ..models.maintenance import Lot, LotTask
|
||||
from ..models.rbac import Role
|
||||
from ..services.rbac import ensure_rbac_catalog, replace_user_roles
|
||||
from ..authorization import has_permission
|
||||
from ..models.planning import WorkSchedule, CollegeClosure, ClosureWorkDay
|
||||
from ..setup_catalog import EQUIPMENT_CATEGORIES, LOTS, grouped_catalog
|
||||
from ..setup_catalog import EQUIPMENT_CATEGORIES, LOTS, LOT_TASKS, grouped_catalog, task_count_by_lot
|
||||
|
||||
setup_wizard_bp = Blueprint('setup_wizard', __name__, url_prefix='/setup-wizard')
|
||||
|
||||
|
|
@ -208,7 +208,8 @@ def index():
|
|||
|
||||
return render_template('setup_wizard/index.html',
|
||||
progress=progress.to_dict(),
|
||||
lots_data=lots_data)
|
||||
lots_data=lots_data,
|
||||
task_counts=task_count_by_lot())
|
||||
|
||||
|
||||
@setup_wizard_bp.route('/api/progress')
|
||||
|
|
@ -439,7 +440,7 @@ def api_save_step(step):
|
|||
return jsonify({'success': True, 'step': 5, 'rooms': saved})
|
||||
|
||||
elif step == 6:
|
||||
# Étape 6 : catalogue fixe des catégories et lots du dump SQL.
|
||||
# Étape 6 : catalogue fixe des catégories, lots et tâches du dump SQL.
|
||||
categories_by_name = {}
|
||||
for name in EQUIPMENT_CATEGORIES:
|
||||
category = EquipmentCategory.query.filter(
|
||||
|
|
@ -451,20 +452,58 @@ def api_save_step(step):
|
|||
db.session.flush()
|
||||
categories_by_name[name] = category
|
||||
|
||||
selected_names = data.get('selected_lots')
|
||||
selected_names = {str(name).strip().casefold() for name in selected_names} if isinstance(selected_names, list) else None
|
||||
imported_lots = []
|
||||
lots_by_name = {}
|
||||
for lot_name, category_name in LOTS:
|
||||
if selected_names is not None and lot_name.casefold() not in selected_names:
|
||||
continue
|
||||
lot = Lot.query.filter(db.func.lower(Lot.name) == lot_name.lower()).first()
|
||||
if lot is None:
|
||||
lot = Lot(name=lot_name)
|
||||
db.session.add(lot)
|
||||
db.session.flush()
|
||||
lot.category_id = categories_by_name[category_name].id
|
||||
lots_by_name[lot_name.casefold()] = lot
|
||||
imported_lots.append(lot_name)
|
||||
|
||||
# Les tâches du catalogue sont ajoutées par clé fonctionnelle. Une
|
||||
# ligne déjà présente n'est jamais écrasée : elle peut avoir été
|
||||
# adaptée par l'établissement depuis le dernier import.
|
||||
task_count = 0
|
||||
for item in LOT_TASKS:
|
||||
lot = lots_by_name.get(item['lot'].casefold())
|
||||
if lot is None:
|
||||
continue
|
||||
existing = LotTask.query.filter(
|
||||
LotTask.lot_id == lot.id,
|
||||
db.func.lower(LotTask.tache) == item['tache'].casefold(),
|
||||
).first() if item.get('tache') else None
|
||||
if existing is not None:
|
||||
continue
|
||||
task = LotTask(lot_id=lot.id)
|
||||
for field in (
|
||||
'num_tache', 'tache', 'periodicite', 'contrat', 'gestionnaire',
|
||||
'jours_entre_interventions', 'type_maintenance', 'duree_minutes',
|
||||
'trigger_type', 'trigger_event', 'meter_threshold',
|
||||
'season_start_month', 'season_end_month', 'advance_days',
|
||||
'is_active', 'application_scope', 'unavailable_policy',
|
||||
'after_repair_policy', 'is_safety_critical',
|
||||
):
|
||||
if field in item:
|
||||
setattr(task, field, item[field])
|
||||
db.session.add(task)
|
||||
task_count += 1
|
||||
|
||||
progress.categories_json = json.dumps(list(EQUIPMENT_CATEGORIES), ensure_ascii=False)
|
||||
progress.set_lots([name for name, _category in LOTS])
|
||||
progress.set_lots(imported_lots)
|
||||
progress.step = max(progress.step or 0, 6)
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
'success': True, 'step': 6,
|
||||
'categories_count': len(EQUIPMENT_CATEGORIES), 'lots_count': len(LOTS),
|
||||
'categories_count': len(EQUIPMENT_CATEGORIES), 'lots_count': len(imported_lots),
|
||||
'lot_tasks_count': task_count,
|
||||
})
|
||||
|
||||
elif step == 7:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ Ces valeurs reprennent les catégories et les lots du dump SQL de référence.
|
|||
Les noms sont les clés fonctionnelles utilisées pour une synchronisation idempotente :
|
||||
le wizard ne supprime jamais une ligne déjà présente dans la base.
|
||||
"""
|
||||
from .preventive_catalog import LOT_TASKS
|
||||
|
||||
EQUIPMENT_CATEGORIES = (
|
||||
"Chauffage", "Courants faibles", "Gros œuvre", "Revêtement de façades",
|
||||
|
|
@ -85,3 +86,11 @@ def grouped_catalog():
|
|||
for lot_name, category_name in LOTS:
|
||||
grouped[category_name].append(lot_name)
|
||||
return grouped
|
||||
|
||||
|
||||
def task_count_by_lot():
|
||||
"""Nombre de règles préventives historiques pour chaque lot."""
|
||||
counts = {}
|
||||
for task in LOT_TASKS:
|
||||
counts[task["lot"]] = counts.get(task["lot"], 0) + 1
|
||||
return counts
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from ..core.models.planning import ScheduledTask
|
|||
from ..core.services.maintenance_engine import generate_due_tasks
|
||||
from ..core.models.company import Company
|
||||
from ..contracts.models import Contract
|
||||
from ..core.models.equipment import Equipment
|
||||
from ..core.models.equipment import Equipment, EquipmentCategory
|
||||
|
||||
lots_bp = Blueprint('lots', __name__)
|
||||
|
||||
|
|
@ -37,6 +37,27 @@ def index():
|
|||
companies = Company.query.order_by(Company.name).all()
|
||||
return render_template('lots/index.html', lots=lots, companies=companies)
|
||||
|
||||
|
||||
@lots_bp.route('/new', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def create():
|
||||
"""Créer un lot indépendamment de la création d'un équipement."""
|
||||
categories = EquipmentCategory.query.order_by(EquipmentCategory.name).all()
|
||||
if request.method == 'POST':
|
||||
name = (request.form.get('name') or '').strip()
|
||||
category_id = request.form.get('category_id', type=int) or None
|
||||
if not name:
|
||||
flash('Le nom du lot est obligatoire.', 'danger')
|
||||
elif category_id and db.session.get(EquipmentCategory, category_id) is None:
|
||||
flash('La catégorie sélectionnée est invalide.', 'danger')
|
||||
else:
|
||||
lot = Lot(name=name, category_id=category_id, is_present=request.form.get('is_present') == 'on')
|
||||
db.session.add(lot)
|
||||
db.session.commit()
|
||||
flash('Lot créé. Ajoutez maintenant ses tâches préventives.', 'success')
|
||||
return redirect(url_for('lots.detail', id=lot.id))
|
||||
return render_template('lots/create.html', categories=categories)
|
||||
|
||||
@lots_bp.route('/<int:id>')
|
||||
@login_required
|
||||
def detail(id):
|
||||
|
|
|
|||
22
app_new/templates/lots/create.html
Normal file
22
app_new/templates/lots/create.html
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}Ajouter un lot — GMAO{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div><h1><i class="bi bi-boxes"></i> Ajouter un lot</h1>
|
||||
<p class="text-muted mb-0">Un lot regroupe des équipements et leurs actions de maintenance préventive.</p></div>
|
||||
<a href="{{ url_for('lots.index') }}" class="btn btn-secondary">Annuler</a>
|
||||
</div>
|
||||
<div class="card"><div class="card-body">
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="mb-3"><label for="name" class="form-label">Nom du lot <span class="text-danger">*</span></label>
|
||||
<input id="name" name="name" class="form-control" required maxlength="500" value="{{ request.form.get('name','') }}" placeholder="Ex. Éclairage des salles"></div>
|
||||
<div class="mb-3"><label for="category_id" class="form-label">Catégorie</label>
|
||||
<select id="category_id" name="category_id" class="form-select"><option value="">-- À choisir --</option>{% for category in categories %}<option value="{{ category.id }}" {% if request.form.get('category_id')|int == category.id %}selected{% endif %}>{{ category.name }}</option>{% endfor %}</select></div>
|
||||
<div class="form-check mb-3"><input id="is_present" name="is_present" class="form-check-input" type="checkbox" checked><label for="is_present" class="form-check-label">Ce lot est présent dans l'établissement</label></div>
|
||||
<button class="btn btn-primary" type="submit"><i class="bi bi-check-lg"></i> Créer le lot</button>
|
||||
</form>
|
||||
</div></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -5,8 +5,11 @@
|
|||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1><i class="bi bi-boxes"></i> Gestion des Lots</h1>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ url_for('wizard.equipment_wizard') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Créer un lot et son équipement
|
||||
<a href="{{ url_for('lots.create') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Ajouter un lot
|
||||
</a>
|
||||
<a href="{{ url_for('wizard.equipment_wizard') }}" class="btn btn-outline-primary">
|
||||
<i class="bi bi-hdd-stack"></i> Ajouter un équipement
|
||||
</a>
|
||||
<a href="{{ url_for('equipments.index') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Retour aux équipements
|
||||
|
|
@ -16,7 +19,7 @@
|
|||
|
||||
<div class="alert alert-info" role="note">
|
||||
Les lots regroupent les équipements suivis ensemble et leurs actions préventives.
|
||||
Vous pouvez créer un premier lot depuis l'assistant d'ajout d'équipement.
|
||||
Vous pouvez créer un lot seul, puis y associer des équipements et des tâches.
|
||||
</div>
|
||||
|
||||
<!-- Filtres -->
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
<div class="accordion" id="catalogAccordion">
|
||||
{% for category, lots in lots_data.items() %}
|
||||
<div class="accordion-item"><h2 class="accordion-header"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#catalog{{ loop.index }}">{{ category }} <span class="badge bg-secondary ms-2">{{ lots|length }}</span></button></h2>
|
||||
<div id="catalog{{ loop.index }}" class="accordion-collapse collapse" data-bs-parent="#catalogAccordion"><div class="accordion-body"><ul class="mb-0">{% for lot in lots %}<li>{{ lot }}</li>{% endfor %}</ul></div></div></div>
|
||||
<div id="catalog{{ loop.index }}" class="accordion-collapse collapse" data-bs-parent="#catalogAccordion"><div class="accordion-body"><ul class="mb-0">{% for lot in lots %}<li>{{ lot }}{% if task_counts.get(lot) %}<span class="badge bg-info text-dark ms-2">{{ task_counts.get(lot) }} tâche(s) préventive(s)</span>{% endif %}</li>{% endfor %}</ul></div></div></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
services:
|
||||
gmao:
|
||||
environment:
|
||||
GMAO_VERSION: ${GMAO_VERSION:-0.1.0-dev.9}
|
||||
GMAO_VERSION: ${GMAO_VERSION:-0.1.0-dev.10}
|
||||
GMAO_GUNICORN_FLAGS: --reload --reload-engine poll
|
||||
FLASK_ENV: development
|
||||
# Ce profil est servi en HTTP sur le réseau de test ; un cookie Secure
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ services:
|
|||
memory: 128M
|
||||
|
||||
gmao:
|
||||
image: gmao-college:${GMAO_VERSION:-0.1.0-dev.9}
|
||||
image: gmao-college:${GMAO_VERSION:-0.1.0-dev.10}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
GMAO_VERSION: ${GMAO_VERSION:-0.1.0-dev.9}
|
||||
GMAO_VERSION: ${GMAO_VERSION:-0.1.0-dev.10}
|
||||
GIT_VERSION: ${GIT_VERSION:-unknown}
|
||||
container_name: gmao-flask
|
||||
restart: unless-stopped
|
||||
|
|
@ -51,7 +51,7 @@ services:
|
|||
- TZ=Europe/Paris
|
||||
- DB_HOST=mariadb
|
||||
- DB_PORT=3306
|
||||
- GMAO_VERSION=${GMAO_VERSION:-0.1.0-dev.9}
|
||||
- GMAO_VERSION=${GMAO_VERSION:-0.1.0-dev.10}
|
||||
- GMAO_GUNICORN_FLAGS=--preload
|
||||
- DOCKER_GMAO_WORKDIR=/app
|
||||
working_dir: /app
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Requalification Phase 2.1 — prérequis métier et interface
|
||||
|
||||
Date : 2026-08-23
|
||||
Version auditée : 0.1.0-dev.8
|
||||
Version auditée initialement : 0.1.0-dev.8 ; correction catalogue : 0.1.0-dev.10
|
||||
|
||||
## Conclusion
|
||||
|
||||
|
|
@ -123,4 +123,5 @@ aucune donnée n'a été nettoyée.
|
|||
La phase est **PARTIELLEMENT VALIDÉE** : l'isolation des horaires et l'absence
|
||||
de fallback silencieux sont corrigées et testées ; le parcours UI préventif
|
||||
réel, la création de deux techniciens depuis l'UI et la génération sans
|
||||
fixture restent à démontrer.
|
||||
fixture restent à démontrer. Le catalogue embarqué contient désormais les 330
|
||||
tâches historiques de `lot_tasks` et son import est idempotent.
|
||||
|
|
|
|||
65
docs/ui-audit/PREVENTIVE_CATALOG_AUDIT.md
Normal file
65
docs/ui-audit/PREVENTIVE_CATALOG_AUDIT.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# Audit du catalogue préventif historique
|
||||
|
||||
Date : 2026-08-23
|
||||
Source de référence : `/opt/gmao/dump_sql_base_remplie.sql` (lecture seule)
|
||||
|
||||
## Tables analysées
|
||||
|
||||
| Table | Lignes dans le dump | Utilité |
|
||||
|---|---:|---|
|
||||
| `equipment_categories` | 36 | catégories patrimoniales |
|
||||
| `lots` | 92 | lots, catégorie par `category_id`, présence |
|
||||
| `lot_tasks` | 330 | référentiel historique des actions par `lot_id` |
|
||||
| `preventive_tasks` | 0 | ancienne table, structure présente mais aucune donnée |
|
||||
| `scheduled_tasks` | 516 | échéances historiques, pas le catalogue initial |
|
||||
|
||||
Le modèle actuel utilise `LotTask`, et non `preventive_tasks`, pour les règles
|
||||
préventives. Le dump contient 36 catégories et 92 lots, déjà représentés dans
|
||||
`setup_catalog.py`; il manquait les 330 lignes de `lot_tasks`.
|
||||
|
||||
## Durée et périodicité
|
||||
|
||||
- 330 tâches ont été extraites sans modification des valeurs historiques ;
|
||||
- 5 tâches ont une `duree_minutes` renseignée ; 325 ont une durée inconnue ;
|
||||
- 327 ont une périodicité textuelle ; 3 ont une périodicité vide ;
|
||||
- les valeurs `jours_entre_interventions`, type, contrat, gestionnaire,
|
||||
déclenchement et politiques ont été conservées lorsqu'elles existent.
|
||||
|
||||
La valeur de 60 minutes de `LotTask.effective_duration()` n'est pas présente
|
||||
dans le dump : c'est un fallback du code actuel, pas une donnée historique.
|
||||
Elle reste temporairement compatible avec les anciennes données, mais une tâche
|
||||
sans durée doit être signalée avant une validation métier définitive.
|
||||
|
||||
## Catalogue embarqué
|
||||
|
||||
`app_new/core/preventive_catalog.py` contient une extraction compressée et
|
||||
figée des 330 tâches. Le dump n'est jamais ouvert par l'application. Le module
|
||||
expose `LOT_TASKS`, utilisé par le setup wizard et les tests.
|
||||
|
||||
Le wizard crée les catégories, les lots sélectionnés et leurs tâches. La clé
|
||||
fonctionnelle de tâche est `(lot, tache)` ; une ligne déjà existante n'est pas
|
||||
écrasée, afin de préserver une personnalisation locale. Les éléments manquants
|
||||
sont ajoutés. La seconde exécution est donc idempotente.
|
||||
|
||||
## Relation équipement
|
||||
|
||||
`Equipment.lot_id -> Lot.id`, avec héritage par `parent.effective_lot_id`.
|
||||
Les tâches sont donc visibles via le lot effectif et peuvent générer des
|
||||
`ScheduledTask` pour les équipements actifs. Un équipement rebuté ou supprimé
|
||||
ne doit pas produire de nouvelle échéance.
|
||||
|
||||
## Première échéance
|
||||
|
||||
Le mécanisme actuel `generate_due_tasks()` calcule la première échéance à
|
||||
partir de la date de génération et de `jours_entre_interventions`/périodicité.
|
||||
Le dump ne fournit pas une date de première échéance catalogue. Une règle
|
||||
métier explicite (date d'association, prochaine échéance saisie ou campagne)
|
||||
reste à valider avant une automatisation plus ambitieuse.
|
||||
|
||||
## Écarts et limites
|
||||
|
||||
- le catalogue est maintenant complet pour les tables historiques validées ;
|
||||
- les tâches sans durée restent à renseigner pour une planification fiable ;
|
||||
- la validation bout en bout par interface (installation, association d'un
|
||||
équipement, génération, Ma journée) reste à exécuter avec un compte UI ;
|
||||
- `preventive_tasks` est documentée mais vide et n'est pas importée.
|
||||
|
|
@ -141,8 +141,8 @@ def test_setup_creates_zones_and_rooms_with_valid_links(client, app, monkeypatch
|
|||
|
||||
def test_setup_imports_embedded_lot_catalog_idempotently(client, app, monkeypatch, tmp_path):
|
||||
from app_new.core.models.equipment import EquipmentCategory
|
||||
from app_new.core.models.maintenance import Lot
|
||||
from app_new.core.setup_catalog import EQUIPMENT_CATEGORIES, LOTS
|
||||
from app_new.core.models.maintenance import Lot, LotTask
|
||||
from app_new.core.setup_catalog import EQUIPMENT_CATEGORIES, LOTS, LOT_TASKS
|
||||
|
||||
_authorize_setup(client, monkeypatch, tmp_path)
|
||||
first = client.post('/setup-wizard/api/step/6', json={})
|
||||
|
|
@ -150,6 +150,7 @@ def test_setup_imports_embedded_lot_catalog_idempotently(client, app, monkeypatc
|
|||
assert first.status_code == second.status_code == 200
|
||||
assert first.get_json()['categories_count'] == len(EQUIPMENT_CATEGORIES)
|
||||
assert first.get_json()['lots_count'] == len(LOTS)
|
||||
assert first.get_json()['lot_tasks_count'] == len(LOT_TASKS)
|
||||
|
||||
with app.app_context():
|
||||
for name in EQUIPMENT_CATEGORIES:
|
||||
|
|
@ -157,6 +158,7 @@ def test_setup_imports_embedded_lot_catalog_idempotently(client, app, monkeypatc
|
|||
for name, category_name in LOTS:
|
||||
lot = Lot.query.filter_by(name=name).one()
|
||||
assert lot.category.name == category_name
|
||||
assert LotTask.query.count() == len(LOT_TASKS)
|
||||
|
||||
|
||||
def test_setup_saves_exact_work_days_inside_school_holidays(client, app, monkeypatch, tmp_path):
|
||||
|
|
|
|||
16
tests/integration/test_preventive_catalog.py
Normal file
16
tests/integration/test_preventive_catalog.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from app_new.core.setup_catalog import LOT_TASKS
|
||||
|
||||
|
||||
def test_embedded_preventive_catalog_has_historical_shape():
|
||||
"""Garde-fou du catalogue extrait du dump de référence."""
|
||||
assert len(LOT_TASKS) == 330
|
||||
assert all(item['lot'] and item['tache'] for item in LOT_TASKS)
|
||||
assert all(item['type_maintenance'] in ('préventive', 'preventive', 'curative', None) for item in LOT_TASKS)
|
||||
assert sum(item['duree_minutes'] is not None for item in LOT_TASKS) == 5
|
||||
assert sum(bool(item['periodicite']) for item in LOT_TASKS) == 327
|
||||
assert len({(item['lot'], item['num_tache']) for item in LOT_TASKS}) == len(LOT_TASKS)
|
||||
|
||||
|
||||
def test_catalog_tasks_have_stable_functional_keys():
|
||||
keys = [(item['lot'].casefold(), item['tache'].casefold()) for item in LOT_TASKS]
|
||||
assert len(keys) == len(set(keys))
|
||||
|
|
@ -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.9'
|
||||
assert get_version() == '0.1.0-dev.10'
|
||||
|
||||
|
||||
def test_environment_version_takes_precedence(monkeypatch):
|
||||
|
|
|
|||
Loading…
Reference in a new issue