# Architecture FabNum

## Vue d'ensemble

FabNum est une application Streamlit d'analyse de risques géopolitiques pour les chaînes d'approvisionnement numériques. Le projet permet de visualiser et d'analyser les vulnérabilités des chaînes d'approvisionnement à travers plusieurs indices de concentration et de criticité.

## Structure du projet

```
FabNum/
├── app/                        # Modules applicatifs Streamlit
│   ├── analyse/               # Analyse des chaînes d'approvisionnement
│   ├── fiches/                # Génération et gestion des fiches techniques
│   ├── ia_nalyse/             # Interface d'analyse IA
│   ├── personnalisation/      # Personnalisation du graphe
│   ├── plan_d_action/         # Analyse de criticité et recommandations
│   └── visualisations/        # Visualisations (graphes, diagrammes)
├── utils/                      # Utilitaires partagés
│   ├── gitea.py               # Intégration API Gitea
│   ├── graph_utils.py         # Manipulation des graphes NetworkX
│   ├── logger.py              # Système de logging
│   ├── persistance.py         # Gestion de la persistence (session Streamlit)
│   ├── translations.py        # Système de traduction i18n
│   ├── visualisation.py       # Visualisations Altair
│   └── widgets.py             # Widgets HTML personnalisés
├── assets/                     # Ressources statiques
├── tests/                      # Tests unitaires et d'intégration
│   ├── unit/                  # Tests unitaires
│   ├── integration/           # Tests d'intégration
│   └── conftest.py            # Configuration pytest et fixtures
├── config.py                   # Configuration globale
└── main.py                     # Point d'entrée Streamlit

```

## Modules principaux

### 1. Module `analyse` - Analyse des chaînes d'approvisionnement

**Responsabilité:** Analyse et visualisation des chemins d'approvisionnement depuis un produit vers les minerais critiques.

**Fichiers clés:**
- `interface.py` : Interface utilisateur principale
- `sankey.py` : Génération de diagrammes de Sankey pour visualiser les flux

**Données manipulées:**
- Graphe NetworkX des dépendances produit → composant → minerai
- Indices IHH, ISG, ICS, IVC pour chaque nœud

**Flux de données:**
1. Utilisateur sélectionne un produit ou composant (niveau 0 ou 1)
2. Extraction des chemins via `graph_utils.extraire_chemins_depuis()`
3. Génération du diagramme de Sankey
4. Affichage des vulnérabilités détectées

---

### 2. Module `fiches` - Génération de fiches techniques

**Responsabilité:** Génération dynamique de fiches markdown pour chaque élément du graphe (produits, composants, minerais).

**Architecture:**
```
fiches/
├── generer.py           # Génération des fiches markdown
├── interface.py         # Interface de visualisation des fiches
└── utils/
    ├── dynamic/         # Générateurs de sections dynamiques
    │   ├── indice/      # Sections pour IHH, ISG, ICS, IVC
    │   ├── assemblage_fabrication/  # Sections production
    │   ├── minerai/     # Sections spécifiques aux minerais
    │   └── utils/       # Utilitaires (pastilles, formatage)
    ├── fiche_utils.py   # Utilitaires génériques
    └── tickets/         # Gestion des tickets Gitea
        ├── core.py      # API Gitea (77% couverture)
        ├── display.py   # Affichage des tickets
        └── creation.py  # Création de tickets
```

**Flux de génération de fiches:**
1. Parcours du graphe pour identifier tous les nœuds
2. Pour chaque nœud, génération de sections markdown dynamiques :
   - Indicateurs de vulnérabilité (IHH, ISG)
   - Données de concentration (ICS, IVC)
   - Chemins critiques
   - Recommandations
3. Commit des fiches sur Gitea (dépôt `DEPOT_FICHES`)
4. Synchronisation avec le système de tickets

**Intégration Gitea:**
- Stockage centralisé des fiches markdown
- Système de tickets pour le suivi des vulnérabilités
- Labels automatiques : opération (Extraction, Traitement, Fabrication, Assemblage) + item (Minerai, Composant)

