Home » AI » Comment créer un assistant financier IA local avec Python ?

Comment créer un assistant financier IA local avec Python ?

Un assistant financier IA local combine un pipeline Python de normalisation, des modèles ML (ex. Isolation Forest) et un LLM local (Ollama) pour analyser relevés et générer explications tout en gardant vos données sur votre machine. Je détaille l’architecture, le pipeline, le ML et l’intégration LLM.

Pourquoi construire un assistant financier IA local

La confidentialité des données bancaires n’est pas un détail technique : c’est une exigence juridique et une confiance client. Les relevés, les habitudes de dépense et les soldes forment un profil financier extrêmement sensible qui, s’il fuit ou est mal utilisé, entraîne des risques de fraude, d’usurpation d’identité et des conséquences réglementaires.

Les applications cloud centralisées facilitent l’accès et l’échelle, mais elles imposent l’envoi systématique de CSV et d’identifiants vers des serveurs tiers. Cette logique expose aux mauvaises configurations, aux fuites via des API et aux politiques commerciales imprévues concernant l’utilisation des données. Je privilégie un assistant local quand l’objectif principal est de minimiser ces vecteurs de risque et de garder un contrôle total sur les pipelines et les modèles.

Les modèles de langage locaux deviennent pratiques grâce à des solutions comme Ollama, qui simplifient le déploiement d’LLM sur machine locale ou serveur privé. Pour la détection d’anomalies sur transactions, des méthodes classiques et robustes existent, comme Isolation Forest (Liu et al., 2008), efficace pour isoler les transactions atypiques sans supervision exhaustive.

Critères de conception recherchés :

  • Traitement 100% local : Aucun envoi de données sensibles vers le cloud afin de préserver la confidentialité et la conformité.
  • Upload instantané de CSV : Interface simple pour importer rapidement des exports bancaires au format CSV sans étapes manuelles compliquées.
  • Explications en langage naturel : Résumés et justification des décisions en français clair pour rendre l’analyse accessible non technique.
  • Visualisations interactives : Graphiques et filtres pour explorer les dépenses, catégories et anomalies en temps réel.
  • Modularité du pipeline : Composants séparés pour ingestion, nettoyage, détection d’anomalies et génération de texte afin de faciliter mises à jour et audits.
Approche Avantages Contraintes
Cloud tout-en-un Déploiement rapide, scalabilité, modèles gérés et mises à jour automatiques. Confidentialité limitée, coûts récurrents, dépendance au fournisseur.
Hybride Équilibre entre performance cloud et contrôle local des données sensibles. Complexité d’architecture, nécessité de règles de routage des données.
100% local Confidentialité maximale, coûts prévisibles, contrôle total du modèle et des données. Requiert ressources machines, gestion des mises à jour et expertise DevOps.

Quelle architecture pour le projet

Réponse: Une architecture modulaire avec couches: interface (Streamlit), preprocessing, ML, visualisations et intégration LLM (Ollama) est adaptée.

App.py gère l’interface utilisateur via Streamlit, les formulaires d’upload, les contrôles de taille/filtres et orchestre les appels vers le preprocessing, les modèles ML, les visualisations et l’intégration LLM en streaming.

Config.py centralise les paramètres: endpoints Ollama locaux, port, chemins de stockage temporaire, seuils ML et options de logging, afin d’éviter les valeurs codées en dur et faciliter les tests.

Preprocessing.py détecte automatiquement les formats CSV (délimiteurs, encodage, présence d’en-têtes), normalise les types (dates, montants), gère les valeurs manquantes et produit un DataFrame prêt pour le ML.

Ml_models.py encapsule la logique de classification (ex: catégorisation des transactions) et de détection d’anomalies (ex: IsolationForest), expose des fonctions fit/predict et conserve les artefacts avec joblib pour réutilisation.

Visualizations.py fournit des fonctions Plotly réutilisables pour séries temporelles, heatmaps et distributions, retournant des composants embeddables par Streamlit pour une exploration interactive.

Llm_integration.py gère les échanges avec Ollama en streaming, construit le contexte utile (prompts + extraits de données agrégées), envoie les requêtes au modèle local et délivre la réponse token par token pour UX réactive.

Dossier sample_data contient exemples CSV annotés et README explique le démarrage, les endpoints et les formats attendus.

app.py Interface Streamlit, upload, contrôles et orchestration.
config.py Paramètres, endpoints Ollama, chemins et seuils.
preprocessing.py Détection auto CSV, parsing, normalisation et validation.
ml_models.py Classification, anomalies, entraînement et persist.
visualizations.py Fonctions Plotly pour graphiques interactifs.
llm_integration.py Client Ollama local, streaming et gestion du contexte.
sample_data/ README.md Exemples et instructions de démarrage.

