Conformité#
Source
Manifeste : app/modules/conformite/__init__.py — 26 permissions, 2 rôles
Routes : app/api/routes/modules/conformite.py — 2350 lignes, ~35 endpoints
Modèles : app/models/common.py:1434-1611 — 7 tables
Service décision : app/services/modules/compliance_service.py — 788 lignes
Job expiration : app/tasks/jobs/compliance_expiry.py — quotidien 06:00
UI : apps/main/src/pages/conformite/ — 9 onglets
Résumé en 30 secondes#
Conformité est le moteur de décision « ce PAX/tiers/asset est-il en règle ? » de toute la plateforme. Il porte cinq objets :
- Types (
compliance_types) — le catalogue : H2S, GWO BST, visite médicale, DT-DICT, certif NDT… 6 catégories : formation, certification, habilitation, audit, medical, epi. - Règles (
compliance_rules) — qui doit avoir quoi : tout le monde, un type de tiers, un asset, un département, une fiche de poste. Versionnées avec historique d'audit. - Enregistrements (
compliance_records) — l'instance : « Jean Dupont a son H2S valable jusqu'au 2026-09-12 ». Lié à un owner_type (user, tier, tier_contact, asset). - Exemptions (
compliance_exemptions) — dérogations temporaires sur un enregistrement non conforme (cert expirée le temps du renouvellement). Workflow d'approbation 3 états. - Fiches de poste (
job_positions) — référentiel HSE : assigner « Mécanicien topside » à un employé impose automatiquement les habilitations du poste.
Le service compliance_service.check_owner_compliance()
calcule un verdict unique consommé par PaxLog (validation ADS), AVM
(check site d'arrivée) et les dashboards. PaxLog n'invente jamais sa propre
règle : il appelle Conformité.
Trois mécanismes différencient ce module d'un simple CRUD de certifs :
- Vérification à 2 phases : à la création, un enregistrement passe en
pending+verification_status='pending'. Le statutvalidn'est posé que par un utilisateur ayantconformite.verify(workflow 4-yeux). Une PJ est exigée par défaut (paramétrable par règle). - Auto-expiration : un job APScheduler tourne tous les jours à 06:00 et
passe en
expiredtous les enregistrements dontexpires_at < now(). Émet les eventsconformite.record.expired,expiring_soon(avant échéance) etpast_grace(au-delà de la période de grâce de la règle). - Matrice multi-couche pour les PAX : pour un PAX qui demande accès à un
asset, le verdict consulte 3 couches dans cet ordre paramétrable :
site_requirements (matrice Asset Registry × CredentialType) → job_profile
(habilitations liées au profil) → self_declaration. Couvert dans
check_pax_asset_compliance().
Le problème métier#
« On ne peut pas envoyer Pierre sur la plateforme demain : sa visite médicale a expiré il y a 3 jours. Combien d'autres comme lui ? Quelles certifs vont sauter dans 30 jours ? Le sous-traitant DRILL-CO a tous ses techniciens habilités H2S ? »
Sur un site industriel multi-sous-traitants, le respect des exigences HSE, réglementaires et contractuelles est un sujet de production :
- Loi : un PAX sans habilitation valide qui a un accident, c'est l'opérateur qui répond pénalement (article L4121-1 Code du travail FR).
- Insurance : assureurs imposent une matrice de credentials par type d'activité. Pas de matrice → pas de couverture.
- Contrats : les majors imposent à leurs sous-traitants une revue de conformité avant chaque mobilisation. Le sous-traitant doit prouver en continu (pas juste à la signature) que son personnel est en règle.
Sans outil :
- Tableurs Excel par site, désynchronisés. Vérification au cas par cas la veille d'une mob, panique le jour J.
- Doublons : la même certif scannée 5 fois par le même contractant pour 5 mobs différentes.
- Auto-déclarations non vérifiées : le sous-traitant déclare avoir un certif, l'opérateur n'a pas la PJ. En cas d'audit, c'est un finding bloquant.
Conformité résout ça en un référentiel unique partagé + un workflow de vérification + des règles automatiques par poste/site/asset + un verdict canonique que tous les autres modules consomment.
Concepts clés#
ComplianceType — le catalogue#
Le type est l'unité de vocabulaire HSE :
app/models/common.py:1434.
| Champ | Sens |
|---|---|
category |
formation, certification, habilitation, audit, medical, epi |
code |
Code court unique : H2S_BASIC, GWO_BST_2024 |
name |
Libellé clair : « Formation H2S de base » |
validity_days |
Durée de validité en jours. null = permanent |
is_mandatory |
Marqueur informatif (la vraie obligation passe par les règles) |
compliance_source |
opsflux (saisi à la main), external (synchronisé via connecteur), both |
external_provider |
riseup, intranet_medical… (utilisé si source = external) |
external_mapping |
JSON : config du connecteur — {"certificate_id": "42"} |
ComplianceRule — qui doit avoir quoi#
La règle lie un ComplianceType à un public cible :
app/models/common.py:1461.
target_type |
target_value |
Sens |
|---|---|---|
all |
null |
Tous les owners de l'entité |
tier_type |
client, subcontractor |
Tous les contacts d'un type de tiers |
asset |
<asset_id> |
Tous les visiteurs d'un asset spécifique |
department |
Operations |
Tout employé d'un département |
job_position |
<jp_id_csv> |
Toute personne avec une de ces fiches de poste (liste CSV) |
Une règle est versionnée (version, superseded_by) avec un historique
(ComplianceRuleHistory) :
chaque modification écrit un snapshot JSON de l'état précédent. Permet de
prouver à l'audit que telle règle s'appliquait bien à telle date.
Champs de politique de la règle (override du type) :
override_validity_days— durcir/assouplir la durée (cf. règle locale plus stricte que le standard groupe)grace_period_days— combien de jours après expiration on accepte encore une PJ rétroactiverenewal_reminder_days— combien de jours avant échéance on émet l'eventconformite.record.expiring_soon(utilisé par le notification engine)attachment_required— sitrue, on refuse la vérification sans PJpriority—high/normal/low(UI uniquement, pas de logique)applicability—permanent(toujours vérifié) oucontextual(vérifié uniquement siinclude_contextual=true)condition_json— conditions structurées ({"all": [{"field": "...", "op": "gte", "value": ...}]}) — utilisé par PackLog pour les règles de cargo
ComplianceRecord — l'instance#
L'enregistrement matérialise « cet owner détient ce type » :
app/models/common.py:1518.
owner_type ∈ { user, tier, tier_contact, asset }
status ∈ { pending, valid, expired, rejected } ← état métier
verification_status ∈ { pending, verified, rejected } ← état du contrôle 4-yeux
Deux statuts orthogonaux ! Un record peut être status=valid mais
verification_status=pending (en attente de double-check). Le verdict de
conformité ne le compte alors pas comme valide.
À la création, le backend force :
status = pendingverification_status = pending
Seul le workflow de vérification (cf. plus bas) promeut en valid.
ComplianceExemption — la dérogation#
Une exemption est une autorisation temporaire de considérer un record
non-conforme comme conforme : app/models/common.py:1544.
Avec start_date/end_date : pendant cette fenêtre et si status=approved,
le service de check considère le record comme valide. Approbation par un user
avec conformite.exemption.approve. Rejet exige une raison écrite.
JobPosition — la fiche de poste#
Référentiel des postes : app/models/common.py:1573.
Champs minimaux : code, name, description, department. C'est le lien
entre un employé (User ou TierContact a job_position_id) et les règles
ciblées par target_type='job_position'.
Workflow : on assigne « Mécanicien topside » à un nouvel embauché → toutes les
règles ayant ce job_position dans leur target_value deviennent applicables
automatiquement. Pas besoin de copier les habilitations à la main.
TierContactTransfer — le transfert d'employé#
Quand un employé change de sous-traitant (DRILL-CO → SERVICE-CO), on logge
le transfert : app/models/common.py:1592.
Effet : tier_contact.tier_id est updaté et un row d'historique est
écrit. Permet de tracer qui possédait le contrat de qui à quelle date — sujet
sensible en cas d'incident.
Architecture data#
erDiagram
COMPLIANCE_TYPE ||--o{ COMPLIANCE_RULE : "définit obligations"
COMPLIANCE_TYPE ||--o{ COMPLIANCE_RECORD : "instances par owner"
COMPLIANCE_RULE ||--o{ COMPLIANCE_RULE_HISTORY : "audit log"
COMPLIANCE_RECORD ||--o{ COMPLIANCE_EXEMPTION : "dérogations"
COMPLIANCE_RECORD ||--o{ ATTACHMENT : "PJ polymorphe"
JOB_POSITION ||--o{ TIER_CONTACT : "porté par employés"
JOB_POSITION ||--o{ USER : "porté par utilisateurs internes"
JOB_POSITION ||--o{ COMPLIANCE_RULE : "ciblée par target_type=job_position"
TIER_CONTACT ||--o{ TIER_CONTACT_TRANSFER : "historique mobilité"
TIER_CONTACT ||--o{ COMPLIANCE_RECORD : "owner_type=tier_contact"
USER ||--o{ COMPLIANCE_RECORD : "owner_type=user"
TIER ||--o{ COMPLIANCE_RECORD : "owner_type=tier (certif entreprise)"
ASSET ||--o{ COMPLIANCE_RECORD : "owner_type=asset (audit site)"
Le pont avec PaxLog#
flowchart TB
subgraph Conformité
CT[ComplianceType]
CR[ComplianceRule]
CRC[ComplianceRecord]
end
subgraph PaxLog
CT_PAX[CredentialType]
PC[PaxCredential]
CME[ComplianceMatrixEntry]
end
subgraph AssetRegistry
AS[Asset / Installation]
end
CME -->|asset_id| AS
CME -->|credential_type_id| CT_PAX
PC -->|credential_type_id| CT_PAX
PC -.->|user_id ou contact_id| OWN[Owner PAX]
CT_PAX -.|FK opt|.- CT
CRC -.|owner_type=user/tier_contact|.- OWN
Service[compliance_service<br/>check_pax_asset_compliance]
Service -->|requirements| CME
Service -->|credentials| PC
Service -->|business records| CRC
PaxLog porte deux notions parallèles :
CredentialType+PaxCredential(app/models/paxlog.py:79-122) — le terrain « PAX » : credentials physiques liés au module.ComplianceMatrixEntry(app/models/paxlog.py:126) — la matrice « ce site/installation impose cette CredentialType à ces personnes ».
Conformité côté business porte les records transverses (audit qualité, ISO,
contrats fournisseur — pas seulement PAX). Le service check_owner_compliance
fait la synthèse des deux mondes pour répondre à : « cet owner est-il en
règle pour rentrer sur cet asset ? ».
Workflows#
Workflow d'un enregistrement#
stateDiagram-v2
[*] --> pending : POST /records<br/>(force status=pending<br/>verification_status=pending)
pending --> valid : POST /verify {action:verify}<br/>(perm conformite.verify<br/>+ PJ si attachment_required)
pending --> rejected : POST /verify {action:reject + reason}
valid --> expired : Auto (job 06:00)<br/>OR list_records inline check
expired --> valid : PATCH /records<br/>(corriger expires_at futur<br/>ET verification_status=verified)
valid --> [*] : DELETE /records (soft archive)
rejected --> [*] : Définitif
Points clés :
- À la création, impossible d'arriver directement en
valid. Le backend réécritstatus='pending'même si l'API reçoit autre chose : ligneapp/api/routes/modules/conformite.py:778. - Pré-validation à la création : on refuse un record déjà expiré à la
soumission, ou un record dont
issued_at + validity_days + grace_period < now(cf. lignes 736-775). - À la vérification, si la règle a
attachment_required=true(défaut) et qu'il n'y a aucune PJ (ni dansattachmentspolymorphes ni dans le legacydocument_url), HTTP 422 (lignes 2048-2055). - Si on corrige
expires_atvers une date future sur un recordexpired, le status revient àvalidautomatiquement (à condition queverification_status=verified) — lignes 829-834.
Workflow d'une exemption#
stateDiagram-v2
[*] --> pending : POST /exemptions
pending --> approved : POST /exemptions/{id}/approve<br/>(perm conformite.exemption.approve)
pending --> rejected : POST /exemptions/{id}/reject<br/>(reason obligatoire)
approved --> expired : Auto-expire si end_date < now<br/>(à câbler côté job)
pending --> [*] : DELETE (soft)
approved --> [*] : DELETE (soft)
Une exemption ne peut être approuvée qu'une fois (les transitions
approved→pending ou rejected→pending sont bloquées par les guards lignes
1491-1499 et 1542-1550).
Workflow de vérification (4-yeux)#
sequenceDiagram
actor U as Utilisateur (owner)
actor V as Vérificateur HSE
participant API as Backend
participant DB as DB
participant E as Event bus
U->>API: POST /records<br/>(issued_at, expires_at, PJ)
API->>DB: INSERT (status=pending,<br/>verification_status=pending)
API-->>U: 201 Created
V->>API: GET /pending-verifications
API-->>V: liste tous les records pending<br/>(compliance + passport + visa + medical_check…)
V->>API: POST /verify/compliance_record/{id}<br/>{action:"verify"}
API->>DB: vérif PJ count > 0
API->>DB: SET verification_status=verified,<br/>status=valid (si pending)
API->>E: emit conformite.record.verified
API->>U: email "votre certif est vérifiée"
API-->>V: 200
L'endpoint /verify couvre 7 types de records (lignes 1992-2000) :
record_type |
Modèle | Origine |
|---|---|---|
compliance_record |
ComplianceRecord |
Conformité |
passport |
UserPassport |
Core User |
visa |
UserVisa |
Core User |
social_security |
SocialSecurity |
Core User |
vaccine |
UserVaccine |
Core User |
driving_license |
DrivingLicense |
Core User |
medical_check |
MedicalCheck |
Core (polymorphe) |
Le vérificateur HSE travaille donc à un seul endroit pour valider tous les documents qui entrent dans le verdict de conformité d'une personne.
Vérification PAX × Asset (le 3-couches)#
Le check le plus complexe : pour décider si un PAX peut rentrer sur un asset.
Implémenté dans check_pax_asset_compliance()
(lignes 178-383).
flowchart TD
Start([Demande accès PAX → Asset])
Start --> Hier[Construire hiérarchie<br/>Installation → Site → Field]
Hier --> Matrix[Lire compliance_matrix<br/>pour ces 3 niveaux<br/>+ scope all_visitors / contractor_only / staff_only]
Matrix --> Hab[Lire profile_habilitation_matrix<br/>via le profile_type du PAX]
Hab --> Seq[Setting paxlog.compliance_sequence<br/>défaut: site_requirements →<br/>job_profile → self_declaration]
Seq --> Loop{Pour chaque<br/>credential requis<br/>dans l'ordre}
Loop -->|missing| Fail[overall_compliant=false<br/>blocking=true]
Loop -->|expired| Fail
Loop -->|pending_validation| Pend[blocking=false<br/>flag self_declaration]
Loop -->|valid| OK[Continuer]
OK --> Loop
Fail --> Result
Pend --> Result
Result([Verdict + détails par couche])
Pourquoi 3 couches ?
site_requirements: la matrice imposée par le HSE central pour cet asset (cf. PaxLogComplianceMatrixEntry). Bloquant.job_profile: exigences liées au profil du PAX (mécanicien topside ≠ technicien bureau). Bloquant.self_declaration: credentials que le PAX a déclaré mais qui restent enpending_validationcôté HSE. Non bloquant → on flag le PAX comme « à surveiller ».
L'ordre est paramétrable par un Setting paxlog.compliance_sequence
au scope entity (get_compliance_verification_sequence).
Step-by-step utilisateur#
Profil 1 — Responsable conformité#
Permissions : tout ce qui commence par conformite.* + conformite.verify
+ conformite.exemption.approve.
Créer un type de conformité#
- Page Conformité → onglet Référentiel
- Bouton + Type de conformité
- Renseigner :
- Catégorie (drop-down 6 valeurs)
- Code unique (ex.
GWO_BST_2024) - Nom complet
- Durée de validité en jours (laisser vide = permanent)
- Source :
OpsFlux(saisi manuellement) ouExternal(sync via connecteur) - Si External : choisir le provider et configurer le mapping JSON
Créer une règle#
- Onglet Règles (vue matrice)
- Cliquer une cellule vide pour pré-remplir le type + la cible
- Configurer :
- Cible (
target_type) : qui est concerné - Override validité : durcir la durée pour cette règle
- Grâce : tolérance après expiration
- PJ requise : décocher uniquement pour les déclarations de bonne foi
- Reminder : nb de jours avant échéance pour notifier
- Applicabilité :
permanent(toujours vérifiée) oucontextual(vérifiée seulement quand le module appelant le demande, ex. règle qui s'active uniquement le jour J de la mob) - Raison du changement : obligatoire si on modifie une règle existante
(alimente
ComplianceRuleHistory)
Vérifier un enregistrement#
- Onglet Vérifications → liste de tous les pending tous types confondus
- Cliquer une ligne → panel de détail
- Vérifier la PJ ouverte côté droit
- Décision :
- Vérifier : promotion vers
verified+ (si compliance_record enpending) →valid - Rejeter : raison obligatoire, écrite dans
rejection_reason - Email auto à l'owner (
render_and_send_emailslugrecord_verified)
Approuver une exemption#
- Onglet Exemptions → filtrer status =
pending - Lire le motif (
reason), la fenêtrestart_date→end_date - Bouton Approuver ou Rejeter (raison obligatoire si rejet)
- Event émis :
conformite.exemption.approved/rejected(consommé par notifications)
Profil 2 — Opérateur conformité#
Permissions : conformite.record.* (sauf delete), conformite.check,
conformite.exemption.create, pas conformite.verify ni approve.
Saisir un nouveau certificat#
- Page Conformité → onglet Enregistrements → + Nouveau
- Owner : choisir user / tier_contact / tier / asset
- Type : sélectionner dans le catalogue
issued_at(date d'émission) — le backend pré-remplitexpires_atsivalidity_daysest défini sur le type- Émetteur, numéro de référence, notes
- Drag & drop la PJ dans la zone d'attachment
- Soumettre — le record entre en
pending. Notification au responsable HSE (eventconformite.record.createdconsommé par le worker)
Demander une exemption#
- Ouvrir le record concerné (en
expiredou enpendingqu'on n'arrive pas à faire vérifier à temps) - Bouton Demander exemption
- Renseigner motif, dates, conditions éventuelles (« remplacé par escorte permanente jusqu'au 2026-05-15 »)
- Soumettre — l'exemption part en
pending. Le responsable la voit dans son onglet Exemptions.
Profil 3 — Manager qui consulte#
Permissions : conformite.record.read, conformite.check.
Vérifier la conformité d'un employé#
- Page Tiers ou Utilisateurs → ouvrir la fiche
- Onglet Conformité (qui agrège tous les records du contact)
- Le badge global est calculé via
GET /api/v1/conformite/check/{owner_type}/{id}
Liste des certifs qui expirent#
- Page Conformité → onglet Dashboard → KPIs
- « Expirations à venir (30j) » — clic pour drill-down
- Onglet Enregistrements + filtre
status=valid+ tri parexpires_atASC
Profil 4 — Sous-traitant externe#
Permissions : conformite.record.read, conformite.record.create (limité
aux tier_contacts de ses propres tiers via UserTierLink).
Le code applique automatiquement un scope filter
(_apply_external_record_scope) :
un user external ne voit que les records des contacts de ses propres tiers,
plus ses records personnels (owner_type=user).
Ce profil ne peut pas :
- Vérifier ses propres records (le 4-yeux est garanti)
- Voir les records des contacts d'un autre sous-traitant
- Approuver des exemptions
Permissions matrix#
| Action | RESPONSABLE_CONFORMITE |
OPERATEUR_CONFORMITE |
Autres rôles ayant la perm |
|---|---|---|---|
| Lire types | ✅ | ✅ | tout owner ayant conformite.type.read |
| CRUD types | ✅ | ❌ | — |
| Lire règles | ✅ | ✅ | — |
| CRUD règles | ✅ | ❌ | — |
| Lire records | ✅ | ✅ | — |
| Créer record | ✅ | ✅ | sous-traitants externes (scope tiers) |
Update record verified |
✅ (verify) |
❌ | bloqué par check_verified_lock |
| Vérifier (verify/reject) | ✅ | ❌ | — |
| Lire exemptions | ✅ | ✅ | — |
| Créer exemption | ✅ | ✅ | — |
| Approuver exemption | ✅ | ❌ | — |
| Lire fiches de poste | ✅ | ✅ | — |
| CRUD fiches de poste | ✅ | ❌ | — |
| Transferts (lecture) | ✅ | ✅ | — |
| Transférer un contact | ✅ | ❌ | — |
| Import / Export | ✅ | ❌ (lecture uniquement via export) | — |
Le check_verified_lock (app/api/deps.py) bloque toute modification d'un record verified sauf si l'utilisateur a conformite.verify. Cela évite qu'un opérateur trafique discrètement une certif déjà validée.
Endpoints HTTP#
Préfixe /api/v1/conformite. Lecture en pagination standard
(?page=&page_size=).
Dashboard#
| Méthode | Endpoint | Sens |
|---|---|---|
| GET | /dashboard-kpis |
KPIs agrégés : total/valid/expired/pending, taux conformité, expirations 30j, breakdown par catégorie |
Référentiel — Types#
| Méthode | Endpoint | Sens |
|---|---|---|
| GET | /types |
Liste paginée (filtres category, search) |
| POST | /types |
Créer |
| PATCH | /types/{id} |
Modifier |
| DELETE | /types/{id} |
Soft-archive |
Règles#
| Méthode | Endpoint | Sens |
|---|---|---|
| GET | /rules?compliance_type_id= |
Liste (non paginée, max 200) |
| POST | /rules |
Créer (écrit history v1 « created ») |
| PATCH | /rules/{id} |
Modifier (écrit history snapshot précédent + change_reason requis) |
| DELETE | /rules/{id}?force= |
Hard-delete si version=1 ou force=true, sinon soft selon la delete policy |
| GET | /rules/{id}/history |
Audit trail JSON |
Enregistrements#
| Méthode | Endpoint | Sens |
|---|---|---|
| GET | /records |
Filtres owner_type, owner_id, compliance_type_id, status, category, search. Auto-expire inline les records dont expires_at < now |
| POST | /records |
Créer (force status=pending, valide pré-soumission) |
| PATCH | /records/{id} |
Modifier (bloqué sur verified sans perm verify) |
| DELETE | /records/{id} |
Soft-archive |
| GET | /expiring?days=30 |
Records valides expirant dans N jours, émet pax.credential.expiring |
| GET | /non-compliant |
Records expirés (overdue first) |
| GET | /check/{owner_type}/{owner_id}?include_contextual=&asset_id= |
Verdict canonique consommé par PaxLog |
| GET | /matrix?owner_type=&category=&expiring_within_days=30&limit=50&offset=0 |
Vue matrice owner × type, cellule encodée valid/expiring/expired/missing/pending/rejected |
Exemptions#
| Méthode | Endpoint | Sens |
|---|---|---|
| GET | /exemptions |
Liste paginée (filtres status, compliance_type_id, search) |
| POST | /exemptions |
Créer (status=pending) |
| PATCH | /exemptions/{id} |
Modifier |
| POST | /exemptions/{id}/approve |
Approuver (perm exemption.approve) |
| POST | /exemptions/{id}/reject |
Rejeter (raison obligatoire) |
| DELETE | /exemptions/{id} |
Soft-archive |
Fiches de poste#
| Méthode | Endpoint | Sens |
|---|---|---|
| GET | /job-positions |
Liste paginée |
| POST | /job-positions |
Créer (génère un code via generate_reference("JBP", ...) si vide) |
| PATCH | /job-positions/{id} |
Modifier |
| DELETE | /job-positions/{id} |
Soft-archive |
Transferts#
| Méthode | Endpoint | Sens |
|---|---|---|
| GET | /transfers |
Liste enrichie (noms tier source/cible) |
| POST | /transfers |
Logger un transfert + déplacer le contact (tier_contact.tier_id muté) |
Vérification#
| Méthode | Endpoint | Sens |
|---|---|---|
| GET | /pending-verifications |
Tous les records pending tous types (7 modèles : compliance_record, passport, visa, social_security, vaccine, driving_license, medical_check) |
| GET | /verification-history |
Historique des verdicts (qui a vérifié quoi) |
| POST | /verify/{record_type}/{record_id} |
{action: "verify" \| "reject", rejection_reason?: str} |
Événements émis#
Tous publiés sur le bus interne (app.core.events). Consommables par n'importe quel module via @subscribe.
| Event | Émis par | Payload clé | Consommateurs typiques |
|---|---|---|---|
conformite.rule.created |
POST /rules |
rule_id, entity_id, target_type, target_value, created_by |
Audit log, notif admin |
conformite.rule.updated |
PATCH /rules/{id} |
idem + updated_by |
Audit log |
conformite.record.expired |
Job compliance_expiry 06:00 + endpoints list_records, list_expiring, list_non_compliant |
record_id, compliance_type_id, owner_type, owner_id, entity_id, expired_at |
Notification engine, dashboards |
conformite.record.expiring_soon |
Job 06:00 (en respectant renewal_reminder_days de la règle) |
record_id, days_remaining, rule_id |
Email rappel à l'owner |
conformite.record.past_grace |
Job 06:00 (lecture grace_period_days) |
record_id, grace_days |
Escalade manager |
conformite.record.verified |
POST /verify action=verify |
record_type, record_id, verified_by |
Email confirmation à l'owner |
conformite.record.rejected |
POST /verify action=reject |
+ reason |
Email rejet |
conformite.exemption.approved |
POST /exemptions/{id}/approve |
exemption_id, record_id, approved_by |
Notif owner + manager |
conformite.exemption.rejected |
POST /exemptions/{id}/reject |
+ rejected_by |
Notif demandeur |
pax.credential.expiring |
GET /expiring (calc inline) |
record_id, days_remaining, type_name |
Worker PaxLog (rappel mob) |
Pièges & FAQ#
Pourquoi mon record reste en pending même après que j'ai uploadé la PJ ?
Le statut pending ne change qu'après une action explicite d'un user
avec conformite.verify. Uploader la PJ ne déclenche pas
automatiquement la vérification — c'est volontaire (4-yeux). Le record est
visible dans /pending-verifications côté responsable HSE, qui doit
cliquer Vérifier.
J'ai une certif valide et le verdict dit que je ne suis pas conforme
Trois pistes :
verification_statusn'est pasverified: un record peut avoirstatus=validmais ne pas être vérifié — il est ignoré par le calcul. Vérifie dans le panel de détail le badge « Vérifié » (vert) vs « En attente » (orange).- Une règle plus stricte applique un override :
override_validity_dayssur la règle peut faire expirer un record qui paraissait valide. - Pour un check PAX × Asset : la matrice
compliance_matrixest interrogée en plus. Un type requis par la matrice site mais absent du compte du PAX → non conforme. Voir l'onglet Matrice côté frontend.
Différence entre compliance_records et pax_credentials ?
pax_credentials: couche bas-niveau du module PaxLog, attachée à unCredentialTypePaxLog. Sert au scan badge / kiosque captain.compliance_records: couche métier transverse, attachée à unComplianceType. Sert aux audits, contrats, réglementaire.
Les deux peuvent pointer vers les mêmes documents physiques, mais ce
sont des objets distincts. Le compliance_service.check_pax_asset_compliance
est ce qui les réconcilie quand on évalue un PAX vs un asset.
Comment supprimer une règle qui a déjà servi ?
Une règle avec version > 1 ou un change_reason (= déjà publiée) ne peut
pas être hard-delete sans ?force=true. Sinon, soft-delete : la règle est
marquée active=false, effective_to=today, et un snapshot est écrit
dans ComplianceRuleHistory. Les records émis sous cette règle restent
valides jusqu'à expiration. Pour un audit, on peut requêter /rules/{id}/history
pour reconstituer la règle telle qu'elle s'appliquait à une date donnée.
Pourquoi mon exemption ne lève pas l'alerte sur le record ?
Trois conditions cumulatives (compliance_service.py lignes 510-521) :
status = approved(pas pending, pas rejected)active = truestart_date <= today <= end_date
Si l'exemption est en pending, elle ne couvre rien — c'est une
demande, pas une autorisation. Vérifie l'onglet Exemptions, status colonne.
Le job d'expiration tourne-t-il vraiment tous les jours ?
Oui : APScheduler avec cron 0 6 * * *
(app/tasks/scheduler.py).
En complément, chaque appel à GET /records, /expiring, /non-compliant
auto-expire les records overdue de manière transactionnelle. Donc même si
le job tombe en panne, les listes affichent l'état correct.
Les sous-traitants externes voient-ils mes règles internes ?
Non. Les règles sont entity-scopées. En revanche un sous-traitant voit
les types et records de ses propres tiers. Le code applique
_apply_external_record_scope à toutes les listes records/expiring/check.
Il ne peut rien modifier sur une PAX d'un autre tiers (404
OWNER_NOT_FOUND côté API si tentative).
Comment changer l'ordre des couches du check PAX × Asset ?
Setting paxlog.compliance_sequence au scope entity, value JSON
{"v": ["site_requirements", "self_declaration", "job_profile"]}.
Toute valeur invalide est ignorée et l'ordre par défaut est restauré
(get_compliance_verification_sequence).
Cette config est rarement modifiée : la séquence par défaut convient à 99%
des cas oil & gas.
Que se passe-t-il si je supprime un type qui a des records ?
Soft-delete uniquement. Les records gardent leur compliance_type_id,
le type est marqué active=false, il disparaît des dropdowns dans l'UI,
mais les enregistrements existants restent consultables et leur lien
fonctionne pour les requêtes historiques.
Cas pratique — comment HSE central peut imposer une obligation à tous les sites ?
- Créer un
ComplianceType(ex. « DUEK CSE 2026 ») avecvalidity_days=365. - Créer une
ComplianceRuletarget_type='all'target_value=null. - Cocher
is_mandatory=true,attachment_required=true,renewal_reminder_days=60,grace_period_days=15. - Le job 06:00 émettra automatiquement les rappels et grace warnings sans autre intervention.
Liens#
- Spec développeur (auth requise) : Conformité — modules-spec
- Cross-module :
- PaxLog — consommateur principal du verdict (validation ADS)
- Tiers — owner_type=tier_contact pour les employés sous-traitants
- Asset Registry — la matrice
compliance_matrixpar installation
- Code source :