Fin du QIF à la Société Générale

J'utilise GnuCash depuis une dizaine d'années pour tenir mes comptes. J'ai l'habitude d'importer le relevé des opérations au format QIF dans GnuCash. Or depuis la refonte du site Internet destiné aux particuliers, la Société Générale ne propose plus l'export du relevé des opération au format QIF. À la place, la banque au carré rouge et noir propose un fichier CSV contenant les opérations réalisées au cours des six derniers mois.

Contactée, la Société Générale m'a informé « ne pas pouvoir changer le format proposé par le site ».

Je constate qu'à l'image des GAFAM, la Société Générale cherche à capter les données de ses clients pour les exploiter et néglige leur partage avec ces mêmes clients qui en sont pourtant les propriétaires. Ainsi, le site Internet de la Société Générale propose d'importer des comptes externes, c'est-à-dire tenus dans d'autres établissements bancaires. Mais quiconque veut rapatrier ses données doit se contenter d'un fichier CSV réduit à la portion congrue.

Nous allons étudier les problèmes liés au fichier CSV fourni par la Société Générale et développer ensemble l'esprit d'équipe une solution pour faciliter son import dans GnuCash.

Du CSV à la française

Après avoir téléchargé le fichier CSV, observons ses propriétés :

$ file Export_17052019_16112019.csv
Export_17052019_16112019.csv: ISO-8859 text, with CRLF line terminators

Le fichier est encodé en ISO 8859 et les retours à la ligne sont matérialisés par deux octets CR+LF comme il est d'usage sous Windows.

Que contiennent donc nos données ?

$ head Export_17052019_16112019.csv
="0201900016400270";17/05/2019;16/11/2019;
date_comptabilisation;libellé_complet_operation;montant_operation;devise;
15/11/2019;CARTE X7527 15/11 METRO    ;-14,90;EUR;
13/11/2019;VIR RECU    8527975563S DE: Boulot SAS MOTIF: BOULOT OCTOBRE 2019 33 REF: BOULOT OCTOBRE 2019 33;2188,28;EUR;
11/11/2019;PRELEVEMENT EUROPEEN 7997325297 DE: DODO GESTION ID: FR65ZZZ233162 MOTIF: 081 LOYER DODO;-614,50;EUR;
10/11/2019;CARTE X7527 10/11 RESTO    ;-11,00;EUR;
09/11/2019;CARTE X7527 09/11 BRICO    ;-14,50;EUR;

Nous avons affaire à la variante française du CSV qui utilise le point-virgule pour délimiter les colonnes et la virgule pour partager la partie entière et la partie décimale d'un nombre.

La première ligne débute par le numéro de compte. La notation ="0123" est une astuce pour indiquer à Microsoft Excel de préserver le zéro au début d'un nombre. Suivent les dates de début et de fin des données exportées. Les dates sont exprimées au format français JJ/MM/AAAA.

La deuxième ligne contient les intitulés de colonnes qui sont donc la date de comptabilisation, le libellé de l'opération, son montant et la devise. Nous remarquons que la ligne se termine par un séparateur point-virgule inutile.

Nettoyer le fichier CSV avec Pandas

Après avoir observé les données, nous ouvrons notre boîte à outils d'apprenti Data Scientist. Nous choisissons Python et Pandas. Python est un langage de programmation qu'on ne présente plus : il est même enseigné au Lycée. Pandas est une bibliothèque Python destinée à la fouille de données. Python et Pandas, voilà une excellente compagnie pour le paresseux que je suis.

Charger

La bibliothèque Pandas fournit la fonction read_csv() avec de nombreux paramètres permettant de nettoyer le fichier CSV.

import pandas as pd

df = pd.read_csv('Export_17052019_16112019.csv',
                 sep=';', decimal=',', header=1,
                 parse_dates=True, dayfirst=True,
                 usecols=range(4), index_col=0,
                 names=['date', 'description', 'amount', 'currency'],
                 encoding='ISO-8859-1')

Parmi les paramètres :

  • sep et decimal adaptent le comportement de read_csv() pour la variante française du CSV.
  • header indique que la ligne des entêtes est la deuxième ligne (la première ligne ayant pour indice zéro).
  • dayfirst sélectionne le format de date européen où le jour apparaît avant le mois (les États-Unis fêtent Noël le 12/25).
  • index_col précise que la première colonne – la date – sert d'index.

La fonction read_csv() retourne un objet de type DataFrame. Voici son contenu affiché par Python :

>>> df
                                                  description   amount currency
date
2019-11-15                        CARTE X7527 15/11 METRO       -14.90      EUR
2019-11-13  VIR RECU    8527975563S DE: Boulot SAS MOTIF: ...  2188.28      EUR
2019-11-11  PRELEVEMENT EUROPEEN 7997325297 DE: DODO GESTI...  -614.50      EUR
2019-11-10                        CARTE X7527 10/11 RESTO       -11.00      EUR
2019-11-09                        CARTE X7527 09/11 BRICO       -14.50      EUR