Étape 1: L’utilisateur charge un CSV via Streamlit et app.py vérifie le format et la taille.

Étape 2: Preprocessing normalise le DataFrame, détecte dates/monnaies et retourne un dataset propre.

Étape 3: Ml_models exécute classification et détection d’anomalies et renvoie scores et labels.

Étape 4: Visualizations génère graphiques interactifs affichés dans l’UI pour validation humaine.

Étape 5: Llm_integration construit un prompt concis (contexte + extraits agrégés) et envoie en streaming à Ollama.

Étape 6: Le modèle local répond en flux; app.py affiche la réponse et propose actions (export, rapport).

Exigences techniques: Python 3.10+, bibliothèques clés pandas, scikit-learn, plotly, streamlit, joblib et requests ou client Ollama.

Conseils sécurité: Restreindre les permissions fichiers, limiter la taille d’upload, chiffrer le stockage local des données sensibles, éviter les logs de données financières en clair et exécuter Ollama en environnement isolé avec firewall.

Comment concevoir un pipeline de prétraitement robuste

Normaliser les relevés dès l’import est essentiel: détecter colonnes, fusionner débit/crédit, parser montants et dates.

Commencez par détecter automatiquement les trois champs standard {date, description, amount} en combinant détection par nom de colonne (expressions régulières) et vérification des échantillons de données. Voici la logique pas à pas (pseudocode) :

Algorithme detect_columns(df):
  1. Préparer motifs regex pour date, description, amount, debit, credit.
  2. Pour chaque colonne:
       a. Tester le nom de colonne avec chaque motif.
       b. Si ambiguïté, échantillonner 10 valeurs:
           - Si >60% parseables en date => classer date.
           - Si >60% numériques => classer amount candidate.
           - Si mix debit/credit (valeurs négatives et positives) => amount.
  3. Si colonne debit et colonne credit détectées => prévoir fusion.
  4. Retourner mapping vers {date, description, amount} et actions (fusion si nécessaire).

Exemple Python concret pour detect_columns(df) qui teste motifs regex et renvoie le mapping :

import re
import pandas as pd
import numpy as np
from dateutil import parser

DATE_PATTERNS = [r'date', r'date[_\s]transaction', r'dt']
DESC_PATTERNS = [r'description', r'libell', r'label', r'merchant', r'payee']
AMOUNT_PATTERNS = [r'amount', r'montant', r'transaction[_\s]amt', r'total']
DEBIT_PATTERNS = [r'debit', r'dbt', r'déb?it']
CREDIT_PATTERNS = [r'credit', r'cr', r'crédit']

def is_date_like(val):
    try:
        parser.parse(str(val), dayfirst=True)
        return True
    except Exception:
        return False

def is_numeric_like(val):
    try:
        float(str(val).replace(' ', '').replace(',', '.').replace('(', '-').replace(')', ''))
        return True
    except:
        return False

def detect_columns(df):
    mapping = {}
    for col in df.columns:
        name = col.lower()
        assigned = False
        for pat in DATE_PATTERNS:
            if re.search(pat, name):
                mapping['date'] = col; assigned=True; break
        if assigned: continue
        for pat in DESC_PATTERNS:
            if re.search(pat, name):
                mapping['description'] = col; assigned=True; break
        if assigned: continue
        for pat in AMOUNT_PATTERNS+DEBIT_PATTERNS+CREDIT_PATTERNS:
            if re.search(pat, name):
                mapping.setdefault('amount_candidates', []).append(col)
                assigned=True; break
        if not assigned:
            # probe values
            sample = df[col].dropna().astype(str).head(10)
            if sample.apply(is_date_like).sum() >= 6:
                mapping['date'] = col
            elif sample.apply(is_numeric_like).sum() >= 6:
                mapping.setdefault('amount_candidates', []).append(col)
    # Resolve amount: if debit & credit present, will merge
    if 'amount_candidates' in mapping:
        if len(mapping['amount_candidates'])==1:
            mapping['amount']=mapping['amount_candidates'][0]
        else:
            # prefer combined debit/credit => create merged amount
            mapping['amount']='__merge__:'+','.join(mapping['amount_candidates'])
    return mapping

Exemple de normalisation pour fusionner debit/credit et parser montants :

def normalize_amount(val):
    if pd.isna(val): return np.nan
    s = str(val).strip()
    s = s.replace(' ', '').replace("'",'').replace('€','')
    if s.startswith('(') and s.endswith(')'):
        s = '-' + s[1:-1]
    s = s.replace(',','.')  # gestion séparateur décimal
    try:
        return float(s)
    except:
        return np.nan

