ArgumentProfile : la fiche d’identité multidimensionnelle d’un argument

EPIC #4960 — Volet B, étape 3 (landing essence EPITA-IS). Ce notebook demontre ArgumentProfile, la vue agreggee de toutes les analyses portant sur un même argument. Il complete Argumentation-02-Fallacies-Detection-Python (detection de sophismes par taxonomie, qui couvre la dimension sophismes) en reunissant les cinq dimensions de l’analyse sur un même objet : sophismes, qualite, contre-arguments, croyances JTMS et résultats formels.

Duree estimee : 35 minutes Prerequis : Argumentation-02-Fallacies-Detection-Python (taxonomie des sophismes)

Objectifs

  • Comprendre pourquoi l’analyse argumentative est multidimensionnelle : un même argument peut etre fallacieux (logique informelle), mal fonde (qualite), attaque (contre-argumentation), non croyable (JTMS) et formalement invalide (logique formelle) – autant de verdicts independants
  • Construire un ArgumentProfile : la fiche agreggee qui reunit, pour un argument, toutes les analyses produites par les dimensions
  • Utiliser les méthodes cross-argument (get_weak_arguments, get_fallacious_arguments) pour trier un debat entier
  • Auto-contenu : aucun appel LLM, aucune donnee externe – les analyses sont injectees deterministement pour se concentrer sur la structure du profil

1. Pourquoi un profil par argument ?

Une analyse rhétorique complète produit plusieurs types de résultats — sophismes détectés, scores de qualité, contre-arguments générés, croyances d’un réseau JTMS, verdicts formels. Ces résultats vivent naturellement dans des structures séparées de l’état partagé (un dict de sophismes, un dict de scores, une liste de contre-arguments…).

Le problème : pour raisonner sur un argument précis, il faut réunir tout ce qui le concerne. « Cet argument est-il fallacieux ET mal fondé ET attaqué ? » C’est exactement le rôle de ArgumentProfile : une vue projetée qui agrège, pour un arg_id donné, toutes les analyses qui le ciblent.

Dimension Source dans l’état Question répondue
Sophismes identified_fallacies (target) L’argument commet-il une erreur rhétorique ?
Qualité argument_quality_scores Est-il clair, bien fondé, pertinent ?
Contre-arguments counter_arguments (target) Comment peut-on l’attaquer ?
Croyances JTMS jtms_beliefs Est-il jugé valide dans le réseau de croyances ?
Résultats formels propositional_analysis_results / fol_analysis_results (par arg_id) Est-il logiquement valide ?

Ces cinq verdicts sont indépendants : un argument peut etre formellement valide (pas d’erreur logique) mais rhétoriquement fallacieux (appel à l’autorité). Le profil est la seule vue qui les reunis.

# --- Imports + texte du debat (deterministe, auto-contenu) ---
import sys, logging
from pathlib import Path

# Imports de la lib portee (argumentation_lib, cote a cote des notebooks).
sys.path.insert(0, str(Path(".").resolve()))
logging.disable(logging.INFO)  # taire les logs de mutation pour lisibilite

from argumentation_lib import UnifiedAnalysisState, ArgumentProfile

# Un debat court, construit pour exercer les 5 dimensions (FR, deterministe).
DEBAT = (
    "Voici mon raisonnement. D'abord, tous les experts s'accordent a dire que le projet "
    "est utile, donc il faut le financer. Ensuite, soit on le finance, soit on abandonne "
    "toute innovation. Enfin, les couts sont eleves, mais le benefice attendu justifie "
    "l'investissement."
)
print("Texte du debat (auto-contenu, deterministe) :")
print(DEBAT)
print(f"\nargumentation_lib importe. ArgumentProfile = {ArgumentProfile.__name__} (dataclass agreggee).")
Texte du debat (auto-contenu, deterministe) :
Voici mon raisonnement. D'abord, tous les experts s'accordent a dire que le projet est utile, donc il faut le financer. Ensuite, soit on le finance, soit on abandonne toute innovation. Enfin, les couts sont eleves, mais le benefice attendu justifie l'investissement.

argumentation_lib importe. ArgumentProfile = ArgumentProfile (dataclass agreggee).

