newskit 0.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- newskit/__init__.py +4 -0
- newskit/__main__.py +3 -0
- newskit/acuite.py +394 -0
- newskit/aiguilleur_cortexdb.py +536 -0
- newskit/angle_critique.py +343 -0
- newskit/archiver_skill.py +88 -0
- newskit/banc.py +1855 -0
- newskit/banc_croise.py +799 -0
- newskit/banc_decision.py +598 -0
- newskit/banc_injection.py +290 -0
- newskit/banc_session.py +226 -0
- newskit/banc_themes.py +572 -0
- newskit/cartes.py +701 -0
- newskit/cerveau_cortexdb.py +668 -0
- newskit/choisir_jalons.py +249 -0
- newskit/classement.py +136 -0
- newskit/cli.py +146 -0
- newskit/collecte_arxiv.py +708 -0
- newskit/collecte_continue.py +177 -0
- newskit/collecte_courriel.py +516 -0
- newskit/collecte_depots.py +246 -0
- newskit/collecte_dossier_local.py +243 -0
- newskit/collecte_linkedin.py +184 -0
- newskit/collecte_medium.py +546 -0
- newskit/collecte_openalex.py +227 -0
- newskit/collecte_pdf_local.py +426 -0
- newskit/collecte_revue.py +219 -0
- newskit/collecte_rss.py +1305 -0
- newskit/collecte_substack.py +216 -0
- newskit/collecte_textes.py +405 -0
- newskit/combler_canon.py +684 -0
- newskit/commun.py +290 -0
- newskit/config_sources.py +1685 -0
- newskit/contexte.py +341 -0
- newskit/controle_production.py +123 -0
- newskit/convertir_page_text.py +56 -0
- newskit/convertir_substack_page_text.py +74 -0
- newskit/convertir_transcription.py +47 -0
- newskit/couts.py +449 -0
- newskit/decision.py +275 -0
- newskit/demenager_gouvernance.py +495 -0
- newskit/deplacer_sources.py +122 -0
- newskit/deposer_session.py +238 -0
- newskit/depots.py +556 -0
- newskit/designations.py +170 -0
- newskit/diachronie.py +437 -0
- newskit/diffusion.py +67 -0
- newskit/digerer_graphe.py +311 -0
- newskit/donnees_temoin/__init__.py +1 -0
- newskit/donnees_temoin/dossier/apiculteur-et-pretre.md +3 -0
- newskit/donnees_temoin/dossier/cierge-pascal.md +3 -0
- newskit/donnees_temoin/dossier/cire-a-cacheter.md +3 -0
- newskit/donnees_temoin/dossier/cire-blanche.md +3 -0
- newskit/donnees_temoin/dossier/cire-des-moines.md +3 -0
- newskit/donnees_temoin/dossier/cire-et-chandelle.md +3 -0
- newskit/donnees_temoin/dossier/cire-et-eau-de-pluie.md +3 -0
- newskit/donnees_temoin/dossier/cire-et-miel.md +3 -0
- newskit/donnees_temoin/dossier/cire.md +3 -0
- newskit/donnees_temoin/dossier/compter-les-essaims.md +3 -0
- newskit/donnees_temoin/dossier/cueillir-un-essaim.md +3 -0
- newskit/donnees_temoin/dossier/distance-du-chemin.md +3 -0
- newskit/donnees_temoin/dossier/droit-des-ruchers.md +3 -0
- newskit/donnees_temoin/dossier/empecher-l-essaimage.md +3 -0
- newskit/donnees_temoin/dossier/essaim-de-la-saint-jean.md +3 -0
- newskit/donnees_temoin/dossier/essaim-et-frontiere.md +3 -0
- newskit/donnees_temoin/dossier/essaim-perdu.md +3 -0
- newskit/donnees_temoin/dossier/essaim-secondaire.md +3 -0
- newskit/donnees_temoin/dossier/essaim-sur-la-maison.md +3 -0
- newskit/donnees_temoin/dossier/essaimage-et-lune.md +3 -0
- newskit/donnees_temoin/dossier/essaimage-et-vent.md +3 -0
- newskit/donnees_temoin/dossier/essaimage.md +7 -0
- newskit/donnees_temoin/dossier/faux-cirier.md +3 -0
- newskit/donnees_temoin/dossier/heritage-des-ruches.md +3 -0
- newskit/donnees_temoin/dossier/impot-de-cire.md +3 -0
- newskit/donnees_temoin/dossier/jur/303/251-des-ruchers.md +3 -0
- newskit/donnees_temoin/dossier/marque-des-ruches.md +3 -0
- newskit/donnees_temoin/dossier/miel-de-dime.md +3 -0
- newskit/donnees_temoin/dossier/moules-a-chandelles.md +3 -0
- newskit/donnees_temoin/dossier/piqure-de-cheval.md +3 -0
- newskit/donnees_temoin/dossier/reine-de-paille.md +3 -0
- newskit/donnees_temoin/dossier/ruche-de-tronc.md +3 -0
- newskit/donnees_temoin/dossier/rucher-abandonne.md +3 -0
- newskit/donnees_temoin/dossier/ruches-et-moulin.md +3 -0
- newskit/donnees_temoin/dossier/vol-de-ruche.md +3 -0
- newskit/donnees_temoin/media_de_test.py +68 -0
- newskit/donnees_temoin/reponses/.gitkeep +0 -0
- newskit/donnees_temoin/reponses/060c0e01eb43eab297d6f52a85bc8d0e63a3489c87fa5b5bf84575a391c62962.json +1 -0
- newskit/donnees_temoin/reponses/0ed64d33eda777fe146b9e4ddcacd74c00ecdd03e082913a4091704df2b0ff57.json +1 -0
- newskit/donnees_temoin/reponses/1df4c2c5417c97016c19230588cf98d54dcc15deff0914fe4347651bc19d29ba.json +1 -0
- newskit/donnees_temoin/reponses/2681359cdb67e96404d50ae225f50392d39c5096bb56a9fb3ab8d1a323689fbd.json +1 -0
- newskit/donnees_temoin/reponses/26f6f46523e212e7ed1f9124fe7514e2b563ff78bbc924504673d4139a98b10d.json +1 -0
- newskit/donnees_temoin/reponses/31cade9fe3f2e72eaeb01fe29ee0fcd1533d536635ddaa97a7a9df2051a619ea.json +1 -0
- newskit/donnees_temoin/reponses/3c621999baa9aed4ffbfc3da7c10b3c32228ea64c8aa95a64d8f2cc9e6bcadab.json +1 -0
- newskit/donnees_temoin/reponses/47f5e61839201d78ac078a8993105a2d430446913b02ed76c978b51cf5afb240.json +1 -0
- newskit/donnees_temoin/reponses/67d94da8f05ff1392466c665982bfc86e220c5e6b92e4dfe01f935212b885911.json +1 -0
- newskit/donnees_temoin/reponses/6eebe892433ffaf75d43b4737fa3f6622e4eaba309382d6b00851ded14af99d4.json +1 -0
- newskit/donnees_temoin/reponses/724ec6b0efce74287726d511c39a2ca1f5d7ca006c28cdca0fd763d38502489c.json +1 -0
- newskit/donnees_temoin/reponses/742cea43c0090631d605a8d6035c3387c26aa71ffa38937ebe2c9caf389fa616.json +1 -0
- newskit/donnees_temoin/reponses/7b1d6f85638f5d76759bc69df17c3f70177856d1024368da940c93718c1c98c1.json +1 -0
- newskit/donnees_temoin/reponses/7c7653e704a2dbc8079078054128f2728ae4414eaf230dd4049d8ede48f54769.json +1 -0
- newskit/donnees_temoin/reponses/812933750d0f35202c502cab3dec8b3e93c66a26b43b39b70f0940ef00fc7727.json +1 -0
- newskit/donnees_temoin/reponses/83e9bbcd1b44ec16534ba4ecc36eedf252bab72e327e9fb73e689ccfa9cf052e.json +1 -0
- newskit/donnees_temoin/reponses/867ca7534835bc45c40fafae8a00c84cc90e4a7caabe6f49b877072b91db38b8.json +1 -0
- newskit/donnees_temoin/reponses/88c31641c99fcf7bae1c94fe02319eb06a3d92b8585ccf758952999389062be4.json +1 -0
- newskit/donnees_temoin/reponses/88dfba086495fe5f771091af9f77207bd6136e678165eb26839f5d8f04705f07.json +1 -0
- newskit/donnees_temoin/reponses/99f7aed5726d865f6f14cedf3e8bfd88b9e7bca4155dc1e16ba612aca4e36390.json +1 -0
- newskit/donnees_temoin/reponses/9f96acd3accfdc74bfa2940f7720fb9dc39b07f35db7d8478fdf7afae2e16749.json +1 -0
- newskit/donnees_temoin/reponses/a36a9073dbef0c6c1fca7aeee39b2fa9a1107d1e157be2fc7c4ea310e1fc2716.json +1 -0
- newskit/donnees_temoin/reponses/a78a46b99cb238b51c83c434e39b2bdc70c2d7fc33c52ca0fbc713a710327ec2.json +1 -0
- newskit/donnees_temoin/reponses/afbb982cf0c6c93b8e8cebc7fae5972f19821ffcfbb2321b3c085d94cf515a0e.json +1 -0
- newskit/donnees_temoin/reponses/bfaabb335129975948a93ac89fb97bf488cb2625ec93cbb455c6661288bce92e.json +1 -0
- newskit/donnees_temoin/reponses/c2c6bbb722258fc78ac580a3ba48ecd67f5b947e30f404852d6cecc8865ad556.json +1 -0
- newskit/donnees_temoin/reponses/c2eb7ddf698ab7b0ff4f5ff613f5505dd0ce51d678ad624f310a08e33a509945.json +1 -0
- newskit/donnees_temoin/reponses/c53a1a04bb7ecde0230389e031f797771631c5786301efc44bc80c17ff3f9aec.json +1 -0
- newskit/donnees_temoin/reponses/c6a3b4308cd5582b9e90f894a811b5bd366438d0619f99292993d759d420c1ee.json +1 -0
- newskit/donnees_temoin/reponses/c838178769ebf75e061f3026274f9aa688831e9c0278716a7f2cda2eab1e5b84.json +1 -0
- newskit/donnees_temoin/reponses/cad1f58dbfbe94bfa5f19df3ce2a49573852d99e6f3be23afb77b4b1b38948c5.json +1 -0
- newskit/donnees_temoin/reponses/d18274b00e9448112dae3beef82ee718eba39c65f3ae23334b50973e736ab41b.json +1 -0
- newskit/donnees_temoin/reponses/dbea2d4177941ce2caee1ae70a09c9a256f7ff16373d294a165b1d2527aafdc8.json +1 -0
- newskit/donnees_temoin/reponses/e0b6e3c2dcb95d24ef3f64233109e51bca7a972f088a2754f629d5b46fc55213.json +1 -0
- newskit/donnees_temoin/reponses/e1229a6cc2304bcf2e22247360b69ecabe50e99179b3c6defbd34d2597a00637.json +1 -0
- newskit/donnees_temoin/reponses/e2de5455db3aba1f9b4ae39ca99c390c19d2f42baab3a71e133bafd10000dc7f.json +1 -0
- newskit/donnees_temoin/reponses/e8023dd1da8106b8bc62a0da71229018c0d12074af195c55bc9a3c1da986b907.json +1 -0
- newskit/donnees_temoin/reponses/edbc6f1aa5c620e09cab0447d3375d8228693616b897070a59707e68fda583a5.json +1 -0
- newskit/donnees_temoin/tests/__init__.py +0 -0
- newskit/donnees_temoin/tests/conftest.py +158 -0
- newskit/donnees_temoin/tests/test_temoin_alias.py +112 -0
- newskit/donnees_temoin/tests/test_temoin_debris.py +86 -0
- newskit/donnees_temoin/tests/test_temoin_enregistrement.py +135 -0
- newskit/donnees_temoin/tests/test_temoin_etancheite.py +221 -0
- newskit/donnees_temoin/tests/test_temoin_limite.py +61 -0
- newskit/donnees_temoin/tests/test_temoin_nuit.py +135 -0
- newskit/donnees_temoin/tests/test_temoin_plafonds.py +58 -0
- newskit/donnees_temoin/tests/test_temoin_refus.py +81 -0
- newskit/donnees_temoin/tests/test_temoin_rejeu.py +86 -0
- newskit/donnees_temoin/tests/test_temoin_reprise.py +74 -0
- newskit/donnees_temoin/tests/test_temoin_routine.py +82 -0
- newskit/donnees_temoin/tests/test_temoin_sans_specifiques.py +77 -0
- newskit/doublons_d_identite.py +221 -0
- newskit/eclaireur.py +640 -0
- newskit/embeddings_locaux.py +67 -0
- newskit/encodeur.py +609 -0
- newskit/evenements.py +108 -0
- newskit/exemples_redaction.py +172 -0
- newskit/exporter_onnx.py +270 -0
- newskit/extraire_concepts.py +608 -0
- newskit/extraire_squelette.py +726 -0
- newskit/extraire_structure.py +100 -0
- newskit/extrait.py +32 -0
- newskit/fiche.py +713 -0
- newskit/fil.py +1649 -0
- newskit/fil_anglais.py +369 -0
- newskit/fil_fiche.py +196 -0
- newskit/fondre_doublons.py +552 -0
- newskit/fraicheur_github.py +316 -0
- newskit/francais.py +264 -0
- newskit/fusionner_graphe.py +530 -0
- newskit/garde_pousse.py +79 -0
- newskit/interroger.py +138 -0
- newskit/interroger_graphe.py +609 -0
- newskit/lacunes.py +328 -0
- newskit/launchd_graphe.plist.modele +67 -0
- newskit/lecture_cortexdb.py +179 -0
- newskit/lecture_web.py +891 -0
- newskit/lettre.py +3682 -0
- newskit/lettre_anglaise.py +109 -0
- newskit/lettre_semaine.py +237 -0
- newskit/media.py +306 -0
- newskit/mesure_pilote_cortexdb.py +304 -0
- newskit/mesure_serveur.py +66 -0
- newskit/mesurer_avance.py +417 -0
- newskit/mesurer_rejets.py +264 -0
- newskit/migrer_frontmatter.py +80 -0
- newskit/naissance.py +256 -0
- newskit/notifier_courriel.py +706 -0
- newskit/nouveau_graphe.py +323 -0
- newskit/nouvelles.py +232 -0
- newskit/ouvrir_session.py +453 -0
- newskit/partage_graphe.py +478 -0
- newskit/partage_sources.py +303 -0
- newskit/passe_partout.py +125 -0
- newskit/peu_couverts.py +332 -0
- newskit/politiques.py +78 -0
- newskit/poseur.py +279 -0
- newskit/prospecter_source.py +410 -0
- newskit/proxies_webshare.py +136 -0
- newskit/publier_cerveau.py +172 -0
- newskit/publier_fil.py +181 -0
- newskit/publier_graphes.py +171 -0
- newskit/publier_index.py +195 -0
- newskit/puits_cortexdb.py +1023 -0
- newskit/purger_fiches.py +128 -0
- newskit/recepteur_local.py +53 -0
- newskit/recuperer_tronquee.py +119 -0
- newskit/refus.py +104 -0
- newskit/reporter.py +3102 -0
- newskit/retirer_domaines.py +70 -0
- newskit/retouche_lettre.py +312 -0
- newskit/routine_assistee.py +261 -0
- newskit/routine_hebdo.py +2074 -0
- newskit/sante_sources.py +175 -0
- newskit/selectionneur.py +203 -0
- newskit/serveur_cortexdb.py +209 -0
- newskit/synthese.py +1877 -0
- newskit/temoin.py +617 -0
- newskit/themes_emergents.py +812 -0
- newskit/valider_fiches.py +75 -0
- newskit/vocabulaire_web2.txt.gz +0 -0
- newskit-0.1.7.dist-info/METADATA +210 -0
- newskit-0.1.7.dist-info/RECORD +213 -0
- newskit-0.1.7.dist-info/WHEEL +4 -0
- newskit-0.1.7.dist-info/entry_points.txt +2 -0
- newskit-0.1.7.dist-info/licenses/LICENSE +21 -0
newskit/__init__.py
ADDED
newskit/__main__.py
ADDED
newskit/acuite.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""L'acuité d'un graphe : ce qui rend lisible son écart avec un modèle de langage.
|
|
3
|
+
|
|
4
|
+
Le but des graphes est de dépasser un modèle en pertinence et en complétude (porteur, 04.10.2026).
|
|
5
|
+
Ce script réunit, par graphe, les nombres qui le disent, en une ligne et un fichier :
|
|
6
|
+
|
|
7
|
+
banc bonnes réponses sourcées sur un banc de questions, dans trois conditions — avec le
|
|
8
|
+
graphe, le modèle seul, le modèle doté de la recherche web (banc.py). La métrique de tête.
|
|
9
|
+
canon part de ce que le modèle tient pour essentiel et que le graphe porte (mesurer_avance.py).
|
|
10
|
+
avance part des notions récentes que le modèle dit ne pas connaître ; `utile` : celles qui sont
|
|
11
|
+
en plus dans le sujet.
|
|
12
|
+
fraîcheur date de la publication la plus récente que le graphe connaît, délai médian entre la
|
|
13
|
+
publication d'un article et sa collecte, fiches encore en attente d'extraction.
|
|
14
|
+
couverture par sujet déclaré : notions du canon portées, et manquantes.
|
|
15
|
+
corroboration part des notions attestées par au moins deux fiches, et par deux sources distinctes.
|
|
16
|
+
lacunes questions restées sans réponse : ouvertes, closes (lacunes.py).
|
|
17
|
+
|
|
18
|
+
Aucun appel à un modèle ici : le banc et le canon sont mesurés ailleurs, ce script les lit.
|
|
19
|
+
|
|
20
|
+
python3 -m newskit.acuite --graphe <graphe> # calcule, écrit graphe/<nom>/acuite.json, affiche
|
|
21
|
+
python3 -m newskit.acuite --tous # tous les graphes, une ligne chacun
|
|
22
|
+
python3 -m newskit.acuite --graphe <graphe> --etat # relit sans recalculer
|
|
23
|
+
|
|
24
|
+
Chaque calcul ajoute une ligne à graphe/<nom>/acuite_historique.jsonl (une par jour) : c'est la pente
|
|
25
|
+
qui dit si la stratégie marche, pas le niveau. À un changement de modèle, les chiffres baissent d'un
|
|
26
|
+
coup — l'historique garde le modèle et son horizon pour qu'on ne le prenne pas pour une régression.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import json
|
|
33
|
+
import re
|
|
34
|
+
import statistics
|
|
35
|
+
import subprocess
|
|
36
|
+
import sys
|
|
37
|
+
import time
|
|
38
|
+
from datetime import date
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
|
|
41
|
+
from newskit.commun import CODE, RACINE # noqa: E402 — deux racines, le code et les données (N1)
|
|
42
|
+
from newskit import config_sources as cfg # noqa: E402
|
|
43
|
+
from newskit import digerer_graphe as dg # noqa: E402
|
|
44
|
+
from newskit import themes_emergents as te # noqa: E402
|
|
45
|
+
|
|
46
|
+
RE_JOUR = re.compile(r"^(\d{4})-(\d{2})-(\d{2})")
|
|
47
|
+
# Les seuils du succès, convenus avec le porteur le 04.10.2026 : (minimum, succès). L'objectif commun aux
|
|
48
|
+
# graphes servis est la colonne « succès ». Avec trente questions, un écart de banc de moins de vingt à
|
|
49
|
+
# vingt-cinq points peut être du hasard : +25 est le plus petit écart qu'on puisse affirmer.
|
|
50
|
+
SEUILS = {
|
|
51
|
+
"banc_ecart_modele": (0.15, 0.25), # graphe − modèle seul, en part de réponses justes
|
|
52
|
+
"banc_ecart_web": (-0.10, 0.0), # graphe − modèle avec recherche web
|
|
53
|
+
"banc_faux": (0.10, 0.05), # réponses fausses du graphe : un plafond
|
|
54
|
+
"canon": (0.80, 0.90),
|
|
55
|
+
"avance_utile": (0.35, 0.50),
|
|
56
|
+
"age_jours": (2, 1), # un plafond
|
|
57
|
+
"corrobore_centrales": (0.60, 0.80), # les notions les plus reliées, attestées par deux sources
|
|
58
|
+
}
|
|
59
|
+
PLAFONDS = {"banc_faux", "age_jours"}
|
|
60
|
+
CENTRALES = 200 # notions les plus reliées, pour la corroboration du cœur du graphe
|
|
61
|
+
FENETRE = 30 # jours de collecte pris pour le délai de fraîcheur
|
|
62
|
+
DELAI_MAX = 14 # au-delà, la fiche est une archive remontée, pas une nouvelle
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def lu(chemin: Path) -> dict:
|
|
66
|
+
try:
|
|
67
|
+
return json.loads(chemin.read_text(encoding="utf-8"))
|
|
68
|
+
except (OSError, json.JSONDecodeError):
|
|
69
|
+
return {}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def jour(texte: str) -> date | None:
|
|
73
|
+
m = RE_JOUR.match(texte or "")
|
|
74
|
+
try:
|
|
75
|
+
return date(int(m.group(1)), int(m.group(2)), int(m.group(3))) if m else None
|
|
76
|
+
except ValueError:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def fraicheur(graphe: str, G, aujourdhui: date) -> dict:
|
|
81
|
+
"""La date la plus récente connue du graphe, et le délai entre publication et collecte des nouvelles."""
|
|
82
|
+
dates = [jour(str(d.get("date") or "")) for _, d in G.nodes(data=True) if d.get("kind") in ("article", "papier")]
|
|
83
|
+
dates = [d for d in dates if d and d <= aujourdhui]
|
|
84
|
+
delais = []
|
|
85
|
+
for dossier in cfg.dossiers_du_graphe(graphe):
|
|
86
|
+
for f in (RACINE / dossier).glob("*.md"):
|
|
87
|
+
publie = jour(f.name)
|
|
88
|
+
m = re.search(
|
|
89
|
+
r'^collected: "?(\d{4}-\d{2}-\d{2})', f.read_text(encoding="utf-8", errors="replace")[:1500], flags=re.M
|
|
90
|
+
)
|
|
91
|
+
collecte = jour(m.group(1)) if m else None
|
|
92
|
+
if (
|
|
93
|
+
publie
|
|
94
|
+
and collecte
|
|
95
|
+
and (aujourdhui - collecte).days <= FENETRE
|
|
96
|
+
and 0 <= (collecte - publie).days <= DELAI_MAX
|
|
97
|
+
):
|
|
98
|
+
delais.append((collecte - publie).days)
|
|
99
|
+
r = subprocess.run(
|
|
100
|
+
[sys.executable, str(CODE / "extraire_concepts.py"), "--graphe", graphe, "--etat"],
|
|
101
|
+
cwd=RACINE,
|
|
102
|
+
capture_output=True,
|
|
103
|
+
text=True,
|
|
104
|
+
timeout=600,
|
|
105
|
+
)
|
|
106
|
+
m = re.search(r"(\d+) fiche\(s\) dense\(s\) en retard", r.stdout)
|
|
107
|
+
return {
|
|
108
|
+
"dernier": max(dates).isoformat() if dates else None,
|
|
109
|
+
"age_jours": (aujourdhui - max(dates)).days if dates else None,
|
|
110
|
+
"delai_median_jours": statistics.median(delais) if delais else None,
|
|
111
|
+
"nouvelles_mesurees": len(delais),
|
|
112
|
+
"en_attente_d_extraction": int(m.group(1)) if m else 0,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def corroboration(G) -> dict:
|
|
117
|
+
"""Part des notions attestées par au moins deux fiches, et par au moins deux sources (dossiers) distinctes."""
|
|
118
|
+
total = deux_fiches = deux_sources = 0
|
|
119
|
+
par_noeud: list[tuple[int, bool]] = []
|
|
120
|
+
for n in G:
|
|
121
|
+
if dg._type(G, n) not in dg.TYPES_CONCEPT:
|
|
122
|
+
continue
|
|
123
|
+
fiches = {str(d.get("source_file") or "") for _, _, d in G.edges(n, data=True)} | {
|
|
124
|
+
str(G.nodes[n].get("source_file") or "")
|
|
125
|
+
}
|
|
126
|
+
fiches.discard("")
|
|
127
|
+
if not fiches:
|
|
128
|
+
continue
|
|
129
|
+
total += 1
|
|
130
|
+
deux_fiches += len(fiches) >= 2
|
|
131
|
+
plusieurs = len({str(Path(f).parent) for f in fiches}) >= 2
|
|
132
|
+
deux_sources += plusieurs
|
|
133
|
+
par_noeud.append((G.degree(n), plusieurs))
|
|
134
|
+
# Le chiffre d'ensemble reste bas par nature : la longue traîne d'un domaine n'est citée qu'une fois.
|
|
135
|
+
# Le cœur du graphe — ses notions les plus reliées — doit, lui, reposer sur plusieurs sources.
|
|
136
|
+
centrales = sorted(par_noeud, reverse=True)[:CENTRALES]
|
|
137
|
+
return {
|
|
138
|
+
"notions": total,
|
|
139
|
+
"deux_fiches": round(deux_fiches / total, 3) if total else None,
|
|
140
|
+
"deux_sources": round(deux_sources / total, 3) if total else None,
|
|
141
|
+
"centrales": round(sum(p for _, p in centrales) / len(centrales), 3) if centrales else None,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def actualite(G) -> dict | None:
|
|
146
|
+
"""Ce que les textes de référence disent du présent des notions (diachronie.py, posé à la composition) : notions
|
|
147
|
+
attestées par un texte de référence, part attestée par une version en vigueur, notions à lire comme périmées
|
|
148
|
+
(version remplacée seulement), notions évoluant entre deux versions. None sans texte de référence."""
|
|
149
|
+
c = {"reference": 0, "en_vigueur": 0, "remplacee_seulement": 0, "evolue": 0}
|
|
150
|
+
for _, d in G.nodes(data=True):
|
|
151
|
+
a = d.get("actualite")
|
|
152
|
+
if a and "en_vigueur" in a:
|
|
153
|
+
c["reference"] += 1
|
|
154
|
+
c["en_vigueur"] += bool(a["en_vigueur"])
|
|
155
|
+
c["remplacee_seulement"] += bool(a["remplacee_seulement"])
|
|
156
|
+
c["evolue"] += bool(a["evolue"])
|
|
157
|
+
if not c["reference"]:
|
|
158
|
+
return None
|
|
159
|
+
c["part_en_vigueur"] = round(c["en_vigueur"] / c["reference"], 3)
|
|
160
|
+
return c
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def calculer(graphe: str) -> dict | None:
|
|
164
|
+
G = te.charger(graphe)
|
|
165
|
+
if G is None:
|
|
166
|
+
return None
|
|
167
|
+
base = cfg.chemins_du_graphe(graphe)["base"]
|
|
168
|
+
aujourdhui = date.today()
|
|
169
|
+
avance, lac, banc = lu(base / "avance.json"), lu(base / "lacunes.json"), lu(base / "banc.json")
|
|
170
|
+
a: dict = {
|
|
171
|
+
"graphe": graphe,
|
|
172
|
+
"date": aujourdhui.isoformat(),
|
|
173
|
+
"modele": avance.get("modele") or te.MODELE,
|
|
174
|
+
"horizon": avance.get("horizon") or cfg.HORIZON_MODELE,
|
|
175
|
+
"fraicheur": fraicheur(graphe, G, aujourdhui),
|
|
176
|
+
"corroboration": corroboration(G),
|
|
177
|
+
}
|
|
178
|
+
if act := actualite(G):
|
|
179
|
+
a["actualite"] = act
|
|
180
|
+
if avance.get("completude") is not None:
|
|
181
|
+
soumises = sum(s["soumises"] for s in avance.get("sources", []))
|
|
182
|
+
a["canon"] = {
|
|
183
|
+
"completude": avance["completude"],
|
|
184
|
+
"mesure_du": avance.get("date"),
|
|
185
|
+
"sujets": [
|
|
186
|
+
{"sujet": s["sujet"], "portees": s["portees"], "canon": s["canon"], "absentes": s["absentes"]}
|
|
187
|
+
for s in sorted(avance.get("sujets", []), key=lambda s: s["portees"] / s["canon"])
|
|
188
|
+
],
|
|
189
|
+
}
|
|
190
|
+
a["avance"] = {
|
|
191
|
+
"posterieures": avance.get("posterieures"),
|
|
192
|
+
"notions": avance.get("notions"),
|
|
193
|
+
"inconnues": round(len(avance.get("inconnues_du_modele", [])) / avance["echantillon"], 3)
|
|
194
|
+
if avance.get("echantillon")
|
|
195
|
+
else None,
|
|
196
|
+
"utiles": round(sum(s["utiles"] for s in avance.get("sources", [])) / soumises, 3) if soumises else None,
|
|
197
|
+
}
|
|
198
|
+
if lac:
|
|
199
|
+
semaine = sum(
|
|
200
|
+
1
|
|
201
|
+
for x in lac.get("closes", {}).values()
|
|
202
|
+
if (j := jour(str(x.get("close_le") or ""))) and (aujourdhui - j).days <= 7
|
|
203
|
+
)
|
|
204
|
+
a["lacunes"] = {
|
|
205
|
+
"ouvertes": len(lac.get("ouvertes", {})),
|
|
206
|
+
"closes_7_jours": semaine,
|
|
207
|
+
"a_trancher": len(lac.get("a_trancher", {})),
|
|
208
|
+
}
|
|
209
|
+
if banc.get("conditions"):
|
|
210
|
+
a["banc"] = {
|
|
211
|
+
"mesure_du": banc.get("date"),
|
|
212
|
+
"questions": banc.get("questions"),
|
|
213
|
+
"conditions": banc["conditions"],
|
|
214
|
+
"methode": banc.get("methode") or "ancienne",
|
|
215
|
+
"restitution": banc.get("restitution"),
|
|
216
|
+
"fond": banc.get("fond"),
|
|
217
|
+
"par_type": banc.get("par_type"),
|
|
218
|
+
}
|
|
219
|
+
a["paliers"] = paliers(a)
|
|
220
|
+
a["ligne"] = ligne(a)
|
|
221
|
+
return a
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def valeurs(a: dict) -> dict:
|
|
225
|
+
"""Les valeurs que les seuils jugent, tirées de l'acuité ; None quand la mesure manque."""
|
|
226
|
+
c = (a.get("banc") or {}).get("conditions") or {}
|
|
227
|
+
g, m, w = (c.get(k, {}).get("juste") for k in ("graphe", "modele", "web"))
|
|
228
|
+
return {
|
|
229
|
+
"banc_ecart_modele": None if g is None or m is None else round(g - m, 3),
|
|
230
|
+
"banc_ecart_web": None if g is None or w is None else round(g - w, 3),
|
|
231
|
+
"banc_faux": c.get("graphe", {}).get("faux"),
|
|
232
|
+
"canon": (a.get("canon") or {}).get("completude"),
|
|
233
|
+
"avance_utile": (a.get("avance") or {}).get("utiles"),
|
|
234
|
+
"age_jours": (a.get("fraicheur") or {}).get("age_jours"),
|
|
235
|
+
"corrobore_centrales": (a.get("corroboration") or {}).get("centrales"),
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def paliers(a: dict) -> dict:
|
|
240
|
+
"""Pour chaque seuil : « succès », « minimum », « en dessous », ou None si la mesure manque."""
|
|
241
|
+
out = {}
|
|
242
|
+
for k, v in valeurs(a).items():
|
|
243
|
+
mini, succes = SEUILS[k]
|
|
244
|
+
if v is None:
|
|
245
|
+
out[k] = None
|
|
246
|
+
elif k in PLAFONDS:
|
|
247
|
+
out[k] = "succès" if v <= succes else "minimum" if v <= mini else "en dessous"
|
|
248
|
+
else:
|
|
249
|
+
out[k] = "succès" if v >= succes else "minimum" if v >= mini else "en dessous"
|
|
250
|
+
return out
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def pct(x) -> str:
|
|
254
|
+
return "—" if x is None else f"{x:.0%}"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def ligne(a: dict) -> str:
|
|
258
|
+
"""La synthèse en une ligne, toujours dans le même ordre : banc, socle, avance, fraîcheur, lacunes."""
|
|
259
|
+
bouts = []
|
|
260
|
+
c = (a.get("banc") or {}).get("conditions") or {}
|
|
261
|
+
if c:
|
|
262
|
+
# Juste · faux · ne sait pas : « ne sait pas » n'est ni l'un ni l'autre, et s'écrit.
|
|
263
|
+
from newskit.banc import trois_parts # noqa: PLC0415
|
|
264
|
+
|
|
265
|
+
jf = lambda k: trois_parts(c.get(k) or {}) if (c.get(k) or {}).get("juste") is not None else "—" # noqa: E731
|
|
266
|
+
bouts.append(f"banc {jf('graphe')} | LLM {jf('modele')} | LLM+web {jf('web')} (juste · faux · ne sait pas)")
|
|
267
|
+
if a.get("canon"):
|
|
268
|
+
bouts.append(f"socle {pct(a['canon']['completude'])}")
|
|
269
|
+
if (a.get("avance") or {}).get("utiles") is not None:
|
|
270
|
+
bouts.append(f"avance utile {pct(a['avance']['utiles'])}")
|
|
271
|
+
f = a.get("fraicheur") or {}
|
|
272
|
+
if f.get("age_jours") is not None:
|
|
273
|
+
bouts.append(
|
|
274
|
+
f"frais à J-{f['age_jours']}"
|
|
275
|
+
+ (f", {f['en_attente_d_extraction']} en attente" if f.get("en_attente_d_extraction") else "")
|
|
276
|
+
)
|
|
277
|
+
if (a.get("corroboration") or {}).get("deux_sources") is not None:
|
|
278
|
+
bouts.append(f"corroboré {pct(a['corroboration']['deux_sources'])}")
|
|
279
|
+
if a.get("lacunes"):
|
|
280
|
+
bouts.append(f"{a['lacunes']['ouvertes']} lacune(s)")
|
|
281
|
+
return " · ".join(bouts) or "pas encore mesuré"
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def ecrire(graphe: str, a: dict) -> None:
|
|
285
|
+
base = cfg.chemins_du_graphe(graphe)["base"]
|
|
286
|
+
(base / "acuite.json").write_text(json.dumps(a, ensure_ascii=False, indent=1), encoding="utf-8")
|
|
287
|
+
# Une ligne par jour dans l'historique : celle du jour remplace la précédente du même jour.
|
|
288
|
+
h = base / "acuite_historique.jsonl"
|
|
289
|
+
lignes = [
|
|
290
|
+
l
|
|
291
|
+
for l in (h.read_text(encoding="utf-8").splitlines() if h.exists() else [])
|
|
292
|
+
if f'"date": "{a["date"]}"' not in l
|
|
293
|
+
]
|
|
294
|
+
c = (a.get("banc") or {}).get("conditions") or {}
|
|
295
|
+
point = {
|
|
296
|
+
"date": a["date"],
|
|
297
|
+
"modele": a["modele"],
|
|
298
|
+
"horizon": a["horizon"],
|
|
299
|
+
"banc_graphe": c.get("graphe", {}).get("juste"),
|
|
300
|
+
"banc_modele": c.get("modele", {}).get("juste"),
|
|
301
|
+
"banc_web": c.get("web", {}).get("juste"),
|
|
302
|
+
"canon": (a.get("canon") or {}).get("completude"),
|
|
303
|
+
"avance_utile": (a.get("avance") or {}).get("utiles"),
|
|
304
|
+
"age_jours": a["fraicheur"]["age_jours"],
|
|
305
|
+
"en_attente": a["fraicheur"]["en_attente_d_extraction"],
|
|
306
|
+
"corrobore": a["corroboration"]["deux_sources"],
|
|
307
|
+
"lacunes": (a.get("lacunes") or {}).get("ouvertes"),
|
|
308
|
+
}
|
|
309
|
+
h.write_text("\n".join([*lignes, json.dumps(point, ensure_ascii=False)]) + "\n", encoding="utf-8")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def afficher(a: dict, detail: bool = True) -> None:
|
|
313
|
+
print(f" {a['graphe']} — {a['ligne']}")
|
|
314
|
+
if not detail:
|
|
315
|
+
return
|
|
316
|
+
f, c = a["fraicheur"], a["corroboration"]
|
|
317
|
+
print(
|
|
318
|
+
f" fraîcheur dernière publication connue : {f['dernier']} ; délai publication → collecte : "
|
|
319
|
+
f"{f['delai_median_jours'] if f['delai_median_jours'] is not None else '—'} j (médiane sur {f['nouvelles_mesurees']} nouvelles) ; "
|
|
320
|
+
f"{f['en_attente_d_extraction']} fiche(s) en attente d'extraction"
|
|
321
|
+
)
|
|
322
|
+
print(
|
|
323
|
+
f" corroboration {pct(c['deux_fiches'])} des {c['notions']} notions par au moins deux fiches, {pct(c['deux_sources'])} par deux "
|
|
324
|
+
f"sources ; {pct(c.get('centrales'))} des {CENTRALES} notions les plus reliées"
|
|
325
|
+
)
|
|
326
|
+
if a.get("actualite"):
|
|
327
|
+
x = a["actualite"]
|
|
328
|
+
print(
|
|
329
|
+
f" actualité {x['reference']} notions attestées par un texte de référence, {pct(x['part_en_vigueur'])} par une version "
|
|
330
|
+
f"en vigueur ; {x['remplacee_seulement']} à lire comme périmée(s) (version remplacée seulement) ; {x['evolue']} évoluant "
|
|
331
|
+
f"entre deux versions"
|
|
332
|
+
)
|
|
333
|
+
if a.get("canon"):
|
|
334
|
+
print(
|
|
335
|
+
f" socle {pct(a['canon']['completude'])} (LLM {a['modele']}, horizon {a['horizon']}, mesure du {a['canon']['mesure_du']})"
|
|
336
|
+
)
|
|
337
|
+
for s in a["canon"]["sujets"]:
|
|
338
|
+
print(f" {s['portees']:2d}/{s['canon']:2d} {s['sujet'][:72]}")
|
|
339
|
+
if a.get("avance"):
|
|
340
|
+
print(
|
|
341
|
+
f" avance {pct(a['avance']['inconnues'])} des notions récentes inconnues du LLM, {pct(a['avance']['utiles'])} utiles (dans le sujet)"
|
|
342
|
+
)
|
|
343
|
+
if a.get("lacunes"):
|
|
344
|
+
l = a["lacunes"]
|
|
345
|
+
print(
|
|
346
|
+
f" lacunes {l['ouvertes']} ouverte(s), {l['closes_7_jours']} close(s) en sept jours, {l['a_trancher']} à trancher"
|
|
347
|
+
)
|
|
348
|
+
v, noms = (
|
|
349
|
+
valeurs(a),
|
|
350
|
+
{
|
|
351
|
+
"banc_ecart_modele": "banc, écart avec le LLM seul",
|
|
352
|
+
"banc_ecart_web": "banc, écart avec le LLM + web",
|
|
353
|
+
"banc_faux": "banc, taux d'erreur du graphe",
|
|
354
|
+
"canon": "socle",
|
|
355
|
+
"avance_utile": "avance utile",
|
|
356
|
+
"age_jours": "fraîcheur (jours)",
|
|
357
|
+
"corrobore_centrales": "corroboration du cœur",
|
|
358
|
+
},
|
|
359
|
+
)
|
|
360
|
+
print(
|
|
361
|
+
" seuils "
|
|
362
|
+
+ " ".join(f"[{p or 'non mesuré'}] {noms[k]}" for k, p in (a.get("paliers") or {}).items())
|
|
363
|
+
)
|
|
364
|
+
atteints = [p for p in (a.get("paliers") or {}).values() if p]
|
|
365
|
+
if atteints:
|
|
366
|
+
print(
|
|
367
|
+
f" {atteints.count('succès')} au succès, {atteints.count('minimum')} au minimum, "
|
|
368
|
+
f"{atteints.count('en dessous')} en dessous, {len(a['paliers']) - len(atteints)} non mesuré(s)"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def main() -> int:
|
|
373
|
+
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
374
|
+
ap.add_argument("--graphe", default=cfg.GRAPHE_PRINCIPAL)
|
|
375
|
+
ap.add_argument("--tous", action="store_true", help="tous les graphes, une ligne chacun")
|
|
376
|
+
ap.add_argument("--etat", action="store_true", help="relire la dernière mesure, sans recalculer")
|
|
377
|
+
args = ap.parse_args()
|
|
378
|
+
graphes = cfg.ordre_d_affichage() if args.tous else [cfg.nom_actuel(args.graphe)]
|
|
379
|
+
for g in graphes:
|
|
380
|
+
if args.etat:
|
|
381
|
+
a = lu(cfg.chemins_du_graphe(g)["base"] / "acuite.json")
|
|
382
|
+
else:
|
|
383
|
+
a = calculer(g)
|
|
384
|
+
if a:
|
|
385
|
+
ecrire(g, a)
|
|
386
|
+
if a:
|
|
387
|
+
afficher(a, detail=not args.tous)
|
|
388
|
+
else:
|
|
389
|
+
print(f" {g} : pas de graphe, ou pas encore de mesure")
|
|
390
|
+
return 0
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
if __name__ == "__main__":
|
|
394
|
+
sys.exit(main())
|