# Fusion si debit/credit
df['amount'] = np.nan
if mapping.get('amount','').startswith('__merge__'):
    cols = mapping['amount'].split(':',1)[1].split(',')
    # Supposer debit et credit; crédit positif, débit négatif
    df['amount'] = df[cols[0]].apply(normalize_amount).fillna(0) - df[cols[1]].apply(normalize_amount).fillna(0)
else:
    df['amount'] = df[mapping['amount']].apply(normalize_amount)

Dates: parser.parse(date_string, dayfirst=True) avec fallback vers formats explicites comme %Y-%m-%d, %d/%m/%Y si exception.

Créer des features basiques permet des modèles plus robustes: extraction MCC approximé via texte de description (règles ou embeddings légers), regroupement de libellés (hashing ou clustering), jour/semaine/mois (datetime.dt.day, .week, .month), montant absolu (abs(amount)), et fenêtres glissantes (rolling 7/30/90 jours: somme, moyenne, count).

Colonne standard Type attendu Exemples
Date Date / datetime 2026-03-15, 15/03/2026
Description Texte Boulangerie Dupont, AMAZON MARKETPLACE
Amount Float (dépense négative) -23.50, 1500.00
Amount_abs Float 23.50, 1500.00

Quels modèles ML pour catégoriser et détecter anomalies

Pour petits jeux de données, privilégiez des modèles simples et robustes: RandomForest/Logistic pour classification de catégories et IsolationForest pour anomalies.
RandomForest est recommandé pour sa robustesse et sa capacité à gérer des features hétérogènes (numériques, catégoriques encodées) sans beaucoup de pré-traitement; Logistic Regression ou Ridge servent d’baseline rapide, interprétable et stable quand les données sont faibles. IsolationForest est adapté pour la détection d’anomalies car il isole les observations rares via des arbres aléatoires (Liu, Ting, Zhou, 2008).
Je fournis ci‑dessous des exemples concrets à intégrer dans ml_models.py.

# Exemples pour ml_models.py
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier, IsolationForest
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.metrics import f1_score, make_scorer
import numpy as np

# Pipeline pour RandomForest (baseline robuste)
numeric_transform = Pipeline([('imputer', SimpleImputer(strategy='median')),
                             ('scaler', StandardScaler())])
# Adapter categorical si nécessaire (exemple)
preproc = ColumnTransformer([('num', numeric_transform, numeric_cols),
                             ('cat', OneHotEncoder(handle_unknown='ignore'), categorical_cols)])

rf_pipeline = Pipeline([('preproc', preproc),
                        ('clf', RandomForestClassifier(n_estimators=100, max_depth=None, random_state=42))])

# Cross-validation stratifiée avec F1
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
f1 = make_scorer(f1_score, average='weighted')
scores = cross_val_score(rf_pipeline, X, y, cv=cv, scoring=f1)
print('F1 CV mean:', np.mean(scores))

# IsolationForest pour anomalies
iso = IsolationForest(n_estimators=100, contamination='auto', random_state=42)
iso.fit(X_train)  # X_train pré-traité numériquement
scores_iso = iso.score_samples(X_val)  # score plus élevé = plus normal
threshold = np.percentile(scores_iso, 5)  # seuil au 5ème percentile
anomalies = scores_iso < threshold

Validation raisonnée pour petits jeux de données.
Privilégiez Stratified K‑Fold pour conserver la distribution des classes. Regroupez catégories rares (data augmentation par regroupement), ou utilisez bootstrapping pour estimer variance des métriques. Évaluez seuils d’IsolationForest via courbes PR et validation humaine.

Conseils pratiques pour anomalies.
Ajustez le paramètre contamination ou choisissez un seuil percentile sur score_samples pour contrôler les faux positifs. Conservez une boucle de feedback humain pour labelliser faux positifs et réentraîner (active learning). Envisagez un vote d’ensembles (isolation + LOF) si le coût des faux positifs est élevé.

Modèle Usage Avantages Inconvénients
RandomForest Classification catégories Robuste, gère features hétérogènes, peu d'optim Moins interprétable, overfit si très petit jeu
Logistic / Ridge Baseline, cas linéaire Simple, interprétable, stable Performances limitées si relations non linéaires
IsolationForest Détection d'anomalies Conçu pour anomalies, scalable Sensible au choix du seuil, faux positifs possibles

Comment intégrer un LLM local et produire des visualisations interactives

Intégrer un LLM local (ex. Ollama) en streaming permet générer explications naturelles sans quitter la machine, et Plotly produit visualisations interactives embarquées dans Streamlit.

Pour l'intégration technique, envoyer au modèle un prompt qui contient un résumé des transactions, des embeddings/metadata (IDs, catégories, montants, timestamps) et récupérer la réponse en streaming permet une interface réactive.