---

### 3. Module `plan_d_action` - Analyse de criticité

**Responsabilité:** Calcul de criticité des chaînes d'approvisionnement et génération de recommandations.

**Architecture:**
```
plan_d_action/
├── interface.py         # Interface utilisateur
└── utils/
    ├── data/
    │   ├── plan_d_action.py    # Logique métier de criticité
    │   ├── pda_interface.py    # Composants UI pour le plan d'action
    │   ├── data_processing.py  # Traitement des données
    │   ├── data_utils.py       # Utilitaires données
    │   └── config.py           # Configuration des seuils
    └── interface/
        ├── selection.py         # Sélection des chaînes
        ├── visualization.py     # Visualisations
        ├── export.py            # Export des résultats
        └── ...
```

**Calcul de criticité:**

La criticité d'une chaîne est calculée selon les poids de vulnérabilité à chaque étape :

```python
def calcul_poids_chaine(
    poids_A: int,  # Assemblage (0-3)
    poids_F: int,  # Fabrication (0-3)
    poids_T: int,  # Traitement (0-3)
    poids_E: int,  # Extraction (0-3)
    poids_M: int   # Substitution minerai (0-3)
) -> tuple[str, dict, int]:
    """
    Retourne: (criticite_chaine, niveau_criticite, poids_total)

    Criticité:
    - "Très critique" : poids_total >= 12
    - "Critique" : 9 <= poids_total < 12
    - "Moyenne" : 6 <= poids_total < 9
    - "Faible" : poids_total < 6
    """
```

**Seuils de vulnérabilité** (définis dans `config.yaml`) :
- **IHH** (concentration géographique/acteurs) : < 15 (vert), 15-25 (orange), > 25 (rouge)
- **ISG** (instabilité géopolitique) : < 40 (vert), 40-70 (orange), > 70 (rouge)
- **ICS** (criticité supply-side) : < 15 (vert), 15-60 (orange), > 60 (rouge)
- **IVC** (vulnérabilité demand-side) : < 15 (vert), 15-60 (orange), > 60 (rouge)

**Tableau de bord:**
La fonction `tableau_de_bord()` permet de :
1. Sélectionner une chaîne d'approvisionnement
2. Afficher la criticité globale et par étape
3. Présenter les préconisations spécifiques et génériques
4. Exporter le plan d'action en markdown

---

### 4. Module `utils` - Utilitaires partagés

#### `gitea.py` - Intégration Gitea (100% couverture)

**API principale:**
```python
def charger_instructions_depuis_gitea(nom_fichier: str) -> str | None:
    """Charge un fichier depuis Gitea avec cache local basé sur timestamp."""

def charger_schema_depuis_gitea(fichier_local: str) -> str | None:
    """Charge le fichier DOT du graphe depuis Gitea."""

def charger_arborescence_fiches() -> dict:
    """Charge l'arborescence complète des fiches markdown."""
```

**Fonctionnalités:**
- Cache local avec vérification de timestamp (évite les téléchargements inutiles)
- Authentification automatique via `GITEA_TOKEN`
- Gestion des erreurs réseau avec fallback sur le cache

#### `graph_utils.py` - Manipulation de graphes (59% couverture)

**Fonctions principales:**
```python
def extraire_chemins_depuis(G: nx.DiGraph, noeud_depart: str) -> list[list[str]]:
    """Extrait tous les chemins depuis un nœud vers les feuilles."""

def extraire_chemins_vers(G: nx.DiGraph, noeud_cible: str, niveau_demande: int) -> list[list[str]]:
    """Extrait les chemins vers un nœud cible depuis le niveau demandé."""

def recuperer_donnees(graph: nx.DiGraph, noeuds: list[str]) -> pd.DataFrame:
    """Récupère les données IHH/ICS pour des nœuds operation-minerai."""

def recuperer_donnees_2(graph: nx.DiGraph, minerais: list[str]) -> list[dict]:
    """Récupère les données IVC/IHH pour les minerais (extraction + réserves)."""

def charger_graphe(dot_file: str = "schema_temp.txt") -> nx.DiGraph:
    """Charge le graphe depuis un fichier DOT."""
```