Filtrer

La version précédente du site Internet de la Société Générale proposait d'exporter les transactions au format QIF depuis le dernier téléchargement. La nouvelle mouture propose un export au format CSV des transactions des six derniers mois.

Par conséquent, nous sommes désormais contraint de relever dans GnuCash la date de la dernière transaction déjà importée afin de filtrer les données CSV à importer et limiter les doublons.

La première colonne – la date – ayant été choisie comme index, l'opération se fait simplement avec un slice :

>>> df = df['2019-11-15':'2019-11-13']
>>> df
                                                  description   amount currency
date
2019-11-15                        CARTE X7527 15/11 METRO       -14.90      EUR
2019-11-13  VIR RECU    8527975563S DE: Boulot SAS MOTIF: ...  2188.28      EUR

Distinguer les dépôts et les retraits

Le CSV exporté depuis le site Internet de la Société Générale comprend une seule colonne montant. Or GnuCash exige de distinguer dépôt et retrait.

Nous devons donc ajouter deux colonnes au tableau et les remplir à partir du montant.

Pour une transaction donnée :

  • Si le montant est positif, il s'agit d'un dépôt. Nous copions la valeur du montant dans la colonne dépôt et nous écrivons zéro dans la colonne débit.
  • Si le montant est négatif, il s'agit d'un retrait. Nous copions la valeur absolue du montant dans la colonne retrait et nous écrivons zéro dans la colonne dépôt.

Avec Pandas, les colonnes dépôt et retrait peuvent être calculées à partir du montant de la manière suivante :

df['deposit']  = df['amount'].apply(lambda x: x if x > 0 else 0)
df['withdraw'] = df['amount'].apply(lambda x: abs(x) if x < 0 else 0)

En Python, le mot clé lambda définit une fonction anonyme. Dans la première instruction, la colonne dépôt est remplie en appliquant au montant la fonction qui pour x retourne x s'il est positif et 0 dans le cas contraire.

Voici à quoi ressemble notre tableau suite à l'ajout des colonnes dépôt et retrait :

                                                  description   amount currency  deposit  withdraw
date
2019-11-15                        CARTE X7527 15/11 METRO       -14.90      EUR     0.00      14.9
2019-11-13  VIR RECU    8527975563S DE: Boulot SAS MOTIF: ...  2188.28      EUR  2188.28       0.0

Exporter à destination de Gnucash

La méthode to_csv() permet de sauvegarder un DataFrame dans un fichier au format CSV.

df.to_csv('gnucash.csv',
          sep=';', decimal=',',
          columns=['description', 'deposit', 'withdraw'])

Seules sont exportées les colonnes utiles à GnuCash : date (implicite car il s'agit de l'index), description, dépôt, et retrait.

Notre fichier CSV « nettoyé » est prêt à être importé dans GnuCash.

Importer le fichier CSV final dans GnuCash

Dans l'interface graphique de GnuCash, nous cliquons sur : fichierimporterimporter des transactions depuis un CSV.

Après avoir sélectionné notre fichier CSV, GnuCash nous présente la fenêtre d'aperçu de l'import.

Aperçu de l'import dans GnuCash

Nous appliquons les réglages suivants :

  • Le point-virgule est défini comme séparateur de colonnes.
  • La première ligne qui correspond à l'entête est ignorée.
  • Le type des données de chaque colonne est précisé.
  • Un compte cible est renseigné pour l'ensemble des transactions (champ account).

Le wiki de GnuCash décrit plus en détail comment importer des transactions au format CSV.

Pour aller plus loin

Le script csv-socgen.py reprend l'ensemble du code présenté ici. Il ne s'agit que d'un premier jet.

Le script mérite d'être amélioré pour remplir automatiquement le compte destinataire et modifier la description si elle correspond à un motif donné. Ainsi, le compte destinataire de la transaction ayant pour description METRO serait automatiquement renseigné en Dépenses:Transports publics:Métro. Remarquons que la banque affiche une catégorie pour chaque transaction sur son site Internet mais que cette information ne figure pas dans le fichier CSV. À nous de ruser.

Un ancien slogan de la Société Générale clamait « On est là pour vous aider ». Cette ambition a disparu en 2019 avec un slogan devenu « C'est vous l'avenir ». Alors, ne comptez que sur vous-même pour tenir vos comptes !

Rénovo X240 Full HD

Après avoir réparé le clavier, voici venu le moment d'améliorer l'écran de mon Lenovo ThinkPad X240 pour le confort de mes petits yeux.