Exemple de prompt envoyé au LLM :

Veuillez résumer les dépenses mensuelles à partir des transactions ci‑dessous.
Fournissez :
1) Total par catégorie pour le mois.
2) Liste des anomalies avec leurs IDs.
3) Recommandations d'optimisation (3 actions).
4) Explication courte (2-3 phrases) pour la transaction suspecte ID=txn_12345.

Transactions (JSON avec embeddings/metadata) : [{ "id":"txn_12345", "date":"2026-03-12", "amount":-1200, "category":"Abonnement", "merchant":"ServiceX", "embedding":[...]} ...]

Snippet Python minimal utilisant l'API Ollama en streaming (requests) :

import requests
url = "http://localhost:11434/api/generate"
payload = {"model":"ollama-model", "prompt":"", "stream":True}
with requests.post(url, json=payload, stream=True) as r:
    for line in r.iter_lines(decode_unicode=True):
        if line:
            # Chaque ligne contient un jeton ou chunk JSON selon le modèle
            print(line)

Pour Plotly, créer : camembert par catégories, histogramme des montants, timeline (scatter time vs amount), heatmap horaire (heure x jour).

Exemple Streamlit + Plotly et export PNG :

import streamlit as st
import plotly.express as px
from io import BytesIO

fig = px.pie(df, names='category', values='amount', title='Dépenses par catégorie')
st.plotly_chart(fig, use_container_width=True)

# Export PNG (kaleido requis)
buf = BytesIO()
fig.write_image(buf, format='png', engine="kaleido")
st.download_button("Télécharger PNG", data=buf.getvalue(), file_name="pie.png", mime="image/png")

Feuille de route de déploiement local rapide :

  • Installer Ollama et télécharger le modèle local.
  • Installer dépendances Python : pip install streamlit plotly requests kaleido.
  • Lancer Streamlit : streamlit run app.py.
  • Configurer sécurité locale : firewall, droits d'accès, chiffrement des données sensibles.
Composant UI Rôle
Zone Conversation LLM Affiche résumé, anomalies, recommandations en streaming
Camembert Vue partagée dépenses par catégorie
Timeline Suivi chronologique des transactions
Heatmap Horaire Révèle pics d'activité selon heure/jour

Prêt à déployer votre assistant financier IA local pour garder le contrôle de vos données ?

Le parcours complet va du nettoyage automatique des CSV à la détection d'anomalies et à la génération d'explications en langage naturel via un LLM local. En standardisant tôt les formats, en privilégiant des modèles simples adaptés aux petits jeux de données et en intégrant Ollama pour le NL, vous obtenez un assistant privé, interactif et actionnable. Bénéfice direct: analyses exploitables sans exposer vos relevés au cloud.

FAQ

Le traitement local garantit-il vraiment la confidentialité ?
Le traitement local évite l'envoi des relevés vers des serveurs tiers, réduisant significativement les risques d'exposition. Il reste nécessaire de sécuriser l'accès au poste, chiffrer les fichiers au repos et limiter les permissions applicatives.
Quels formats bancaires sont supportés ?
Le pipeline détecte automatiquement les CSV hétérogènes (colonnes différentes, débit/crédit séparés) via regex et mapping. Les exports CSV communs de banques nationales sont pris en charge; pour formats spécifiques, ajouter un mapping dans preprocessing.py suffit.
Quelle précision pour la détection d'anomalies ?
L'Isolation Forest est efficace sur anomalies isolées mais dépend du volume et de la qualité des features. Pour petits jeux de données, combinez règles simples (seuils, heuristiques) et Isolation Forest, et validez manuellement les premiers résultats pour ajuster le seuil.
Quels prérequis pour exécuter le projet localement ?
Python 3.9+ (ou 3.10+ recommandé), bibliothèques pandas, scikit-learn, plotly, streamlit et un runtime LLM local comme Ollama. Assurez-vous d'avoir suffisamment de RAM pour le modèle LLM choisi et espace disque pour les données.
Peut-on personnaliser les catégories de dépenses ?
Oui. Le modèle de classification accepte des libellés personnalisés: enrichissez les jeux d'entraînement avec vos catégories, utilisez des règles de mapping lexical dans le preprocessing et ré-entraînez le modèle pour améliorer la précision.

 

 

A propos de l'auteur

Franck Scandolera — expert & formateur en Tracking avancé server-side, Analytics Engineering, Automatisation No/Low Code (n8n) et intégration d'IA en entreprise. Responsable de l'agence webAnalyste et de l'organisme de formation Formations Analytics. Références clients: Logis Hôtel, Yelloh Village, BazarChic, Fédération Française de Football, Texdecor. Dispo pour aider les entreprises => contactez-moi.

Retour en haut
Vizyz