**Structure du graphe:**
- **Niveaux:**
  - 0 : Produits finaux
  - 1 : Composants
  - 2 : Minerais
  - 10 : Opérations (Assemblage, Fabrication, Traitement, Extraction, Réserves)
  - 11 : Pays d'opération
  - 99 : Pays géographiques

- **Attributs de nœuds:**
  - `niveau` : Niveau hiérarchique
  - `ihh_pays`, `ihh_acteurs` : Indices de concentration
  - `isg` : Indice de stabilité géopolitique
  - `ivc` : Indice de vulnérabilité (minerais)
  - `ics` : Indice de criticité supply-side (arêtes)

#### `persistance.py` - Gestion de session (0% couverture)

Gère la persistence d'état via `st.session_state` :
```python
def get_session_id() -> str:
    """Récupère l'ID de session Streamlit."""

def update_session_paths(session_id: str, paths: list):
    """Met à jour les chemins en session."""

def get_champ_statut(cle: str) -> Any:
    """Récupère une valeur du session_state."""
```

#### `logger.py` - Système de logging (94% couverture)

```python
def setup_logger(name: str, level: str = "INFO", log_to_file: bool = False) -> logging.Logger:
    """Configure un logger avec handler console et optionnellement fichier."""

def get_logger(name: str) -> logging.Logger:
    """Récupère ou crée un logger."""
```

---

## Flux de données principal

### 1. Chargement initial
```
main.py
  → config.py (variables d'environnement)
  → utils/gitea.charger_schema_depuis_gitea()
  → utils/graph_utils.charger_graphe()
  → Graphe NetworkX en st.session_state
```

### 2. Navigation utilisateur (module Analyse)
```
app/analyse/interface.py
  → Sélection produit/composant
  → graph_utils.extraire_chemins_depuis()
  → analyse/sankey.py pour visualisation
  → Affichage des vulnérabilités (IHH, ICS, IVC)
```

### 3. Génération de fiches (module Fiches)
```
app/fiches/generer.py
  → Parcours du graphe
  → Pour chaque nœud:
      → utils/dynamic/indice/*.py (génération sections)
      → utils/dynamic/minerai/minerai.py (sections minerai)
  → Commit vers Gitea (DEPOT_FICHES)
  → Création/mise à jour tickets
```

### 4. Plan d'action (module Plan d'action)
```
app/plan_d_action/interface.py
  → utils/data/plan_d_action.analyser_chaines()
  → Calcul des poids A/F/T/E/M
  → Détermination de la criticité
  → utils/data/plan_d_action.tableau_de_bord()
  → Affichage préconisations et export
```

---

## Configuration et environnement

### Fichiers de configuration

**`config.py`** - Configuration globale:
```python
GITEA_URL = os.getenv("GITEA_URL")
GITEA_TOKEN = os.getenv("GITEA_TOKEN")
ORGANISATION = os.getenv("ORGANISATION")
DEPOT_FICHES = os.getenv("DEPOT_FICHES")
DEPOT_CODE = os.getenv("DEPOT_CODE")
DOT_FILE = os.getenv("DOT_FILE")
ENV = os.getenv("ENV")  # Branche Gitea (dev/main)
```

**`config.yaml`** - Seuils de vulnérabilité:
```yaml
seuils:
  IHH:
    vert: {max: 15}
    orange: {min: 15, max: 25}
    rouge: {min: 25}
  ISG:
    vert: {max: 40}
    orange: {min: 40, max: 70}
    rouge: {min: 70}
  ICS:
    vert: {max: 15}
    orange: {min: 15, max: 60}
    rouge: {min: 60}
  IVC:
    vert: {max: 15}
    orange: {min: 15, max: 60}
    rouge: {min: 60}
```

### Variables d'environnement requises