La lecture de ce fil de discussion sur Reddit m'a convaincu de remplacer l'écran LCD d'origine d'une résolution de 1366×768 pixels par un modèle Full HD plus récent atteignant 1920×1080 pixels.

J'ai opté pour le modèle d'écran LCD suivant: 00HN899. Je l'ai acheté sur eBay pour un peu moins de 80 euros et je l'ai reçu chez moi deux jours plus tard en provenance de Tallinn (Estonie).

Je me suis assuré que le câble qui relie la carte mère à l'écran est compatible Full HD : la mention FHD figure bien sur le câble et je n'ai donc pas eu besoin de changer le câble.

Câble LCD compatible Full HD (FHD)

L'écran LCD est maintenu par des clips sur ses bords et son démontage ne nécessite aucun outil particulier comme illustré dans cette vidéo.

Sous Debian, j'ai constaté que l'affichage était plus fin mais que les polices de caractères étaient bien trop petites pour être lisible. Par défaut, le serveur graphique X définit la résolution à 96×96 DPI. J'ai corrigé le problème en augmentant la résolution à 144×144 DPI dans le fichier ~/.Xresources. Il faut définir le paramètre Xft.dpi:

$ grep dpi ~/.Xresources
Xft.dpi: 144

Rénovo X240

J'ai récupéré un ordinateur portable Lenovo ThinkPad X240 pour lui donner une seconde jeunesse1. Au départ, il s'agissait simplement de changer le clavier qui était défectueux depuis qu'il avait été aspergé d'une tasse de café. Et puis la loi de Murphy a frappé une deuxième fois cette machine.

La gamme des ordinateurs ThinkPad est très répandue en entreprise ce qui explique qu'on trouve aisément des pièces de rechange. Après avoir suivi les instructions de cet excellent tutoriel vidéo pour changer le clavier, je démarrais l'ordinateur déjà fier de ma réparation. Aussitôt, j'entendis un léger claquement – Plop – accompagné par l'odeur âcre d'un composant électronique qui venait de brûler. Le message d'erreur suivant s'affichait à l'écran : Fan error. Ça ne sentait pas bon…

Effectivement, le ventilateur ne tournait plus ce qui interrompait la séquence de démarrage. J'essayais alors une astuce trouvée sur Internet : lors du démarrage, je soufflais fortement à travers la sortie d'aération du ventilateur pour le mettre en mouvement et faire croire au BIOS que le ventilateur fonctionnait. L'ordinateur démarrait bien mais le ventilateur demeurait immobile sous Linux ; je décidais sagement d'éteindre la machine pour éviter que son processeur ne surchauffe.

Le lendemain, je démontais le ventilateur pour le tester séparément. Ne disposant pas d'une source de courant continu de 5 volts, j'en bricolais une en coupant un câble USB. En effet, entre le fil rouge et le fil noir, la tension est de 5 volts. Ainsi alimenté, le ventilateur brassait l'air à plein régime.

En observant avec plus d'attention la carte mère, je remarquais une tâche noire à côté du connecteur du ventilateur. Je tenais le coupable : un composant électronique avait bel et bien grillé.

Fusible grillé en amont du ventilateur

À ce stade, je ne connaissais ni la nature ni les caractéristiques du composant qui avait grillé. Heureusement, un collègue trouvait sur ce forum les schémas électroniques du Lenovo X2402. À la page 50 du document, se trouvait la réponse à ma question : le défunt composant était un fusible qui pouvait supporter un courant d'une intensité de 2 ampères. Il a eu très chaud !

Schéma du connecteur du ventilateur

J'ai la chance de travailler dans l'électronique grand public et d'avoir des collègues électroniciens qui savent souder des composants montés en surface. Ils sont très adroits parce que les composants montés en surface sont minuscules (à titre d'exemple le fusible mentionné mesure 1 mm × 0.5 mm). Il n'aura fallu que cinq minutes à l'un de mes collègues pour remplacer le regretté fusible par un fil, faute de disposer d'un remplaçant3. Voici le résultat :

Remplacement du fusible grillé par un fil

Et le patient dans tout ça ? Il se porte bien puisque c'est depuis le Rénovo X240 que j'écris ces lignes.


  1. Merci Guillaume. 

  2. Merci Rémi. 

  3. Merci Jean-Luc. 

Rusticity: convert an integer to an enum

I am learning how to program in Rust. Thinking in Rust is a delightful experience and the more I practice Rust the more I feel how it empowers developers to solve complex problems with confidence.

However, I sometimes get frustrated on my way to rusticity. For instance, when a programming task easily done in C or Python requires more work in Rust. This happened to me not so long ago when I had to convert an integer to an enum. Let's see how this is usually done in C and how this can be done in Rust.

Converting an integer to an enum in C

In C, the enumeration constants have the type int. Thus, an integer value can be directly assigned to an enum.

#include <stdio.h>

