feat: implement automated status calculation and unit tests for GIEP inspections

This commit is contained in:
kdeterme 2026-09-14 12:15:48 +02:00
parent 660c40f0f7
commit fe535683a1
4 changed files with 623 additions and 36 deletions

View file

@ -545,6 +545,148 @@ class GiepInspection(Inspection):
verbose_name = _("Inspection GIEP / Eaux pluviales")
verbose_name_plural = _("Inspections GIEP / Eaux pluviales")
@classmethod
def compute_status_from_data(cls, data):
"""
Calcule le résultat global ('damaged', 'non_compliant', 'compliant')
sur base des réponses aux questions de chaque ouvrage d'une inspection GIEP.
Priorité : damaged > non_compliant > compliant.
"""
if not data or not isinstance(data, dict):
return 'compliant'
ouvrages = data.get('ouvrages', [])
if not isinstance(ouvrages, list) or not ouvrages:
return 'compliant'
has_damaged = False
has_non_compliant = False
for ouv in ouvrages:
if not isinstance(ouv, dict):
continue
device_type = (ouv.get('device_type') or 'evc').lower()
q = ouv.get('questions') or {}
if not isinstance(q, dict):
continue
def is_val(key, *expected):
v = q.get(key)
if isinstance(v, bool):
v_str = 'true' if v else 'false'
elif v is None:
v_str = 'null'
else:
v_str = str(v).strip().lower()
return v_str in [str(e).strip().lower() for e in expected]
if device_type == 'evc':
# 1. Point d'entrée
if is_val('evc_pe_ok', 'false', 'non'):
has_non_compliant = True
# 2. Empierrement
if is_val('evc_empierrement_present', True, 'true'):
if is_val('evc_empierrement_ok', 'false', 'non'):
has_non_compliant = True
if is_val('evc_empierrement_emporte', True, 'true'):
has_damaged = True
# 3. Végétation
if is_val('evc_ev_veg_etat', 'mauvais'):
has_non_compliant = True
if is_val('evc_ev_plantes_mortes', 'plus_10', '+ de 10%'):
has_damaged = True
# 4. Chenaux d'érosion
if is_val('evc_ev_chenaux_erosion', 'true', 'oui', True):
has_non_compliant = True
# 5. Flaques d'eau
if is_val('evc_ev_flaques', 'true', 'oui', True):
has_non_compliant = True
# 6. Sédiments
if is_val('evc_ev_sediments', 'true', 'oui', True):
has_non_compliant = True
if is_val('evc_ev_croute_sediments', 'true', 'oui', True):
has_damaged = True
# 7. Espèces envahissantes
if is_val('evc_ev_envahissantes', 'true', 'oui', True):
has_non_compliant = True
# 8. Surverse
if is_val('evc_surverse_presente', True, 'true'):
if is_val('evc_sv_grille_propre', 'false', 'non'):
has_non_compliant = True
if is_val('evc_sv_decantation_vide', 'false', 'non'):
has_non_compliant = True
if is_val('evc_sv_haute', 'false', 'non'):
has_damaged = True
elif device_type == 'oed':
# 1. Revêtement général
if is_val('oed_revetement_ok', 'false', 'non'):
has_non_compliant = True
# 2. Bouche d'injection
if is_val('oed_injection_presente', True, 'true'):
if is_val('oed_bi_niveaux_ok', 'false', 'non'):
has_damaged = True
if is_val('oed_bi_grille_propre', 'false', 'non'):
has_non_compliant = True
if is_val('oed_bi_decantation_vide', 'false', 'non'):
has_damaged = True
if is_val('oed_bi_drain_colmate', 'true', 'oui', True):
has_damaged = True
# 3. Structure enherbée / minérale
if is_val('oed_rhm_presente', True, 'true'):
if is_val('oed_rhm_stagne', 'true', 'oui', True):
has_non_compliant = True
if is_val('oed_rhm_joints_ok', 'false', 'non'):
has_non_compliant = True
if is_val('oed_rhm_joints_fines', 'true', 'oui', True):
has_damaged = True
if is_val('oed_rhm_joints_remplissage', 'true', 'oui', True):
has_non_compliant = True
# 4. Modulaire poreux / continu poreux
if is_val('oed_rp_presente', True, 'true'):
if is_val('oed_rp_stagne', 'true', 'oui', True):
has_non_compliant = True
if is_val('oed_rp_depots', 'true', 'oui', True):
has_non_compliant = True
elif device_type == 'ta':
# 1. Point d'entrée
if is_val('ta_pe_ok', 'false', 'non'):
has_non_compliant = True
if is_val('ta_pe_conduits_ok', 'false', 'non'):
has_damaged = True
if is_val('ta_pe_decantation_vide', 'false', 'non'):
has_non_compliant = True
# 2. Bassin d'orage
if is_val('ta_bo_vegetation_etat', 'mauvais'):
has_non_compliant = True
if is_val('ta_bo_plantes_mortes', 'plus_10', '+ de 10%'):
has_damaged = True
if is_val('ta_bo_chenaux', 'true', 'oui', True):
has_non_compliant = True
if is_val('ta_sediments', 'true', 'oui', True):
has_non_compliant = True
if is_val('ta_sediments_rempli', 'true', 'oui', True):
has_damaged = True
if is_val('ta_bo_envahissantes', 'true', 'oui', True):
has_non_compliant = True
if has_damaged:
return 'damaged'
if has_non_compliant:
return 'non_compliant'
return 'compliant'
class SignPanelInspection(Inspection):
"""

View file

@ -97,21 +97,25 @@
{% endwith %}
{% with val=q|get_q_val:'evc_pe_feuilles' %}
{% if val != 'null' and val is not None %}
<div class="d-flex align-items-center justify-content-between p-2 mb-1.5 rounded border {% if val == 'null' %}bg-light opacity-75{% else %}bg-white{% endif %}">
<span class="small fw-medium {% if val == 'null' %}text-muted{% else %}text-dark{% endif %} me-2">{% translate "Des feuilles ou branches bloquent-elles l'eau ?" %}</span>
<span class="badge {% if val == 'true' or val == True or val == 'oui' %}bg-danger-subtle text-danger border border-danger-subtle{% elif val == 'false' or val == False or val == 'non' %}bg-success-subtle text-success border border-success-subtle{% else %}bg-light text-muted border{% endif %} px-2 py-1">
{% if val == 'true' or val == True or val == 'oui' %}Oui{% elif val == 'false' or val == False or val == 'non' %}Non{% else %}Non vérifié{% endif %}
</span>
</div>
{% endif %}
{% endwith %}
{% with val=q|get_q_val:'evc_pe_detritus' %}
{% if val != 'null' and val is not None %}
<div class="d-flex align-items-center justify-content-between p-2 mb-1.5 rounded border {% if val == 'null' %}bg-light opacity-75{% else %}bg-white{% endif %}">
<span class="small fw-medium {% if val == 'null' %}text-muted{% else %}text-dark{% endif %} me-2">{% translate "Un amas de détritus bloque-t-il l'eau ?" %}</span>
<span class="badge {% if val == 'true' or val == True or val == 'oui' %}bg-danger-subtle text-danger border border-danger-subtle{% elif val == 'false' or val == False or val == 'non' %}bg-success-subtle text-success border border-success-subtle{% else %}bg-light text-muted border{% endif %} px-2 py-1">
{% if val == 'true' or val == True or val == 'oui' %}Oui{% elif val == 'false' or val == False or val == 'non' %}Non{% else %}Non vérifié{% endif %}
</span>
</div>
{% endif %}
{% endwith %}
{% with val=q|get_q_val:'evc_empierrement_present' %}
@ -283,6 +287,17 @@
</span>
</div>
{% endwith %}
{% with val=q|get_q_val:'ta_pe_decantation_vide' %}
{% if val != 'null' and val is not None %}
<div class="d-flex align-items-center justify-content-between p-2 mb-1.5 rounded border {% if val == 'null' %}bg-light opacity-75{% else %}bg-white{% endif %}">
<span class="small fw-medium {% if val == 'null' %}text-muted{% else %}text-dark{% endif %} me-2">{% translate "La décantation est-elle vide ou presque ?" %}</span>
<span class="badge {% if val == 'true' or val == True or val == 'oui' %}bg-success-subtle text-success border border-success-subtle{% elif val == 'false' or val == False or val == 'non' %}bg-danger-subtle text-danger border border-danger-subtle{% else %}bg-light text-muted border{% endif %} px-2 py-1">
{% if val == 'true' or val == True or val == 'oui' %}Oui{% elif val == 'false' or val == False or val == 'non' %}Non{% else %}Non vérifié{% endif %}
</span>
</div>
{% endif %}
{% endwith %}
</div>
</div>

View file

@ -15,6 +15,9 @@
<!-- Hidden Field pour la sérialisation JSON -->
<input type="hidden" name="giep_data" id="giep_data_serialized" value="{}">
<!-- Bandeau de statut automatique GIEP -->
<div id="giep_status_auto_banner" class="alert alert-info border border-info-subtle rounded-3 p-2.5 mb-3 shadow-sm d-none"></div>
<!-- 1. Conditions météorologiques -->
<div class="card border-primary-subtle bg-light-subtle mb-3 rounded-4 shadow-sm">
<div class="card-body p-3">
@ -172,8 +175,6 @@
questions: {
// EVC
evc_pe_ok: null,
evc_pe_feuilles: null,
evc_pe_detritus: null,
evc_empierrement_present: false,
evc_empierrement_ok: null,
evc_empierrement_colmate: false,
@ -200,9 +201,6 @@
oed_bi_niveaux_ok: null,
oed_bi_grille_propre: null,
oed_bi_decantation_vide: null,
oed_bi_filtre_present: null,
oed_bi_filtre_propre: null,
oed_bi_filtre_bon: null,
oed_bi_drain_colmate: null,
oed_rhm_presente: false,
oed_rhm_stagne: null,
@ -224,16 +222,12 @@
// TA
ta_pe_ok: null,
ta_pe_conduits_ok: null,
ta_pe_grilles: null,
ta_pe_feuilles: null,
ta_pe_detritus: null,
ta_pe_decantation_vide: null,
ta_bo_vegetation_etat: null,
ta_bo_plantes_mortes: null,
ta_bo_chenaux: null,
ta_bo_flaques: null,
ta_sediments: null,
ta_sediments_rempli: null,
ta_bo_detritus: null,
ta_bo_envahissantes: null
},
actions: {}
@ -527,8 +521,6 @@
// et "Non" représente un bon fonctionnement (absence de défaut).
const GIEP_DEFECT_KEYS = new Set([
// EVC
'evc_pe_feuilles', // Des feuilles ou branches bloquent-elles l'eau ?
'evc_pe_detritus', // Un amas de détritus bloque-t-il l'eau ?
'evc_ev_chenaux_erosion', // Présence de chenaux d'érosion ?
'evc_ev_flaques', // Présence de flaques d'eau persistantes ?
'evc_ev_sediments', // Présence de sédiments dans l'ouvrage ?
@ -548,13 +540,9 @@
'oed_rna_stagne_plus_2h', // L'eau stagne plus de 2 heures ?
'oed_rna_geotextile_visible', // Le géotextile est-il visible ?
// TA
'ta_pe_feuilles', // Des feuilles ou branches bloquent-elles l'eau ?
'ta_pe_detritus', // Un amas de détritus bloque-t-il l'eau ?
'ta_bo_chenaux', // Présence de chenaux d'érosion ?
'ta_bo_flaques', // Présence de flaques d'eau persistantes ?
'ta_sediments', // Présence de sédiments dans l'ouvrage ?
'ta_sediments_rempli', // L'ouvrage est-il rempli au 5e de sa capacité ?
'ta_bo_detritus', // Présence de détritus dans l'ouvrage ?
'ta_bo_envahissantes' // Présence d'espèces exotiques envahissantes ?
]);
@ -603,6 +591,265 @@
`;
}
// Gestion du statut calculé et surcharge manuelle
let isGiepStatusManuallyOverridden = false;
function getStatusLabel(status) {
if (status === 'damaged') return '{% filter escapejs %}{% translate "Endommagé" %}{% endfilter %}';
if (status === 'non_compliant') return '{% filter escapejs %}{% translate "Non conforme" %}{% endfilter %}';
return '{% filter escapejs %}{% translate "Conforme" %}{% endfilter %}';
}
function evaluateGiepInspectionStatus(ouvrages) {
if (!Array.isArray(ouvrages) || ouvrages.length === 0) {
return 'compliant';
}
let hasDamaged = false;
let hasNonCompliant = false;
for (const ouv of ouvrages) {
if (!ouv) continue;
const deviceType = (ouv.device_type || 'evc').toLowerCase();
const q = ouv.questions || {};
const isVal = (key, ...expected) => {
const v = q[key];
if (v === undefined || v === null) return expected.includes('null');
const vStr = String(v).trim().toLowerCase();
return expected.map(e => String(e).trim().toLowerCase()).includes(vStr);
};
if (deviceType === 'evc') {
// 1. Point d'entrée
if (isVal('evc_pe_ok', 'false', 'non')) {
hasNonCompliant = true;
}
// 2. Empierrement
if (isVal('evc_empierrement_present', true, 'true')) {
if (isVal('evc_empierrement_ok', 'false', 'non')) {
hasNonCompliant = true;
if (isVal('evc_empierrement_emporte', true, 'true')) {
hasDamaged = true;
}
}
}
// 3. Végétation
if (isVal('evc_ev_veg_etat', 'mauvais')) {
hasNonCompliant = true;
if (isVal('evc_ev_plantes_mortes', 'plus_10', '+ de 10%')) {
hasDamaged = true;
}
}
// 4. Chenaux d'érosion
if (isVal('evc_ev_chenaux_erosion', true, 'true', 'oui')) {
hasNonCompliant = true;
}
// 5. Flaques
if (isVal('evc_ev_flaques', true, 'true', 'oui')) {
hasNonCompliant = true;
}
// 6. Sédiments
if (isVal('evc_ev_sediments', true, 'true', 'oui')) {
hasNonCompliant = true;
if (isVal('evc_ev_croute_sediments', true, 'true', 'oui')) {
hasDamaged = true;
}
}
// 7. Espèces envahissantes
if (isVal('evc_ev_envahissantes', true, 'true', 'oui')) {
hasNonCompliant = true;
}
// 8. Surverse
if (isVal('evc_surverse_presente', true, 'true')) {
if (isVal('evc_sv_grille_propre', 'false', 'non')) {
hasNonCompliant = true;
}
if (isVal('evc_sv_decantation_vide', 'false', 'non')) {
hasNonCompliant = true;
}
if (isVal('evc_sv_haute', 'false', 'non')) {
hasDamaged = true;
}
}
} else if (deviceType === 'oed') {
// 1. Revêtement
if (isVal('oed_revetement_ok', 'false', 'non')) {
hasNonCompliant = true;
}
// 2. Bouche d'injection
if (isVal('oed_injection_presente', true, 'true')) {
if (isVal('oed_bi_niveaux_ok', 'false', 'non')) {
hasDamaged = true;
}
if (isVal('oed_bi_grille_propre', 'false', 'non')) {
hasNonCompliant = true;
}
if (isVal('oed_bi_decantation_vide', 'false', 'non')) {
hasDamaged = true;
}
if (isVal('oed_bi_drain_colmate', true, 'true', 'oui')) {
hasDamaged = true;
}
}
// 3. Structure enherbée / minérale
if (isVal('oed_rhm_presente', true, 'true')) {
if (isVal('oed_rhm_stagne', true, 'true', 'oui')) {
hasNonCompliant = true;
}
if (isVal('oed_rhm_joints_ok', 'false', 'non')) {
hasNonCompliant = true;
if (isVal('oed_rhm_joints_fines', true, 'true', 'oui')) {
hasDamaged = true;
}
if (isVal('oed_rhm_joints_remplissage', true, 'true', 'oui')) {
hasNonCompliant = true;
}
}
}
// 4. Modulaire poreux / continu poreux
if (isVal('oed_rp_presente', true, 'true')) {
if (isVal('oed_rp_stagne', true, 'true', 'oui')) {
hasNonCompliant = true;
}
if (isVal('oed_rp_depots', true, 'true', 'oui')) {
hasNonCompliant = true;
}
}
} else if (deviceType === 'ta') {
// 1. Point d'entrée
if (isVal('ta_pe_ok', 'false', 'non')) {
hasNonCompliant = true;
}
if (isVal('ta_pe_conduits_ok', 'false', 'non')) {
hasDamaged = true;
}
if (isVal('ta_pe_decantation_vide', 'false', 'non')) {
hasNonCompliant = true;
}
// 2. Bassin d'orage
if (isVal('ta_bo_vegetation_etat', 'mauvais')) {
hasNonCompliant = true;
if (isVal('ta_bo_plantes_mortes', 'plus_10', '+ de 10%')) {
hasDamaged = true;
}
}
if (isVal('ta_bo_chenaux', true, 'true', 'oui')) {
hasNonCompliant = true;
}
if (isVal('ta_sediments', true, 'true', 'oui')) {
hasNonCompliant = true;
if (isVal('ta_sediments_rempli', true, 'true', 'oui')) {
hasDamaged = true;
}
}
if (isVal('ta_bo_envahissantes', true, 'true', 'oui')) {
hasNonCompliant = true;
}
}
}
if (hasDamaged) return 'damaged';
if (hasNonCompliant) return 'non_compliant';
return 'compliant';
}
function getAllStatusRadios() {
return document.querySelectorAll('input[type="radio"][name="result_status"]');
}
function getActiveStatusRadio(status) {
const editRad = document.getElementById('edit_res_' + status);
if (editRad && editRad.closest('.modal.show, form')) return editRad;
const createRad = document.getElementById('res_' + status);
if (createRad && createRad.closest('.modal.show, form')) return createRad;
return createRad || editRad || null;
}
function syncGiepCalculatedStatus() {
initStatusRadioListeners();
const calculated = evaluateGiepInspectionStatus(giepOuvrages);
const banner = document.getElementById('giep_status_auto_banner');
if (!isGiepStatusManuallyOverridden) {
const rad = getActiveStatusRadio(calculated);
if (rad && !rad.checked) {
rad.checked = true;
rad.dispatchEvent(new Event('change', { bubbles: true }));
}
}
if (banner) {
banner.classList.remove('d-none');
const calcLabel = getStatusLabel(calculated);
let badgeClass = 'bg-success';
if (calculated === 'damaged') badgeClass = 'bg-danger';
else if (calculated === 'non_compliant') badgeClass = 'bg-warning text-dark';
if (isGiepStatusManuallyOverridden) {
let checkedVal = '';
getAllStatusRadios().forEach(r => { if (r.checked) checkedVal = r.value; });
const currentLabel = getStatusLabel(checkedVal);
banner.className = 'alert alert-warning border border-warning-subtle rounded-3 p-2.5 mb-3 shadow-sm d-flex align-items-center justify-content-between flex-wrap gap-2';
banner.innerHTML = `
<div class="small">
<i class="bi bi-pencil-square me-1"></i>
<strong>{% filter escapejs %}{% translate "Statut modifié manuellement" %}{% endfilter %} :</strong>
<span class="badge bg-secondary me-1">${currentLabel}</span>
<span class="text-muted">({% filter escapejs %}{% translate "Calculé" %}{% endfilter %} : <span class="badge ${badgeClass}">${calcLabel}</span>)</span>
</div>
<button type="button" class="btn btn-sm btn-outline-dark py-0 px-2 fw-semibold" id="btn_reset_giep_auto_status">
<i class="bi bi-arrow-counterclockwise me-1"></i>{% filter escapejs %}{% translate "Rétablir le calcul auto" %}{% endfilter %}
</button>
`;
document.getElementById('btn_reset_giep_auto_status')?.addEventListener('click', () => {
isGiepStatusManuallyOverridden = false;
syncGiepCalculatedStatus();
});
} else {
banner.className = 'alert alert-light border border-info-subtle bg-info-subtle text-info-emphasis rounded-3 p-2.5 mb-3 shadow-sm d-flex align-items-center justify-content-between flex-wrap gap-2';
banner.innerHTML = `
<div class="small">
<i class="bi bi-magic me-1 text-primary"></i>
<strong>{% filter escapejs %}{% translate "Statut calculé automatiquement" %}{% endfilter %} :</strong>
<span class="badge ${badgeClass} ms-1">${calcLabel}</span>
</div>
`;
}
}
}
function initStatusRadioListeners() {
getAllStatusRadios().forEach(radio => {
if (radio.dataset.giepListenerAttached) return;
radio.dataset.giepListenerAttached = 'true';
radio.addEventListener('click', () => {
const calculated = evaluateGiepInspectionStatus(giepOuvrages);
if (radio.value !== calculated) {
isGiepStatusManuallyOverridden = true;
} else {
isGiepStatusManuallyOverridden = false;
}
syncGiepCalculatedStatus();
});
});
}
function populateModalAssetsList(filterText = '', filterType = '') {
// Obsolete list, filtering operates directly on map layers
}
@ -878,11 +1125,6 @@
<h6 class="fw-bold mb-3 small text-success text-uppercase border-bottom pb-1"><i class="bi bi-box-arrow-in-right me-1"></i>{% translate "Point d'entrée dans l'ouvrage" %}</h6>
${renderSegGroup(idx, 'evc_pe_ok', '{% filter escapejs %}{% translate "L'eau peut-elle facilement entrer dans l'EVC ?" %}{% endfilter %}', q.evc_pe_ok)}
<div class="ps-3 border-start border-2 border-danger-subtle mt-2 ${q.evc_pe_ok === 'false' ? '' : 'd-none'}">
${renderSegGroup(idx, 'evc_pe_feuilles', '{% filter escapejs %}{% translate "Des feuilles ou branches bloquent-elles l'eau ?" %}{% endfilter %}', q.evc_pe_feuilles, true)}
${renderSegGroup(idx, 'evc_pe_detritus', '{% filter escapejs %}{% translate "Un amas de détritus bloque-t-il l'eau ?" %}{% endfilter %}', q.evc_pe_detritus, true)}
</div>
<!-- Empierrement switch -->
<div class="form-check form-switch mt-3 pt-2 border-top">
<input class="form-check-input q-check-dyn" type="checkbox" data-idx="${idx}" data-q="evc_empierrement_present" id="emp_pres_${idx}" ${q.evc_empierrement_present ? 'checked' : ''}>
@ -998,13 +1240,6 @@
${renderSegGroup(idx, 'oed_bi_niveaux_ok', '{% filter escapejs %}{% translate "Les niveaux du revêtement conduisent bien l'eau vers la bouche ?" %}{% endfilter %}', q.oed_bi_niveaux_ok)}
${renderSegGroup(idx, 'oed_bi_grille_propre', '{% filter escapejs %}{% translate "La grille est propre et dégagée ?" %}{% endfilter %}', q.oed_bi_grille_propre)}
${renderSegGroup(idx, 'oed_bi_decantation_vide', '{% filter escapejs %}{% translate "La décantation est-elle vide ou presque ?" %}{% endfilter %}', q.oed_bi_decantation_vide)}
${renderSegGroup(idx, 'oed_bi_filtre_present', '{% filter escapejs %}{% translate "La bouche d'injection dispose d'un filtre ?" %}{% endfilter %}', q.oed_bi_filtre_present)}
<div class="ps-3 border-start border-2 border-info-subtle mb-2 ${q.oed_bi_filtre_present === 'true' ? '' : 'd-none'}">
${renderSegGroup(idx, 'oed_bi_filtre_propre', '{% filter escapejs %}{% translate "Le filtre est propre ?" %}{% endfilter %}', q.oed_bi_filtre_propre)}
${renderSegGroup(idx, 'oed_bi_filtre_bon', '{% filter escapejs %}{% translate "Le filtre est en bon état ?" %}{% endfilter %}', q.oed_bi_filtre_bon)}
</div>
${renderSegGroup(idx, 'oed_bi_drain_colmate', '{% filter escapejs %}{% translate "Le drain semble colmaté ?" %}{% endfilter %}', q.oed_bi_drain_colmate, true)}
</div>
</div>
@ -1091,12 +1326,7 @@
<h6 class="fw-bold mb-3 small text-teal text-uppercase" style="color:#00796b;"><i class="bi bi-box-arrow-in-right me-1"></i>{% translate "Points d'entrée dans l'ouvrage" %}</h6>
${renderSegGroup(idx, 'ta_pe_ok', '{% filter escapejs %}{% translate "L'eau peut-elle facilement entrer dans l'ouvrage ?" %}{% endfilter %}', q.ta_pe_ok)}
${renderSegGroup(idx, 'ta_pe_conduits_ok', '{% filter escapejs %}{% translate "Les conduits et dispositifs amenant l'eau sont-ils fonctionnels ?" %}{% endfilter %}', q.ta_pe_conduits_ok)}
<div class="ps-3 border-start border-2 border-danger-subtle mb-2 ${q.ta_pe_conduits_ok === 'false' ? '' : 'd-none'}">
${renderSegGroup(idx, 'ta_pe_grilles', '{% filter escapejs %}{% translate "Les grilles sont-elles propres et dégagées ?" %}{% endfilter %}', q.ta_pe_grilles)}
${renderSegGroup(idx, 'ta_pe_feuilles', '{% filter escapejs %}{% translate "Des feuilles ou branches bloquent-elles l'eau ?" %}{% endfilter %}', q.ta_pe_feuilles, true)}
${renderSegGroup(idx, 'ta_pe_detritus', '{% filter escapejs %}{% translate "Un amas de détritus bloque-t-il l'eau ?" %}{% endfilter %}', q.ta_pe_detritus, true)}
</div>
${renderSegGroup(idx, 'ta_pe_decantation_vide', '{% filter escapejs %}{% translate "La décantation est-elle vide ou presque ?" %}{% endfilter %}', q.ta_pe_decantation_vide)}
</div>
<!-- TA: Bassin d'orage -->
@ -1118,14 +1348,12 @@
</div>
${renderSegGroup(idx, 'ta_bo_chenaux', '{% filter escapejs %}{% translate "Présence de chenaux d'érosion ?" %}{% endfilter %}', q.ta_bo_chenaux, true)}
${renderSegGroup(idx, 'ta_bo_flaques', '{% filter escapejs %}{% translate "Présence de flaques d'eau persistantes ?" %}{% endfilter %}', q.ta_bo_flaques, true)}
${renderSegGroup(idx, 'ta_sediments', '{% filter escapejs %}{% translate "Présence de sédiments dans l'ouvrage ?" %}{% endfilter %}', q.ta_sediments, true)}
<div class="ps-3 border-start border-2 border-danger-subtle mb-2 ${q.ta_sediments === 'true' ? '' : 'd-none'}">
${renderSegGroup(idx, 'ta_sediments_rempli', '{% filter escapejs %}{% translate "L'ouvrage est-il rempli au 5e de sa capacité ?" %}{% endfilter %}', q.ta_sediments_rempli, true)}
</div>
${renderSegGroup(idx, 'ta_bo_detritus', '{% filter escapejs %}{% translate "Présence de détritus dans l'ouvrage ?" %}{% endfilter %}', q.ta_bo_detritus, true)}
${renderSegGroup(idx, 'ta_bo_envahissantes', '{% filter escapejs %}{% translate "Présence d'espèces exotiques envahissantes ?" %}{% endfilter %}', q.ta_bo_envahissantes, true)}
</div>
`;
@ -1341,6 +1569,7 @@
});
updateSerializedGiepData();
syncGiepCalculatedStatus();
}
if (addBtn) {
@ -1395,10 +1624,20 @@
} else {
giepOuvrages = [];
}
// Si le statut existant diffère du statut calculé automatiquement, marquer comme surchargé
const autoStatus = evaluateGiepInspectionStatus(giepOuvrages);
if (data.result_status && data.result_status !== autoStatus) {
isGiepStatusManuallyOverridden = true;
} else {
isGiepStatusManuallyOverridden = false;
}
renderOuvrages();
updateSerializedGiepData();
};
initStatusRadioListeners();
renderOuvrages();
})();
</script>

View file

@ -1276,3 +1276,194 @@ class ViewerInspectionPermissionsAndGeojsonTestCase(TestCase):
# (O(1) queries instead of O(N), unaffected by inspection count)
self.assertLessEqual(len(ctx), 28)
class GiepInspectionStatusCalculationTestCase(TestCase):
def test_empty_or_none_data(self):
self.assertEqual(GiepInspection.compute_status_from_data(None), 'compliant')
self.assertEqual(GiepInspection.compute_status_from_data({}), 'compliant')
self.assertEqual(GiepInspection.compute_status_from_data({'ouvrages': []}), 'compliant')
def test_evc_compliant(self):
data = {
'ouvrages': [{
'device_type': 'evc',
'questions': {
'evc_pe_ok': 'true',
'evc_empierrement_present': False,
'evc_ev_veg_etat': 'bon',
'evc_ev_chenaux_erosion': 'false',
'evc_ev_flaques': 'false',
'evc_ev_sediments': 'false',
'evc_ev_envahissantes': 'false',
'evc_surverse_presente': False
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data), 'compliant')
def test_evc_non_compliant(self):
data = {
'ouvrages': [{
'device_type': 'evc',
'questions': {
'evc_pe_ok': 'false',
'evc_empierrement_present': False
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data), 'non_compliant')
def test_evc_damaged_empierrement(self):
data = {
'ouvrages': [{
'device_type': 'evc',
'questions': {
'evc_pe_ok': 'true',
'evc_empierrement_present': True,
'evc_empierrement_ok': 'false',
'evc_empierrement_emporte': True
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data), 'damaged')
def test_evc_damaged_vegetation_mortes(self):
data = {
'ouvrages': [{
'device_type': 'evc',
'questions': {
'evc_ev_veg_etat': 'mauvais',
'evc_ev_plantes_mortes': 'plus_10'
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data), 'damaged')
def test_evc_damaged_croute_sediments(self):
data = {
'ouvrages': [{
'device_type': 'evc',
'questions': {
'evc_ev_sediments': 'true',
'evc_ev_croute_sediments': 'true'
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data), 'damaged')
def test_evc_damaged_surverse_haute(self):
data = {
'ouvrages': [{
'device_type': 'evc',
'questions': {
'evc_surverse_presente': True,
'evc_sv_haute': 'false'
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data), 'damaged')
def test_oed_non_compliant(self):
data = {
'ouvrages': [{
'device_type': 'oed',
'questions': {
'oed_revetement_ok': 'false'
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data), 'non_compliant')
def test_oed_damaged_drain_colmate(self):
data = {
'ouvrages': [{
'device_type': 'oed',
'questions': {
'oed_injection_presente': True,
'oed_bi_drain_colmate': 'true'
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data), 'damaged')
def test_oed_damaged_joints_fines(self):
data = {
'ouvrages': [{
'device_type': 'oed',
'questions': {
'oed_rhm_presente': True,
'oed_rhm_joints_ok': 'false',
'oed_rhm_joints_fines': 'true'
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data), 'damaged')
def test_ta_non_compliant_and_damaged(self):
# TA non conforme: point d'entrée non ok
data_nc = {
'ouvrages': [{
'device_type': 'ta',
'questions': {
'ta_pe_ok': 'false'
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data_nc), 'non_compliant')
# TA endommagé: conduits non fonctionnels
data_dmg = {
'ouvrages': [{
'device_type': 'ta',
'questions': {
'ta_pe_ok': 'true',
'ta_pe_conduits_ok': 'false'
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data_dmg), 'damaged')
# TA endommagé: capacité remplie au 5e
data_rempli = {
'ouvrages': [{
'device_type': 'ta',
'questions': {
'ta_sediments': 'true',
'ta_sediments_rempli': 'true'
}
}]
}
self.assertEqual(GiepInspection.compute_status_from_data(data_rempli), 'damaged')
def test_multi_ouvrages_priority(self):
# Ouvrage 1: compliant, Ouvrage 2: non_compliant -> overall non_compliant
data_mix1 = {
'ouvrages': [
{
'device_type': 'evc',
'questions': {'evc_pe_ok': 'true'}
},
{
'device_type': 'oed',
'questions': {'oed_revetement_ok': 'false'}
}
]
}
self.assertEqual(GiepInspection.compute_status_from_data(data_mix1), 'non_compliant')
# Ouvrage 1: non_compliant, Ouvrage 2: damaged -> overall damaged (priority)
data_mix2 = {
'ouvrages': [
{
'device_type': 'evc',
'questions': {'evc_pe_ok': 'false'} # non_compliant
},
{
'device_type': 'ta',
'questions': {'ta_pe_conduits_ok': 'false'} # damaged
}
]
}
self.assertEqual(GiepInspection.compute_status_from_data(data_mix2), 'damaged')