- `GITEA_URL` : URL de l'instance Gitea
- `GITEA_TOKEN` : Token d'authentification API
- `ORGANISATION` : Organisation Gitea
- `DEPOT_FICHES` : Nom du dépôt pour les fiches markdown
- `DEPOT_CODE` : Nom du dépôt contenant le graphe DOT
- `DOT_FILE` : Nom du fichier DOT du graphe
- `ENV` : Branche Gitea à utiliser (dev/main)

---

## Tests

### Couverture actuelle

**Couverture globale:** 16%

**Modules testés:**
- `utils/gitea.py` : 100% ✓
- `utils/widgets.py` : 100% ✓
- `utils/logger.py` : 94% ✓
- `app/fiches/utils/tickets/core.py` : 77% ✓
- `utils/graph_utils.py` : 59%

### Structure des tests

```
tests/
├── conftest.py                 # Fixtures globales
│   ├── simple_graph()         # Graphe simple pour tests
│   ├── complex_graph()        # Graphe avec chemins multiples
│   └── sample_config_yaml()   # Config YAML de test
├── unit/
│   ├── test_gitea.py          # Tests utils/gitea.py
│   ├── test_fiches_tickets.py # Tests app/fiches/utils/tickets/core.py
│   ├── test_graph_utils.py    # Tests utils/graph_utils.py
│   ├── test_logger.py         # Tests utils/logger.py
│   └── test_widgets.py        # Tests utils/widgets.py
└── integration/
    └── (à venir)
```

### Exécuter les tests

```bash
# Tous les tests
pytest -v

# Avec couverture
pytest --cov=utils --cov=app --cov-report=html

# Tests spécifiques
pytest tests/unit/test_gitea.py -v
```

---

## Dépendances principales

**Core:**
- `streamlit` : Framework web
- `networkx` : Manipulation de graphes
- `pandas` : Manipulation de données
- `altair` : Visualisations
- `pydot` : Parsing de fichiers DOT

**API:**
- `requests` : Requêtes HTTP pour Gitea
- `python-dateutil` : Parsing de dates ISO

**Tests:**
- `pytest` : Framework de tests
- `pytest-cov` : Couverture de code
- `pytest-mock` : Mocking

**Qualité de code:**
- `ruff` : Linter et formatter Python moderne

---

## Conventions de code

### Style
- **Formatage:** Ruff (line-length: 120)
- **Docstrings:** Google style
- **Langue:** Français pour les docstrings et l'UI, anglais pour le code

### Nommage
- **Fonctions:** `snake_case`
- **Classes:** `PascalCase`
- **Constantes:** `UPPER_SNAKE_CASE`
- **Fichiers:** `snake_case.py`

### Organisation des imports
```python
# 1. Standard library
import os
from datetime import datetime

# 2. Third-party
import pandas as pd
import streamlit as st

# 3. Local
from config import GITEA_URL
from utils.graph_utils import charger_graphe
```

---

## Roadmap et améliorations futures

### Tests
- [ ] Augmenter la couverture de `app/plan_d_action` (actuellement 0%)
- [ ] Tests d'intégration pour les flux complets
- [ ] Tests de performance pour les graphes volumineux

### Documentation
- [x] Documentation d'architecture (ce fichier)
- [ ] Guide utilisateur
- [ ] API documentation (docstrings complètes)
- [ ] Diagrammes de séquence pour les flux critiques

### Fonctionnalités
- [ ] Export des analyses en PDF
- [ ] Alertes automatiques sur nouvelles vulnérabilités
- [ ] Comparaison historique des indices
- [ ] Interface d'administration pour gérer les seuils

### Infrastructure
- [ ] CI/CD avec Gitea Actions
- [ ] Déploiement automatique
- [ ] Monitoring et logging centralisé

---

## Contact et contribution

Ce projet est développé par Stéphan Peccini Conseil dans le cadre de l'Observatoire des Polycrises.

Pour contribuer ou signaler des bugs, veuillez consulter le dépôt Gitea de l'organisation.