2. L’etat partage : register central des analyses

On instancie UnifiedAnalysisState avec le texte brut. C’est le register central où chaque dimension viendra écrire ses résultats. Les arguments identifiés reçoivent un arg_id (arg_1, arg_2…) — la clé qui reliera tout.

# --- Etat partage + identification des arguments ---
etat = UnifiedAnalysisState(DEBAT)

# Trois arguments extraits du texte (en pratique par un extracteur, ici deterministe).
a_expert = etat.add_argument("Tous les experts s'accordent => le projet est utile (appeal to authority + hasty generalization)")
a_fauxdilemme = etat.add_argument("Soit on finance, soit on abandonne toute innovation (faux dilemme)")
a_cout = etat.add_argument("Les couts sont eleves mais le benefice justifie l'investissement (balance cout-benefice)")

print("Arguments identifies :")
for aid, desc in etat.identified_arguments.items():
    print(f"  {aid}: {desc[:70]}...")
Arguments identifies :
  arg_1: Tous les experts s'accordent => le projet est utile (appeal to authori...
  arg_2: Soit on finance, soit on abandonne toute innovation (faux dilemme)...
  arg_3: Les couts sont eleves mais le benefice justifie l'investissement (bala...

Dimension 1 — Sophismes (lien avec Argumentation-02-Fallacies-Detection-Python)

On attache des sophismes aux arguments, en suivant la taxonomie en familles (cf. Agentic-1-informal). Chaque sophisme cible un arg_id précis.

# --- Dimension 1 : sophismes (taxonomie familles) ---
etat.add_fallacy("appel_a_l_autorite", "aucun expert n'est nomme ni cite",
                 target_arg_id=a_expert, family="Obstruction", taxonomy_path="fallacies/obstruction/appeal_authority")
etat.add_fallacy("generalisation_hative", "tous les experts' sans quantifier ni echantillon",
                 target_arg_id=a_expert, family="Obstruction")
etat.add_fallacy("faux_dilemme", "tiers exclu (co-partage, re-echelonnage, prototypage...)",
                 target_arg_id=a_fauxdilemme, family="Obstruction")

print(f"Sophismes identifies : {len(etat.identified_fallacies)}")
for fid, info in etat.identified_fallacies.items():
    print(f"  {fid} [{info.get('family','?')}] {info['type']} -> cible {info.get('target_argument_id','?')}")
Sophismes identifies : 3
  fallacy_1 [Obstruction] appel_a_l_autorite -> cible arg_1
  fallacy_2 [Obstruction] generalisation_hative -> cible arg_1
  fallacy_3 [Obstruction] faux_dilemme -> cible arg_2

Dimension 2 — Qualite (9 vertus, agregees en note globale)

