feat: lint_audit.py calibre + TYPES_CANONIQUES source unique
- lint_audit.py : moteur d'audit groupe par data quality dimensions (completeness, accuracy, consistency, validity, uniqueness, freshness) - charger() : passe #type globale (voit tous les types reels, dont les intrus) - VAL-type : valide le label type contre la liste canonique (ignore container) - dependDe elargi (domaine +service, portee +outil) : faux positif supprime - TYPES_CANONIQUES dans trilium_api.py : source unique partagee Lint + create_entite - create_entite aligne sur TYPES_CANONIQUES, couvre les 10 types connaissance/technique Etage 1 lecture seule : 348 notes, 29 anomalies reelles, 0 faux positif.
This commit is contained in:
+512
@@ -0,0 +1,512 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
lint_audit.py - Moteur d'audit (Lint) du knowledge graph Context Continuity.
|
||||
|
||||
ETAGE 1 : LECTURE SEULE. Detecte et rapporte des anomalies ; ne corrige RIEN
|
||||
et n'ecrit RIEN dans Trilium (rapport texte : stdout + fichier horodate dans
|
||||
lint_reports/).
|
||||
|
||||
ORGANISATION PAR DATA QUALITY DIMENSIONS (referentiel autoporteur, extensible).
|
||||
Chaque controle declare la dimension qu'il sert ; le rapport est groupe par
|
||||
dimension. Ajouter un controle = decorer une fonction avec @check(dimension,...).
|
||||
Ajouter une dimension = l'ajouter a DIMENSIONS. Le rapport suit automatiquement.
|
||||
|
||||
Chaque anomalie porte aussi un bac d'action (prepare l'etage 2) :
|
||||
AUTO : correction mecanique sure (accent, casse, enum non ambigue)
|
||||
SIGNALER : deterministe mais sans correction evidente -> decision humaine
|
||||
JUGEMENT : demande du contexte metier -> revue par l'agent / l'humain
|
||||
A l'etage 1, AUCUNE correction n'est appliquee, quel que soit le bac.
|
||||
|
||||
Usage (sur GrosseBertha, dans le venv) :
|
||||
cd ~/App/Context_continuity && source venv/bin/activate
|
||||
python3 lint_audit.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import unicodedata
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from trilium_api import (
|
||||
search_by_label, get_note, get_note_content,
|
||||
RELATIONS_ONTOLOGIE, TYPES_CANONIQUES,
|
||||
)
|
||||
|
||||
BASE = os.path.expanduser("~/App/Context_continuity")
|
||||
IDS = json.load(open(os.path.join(BASE, "trilium_ids.json")))
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# 1. LA TAXONOMIE : data quality dimensions (autoporteur)
|
||||
# ==========================================================================
|
||||
|
||||
# Ordre = ordre d'affichage dans le rapport. Definitions = celles de Bastien
|
||||
# (Data Governance), alignees sur les dimensions DAMA standard.
|
||||
DIMENSIONS = {
|
||||
"completeness": "Les champs obligatoires sont remplis ET les entites attendues sont toutes saisies (exhaustivite).",
|
||||
"accuracy": "Le niveau de la donnee reflete la realite (bon rangement, contenu conforme au type, statut a jour).",
|
||||
"consistency": "Coherence dans le temps et a travers les entites (vocabulaire courant, informations repetees a jour partout).",
|
||||
"validity": "Les valeurs et relations respectent les regles en vigueur (enums, conventions de nommage, ontologie des relations).",
|
||||
"uniqueness": "Absence de doublons.",
|
||||
"freshness": "Les entites ne portent pas un etat perime (anciennete anormale, non-cloture).",
|
||||
}
|
||||
|
||||
# Controles PREVUS mais NON deterministes : delegues a l'agent reviseur (etage 2).
|
||||
# Rendus visibles pour l'honnetete de la couverture.
|
||||
CHECKS_DELEGUES = [
|
||||
{"dimension": "completeness", "code": "COMP-exhaustivite",
|
||||
"libelle": "Entites manquantes vs realite du projet (ex : backlog incomplet)",
|
||||
"raison": "Non detectable en deterministe : requiert la comparaison avec la realite (code, conversations). -> agent reviseur."},
|
||||
{"dimension": "accuracy", "code": "ACC-contenu-type",
|
||||
"libelle": "Le contenu redige d'une note reflete-t-il bien son type",
|
||||
"raison": "Requiert un jugement semantique. -> agent reviseur."},
|
||||
{"dimension": "consistency", "code": "CONS-chiffres-croises",
|
||||
"libelle": "Une information repetee (ex : nombre de tools) est-elle a jour dans toutes les notes",
|
||||
"raison": "Partiellement couvert par CONS-vocab ; la verification exhaustive des chiffres croises -> agent reviseur."},
|
||||
]
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# 2. REFERENTIEL : ce que le systeme considere comme valide
|
||||
# ==========================================================================
|
||||
|
||||
ENUMS = {
|
||||
("projet", "statut"): {"actif", "en-pause", "archive"},
|
||||
("conversation", "statut"): {"en-cours", "clos"},
|
||||
("backlogItem", "statut"): {"a faire", "en cours", "bloque", "fait", "abandonne"},
|
||||
("backlogItem", "priorite"): {"haute", "moyenne", "basse"},
|
||||
("decision", "statut"): {"active", "revisee", "annulee"},
|
||||
("historiqueItem", "typeHistorique"): {
|
||||
"Fait etabli", "Test effectue", "Hypothese invalidee", "Contrainte decouverte",
|
||||
},
|
||||
("historiqueItem", "encoreValide"): {"true", "false"},
|
||||
}
|
||||
|
||||
LABELS_OBLIGATOIRES = {
|
||||
"projet": ["projet", "statut"],
|
||||
"conversation": ["projet", "llm", "statut", "date"],
|
||||
"backlogItem": ["projet", "statut", "priorite"],
|
||||
"decision": ["projet", "statut"],
|
||||
"historiqueItem": ["projet", "typeHistorique", "encoreValide"],
|
||||
"concept": ["projet"],
|
||||
"contexteReprise": ["projet", "llmCible", "version"],
|
||||
"composantLogiciel": ["projet"],
|
||||
"service": ["projet"],
|
||||
"infrastructure": ["projet"],
|
||||
"outil": ["projet"],
|
||||
"contrainte": ["projet"],
|
||||
"principe": ["projet"],
|
||||
"convention": ["projet"],
|
||||
"methode": ["projet"],
|
||||
"version": ["projet"],
|
||||
}
|
||||
|
||||
DOSSIER_ATTENDU = {
|
||||
"backlogItem": "Backlog",
|
||||
"decision": "Decisions",
|
||||
"historiqueItem": "Historique",
|
||||
"conversation": "Conversations",
|
||||
"concept": "Concepts",
|
||||
"contexteReprise": "ContextesReprise",
|
||||
"projet": "Projets",
|
||||
}
|
||||
|
||||
TYPES_METIER = list(LABELS_OBLIGATOIRES.keys()) + ["skill", "documentation"]
|
||||
|
||||
TYPES_DEPRECIES = {"termeGlossaire"}
|
||||
MOTS_DEPRECIES = ["type systeme", "type système", "termeGlossaire"]
|
||||
|
||||
JOURS_STALE_ENCOURS = 90
|
||||
JOURS_STALE_CONVO = 30
|
||||
|
||||
# Domaine -> portee (sous-ensemble ROBUSTE ; classes mappees aux types reels).
|
||||
# domaine None = seule la portee est verifiee. Table volontairement partielle :
|
||||
# uniquement les relations dont le mapping classe-ontologie -> type est certain.
|
||||
RELATION_DP = {
|
||||
"impacte": ({"decision", "historiqueItem"}, {"composantLogiciel", "service"}),
|
||||
"revise": ({"decision"}, {"decision"}),
|
||||
"dependDe": ({"composantLogiciel", "service"}, {"composantLogiciel", "service", "outil"}),
|
||||
"aVersion": ({"composantLogiciel", "service"}, {"version"}),
|
||||
"implementePar": ({"service"}, {"composantLogiciel"}),
|
||||
"contraintPar": ({"decision", "composantLogiciel"}, {"contrainte", "principe"}),
|
||||
"illustre": ({"concept", "composantLogiciel", "service"}, {"concept"}),
|
||||
"documentePar": ({"service", "outil", "methode", "composantLogiciel"}, {"skill", "documentation"}),
|
||||
"concerneProjet": (None, {"projet"}),
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# 3. REGISTRE DES CONTROLES (decorateur autoporteur)
|
||||
# ==========================================================================
|
||||
|
||||
CHECKS: List[dict] = [] # {dimension, code, libelle, fn}
|
||||
_CTX = {"dimension": None} # dimension du check en cours d'execution
|
||||
|
||||
|
||||
def check(dimension: str, code: str, libelle: str):
|
||||
"""Enregistre une fonction de controle sous une dimension de qualite."""
|
||||
def deco(fn):
|
||||
CHECKS.append({"dimension": dimension, "code": code, "libelle": libelle, "fn": fn})
|
||||
return fn
|
||||
return deco
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# 4. UTILITAIRES + CHARGEMENT (une passe, cache)
|
||||
# ==========================================================================
|
||||
|
||||
def sans_accents(s: str) -> str:
|
||||
return "".join(c for c in unicodedata.normalize("NFD", s)
|
||||
if unicodedata.category(c) != "Mn")
|
||||
|
||||
|
||||
NOTES: Dict[str, dict] = {}
|
||||
BY_TYPE: Dict[str, List[str]] = {}
|
||||
|
||||
|
||||
def labels_of(note: dict) -> Dict[str, str]:
|
||||
return {a.get("name"): a.get("value", "")
|
||||
for a in note.get("attributes", []) if a.get("type") == "label"}
|
||||
|
||||
|
||||
def relations_of(note: dict) -> List[Dict[str, str]]:
|
||||
return [{"name": a.get("name"), "value": a.get("value", "")}
|
||||
for a in note.get("attributes", []) if a.get("type") == "relation"]
|
||||
|
||||
|
||||
def type_of(note: dict) -> Optional[str]:
|
||||
return labels_of(note).get("type")
|
||||
|
||||
|
||||
def age_jours(note: dict) -> Optional[int]:
|
||||
dm = note.get("utcDateModified") or note.get("dateModified")
|
||||
if not dm:
|
||||
return None
|
||||
for fmt in ("%Y-%m-%d %H:%M:%S.%fZ", "%Y-%m-%d %H:%M:%SZ",
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%d %H:%M:%S.%f"):
|
||||
try:
|
||||
d = datetime.strptime(dm, fmt).replace(tzinfo=timezone.utc)
|
||||
return (datetime.now(timezone.utc) - d).days
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def charger():
|
||||
"""Charge TOUTES les notes portant un label type (recherche #type globale),
|
||||
quel que soit le type - y compris les types invalides/inconnus, pour que
|
||||
VAL-type puisse les detecter. Indexe par la valeur reelle du label type."""
|
||||
try:
|
||||
res = search_by_label("type", "") # #type sans valeur = toutes les notes typees
|
||||
except Exception as e:
|
||||
print(" ! echec recherche #type : %s" % e)
|
||||
res = []
|
||||
for r in res:
|
||||
nid = r.get("noteId")
|
||||
if not nid or nid in NOTES:
|
||||
continue
|
||||
try:
|
||||
note = get_note(nid)
|
||||
except Exception:
|
||||
continue
|
||||
NOTES[nid] = note
|
||||
tv = type_of(note) or "(sans type)"
|
||||
BY_TYPE.setdefault(tv, []).append(nid)
|
||||
print(" Charge : %d notes sur %d valeurs de type" % (len(NOTES), len(BY_TYPE)))
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# 5. COLLECTE DES ANOMALIES
|
||||
# ==========================================================================
|
||||
|
||||
ANOMALIES: List[dict] = []
|
||||
|
||||
|
||||
def signaler(code, bac, note_id, titre, detail, suggestion=""):
|
||||
"""Enregistre une anomalie ; la dimension est heritee du check courant."""
|
||||
ANOMALIES.append({
|
||||
"dimension": _CTX["dimension"], "code": code, "bac": bac,
|
||||
"note_id": note_id, "titre": titre, "detail": detail, "suggestion": suggestion,
|
||||
})
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# 6. LES CONTROLES, GROUPES PAR DIMENSION
|
||||
# ==========================================================================
|
||||
|
||||
# ---- COMPLETENESS --------------------------------------------------------
|
||||
|
||||
@check("completeness", "COMP-labels", "Labels obligatoires presents selon le type")
|
||||
def c_labels_obligatoires():
|
||||
for typ, obligatoires in LABELS_OBLIGATOIRES.items():
|
||||
for nid in BY_TYPE.get(typ, []):
|
||||
note = NOTES[nid]
|
||||
labels = labels_of(note)
|
||||
manquants = [l for l in obligatoires if not labels.get(l)]
|
||||
if manquants:
|
||||
signaler("COMP-labels", "SIGNALER", nid, note.get("title", "?"),
|
||||
"type=%s : labels manquants %s" % (typ, manquants),
|
||||
"ajouter les labels manquants")
|
||||
|
||||
|
||||
# ---- ACCURACY ------------------------------------------------------------
|
||||
|
||||
@check("accuracy", "ACC-rangement", "Note rangee dans le dossier attendu de son type")
|
||||
def a_rangement():
|
||||
for typ, cle_dossier in DOSSIER_ATTENDU.items():
|
||||
dossier_id = IDS.get(cle_dossier)
|
||||
if not dossier_id:
|
||||
continue
|
||||
for nid in BY_TYPE.get(typ, []):
|
||||
note = NOTES[nid]
|
||||
parents = note.get("parentNoteIds", [])
|
||||
if parents and dossier_id not in parents:
|
||||
signaler("ACC-rangement", "JUGEMENT", nid, note.get("title", "?"),
|
||||
"type=%s hors du dossier %s (parents=%s)" % (typ, cle_dossier, parents),
|
||||
"verifier le rangement (clone legitime possible)")
|
||||
|
||||
|
||||
@check("accuracy", "ACC-decision", "Statut des decisions actives reflete la realite (v1 vs v2)")
|
||||
def a_decisions_obsoletes():
|
||||
SEUIL = 12
|
||||
par_projet = {}
|
||||
for nid in BY_TYPE.get("decision", []):
|
||||
labels = labels_of(NOTES[nid])
|
||||
if labels.get("statut") == "active":
|
||||
par_projet.setdefault(labels.get("projet", "?"), []).append(nid)
|
||||
for proj, ids in par_projet.items():
|
||||
if len(ids) >= SEUIL:
|
||||
signaler("ACC-decision", "JUGEMENT", ids[0], "(projet %s)" % proj,
|
||||
"%d decisions actives sur %s : possibles v1 non revisees" % (len(ids), proj),
|
||||
"revue : tisser ~revise, passer les v1 obsoletes en revisee")
|
||||
|
||||
|
||||
# ---- CONSISTENCY ---------------------------------------------------------
|
||||
|
||||
@check("consistency", "CONS-vocab", "Vocabulaire courant (types et termes non deprecies)")
|
||||
def cons_vocabulaire():
|
||||
# Types deprecies
|
||||
for typ in TYPES_DEPRECIES:
|
||||
for nid in BY_TYPE.get(typ, []):
|
||||
signaler("CONS-type", "SIGNALER", nid, NOTES[nid].get("title", "?"),
|
||||
"type deprecie : %s" % typ, "migrer vers concept")
|
||||
# Mots deprecies dans le contenu des skills / documentation
|
||||
for typ in ("skill", "documentation"):
|
||||
for nid in BY_TYPE.get(typ, []):
|
||||
try:
|
||||
contenu = get_note_content(nid)
|
||||
except Exception:
|
||||
continue
|
||||
trouves = [m for m in MOTS_DEPRECIES if m in contenu]
|
||||
if trouves:
|
||||
signaler("CONS-vocab", "JUGEMENT", nid, NOTES[nid].get("title", "?"),
|
||||
"mots deprecies dans le contenu : %s" % trouves,
|
||||
"reformuler (faux positif possible si mention explicative legitime)")
|
||||
|
||||
|
||||
# ---- VALIDITY ------------------------------------------------------------
|
||||
|
||||
@check("validity", "VAL-enum", "Valeurs de labels conformes aux enumerations permises")
|
||||
def v_enums():
|
||||
for (typ, label), valides in ENUMS.items():
|
||||
for nid in BY_TYPE.get(typ, []):
|
||||
note = NOTES[nid]
|
||||
val = labels_of(note).get(label)
|
||||
if val is None or val in valides:
|
||||
continue
|
||||
titre = note.get("title", "?")
|
||||
cible = None
|
||||
for v in valides:
|
||||
if sans_accents(val.lower()) == sans_accents(v.lower()):
|
||||
cible = v
|
||||
break
|
||||
if cible is not None:
|
||||
signaler("VAL-enum", "AUTO", nid, titre,
|
||||
"%s=%r (casse/accent)" % (label, val), "corriger en %r" % cible)
|
||||
else:
|
||||
signaler("VAL-enum", "SIGNALER", nid, titre,
|
||||
"%s=%r hors valeurs permises %s" % (label, val, sorted(valides)),
|
||||
"corriger manuellement")
|
||||
|
||||
|
||||
@check("validity", "VAL-type", "Label type appartient a la liste canonique de l ontologie")
|
||||
def v_type():
|
||||
for nid, note in NOTES.items():
|
||||
tv = type_of(note)
|
||||
if tv is None or tv == "container" or tv in TYPES_CANONIQUES:
|
||||
continue # container = dossier structurel, hors perimetre
|
||||
titre = note.get("title", "?")
|
||||
cible = None
|
||||
for t in TYPES_CANONIQUES:
|
||||
if sans_accents(tv.lower()) == sans_accents(t.lower()):
|
||||
cible = t
|
||||
break
|
||||
if cible is not None:
|
||||
signaler("VAL-type", "AUTO", nid, titre,
|
||||
"type=%r (casse)" % tv, "corriger en %r" % cible)
|
||||
else:
|
||||
signaler("VAL-type", "SIGNALER", nid, titre,
|
||||
"type=%r hors liste canonique" % tv, "corriger ou ajouter a l ontologie")
|
||||
|
||||
|
||||
@check("validity", "VAL-nommage", "Conventions de nommage (projet sans espace)")
|
||||
def v_nommage():
|
||||
for nid, note in NOTES.items():
|
||||
val = labels_of(note).get("projet")
|
||||
if val and " " in val:
|
||||
signaler("VAL-nommage", "SIGNALER", nid, note.get("title", "?"),
|
||||
"label projet=%r contient un espace" % val,
|
||||
"renommer sans espace + repercuter partout")
|
||||
|
||||
|
||||
@check("validity", "VAL-relation", "Relations valides (ontologie, cible existante, domaine/portee)")
|
||||
def v_relations():
|
||||
for nid, note in NOTES.items():
|
||||
typ_src = type_of(note)
|
||||
titre = note.get("title", "?")
|
||||
for rel in relations_of(note):
|
||||
nom, cible = rel["name"], rel["value"]
|
||||
if nom not in RELATIONS_ONTOLOGIE:
|
||||
signaler("VAL-rel-nom", "SIGNALER", nid, titre,
|
||||
"relation ~%s hors ontologie" % nom, "renommer ou retirer")
|
||||
continue
|
||||
cible_note = NOTES.get(cible)
|
||||
if cible_note is None:
|
||||
try:
|
||||
cible_note = get_note(cible)
|
||||
except Exception:
|
||||
cible_note = None
|
||||
if not cible_note:
|
||||
signaler("VAL-rel-cible", "SIGNALER", nid, titre,
|
||||
"~%s pointe vers une note absente (%s)" % (nom, cible),
|
||||
"corriger ou retirer la relation")
|
||||
continue
|
||||
dp = RELATION_DP.get(nom)
|
||||
if dp:
|
||||
dom, por = dp
|
||||
typ_cible = type_of(cible_note)
|
||||
if dom is not None and typ_src not in dom:
|
||||
signaler("VAL-rel-domaine", "JUGEMENT", nid, titre,
|
||||
"~%s : source type=%s hors domaine %s" % (nom, typ_src, sorted(dom)),
|
||||
"verifier la pertinence")
|
||||
if por is not None and typ_cible not in por:
|
||||
signaler("VAL-rel-portee", "JUGEMENT", nid, titre,
|
||||
"~%s : cible type=%s hors portee %s" % (nom, typ_cible, sorted(por)),
|
||||
"verifier la pertinence")
|
||||
|
||||
|
||||
# ---- UNIQUENESS ----------------------------------------------------------
|
||||
|
||||
@check("uniqueness", "UNIQ-doublon", "Absence de notes identiques (titre + type + projet)")
|
||||
def u_doublons():
|
||||
vus = {}
|
||||
for nid, note in NOTES.items():
|
||||
cle = ((note.get("title") or "").strip().lower(),
|
||||
type_of(note), labels_of(note).get("projet", ""))
|
||||
vus.setdefault(cle, []).append(nid)
|
||||
for (titre, typ, proj), ids in vus.items():
|
||||
if len(ids) > 1:
|
||||
signaler("UNIQ-doublon", "SIGNALER", ids[0], titre or "(sans titre)",
|
||||
"%d notes identiques (type=%s projet=%s) : %s" % (len(ids), typ, proj, ids),
|
||||
"fusionner ou supprimer les doublons")
|
||||
|
||||
|
||||
# ---- FRESHNESS -----------------------------------------------------------
|
||||
|
||||
@check("freshness", "FRESH-staleness", "Entites sans etat perime (anciennete, non-cloture)")
|
||||
def f_staleness():
|
||||
for nid in BY_TYPE.get("backlogItem", []):
|
||||
note = NOTES[nid]
|
||||
if labels_of(note).get("statut") == "en cours":
|
||||
age = age_jours(note)
|
||||
if age is not None and age > JOURS_STALE_ENCOURS:
|
||||
signaler("FRESH-backlog", "JUGEMENT", nid, note.get("title", "?"),
|
||||
"backlog 'en cours' inchange depuis %d j" % age, "verifier si toujours actif")
|
||||
for nid in BY_TYPE.get("conversation", []):
|
||||
note = NOTES[nid]
|
||||
if labels_of(note).get("statut") == "en-cours":
|
||||
age = age_jours(note)
|
||||
if age is not None and age > JOURS_STALE_CONVO:
|
||||
signaler("FRESH-conversation", "JUGEMENT", nid, note.get("title", "?"),
|
||||
"conversation 'en-cours' non cloturee depuis %d j" % age, "cloturer si terminee")
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# 7. ORCHESTRATION + RAPPORT (groupes par dimension)
|
||||
# ==========================================================================
|
||||
|
||||
def executer():
|
||||
print("Chargement des notes...")
|
||||
charger()
|
||||
print("Execution des controles par dimension...")
|
||||
ordre = {d: i for i, d in enumerate(DIMENSIONS)}
|
||||
for chk in sorted(CHECKS, key=lambda c: ordre.get(c["dimension"], 99)):
|
||||
_CTX["dimension"] = chk["dimension"]
|
||||
try:
|
||||
chk["fn"]()
|
||||
except Exception as e:
|
||||
print(" ! controle %s (%s) a echoue : %s" % (chk["code"], chk["dimension"], e))
|
||||
|
||||
|
||||
def rapport() -> str:
|
||||
date = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
L = ["# RAPPORT LINT - Context Continuity - %s" % date,
|
||||
"# ETAGE 1 (lecture seule) - groupe par data quality dimension", ""]
|
||||
|
||||
# Inventaire
|
||||
L.append("## Inventaire")
|
||||
L.append("- Notes chargees : %d" % len(NOTES))
|
||||
for t in sorted(BY_TYPE):
|
||||
if BY_TYPE[t]:
|
||||
L.append(" - %s : %d" % (t, len(BY_TYPE[t])))
|
||||
L.append("")
|
||||
|
||||
# Synthese
|
||||
par_dim = {d: 0 for d in DIMENSIONS}
|
||||
par_bac = {"AUTO": 0, "SIGNALER": 0, "JUGEMENT": 0}
|
||||
for a in ANOMALIES:
|
||||
par_dim[a["dimension"]] = par_dim.get(a["dimension"], 0) + 1
|
||||
par_bac[a["bac"]] = par_bac.get(a["bac"], 0) + 1
|
||||
L.append("## Synthese : %d anomalies" % len(ANOMALIES))
|
||||
L.append(" Par dimension : " + ", ".join("%s=%d" % (d, par_dim[d]) for d in DIMENSIONS))
|
||||
L.append(" Par bac d'action : " + ", ".join("%s=%d" % (b, par_bac[b]) for b in ("AUTO", "SIGNALER", "JUGEMENT")))
|
||||
L.append("")
|
||||
|
||||
# Detail par dimension
|
||||
for dim, definition in DIMENSIONS.items():
|
||||
anos = [a for a in ANOMALIES if a["dimension"] == dim]
|
||||
actifs = [c for c in CHECKS if c["dimension"] == dim]
|
||||
delegues = [c for c in CHECKS_DELEGUES if c["dimension"] == dim]
|
||||
L.append("## %s (%d anomalie%s)" % (dim.upper(), len(anos), "s" if len(anos) != 1 else ""))
|
||||
L.append(" %s" % definition)
|
||||
L.append(" Controles deterministes : " + ", ".join(c["code"] for c in actifs))
|
||||
for d in delegues:
|
||||
L.append(" [delegue agent] %s : %s" % (d["code"], d["libelle"]))
|
||||
if anos:
|
||||
L.append("")
|
||||
for a in anos:
|
||||
L.append(" - [%s | %s] %s (%s)" % (a["code"], a["bac"], a["titre"], a["note_id"]))
|
||||
L.append(" %s" % a["detail"])
|
||||
if a["suggestion"]:
|
||||
L.append(" -> %s" % a["suggestion"])
|
||||
L.append("")
|
||||
return "\n".join(L)
|
||||
|
||||
|
||||
def main():
|
||||
executer()
|
||||
txt = rapport()
|
||||
print("\n" + txt)
|
||||
rep_dir = os.path.join(BASE, "lint_reports")
|
||||
os.makedirs(rep_dir, exist_ok=True)
|
||||
chemin = os.path.join(rep_dir, "lint_%s.txt" % datetime.now().strftime("%Y%m%d_%H%M%S"))
|
||||
with open(chemin, "w", encoding="utf-8") as f:
|
||||
f.write(txt)
|
||||
print("\nRapport ecrit : %s" % chemin)
|
||||
if len(ANOMALIES) > 100:
|
||||
print("\n!!! ALERTE : %d anomalies (>100). Investiguer avant toute correction." % len(ANOMALIES))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,152 @@
|
||||
# RAPPORT LINT - Context Continuity - 2026-07-03 08:39
|
||||
# ETAGE 1 (lecture seule) - groupe par data quality dimension
|
||||
|
||||
## Inventaire
|
||||
- Notes chargees : 336
|
||||
- backlogItem : 96
|
||||
- composantLogiciel : 12
|
||||
- concept : 2
|
||||
- contexteReprise : 11
|
||||
- contrainte : 2
|
||||
- convention : 4
|
||||
- conversation : 29
|
||||
- decision : 43
|
||||
- documentation : 25
|
||||
- historiqueItem : 90
|
||||
- infrastructure : 1
|
||||
- outil : 2
|
||||
- principe : 3
|
||||
- projet : 2
|
||||
- service : 4
|
||||
- skill : 10
|
||||
|
||||
## Synthese : 32 anomalies
|
||||
Par dimension : completeness=0, accuracy=2, consistency=0, validity=25, uniqueness=3, freshness=2
|
||||
Par bac d'action : AUTO=4, SIGNALER=7, JUGEMENT=21
|
||||
|
||||
## COMPLETENESS (0 anomalies)
|
||||
Les champs obligatoires sont remplis ET les entites attendues sont toutes saisies (exhaustivite).
|
||||
Controles deterministes : COMP-labels
|
||||
[delegue agent] COMP-exhaustivite : Entites manquantes vs realite du projet (ex : backlog incomplet)
|
||||
|
||||
## ACCURACY (2 anomalies)
|
||||
Le niveau de la donnee reflete la realite (bon rangement, contenu conforme au type, statut a jour).
|
||||
Controles deterministes : ACC-rangement, ACC-decision
|
||||
[delegue agent] ACC-contenu-type : Le contenu redige d'une note reflete-t-il bien son type
|
||||
|
||||
- [ACC-decision | JUGEMENT] (projet ContextContinuity) (f9qMQOFVyVCy)
|
||||
12 decisions actives sur ContextContinuity : possibles v1 non revisees
|
||||
-> revue : tisser ~revise, passer les v1 obsoletes en revisee
|
||||
- [ACC-decision | JUGEMENT] (projet SlidingAutomation) (XOtJDSeSE8Ny)
|
||||
23 decisions actives sur SlidingAutomation : possibles v1 non revisees
|
||||
-> revue : tisser ~revise, passer les v1 obsoletes en revisee
|
||||
|
||||
## CONSISTENCY (0 anomalies)
|
||||
Coherence dans le temps et a travers les entites (vocabulaire courant, informations repetees a jour partout).
|
||||
Controles deterministes : CONS-vocab
|
||||
[delegue agent] CONS-chiffres-croises : Une information repetee (ex : nombre de tools) est-elle a jour dans toutes les notes
|
||||
|
||||
## VALIDITY (25 anomalies)
|
||||
Les valeurs et relations respectent les regles en vigueur (enums, conventions de nommage, ontologie des relations).
|
||||
Controles deterministes : VAL-enum, VAL-nommage, VAL-relation
|
||||
|
||||
- [VAL-enum | AUTO] Fix Python 3.9 : Union[X|Y] non supporté (LbUxsCkI5FQw)
|
||||
typeHistorique='Contrainte découverte' (casse/accent)
|
||||
-> corriger en 'Contrainte decouverte'
|
||||
- [VAL-enum | AUTO] Fix polices : détection via assets/fonts/ (pas fc-list) (3GmGgfoK4aJI)
|
||||
typeHistorique='Fait établi' (casse/accent)
|
||||
-> corriger en 'Fait etabli'
|
||||
- [VAL-enum | AUTO] Fix semantic key : self.theme['semantic'] (i5M20n936xmD)
|
||||
typeHistorique='Fait établi' (casse/accent)
|
||||
-> corriger en 'Fait etabli'
|
||||
- [VAL-enum | AUTO] POST /etapi/notes inexistant sur TriliumNext 0.95 (SCYdo6cV20js)
|
||||
typeHistorique='Contrainte découverte' (casse/accent)
|
||||
-> corriger en 'Contrainte decouverte'
|
||||
- [VAL-nommage | SIGNALER] Configurer l'accès SSH (port 2222) et tester le clone via SSH (VBTUmbai1IAE)
|
||||
label projet='Code Versioning' contient un espace
|
||||
-> renommer sans espace + repercuter partout
|
||||
- [VAL-nommage | SIGNALER] Configurer le reverse proxy HTTPS pour forgejo.bertha-cloud.fr (oUFzjF3Ef0UU)
|
||||
label projet='Code Versioning' contient un espace
|
||||
-> renommer sans espace + repercuter partout
|
||||
- [VAL-nommage | SIGNALER] Créer le premier dépôt de test et valider le workflow git (e5rBCaJHEP8a)
|
||||
label projet='Code Versioning' contient un espace
|
||||
-> renommer sans espace + repercuter partout
|
||||
- [VAL-nommage | SIGNALER] Finaliser la configuration initiale de Forgejo (base de données, utilisateur admin, etc.) (VN7HUNymHGYn)
|
||||
label projet='Code Versioning' contient un espace
|
||||
-> renommer sans espace + repercuter partout
|
||||
- [VAL-rel-portee | JUGEMENT] patch_pptx_theme (Eh2R3GGFdt1v)
|
||||
~dependDe : cible type=outil hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] render_engine.py (PMdUlYZqzgn6)
|
||||
~dependDe : cible type=outil hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Architecture pipeline : Narrator → Designer (ou Free Designer) → Encoder → render_engine_v2.py (LxsGi9O4wPKT)
|
||||
~impacte : cible type=ComposantLogiciel hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Design system v2 « PR Editorial » : 21 layouts + freeform, charte navy/coral/glacier, Cambria/Calibri (EWq0H6IhfcAG)
|
||||
~impacte : cible type=ComposantLogiciel hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Freeform : palier 3 (primitives + helpers charte), charte IMPOSÉE (tokens couleur uniquement, pas de hex) (bwcIY5LNiCI2)
|
||||
~impacte : cible type=ComposantLogiciel hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Refonte majeure v1→v2 du pipeline : moteur, design system et facilitator entièrement reconstruits (B6ZL5ZO7PjxG)
|
||||
~impacte : cible type=ComposantLogiciel hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] render_engine durci : org_chart et renderers tolèrent les nœuds en string OU dict (anti-crash Encoder) (Rnu3kQ178XeY)
|
||||
~impacte : cible type=ComposantLogiciel hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-domaine | JUGEMENT] Deck 35 slides (data-domain-pitch-deck) rendu sans crash après fix org_chart string/dict (32SqGgA5SzCV)
|
||||
~illustre : source type=historiqueItem hors domaine ['composantLogiciel', 'concept', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Deck 35 slides (data-domain-pitch-deck) rendu sans crash après fix org_chart string/dict (32SqGgA5SzCV)
|
||||
~illustre : cible type=ComposantLogiciel hors portee ['concept']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-domaine | JUGEMENT] Encoder laisse passer du Markdown brut (**gras**) dans les valeurs texte (visible slide 19) (sL21Grl1N62Q)
|
||||
~contraintPar : source type=historiqueItem hors domaine ['composantLogiciel', 'decision']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Encoder laisse passer du Markdown brut (**gras**) dans les valeurs texte (visible slide 19) (sL21Grl1N62Q)
|
||||
~contraintPar : cible type=composantLogiciel hors portee ['contrainte', 'principe']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-domaine | JUGEMENT] Mode collage /coller validé : les commandes dans un texte collé ne sont pas interprétées (E3DDmSw5bFsH)
|
||||
~illustre : source type=historiqueItem hors domaine ['composantLogiciel', 'concept', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Mode collage /coller validé : les commandes dans un texte collé ne sont pas interprétées (E3DDmSw5bFsH)
|
||||
~illustre : cible type=composantLogiciel hors portee ['concept']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-domaine | JUGEMENT] Persistance et rechargement exact du plan compact validés (anti-régression entre sessions) (EwfkXXLfVdyN)
|
||||
~illustre : source type=historiqueItem hors domaine ['composantLogiciel', 'concept', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Persistance et rechargement exact du plan compact validés (anti-régression entre sessions) (EwfkXXLfVdyN)
|
||||
~illustre : cible type=composantLogiciel hors portee ['concept']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-domaine | JUGEMENT] Forgejo (ArTsdwSCqXod)
|
||||
~dependDe : source type=service hors domaine ['composantLogiciel']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Forgejo (ArTsdwSCqXod)
|
||||
~dependDe : cible type=outil hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
|
||||
## UNIQUENESS (3 anomalies)
|
||||
Absence de doublons.
|
||||
Controles deterministes : UNIQ-doublon
|
||||
|
||||
- [UNIQ-doublon | SIGNALER] installation réussie de forgejo (via gitea) en docker sur synology (nAxyy3POFpgr)
|
||||
2 notes identiques (type=historiqueItem projet=CodeVersioning) : ['nAxyy3POFpgr', 'e2TZu6UWdVcO']
|
||||
-> fusionner ou supprimer les doublons
|
||||
- [UNIQ-doublon | SIGNALER] post /etapi/notes inexistant sur triliumnext 0.95 (SCYdo6cV20js)
|
||||
2 notes identiques (type=historiqueItem projet=SlidingAutomation) : ['SCYdo6cV20js', 'PkKQcJZUOSFk']
|
||||
-> fusionner ou supprimer les doublons
|
||||
- [UNIQ-doublon | SIGNALER] synoinfo.conf unique doit rester synology_rtd1296_ds220j (B4cW13Ell9El)
|
||||
2 notes identiques (type=historiqueItem projet=SlidingAutomation) : ['B4cW13Ell9El', 'KCecvxr2KUNG']
|
||||
-> fusionner ou supprimer les doublons
|
||||
|
||||
## FRESHNESS (2 anomalies)
|
||||
Les entites ne portent pas un etat perime (anciennete anormale, non-cloture).
|
||||
Controles deterministes : FRESH-staleness
|
||||
|
||||
- [FRESH-conversation | JUGEMENT] [Claude Sonnet 4.6] 2026-06-01 22:35 — Session 2026-06-01 - Slide 1 cover_split + Trilium install (DbgPmpROLCDR)
|
||||
conversation 'en-cours' non cloturee depuis 31 j
|
||||
-> cloturer si terminee
|
||||
- [FRESH-conversation | JUGEMENT] [Le Chat Large] 2026-05-30 22:30 — Reprise post-doc et style (zJZwyQbOMORY)
|
||||
conversation 'en-cours' non cloturee depuis 33 j
|
||||
-> cloturer si terminee
|
||||
@@ -0,0 +1,145 @@
|
||||
# RAPPORT LINT - Context Continuity - 2026-07-03 13:03
|
||||
# ETAGE 1 (lecture seule) - groupe par data quality dimension
|
||||
|
||||
## Inventaire
|
||||
- Notes chargees : 348
|
||||
- ComposantLogiciel : 1
|
||||
- backlogItem : 99
|
||||
- composantLogiciel : 11
|
||||
- concept : 2
|
||||
- container : 8
|
||||
- contexteReprise : 11
|
||||
- contrainte : 2
|
||||
- convention : 4
|
||||
- conversation : 29
|
||||
- decision : 44
|
||||
- documentation : 25
|
||||
- historiqueItem : 90
|
||||
- infrastructure : 1
|
||||
- outil : 2
|
||||
- principe : 3
|
||||
- projet : 2
|
||||
- service : 4
|
||||
- skill : 10
|
||||
|
||||
## Synthese : 29 anomalies
|
||||
Par dimension : completeness=0, accuracy=2, consistency=0, validity=22, uniqueness=3, freshness=2
|
||||
Par bac d'action : AUTO=5, SIGNALER=7, JUGEMENT=17
|
||||
|
||||
## COMPLETENESS (0 anomalies)
|
||||
Les champs obligatoires sont remplis ET les entites attendues sont toutes saisies (exhaustivite).
|
||||
Controles deterministes : COMP-labels
|
||||
[delegue agent] COMP-exhaustivite : Entites manquantes vs realite du projet (ex : backlog incomplet)
|
||||
|
||||
## ACCURACY (2 anomalies)
|
||||
Le niveau de la donnee reflete la realite (bon rangement, contenu conforme au type, statut a jour).
|
||||
Controles deterministes : ACC-rangement, ACC-decision
|
||||
[delegue agent] ACC-contenu-type : Le contenu redige d'une note reflete-t-il bien son type
|
||||
|
||||
- [ACC-decision | JUGEMENT] (projet ContextContinuity) (f9qMQOFVyVCy)
|
||||
13 decisions actives sur ContextContinuity : possibles v1 non revisees
|
||||
-> revue : tisser ~revise, passer les v1 obsoletes en revisee
|
||||
- [ACC-decision | JUGEMENT] (projet SlidingAutomation) (XOtJDSeSE8Ny)
|
||||
23 decisions actives sur SlidingAutomation : possibles v1 non revisees
|
||||
-> revue : tisser ~revise, passer les v1 obsoletes en revisee
|
||||
|
||||
## CONSISTENCY (0 anomalies)
|
||||
Coherence dans le temps et a travers les entites (vocabulaire courant, informations repetees a jour partout).
|
||||
Controles deterministes : CONS-vocab
|
||||
[delegue agent] CONS-chiffres-croises : Une information repetee (ex : nombre de tools) est-elle a jour dans toutes les notes
|
||||
|
||||
## VALIDITY (22 anomalies)
|
||||
Les valeurs et relations respectent les regles en vigueur (enums, conventions de nommage, ontologie des relations).
|
||||
Controles deterministes : VAL-enum, VAL-type, VAL-nommage, VAL-relation
|
||||
|
||||
- [VAL-enum | AUTO] Fix Python 3.9 : Union[X|Y] non supporté (LbUxsCkI5FQw)
|
||||
typeHistorique='Contrainte découverte' (casse/accent)
|
||||
-> corriger en 'Contrainte decouverte'
|
||||
- [VAL-enum | AUTO] Fix polices : détection via assets/fonts/ (pas fc-list) (3GmGgfoK4aJI)
|
||||
typeHistorique='Fait établi' (casse/accent)
|
||||
-> corriger en 'Fait etabli'
|
||||
- [VAL-enum | AUTO] Fix semantic key : self.theme['semantic'] (i5M20n936xmD)
|
||||
typeHistorique='Fait établi' (casse/accent)
|
||||
-> corriger en 'Fait etabli'
|
||||
- [VAL-enum | AUTO] POST /etapi/notes inexistant sur TriliumNext 0.95 (SCYdo6cV20js)
|
||||
typeHistorique='Contrainte découverte' (casse/accent)
|
||||
-> corriger en 'Contrainte decouverte'
|
||||
- [VAL-type | AUTO] render_engine.py (mKmOPjBQawu4)
|
||||
type='ComposantLogiciel' (casse)
|
||||
-> corriger en 'composantLogiciel'
|
||||
- [VAL-nommage | SIGNALER] Configurer l'accès SSH (port 2222) et tester le clone via SSH (VBTUmbai1IAE)
|
||||
label projet='Code Versioning' contient un espace
|
||||
-> renommer sans espace + repercuter partout
|
||||
- [VAL-nommage | SIGNALER] Configurer le reverse proxy HTTPS pour forgejo.bertha-cloud.fr (oUFzjF3Ef0UU)
|
||||
label projet='Code Versioning' contient un espace
|
||||
-> renommer sans espace + repercuter partout
|
||||
- [VAL-nommage | SIGNALER] Créer le premier dépôt de test et valider le workflow git (e5rBCaJHEP8a)
|
||||
label projet='Code Versioning' contient un espace
|
||||
-> renommer sans espace + repercuter partout
|
||||
- [VAL-nommage | SIGNALER] Finaliser la configuration initiale de Forgejo (base de données, utilisateur admin, etc.) (VN7HUNymHGYn)
|
||||
label projet='Code Versioning' contient un espace
|
||||
-> renommer sans espace + repercuter partout
|
||||
- [VAL-rel-portee | JUGEMENT] Architecture pipeline : Narrator → Designer (ou Free Designer) → Encoder → render_engine_v2.py (LxsGi9O4wPKT)
|
||||
~impacte : cible type=ComposantLogiciel hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Design system v2 « PR Editorial » : 21 layouts + freeform, charte navy/coral/glacier, Cambria/Calibri (EWq0H6IhfcAG)
|
||||
~impacte : cible type=ComposantLogiciel hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Freeform : palier 3 (primitives + helpers charte), charte IMPOSÉE (tokens couleur uniquement, pas de hex) (bwcIY5LNiCI2)
|
||||
~impacte : cible type=ComposantLogiciel hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Refonte majeure v1→v2 du pipeline : moteur, design system et facilitator entièrement reconstruits (B6ZL5ZO7PjxG)
|
||||
~impacte : cible type=ComposantLogiciel hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] render_engine durci : org_chart et renderers tolèrent les nœuds en string OU dict (anti-crash Encoder) (Rnu3kQ178XeY)
|
||||
~impacte : cible type=ComposantLogiciel hors portee ['composantLogiciel', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-domaine | JUGEMENT] Deck 35 slides (data-domain-pitch-deck) rendu sans crash après fix org_chart string/dict (32SqGgA5SzCV)
|
||||
~illustre : source type=historiqueItem hors domaine ['composantLogiciel', 'concept', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Deck 35 slides (data-domain-pitch-deck) rendu sans crash après fix org_chart string/dict (32SqGgA5SzCV)
|
||||
~illustre : cible type=ComposantLogiciel hors portee ['concept']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-domaine | JUGEMENT] Encoder laisse passer du Markdown brut (**gras**) dans les valeurs texte (visible slide 19) (sL21Grl1N62Q)
|
||||
~contraintPar : source type=historiqueItem hors domaine ['composantLogiciel', 'decision']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Encoder laisse passer du Markdown brut (**gras**) dans les valeurs texte (visible slide 19) (sL21Grl1N62Q)
|
||||
~contraintPar : cible type=composantLogiciel hors portee ['contrainte', 'principe']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-domaine | JUGEMENT] Mode collage /coller validé : les commandes dans un texte collé ne sont pas interprétées (E3DDmSw5bFsH)
|
||||
~illustre : source type=historiqueItem hors domaine ['composantLogiciel', 'concept', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Mode collage /coller validé : les commandes dans un texte collé ne sont pas interprétées (E3DDmSw5bFsH)
|
||||
~illustre : cible type=composantLogiciel hors portee ['concept']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-domaine | JUGEMENT] Persistance et rechargement exact du plan compact validés (anti-régression entre sessions) (EwfkXXLfVdyN)
|
||||
~illustre : source type=historiqueItem hors domaine ['composantLogiciel', 'concept', 'service']
|
||||
-> verifier la pertinence
|
||||
- [VAL-rel-portee | JUGEMENT] Persistance et rechargement exact du plan compact validés (anti-régression entre sessions) (EwfkXXLfVdyN)
|
||||
~illustre : cible type=composantLogiciel hors portee ['concept']
|
||||
-> verifier la pertinence
|
||||
|
||||
## UNIQUENESS (3 anomalies)
|
||||
Absence de doublons.
|
||||
Controles deterministes : UNIQ-doublon
|
||||
|
||||
- [UNIQ-doublon | SIGNALER] installation réussie de forgejo (via gitea) en docker sur synology (nAxyy3POFpgr)
|
||||
2 notes identiques (type=historiqueItem projet=CodeVersioning) : ['nAxyy3POFpgr', 'e2TZu6UWdVcO']
|
||||
-> fusionner ou supprimer les doublons
|
||||
- [UNIQ-doublon | SIGNALER] post /etapi/notes inexistant sur triliumnext 0.95 (SCYdo6cV20js)
|
||||
2 notes identiques (type=historiqueItem projet=SlidingAutomation) : ['SCYdo6cV20js', 'PkKQcJZUOSFk']
|
||||
-> fusionner ou supprimer les doublons
|
||||
- [UNIQ-doublon | SIGNALER] synoinfo.conf unique doit rester synology_rtd1296_ds220j (B4cW13Ell9El)
|
||||
2 notes identiques (type=historiqueItem projet=SlidingAutomation) : ['B4cW13Ell9El', 'KCecvxr2KUNG']
|
||||
-> fusionner ou supprimer les doublons
|
||||
|
||||
## FRESHNESS (2 anomalies)
|
||||
Les entites ne portent pas un etat perime (anciennete anormale, non-cloture).
|
||||
Controles deterministes : FRESH-staleness
|
||||
|
||||
- [FRESH-conversation | JUGEMENT] [Claude Sonnet 4.6] 2026-06-01 22:35 — Session 2026-06-01 - Slide 1 cover_split + Trilium install (DbgPmpROLCDR)
|
||||
conversation 'en-cours' non cloturee depuis 31 j
|
||||
-> cloturer si terminee
|
||||
- [FRESH-conversation | JUGEMENT] [Le Chat Large] 2026-05-30 22:30 — Reprise post-doc et style (zJZwyQbOMORY)
|
||||
conversation 'en-cours' non cloturee depuis 33 j
|
||||
-> cloturer si terminee
|
||||
+15
-2
@@ -504,14 +504,27 @@ def tool_create_entite(type, projet, titre, contenu=None, definition=None):
|
||||
via trilium_ids.json. Garde-fou anti-doublon sur le titre dans le dossier cible.
|
||||
La permission projet est comportementale (le tool accepte tout projet)."""
|
||||
import json, os
|
||||
from trilium_api import create_note, get_note_id, set_label, find_note_by_title
|
||||
from trilium_api import create_note, get_note_id, set_label, find_note_by_title, TYPES_CANONIQUES
|
||||
# Routage dossier : uniquement la couche connaissance/technique.
|
||||
# Les types de pilotage (backlogItem, decision...) ont leurs tools dedies.
|
||||
MAPPING = {
|
||||
"concept": "Concepts",
|
||||
"composantLogiciel": "EnvironnementTechnique",
|
||||
"service": "EnvironnementTechnique",
|
||||
"infrastructure": "EnvironnementTechnique",
|
||||
"outil": "EnvironnementTechnique",
|
||||
"contrainte": "EnvironnementTechnique",
|
||||
"principe": "EnvironnementTechnique",
|
||||
"convention": "EnvironnementTechnique",
|
||||
"methode": "Methodes",
|
||||
"version": "EnvironnementTechnique",
|
||||
}
|
||||
# 1. Validite du type (source unique : ontologie via TYPES_CANONIQUES)
|
||||
if type not in TYPES_CANONIQUES:
|
||||
return {"error": "Type inconnu : %s. Types canoniques : %s" % (type, ", ".join(TYPES_CANONIQUES))}
|
||||
# 2. Ce tool ne cree que la couche connaissance/technique
|
||||
if type not in MAPPING:
|
||||
return {"error": "Type non supporte : %s. Types valides : %s" % (type, ", ".join(MAPPING.keys()))}
|
||||
return {"error": "Type '%s' valide mais non creable ici (type de pilotage : utiliser son tool dedie add_*)." % type}
|
||||
with open(os.path.expanduser("~/App/Context_continuity/trilium_ids.json")) as f:
|
||||
ids = json.load(f)
|
||||
dossier_cle = MAPPING[type]
|
||||
|
||||
@@ -191,6 +191,18 @@ RELATIONS_ONTOLOGIE = [
|
||||
"reference", "illustre", "provient",
|
||||
]
|
||||
|
||||
# Types d'entites canoniques de l'ontologie (source unique cote code).
|
||||
# NE contient PAS 'container' (= dossiers structurels, hors couche connaissance).
|
||||
# Cible a terme : lire cette liste depuis les notes d'ontologie (dossier
|
||||
# Modeles & Ontologie) plutot que la maintenir ici. En attendant, source unique
|
||||
# partagee par le Lint (VAL-type) et create_entite.
|
||||
TYPES_CANONIQUES = [
|
||||
"projet", "conversation", "backlogItem", "decision", "historiqueItem",
|
||||
"concept", "contexteReprise", "skill", "documentation",
|
||||
"composantLogiciel", "service", "infrastructure", "outil",
|
||||
"contrainte", "principe", "convention", "methode", "version",
|
||||
]
|
||||
|
||||
def add_relation_safe(source_id, nom, cible_id):
|
||||
"""Cree une relation (object property) entre deux notes.
|
||||
Garde-fou Option B : nom dans l ontologie + source/cible existent.
|
||||
|
||||
Reference in New Issue
Block a user