enum atomic_number {
    HYDROGEN = 1,
    HELIUM = 2,
    // ...
    IRON = 26,
};

int main(void)
{
    enum atomic_number element = 26;

    if (element == IRON) {
        printf("Beware of Rust!\n");
    }

    return 0;
}

While it is easy to assign an integer value to an enum, the C compiler performs no bounds checking. Nothing prevents us from assigning an impossible value to an atomic_number enum.

Converting an integer to an enum in Rust, the naïve way

Let's write an equivalent program in Rust:

enum AtomicNumber {
    HYDROGEN = 1,
    HELIUM = 2,
    // ...
    IRON = 26,
}

fn main() {
    let element: AtomicNumber = 26;
}

When we try to compile and run the program with cargo run, the Rust compiler reports a mismatched types error:

error[E0308]: mismatched types
 --> src/main.rs:9:34
  |
9 |     let element: AtomicNumber = 26;
  |                                 ^^ expected enum `AtomicNumber`, found integral variable
  |
  = note: expected type `AtomicNumber`
             found type `{integer}`

The compiler error clearly indicates that AtomicNumber and integer are two different types.

To explicitly convert an integer to our AtomicNumber enum, we can write a conversion function that takes an unsigned 32-bits integer as parameter and returns an AtomicNumber.

enum AtomicNumber {
    HYDROGEN = 1,
    HELIUM = 2,
    // ...
    IRON = 26,
}

impl AtomicNumber {
    fn from_u32(value: u32) -> AtomicNumber {
        match value {
            1 => AtomicNumber::HYDROGEN,
            2 => AtomicNumber::HELIUM,
            // ...
            26 => AtomicNumber::IRON,
            _ => panic!("Unknown value: {}", value),
        }
    }
}

fn main() {
    let element = AtomicNumber::from_u32(26);
}

The from_u32() function is an associated function of the AtomicNumber type because it is defined only in the context of this type and unlike a method does not take a first parameter named self.

There are several issues in from_u32():

  • When the given value does not match any variant in the enumeration, the execution is aborted with panic!().
  • The integer value for each enumeration variant is duplicated in the enumeration definition and in the conversion function. We must take care to use the same value in both locations.
  • If the enumeration contains a large number of variants, the conversion function becomes very long.

Since there are more than 100 atomic numbers, implementing a conversion function quickly becomes boring. There should be a better way.

Converting an integer to an enum in Rust with num-derive

A more elegant solution is to use the FromPrimitive trait from the num crate coupled with syntax extensions from the num-derive crate.

In Cargo.toml, add dependencies for num, num-derive, and num-traits:

[dependencies]
num = "0.2"
num-derive = "0.3"
num-traits = "0.2"

Then, use the #[derive] attribute:

extern crate num;
#[macro_use]
extern crate num_derive;

#[derive(FromPrimitive)]
enum AtomicNumber {
    HYDROGEN = 1,
    HELIUM = 2,
    // ...
    IRON = 26,
}

fn main() {
    let element = num::FromPrimitive::from_u32(26);
    match element {
        Some(AtomicNumber::IRON) => println!("Beware of Rust!"),
        Some(_) => {},
        None => println!("Unknown atomic number")
    }
}

The #[derive(FromPrimitive)] attribute instructs the Rust compiler to generate a basic implementation of the FromPrimitive trait for the AtomicNumber enumeration. Unlike our handwritten from_u32() function, the conversion function generated by the Rust compiler returns an Option which is either Some atomic number or None if the given integer does not match any known atomic number. This is much safer than calling panic!().

With the #[derive(FromPrimitive)] attribute our Rust program is nearly as concise as the equivalent program written in C with the bonus of being safer.

Harder, better, safer, rustier

While I had some hard time figuring how to convert an integer value to an enum variant in Rust, I feel reassured by its type safety and pleased with how its ecosystem of crates can simplify my work as a programmer.

Rust on the pont de Bir-Hakeim

Rust on the pont de Bir-Hakeim

Le paquet targetcli-fb intègre Debian

Depuis quelques mois, le paquet targetcli-fb qui permet de configurer une target iSCSI avec LIO fait partie de Debian Testing (la version en cours de développement de Debian 9 "Stretch").

L'intégration de targetcli-fb vient combler un manque dans Debian. En effet, aucun utilitaire n'est fourni dans l'actuelle Debian Stable (Debian 8 "Jessie") pour configurer une target iSCSI avec LIO.

Je participe à la maintenance du paquet targetcli-fb dans Debian avec deux contributeurs plus expérimentés : Christian Seiler et Ritesh Raj Sarraf. J'utilise Debian depuis de nombreuses années et c'est un juste retour des choses que de contribuer modestement (et librement) au projet.

L'iSCSI sous Linux étant un sujet rarement traité sur le web francophone, j'évoquerai comment s'en servir dans un futur article.