Chaque argument est scorisé sur un sous-ensemble des vertus (clarté, fondement, pertinence…), chaque vertu dans [0, 1]. Le score overall est la somme des vertus évaluées (contrat d’unité #1942 — pas une note sur 10) ; la fraction lisible vaut overall / len(scores), et le seuil faible de référence est 0.5.

# --- Dimension 2 : scores de qualite (vertus) ---
# Contrat d'unite (#1942) : vertus dans [0, 1], overall = SOMME des vertus evaluees.
etat.add_quality_score(a_expert, scores={"clarte": 0.6, "fondement": 0.2, "pertinence": 0.5}, overall=1.3)
etat.add_quality_score(a_fauxdilemme, scores={"clarte": 0.7, "fondement": 0.25, "pertinence": 0.6}, overall=1.55)
etat.add_quality_score(a_cout, scores={"clarte": 0.8, "fondement": 0.7, "pertinence": 0.8}, overall=2.3)

print("Scores de qualite :")
for aid, sc in etat.argument_quality_scores.items():
    fraction = sc['overall'] / len(sc['scores'])
    print(f"  {aid}: overall={sc['overall']:.2f} (somme)  fraction={fraction:.3f}  vertus={sc['scores']}")
Scores de qualite :
  arg_1: overall=1.30 (somme)  fraction=0.433  vertus={'clarte': 0.6, 'fondement': 0.2, 'pertinence': 0.5}
  arg_2: overall=1.55 (somme)  fraction=0.517  vertus={'clarte': 0.7, 'fondement': 0.25, 'pertinence': 0.6}
  arg_3: overall=2.30 (somme)  fraction=0.767  vertus={'clarte': 0.8, 'fondement': 0.7, 'pertinence': 0.8}

Dimension 3 — Contre-arguments (5 stratégies)

Pour chaque argument attaqué, on génère des contre-arguments selon des stratégies canoniques (contester la source, l’inférence, la portée…). Chaque contre-argument a un score de force.

# --- Dimension 3 : contre-arguments (strategies) ---
etat.add_counter_argument(original_arg="experts s'accordent",
                          counter_content="aucun expert nomme => source inexistante",
                          strategy="contester_source", score=0.85, target_arg_id=a_expert)
etat.add_counter_argument(original_arg="tous les experts",
                          counter_content="aucun sondage => generalisation non etayee",
                          strategy="contester_inference", score=0.75, target_arg_id=a_expert)
etat.add_counter_argument(original_arg="soit... soit...",
                          counter_content="tiers exclu : co-partage, re-echelonnage, prototypage",
                          strategy="denoncer_faux_dilemme", score=0.90, target_arg_id=a_fauxdilemme)

print(f"Contre-arguments generes : {len(etat.counter_arguments)}")
for ca in etat.counter_arguments:
    print(f"  {ca['id']} [{ca['strategy']}] force={ca['score']:.2f} -> cible {ca.get('target_arg_id','?')}")
Contre-arguments generes : 3
  ca_1 [contester_source] force=0.85 -> cible arg_1
  ca_2 [contester_inference] force=0.75 -> cible arg_1
  ca_3 [denoncer_faux_dilemme] force=0.90 -> cible arg_2

Dimension 4 — Croyances JTMS (reseau de justification)

Le JTMS (Justification-Based Truth Maintenance System) trace quelles croyances sont valides/invalides et pourquoi. Une croyance se rattache a un argument quand son nom reprend le texte de l’argument (le profil matche sur le prefixe de la description) — c’est la convention de projection qui lie une croyance JTMS debat-large a un argument précis.

# --- Dimension 4 : croyances JTMS ---
# Le nom reprend le debut de l'argument -> la croyance se rattache au profil de cet argument.
etat.add_jtms_belief(name="Tous les experts s'accordent => le projet est utile", valid=False,
                     justifications=["autorite_non_citee", "generalisation_non_quantifiee"])
etat.add_jtms_belief(name="Les couts sont eleves mais le benefice justifie l'investissement", valid=True,
                     justifications=["benefice > cout (balance)"])

print(f"Croyances JTMS : {len(etat.jtms_beliefs)}")
for bid, b in etat.jtms_beliefs.items():
    print(f"  {bid}: {b['name'][:55]}... valid={b['valid']}")
print("\nConvention : le nom reprenant le texte de l'argument, la croyance se rattache")
print("au profil de cet argument (match par prefixe de description).")
Croyances JTMS : 2
  jtms_1: Tous les experts s'accordent => le projet est utile... valid=False
  jtms_2: Les couts sont eleves mais le benefice justifie l'inves... valid=True

Convention : le nom reprenant le texte de l'argument, la croyance se rattache
au profil de cet argument (match par prefixe de description).

Dimension 5 — Résultats formels (logique PL/FOL)

La dimension formelle agrège les verdicts des solveurs logiques. get_argument_profile lit les résultats lies a un argument (arg_id=) : add_propositional_analysis_result (PL), add_fol_analysis_result (FOL) et add_nl_to_logic_translation. Un argument peut etre formellement satisfiable (pas de contradiction interne) tout en etant fallacieux — d’ou l’indépendance des dimensions.

Note : add_formal_synthesis_report existe aussi, mais produit une synthese debat-large (non liee a un arg_id) que le profil, par conception, ne projette pas. La synthese globale et l’analyse par-argument sont deux objets distincts.

# --- Dimension 5 : resultats formels par argument (PL + FOL) ---
etat.add_propositional_analysis_result(
    formulas=["Expert => Utile", "Expert", "Utile"], satisfiable=True,
    model={"Expert": True, "Utile": True}, arg_id=a_expert)
etat.add_fol_analysis_result(
    formulas=["forall x. Expert(x) => Utile(x)"], consistent=True,
    inferences=["Utile(projet)"], confidence=0.4, arg_id=a_expert)
etat.add_propositional_analysis_result(
    formulas=["Finance OU Abandon", "non Abandon"], satisfiable=True,
    model={"Finance": True, "Abandon": False}, arg_id=a_fauxdilemme)

print("Resultats formels par argument :")
for aid in [a_expert, a_fauxdilemme, a_cout]:
    pl = [r for r in etat.propositional_analysis_results if r.get('arg_id') == aid]
    fol = [r for r in etat.fol_analysis_results if r.get('arg_id') == aid]
    print(f"  {aid}: PL={len(pl)} FOL={len(fol)}")
Resultats formels par argument :
  arg_1: PL=1 FOL=1
  arg_2: PL=1 FOL=0
  arg_3: PL=0 FOL=0

3. Le profil : reunir les cinq dimensions pour UN argument

C’est le c ur du notebook. get_argument_profile(arg_id) projette l’état partagé sur un seul argument : il récupère les sophismes qui le ciblent, son score de qualité, ses contre-arguments, etc. On obtient une fiche d’identité exploitable pour le raisonnement.

Regardons le profil de a_expert — l’argument le plus attaqué du debat.

# --- Le profil agregge d'un argument (la valeur centrale) ---
profil_expert = etat.get_argument_profile(a_expert)

print("=" * 64)
print(f"PROFIL DE {profil_expert.arg_id}")
print("=" * 64)
print(f"Description       : {profil_expert.description[:70]}...")
print(f"Sophismes (dim 1) : {len(profil_expert.fallacies)}  ->  {[f.get('type') for f in profil_expert.fallacies]}")
if profil_expert.quality_score:
    _q = profil_expert.quality_score
    print(f"Qualite   (dim 2) : overall={_q['overall']:.2f} (somme)  fraction={_q['overall'] / len(_q['scores']):.3f}  vertus={_q['scores']}")
print(f"Contre-arg(dim 3) : {len(profil_expert.counter_arguments)}  strategies={[c.get('strategy') for c in profil_expert.counter_arguments]}")
print(f"JTMS      (dim 4) : {len(profil_expert.jtms_beliefs)} croyances attachees")
print(f"Formel    (dim 5) : {len(profil_expert.formal_results)} synthese(s)")
print()
print("LECTURE : cet argument est fallacieux (2 sophismes), mal fonde (fraction 1.3/3 ~ 0.43 < 0.5),")
print("fortement attaque (2 contre-arguments, force moy ~0.80) -> profil COHERENT")
print("de faiblesse multidimensionnelle. Aucune dimension ne le defend.")
================================================================
PROFIL DE arg_1
================================================================
Description       : Tous les experts s'accordent => le projet est utile (appeal to authori...
Sophismes (dim 1) : 2  ->  ['appel_a_l_autorite', 'generalisation_hative']
Qualite   (dim 2) : overall=1.30 (somme)  fraction=0.433  vertus={'clarte': 0.6, 'fondement': 0.2, 'pertinence': 0.5}
Contre-arg(dim 3) : 2  strategies=['contester_source', 'contester_inference']
JTMS      (dim 4) : 1 croyances attachees
Formel    (dim 5) : 2 synthese(s)

LECTURE : cet argument est fallacieux (2 sophismes), mal fonde (fraction 1.3/3 ~ 0.43 < 0.5),
fortement attaque (2 contre-arguments, force moy ~0.80) -> profil COHERENT
de faiblesse multidimensionnelle. Aucune dimension ne le defend.

Verdict multidimensionnel : l’indépendance des dimensions

Comparons les trois profils. L’apport du profil est de révéler que les dimensions sont independantes : a_cout est solide partout (pas fallacieux, bien fondé, non attaqué), tandis qu’a_expert est faible sur les dimensions informelle ET de qualité mais n’a pas de verdict formel défavorable distinct.

# --- Comparaison des 3 profils (independance des dimensions) ---
print(f"{'arg':>8}  {'soph':>5}  {'qual':>5}  {'contre':>6}  {'verdict':>20}")
print("-" * 55)
for aid in [a_expert, a_fauxdilemme, a_cout]:
    p = etat.get_argument_profile(aid)
    n_fall = len(p.fallacies)
    qual = (p.quality_score['overall'] / len(p.quality_score['scores'])) if p.quality_score else float('nan')
    n_ca = len(p.counter_arguments)
    if n_fall >= 1 and qual < 0.5:
        verdict = "FAIBLE (fallacieux+mal fonde)"
    elif n_fall == 0 and qual >= 0.7:
        verdict = "SOLIDE"
    else:
        verdict = "MIXTE"
    print(f"{aid:>8}  {n_fall:>5}  {qual:>5.2f}  {n_ca:>6}  {verdict:>20}")
print()
print("Les dimensions sont INDEPENDANTES : a_fauxdilemme est fallacieux mais clair (clarte 0.7) ;")
print("a_cout est solide partout. Le profil revele ces profils de force differents.")
     arg   soph   qual  contre               verdict
-------------------------------------------------------
   arg_1      2   0.43       2  FAIBLE (fallacieux+mal fonde)
   arg_2      1   0.52       1                 MIXTE
   arg_3      0   0.77       0                SOLIDE

Les dimensions sont INDEPENDANTES : a_fauxdilemme est fallacieux mais clair (clarte 0.7) ;
a_cout est solide partout. Le profil revele ces profils de force differents.

4. Trier le debat entier : analyses cross-argument

Le profil n’est pas qu’une vue passive — il alimente des requêtes cross-argument : quels sont les arguments faibles du debat ? Lesquels sont fallacieux ? Ces méthodes retournent des listes de profils, prêtes pour la priorisation (faut-il réviser les arguments faibles d’abord ? Les écarter ?).

# --- Analyses cross-argument (trier le debat) ---
faibles = etat.get_weak_arguments(threshold=0.5)
fallacieux = etat.get_fallacious_arguments()

print(f"Arguments FAIBLES (fraction < 0.5) : {len(faibles)}")
for p in faibles:
    _q = p.quality_score
    print(f"  - {p.arg_id}: fraction={_q['overall'] / len(_q['scores']):.3f} (overall={_q['overall']:.2f})")

print(f"\nArguments FALLACIEUX (>= 1 sophisme) : {len(fallacieux)}")
for p in fallacieux:
    types = [f.get('type') for f in p.fallacies]
    print(f"  - {p.arg_id}: {types}")

print("\nUtilisation : un agent peut prioriser la revision des arguments faibles,")
print("ecarter les fallacieux de la conclusion, ou cibler les contre-arguments les plus forts.")
print("\nResume agregge (get_enrichment_summary) :")
import json as _json
print(_json.dumps(etat.get_enrichment_summary(), indent=2, ensure_ascii=False)[:600])
Arguments FAIBLES (fraction < 0.5) : 1
  - arg_1: fraction=0.433 (overall=1.30)

Arguments FALLACIEUX (>= 1 sophisme) : 2
  - arg_1: ['appel_a_l_autorite', 'generalisation_hative']
  - arg_2: ['faux_dilemme']

Utilisation : un agent peut prioriser la revision des arguments faibles,
ecarter les fallacieux de la conclusion, ou cibler les contre-arguments les plus forts.

Resume agregge (get_enrichment_summary) :
{
  "total_arguments": 3,
  "with_fallacy_analysis": 2,
  "with_quality_score": 3,
  "with_counter_argument": 2,
  "with_formal_verification": 2,
  "with_jtms_belief": 2,
  "gaps": [
    "arg_3 has no formal verification"
  ]
}

5. Exercices

Exercice 1 — Ajouter un quatrieme argument et son profil

Ajoutez un argument a_risque (« le projet est risqué donc à éviter ») au debat. Scoriez-le (faible fondement car aucun risque chiffré), attachez-lui le sophisme appel_a_la_peur. Construire son profil et vérifier qu’il apparaît dans get_fallacious_arguments(). - Indice : etat.add_argument(...), etat.add_fallacy(..., target_arg_id=a_risque), etat.add_quality_score(a_risque, ...), puis etat.get_argument_profile(a_risque).

# Exercice 1 a completer
# TODO etudiant : ajouter a_risque, le scorer, lui attacher un sophisme, construire son profil.
print("Exercice 1 a completer : profil d'un quatrieme argument fallacieux.")
Exercice 1 a completer : profil d'un quatrieme argument fallacieux.

Exercice 2 — Seuil de faiblesse : sensitivity analysis

Faites varier le threshold de get_weak_arguments entre 0.3 et 0.8 par pas de 0.1 (le threshold est une fraction dans [0, 1] — contrat #1942 : une valeur > 1.0 lève ValueError). Pour chaque seuil, affichez combien d’arguments sont jugés faibles. À partir de quel seuil a_fauxdilemme (fraction 1.55/3 ≈ 0.52) bascule-t-il dans la catégorie faible ? - Indice : bouclez sur les seuils, appelez get_weak_arguments(threshold=s), affichez len(...). Le basculement se produit quand threshold > 1.55/3 ≈ 0.52.

# Exercice 2 a completer
# TODO etudiant : balayer threshold, tracer le compte d'arguments faibles vs seuil.
print("Exercice 2 a completer : sensitivity du seuil de faiblesse.")
Exercice 2 a completer : sensitivity du seuil de faiblesse.

Exercice 3 — Verdict aggregge pondere

Implémentez une fonction verduit_global(profil) qui combine les dimensions en un score unique : par exemple fraction_qualite * 0.4 + (1 - n_sophismes/3) * 0.3 + (1 - max_force_contre) * 0.3 où fraction_qualite = overall / len(scores). Calculez ce verdict pour les trois arguments et vérifiez qu’il classe a_cout au-dessus d’a_expert. - Indice : profil.quality_score['overall'] / len(profil.quality_score['scores']), len(profil.fallacies), max(c['score'] for c in profil.counter_arguments). Le verdict aggregge est une décision de design — justifiez vos poids.

# Exercice 3 a completer
# TODO etudiant : verdict_global(profil) combine les dimensions, classer les 3 arguments.
print("Exercice 3 a completer : verdict aggregge pondere des 5 dimensions.")
Exercice 3 a completer : verdict aggregge pondere des 5 dimensions.

6. Ponts avec la serie

Direction Lien Relation
<-> Agentic-1-informal Detection de sophismes par taxonomie Couvre la dimension 1 (sophismes). Ce notebook la reunis avec les 4 autres dans le profil.
<-> Dung AF Semantics Sémantiques de Dung Voisin conceptuel — les frameworks Dung vivent dans dung_frameworks (comptes dans les statistiques globales) et ne sont pas projetes dans le profil par argument (seuls les verdicts PL/FOL de la dimension 5 y entrent).
<-> Restitution 3 Actes Restitution narrative Le profil est l’unite de base restituee dans le recit en 3 actes (un argument = un personnage).

7. Conclusion

ArgumentProfile est la vue projetee qui transforme un etat d’analyse multidimensionnel en une fiche exploitable par argument. Sans lui, chaque dimension vivrait en silo ; avec lui, un agent peut raisonner sur un argument précis en voyant d’un coup d’oeil ses forces et faiblesses sur les cinq axes.

Lecon centrale : les dimensions sont independantes. Un argument peut etre formellement valide mais rhétoriquement fallacieux ; clair mais mal fondé ; attaqué mais vrai. Le profil est le seul endroit ou cette indépendance devient visible — c’est pourquoi il est l’unite canonique de l’analyse argumentative, et le point d’ancrage de toute restitution narrative (cf Restitution 3 Actes).

Note d’honnetete (G.2) : ce notebook injecte les analyses deterministement (pas d’appel LLM) pour se concentrer sur la structure du profil. En production, les dimensions sont peuplées par les agents (detection de sophismes CamemBERT, scoring LLM, solveurs Tweety) — mais le contrat du profil, lui, est invariant.

Retour au sommet