Maison > développement back-end > Tutoriel Python > Codage du week-end : transformez les fiches de paie PDF en un seul rapport CSV

Codage du week-end : transformez les fiches de paie PDF en un seul rapport CSV

Susan Sarandon
Libérer: 2024-12-25 22:20:17
original
119 Les gens l'ont consulté

Avez-vous déjà programmé avec des fichiers PDF ? Écrivez un script Python avec moi !

Weekend Coding: Turn PDF Payslips Into a Single CSV Report

Comme vous le verrez dans les prochains articles du blog, je suis dans mon ère de littératie financière. À l’approche de la fin de l’année, j’avais envie de regarder mes chiffres : combien d’impôts ai-je payé ? Combien ai-je gagné pour les quarts de garde ? Plusieurs fichiers PDF ne constituent pas le moyen le plus confortable de visualiser ces données, et je voulais un seul fichier CSV avec lequel je pourrais jouer dans Excel.

Comme beaucoup de bons développeurs, j'étais trop paresseux pour insérer les chiffres manuellement, alors j'ai écrit un script. Si vous aimez la programmation, rejoignez-moi dans une aventure ! Et si vous n'êtes pas d'humeur, je vais vous montrer comment modifier le code pour qu'il corresponde à la structure de votre fiche de paie :D

Weekend Coding: Turn PDF Payslips Into a Single CSV Report
This script receives a directory with payslip PDFs and returns a CSV file with the desired data

Le plan

main.py:
# translate 1 pdf to 1 dict
# loop over the pdf dir
# save all dicts to 1 json file
# translate json report to csv report
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous commencerons par écrire le code pour lire un PDF, notamment en décidant quels champs nous voulons dans notre rapport. C’est la partie que vous devrez modifier pour qu’elle corresponde à la structure de votre fiche de paie. Une fois cela compris, nous parcourrons tout le répertoire des fiches de paie.

Dans la troisième étape, j'ai choisi d'ajouter une étape supplémentaire entre le PDF et le CSV : un rapport JSON. Une fois que nous verrons que tout fonctionne, nous supprimerons l’utilisation de ce fichier.

Enfin, nous traduirons ces données JSON en un fichier CSV. Ce CSV peut ensuite être facilement converti en Google Sheets (cliquez simplement sur "Ouvrir avec") ou en Excel (les instructions peuvent être trouvées ici).

C'est un plan simple et sympa, mais vous savez comment ça se passe : des défis sont découverts en cours de route... Pouvez-vous deviner où les choses pourraient devenir complexes ?

Avant de commencer, une remarque importante : GARDEZ VOS BULLETIN DE PAIE PRIVÉS ! Si vous téléchargez votre projet sur GitHub, assurez-vous de ne pas partager ces informations personnelles ! Vous pouvez utiliser .gitignore pour cela :

/payslips_pdf
pdf_rows.txt
report.json
report.csv
Copier après la connexion
Copier après la connexion

On commence ?

Weekend Coding: Turn PDF Payslips Into a Single CSV Report

Lire le fichier PDF

Nous allons commencer par lire le PDF et imprimer toutes les lignes. De cette façon, nous saurons ce qui apparaît dans chaque ligne. Cela ne doit être fait qu'une seule fois (alors qu'un rapport sera probablement créé une fois par mois ou une fois par an), et cela ne fait pas partie du rapport - nous allons donc le créer dans un fichier séparé.

Commencez par créer un nouveau fichier Python (j'ai appelé le mien pdf_to_txt.py) et écrivez une fonction qui lit le pdf et imprime le résultat dans un fichier .txt :



Cela fonctionnera, mais il y a 3 changements pour le rendre plus convivial :
  • Obtenez le chemin du fichier comme argument de ligne de commande, afin que l'utilisateur puisse l'exécuter sans toucher au code.
  • Ajoutez une détection d'erreurs avec des instructions au cas où l'utilisateur exécute mal la commande. (J'ai ajouté une couleur d'avertissement mais ce n'est pas obligatoire)

  • Nous lirons également les fichiers PDF dans le script principal, il est donc préférable de déplacer cette fonction là-bas.

Essayez-le ! Essayez d'exécuter la commande avec les bons et les mauvais arguments :)


Lorsque vous exécutez py ./main.py, vous verrez un nouveau fichier dans le répertoire du projet appelé pdf_rows.txt.
<script></script> <script></script> <script></script>
(Ne vous inquiétez pas, ce ne sont pas mes données – elles sont basées sur l'exemple de fiche de paie de l'image ci-dessous)

Traiter les données PDF

Maintenant que nous connaissons la structure dans laquelle le PDF est lu, nous pouvons pêcher les valeurs souhaitées. Dans mon cas, voici les informations qui m'intéressaient :

Weekend Coding: Turn PDF Payslips Into a Single CSV Report

Remarquez qu'il y a des données dans le tableau (catégories qui peuvent varier chaque mois) et des données en dehors du tableau.

Données hors tableau :

Période de paie — Se trouve à la ligne 19

Salaire brut — Il était difficile de trouver une règle pour celle-ci car elle apparaît après la liste des paiements et n'a pas le titre « Salaire brut ».

Comme mentionné précédemment, les paiements et les déductions peuvent varier, et tous les mois ne sont pas identiques. Par conséquent, le salaire brut peut apparaître sur une ligne différente selon les mois.

J'ai remarqué qu'il apparaît juste après le nom de l'employé — c'est donc ce que j'ai utilisé. Commencez par l'ajouter codé en dur, et plus tard nous l'obtiendrons en externe.

Nett Pay : Celui-ci est simple : il apparaît à la ligne 17.

J'ai rassemblé ces valeurs hors table dans une fonction :


Données à l'intérieur du tableau

Détails du paiement et de la déduction : C'est la partie juteuse ! Nous allons commencer par couper le tableau rows pour gagner quelques millisecondes dans la boucle for à venir. Ensuite, j'ai dû faire la différence entre les éléments de la liste

et les autres lignes.

J'ai remarqué que dans l'ensemble du fichier, les éléments de la liste sont les seuls qui répondent à cette règle : Commencez par un caractère alphabétique et terminez par un caractère numérique et contiennent un espace (la dernière condition est de filtrer les mauvaises lignes dans ma
fiche de paie, vous n'en aurez peut-être pas besoin).

<script></script> <script></script>
Maintenant que nous avons le groupe de lignes, traitons-les pour les enregistrer dans l'objet JSON. À ce stade, cela ne nous dérange pas s’il s’agit d’un paiement ou d’une déduction.

Par exemple, nous examinerons le poste retraite :

main.py:
# translate 1 pdf to 1 dict
# loop over the pdf dir
# save all dicts to 1 json file
# translate json report to csv report
Copier après la connexion
Copier après la connexion
Copier après la connexion

Je me fiche du solde (le chiffre à droite), mais je me soucie du code (G signifie qu'il est déduit du salaire brut — avant impôts — et N signifie qu'il est déduit du salaire net — après impôts). Donc idéalement, nous aurons json_obj["Pension (G)"]=150.00.

Nous utiliserons les espaces pour diviser la ligne. C'est bien qu'il y ait des espaces en double - de cette façon, nous pouvons faire la différence entre la répartition de l'espace entre quelques mots et la répartition de l'espace entre quelques champs.

Le descriptif :

Nous trouverons le premier double-espace et le diviserons.

Le code :

Le nombre d'espaces dépend de la longueur de la description, nous ne pouvons donc pas savoir à l'avance combien il y en a — c'est pourquoi j'utiliserai également lstrip(). Maintenant, le reste de la ligne commence par un caractère autre qu'un espace.

Tous les éléments de la liste n'ont pas de code, nous voulons donc vérifier si la ligne commence par un code ou un chiffre. S'il s'agit d'un code, je l'enveloppe entre () (y compris un espace avant la parenthèse ouvrante) et je l'attache à la chaîne de description. et sinon, n'ajoutez rien.

Le montant :

S'il y avait du code, nous aurons plus d'espaces à supprimer. Sinon, notre ligne pourrait contenir deux montants : Le mensuel et le solde.

Il y a 4 cas que j'ai remarqué :

/payslips_pdf
pdf_rows.txt
report.json
report.csv
Copier après la connexion
Copier après la connexion

Après avoir extrait la catégorie et le code, il nous reste :

PENSION     G   150.00   587.49
Copier après la connexion

Pour couvrir les cas 2 à 3, on va trouver l'index des espaces séparant les montants et couper la queue. Cela fonctionne également pour le premier cas, où il n'y a pas d'espace (c'est-à-dire pas de queue).

Pour couvrir le cas 4, je m'appuie sur la différence entre deux types de catégories avec un seul montant dans la ligne : la première est comme le salaire — où on veut économiser le montant, et le deuxième type est comme les retenues d’impôt – là où nous voulons l’ignorer. La différence est que seules les déductions permettent de suivre le solde annuel dans le tableau — je vérifie donc -.

Tous ensemble, voilà à quoi ça ressemble :

Écrire dans un fichier JSON


Ce n'est pas une étape obligatoire — on peut travailler avec un objet JSON sans exporter les valeurs. Je préfère voir à quoi ça ressemble, au moins pour la phase de codage.

Mettre à l'échelle plusieurs fichiers PDF

La seule raison pour laquelle cette étape nécessite une section dédiée est que l'enroulement de pdf_to_dict dans une boucle for révèle une surprise désagréable. Pour le démontrer, j'ai créé une fonction appelée iterate_over_pdfs() :
<script></script> <script></script> <script></script>
Cela se produit parce que la liste des fichiers est triée par ordre alphabétique, donc 10 apparaît avant 2. Avoir les entrées du rapport par ordre chronologique peut être considéré comme crucial, et pas seulement comme une fonctionnalité intéressante. Il faut donc y remédier !

Au départ, je pensais devoir renommer les fichiers (Payslip1.pdf -> Payslip01.pdf), mais il existe une meilleure solution :


Une fois que nous avons trié la liste des noms de fichiers par longueur, 10 apparaîtra après 2. Sur la dernière ligne, j'ai décodé les noms pour supprimer le b'' structure par défaut.

Créer le rapport CSV

Étant donné que les éléments des paiements et des retenues peuvent varier d'une fiche de paie à l'autre, cette section est plus qu'une simple traduction directe. CSV est un ensemble de données relationnelles, ce qui signifie que nous devons connaître à l'avance toutes les catégories de paiements et de retenues et garder l'entrée vide pour une fiche de paie là où elle n'existe pas. JSON, en revanche, est non relationnel et chaque entrée précise ses clés.

Dans cet esprit, la première étape de notre rapport CSV consiste à collecter les catégories. Toutes les catégories.

Collectez les catégories :


Maintenant, à première vue, vous pourriez penser à utiliser Set pour cela, car nous voulons que toutes les catégories n'apparaissent qu'une seule fois. J'ai essayé ça. Le problème est que les ensembles ne sont pas répertoriés et je trouve important de faire correspondre l'ordre des éléments qui apparaissent dans les fiches de paie originales. Lorsque vous utilisez des listes, n'oubliez pas de vérifier si l'élément existe dans la liste avant de l'ajouter :


Maintenant que nous avons compris cela : vous vous souvenez plus tôt lorsque nous avons dit que nous ne nous soucions pas de savoir quelle liste d'articles est un paiement et laquelle est une déduction ? Eh bien, nous nous en soucions maintenant ! Nous ne sommes pas

obligés
de nous séparer, mais je m'attendrais à ce qu'un rapport de fiche de paie ait tous les paiements à droite et toutes les retenues à gauche, et non mélangés.

Bien que chaque fiche de paie puisse comporter des éléments de liste différents, certains existeront toujours (car vous paierez toujours vos impôts ;) ). Nous pouvons utiliser cela à notre avantage – et signaler PAYE comme début des déductions ! (Je suis presque sûr que PAYE n'est qu'en Irlande, vous devrez donc le modifier pour qu'il corresponde à votre fiche de salaire)

Enfin, je renvoie une seule liste, car il ne sert à rien de séparer les paiements des déductions : la séparation visait à garantir que les paiements apparaîtront à droite et les déductions à gauche.

Remplissez le tableau CSV :


Maintenant que nous avons les catégories, nous pouvons commencer à remplir le tableau CSV :

Chaque fiche de paie sera une ligne, et chaque ligne aura les champs dans un ordre spécifique divisés par une virgule. Je trouve plus facile d'organiser les champs dans une liste, puis de les joindre. Les champs qui apparaissent dans les catégories mais pas dans la fiche de paie — resteront vides :


<script></script> Enfin, nous écrirons dans le fichier CSV :<script></script> <script></script> <script></script> <script></script>
Après cela, vous aurez un joli rapport CSV avec toutes vos fiches de paie !

Vous pouvez faciliter la lecture en téléchargeant l'extension VS RainbowCSV (ou tout parallèle d'un autre IDE)

Supprimer l'utilisation du fichier .json

Une fois que nous savons que les choses fonctionnent, nous n'avons plus besoin d'écrire ni de lire dans le fichier JSON — nous pouvons utiliser l'objet JSON directement :

Au lieu d'utiliser json_object (comme dans json_object = json.dumps(json_payslips)) – nous utiliserons directement json_payslips :

  1. Écrire : pas besoin d'écrire dans report.json - nous pouvons supprimer cette section.

  2. Lire : Passez json_payslips directement à la fonction json_to_csv() :

Obtenez le nom de l’employé comme argument

Une fois que votre script est prêt, vous aurez envie de le partager avec vos collègues et amis ! Pour une expérience utilisateur agréable, nous exporterons le nom de l'employé depuis la ligne de commande, plutôt que de lui demander d'ouvrir le code.

Lire l'argumentation

Nous allons commencer par la voie heureuse — en supposant que l'utilisateur ait saisi le nom de l'employé — et ajouter le code qui l'utilise :

Dans pdf_to_dict(), au lieu de coder en dur EMPLOYEE_NAME = "IFAT NEUMANN", nous le lirons à partir des arguments : Employee_name = sys.argv[1]. N'oubliez pas d'importer sys !

Maintenant, nous allons penser à d'autres scénarios :

Aucun nom d'employé n'a été donné

Que se passe-t-il si l'utilisateur n'a saisi aucun nom d'employé ? Nous voudrions l’attraper le plus tôt possible et les informer !

Par conséquent, nous ajouterons une coche dans la première ligne de la fonction principale. Maintenant, l'intuition est d'initialiser la variable employe_name ici - mais cela provoquera des propriétés de fonction bouillonnantes jusqu'à ce qu'elle atteigne la fonction qui utilise cette variable - et je ne trouve pas que ce soit une approche très propre.


Enfin, je vais simplement essayer d'accéder à ce champ — et détecter s'il n'y est pas :


main.py:
# translate 1 pdf to 1 dict
# loop over the pdf dir
# save all dicts to 1 json file
# translate json report to csv report
Copier après la connexion
Copier après la connexion
Copier après la connexion

Notez que l'ajout d'exceptions signifie que la fonction print_warning() se déplace vers main.py. Sinon, vous obtiendrez une erreur :

Nom de l'employé sans guillemets


Nous demandons à l'utilisateur d'entourer le nom de guillemets car les arguments de la ligne de commande sont divisés par des espaces. Le seul argument que nous attendons est le nom de l'employé, donc s'il y a un autre argument, nous savons qu'ils n'ont pas utilisé de guillemets et nous pouvons les informer :



Vous pouvez ignorer les exigences de citation et parcourir les arguments, en collectant toutes les parties du nom d'utilisateur, mais je trouve que cette approche ajoute une complexité inutile.

Le nom du salarié n’apparaît pas sur la fiche de paie


Nous ne pourrons pas retrouver le salaire brut si nous n’avons pas le nom du salarié tel qu’il apparaît sur la fiche de paie.

Le premier endroit pour identifier l'inadéquation est lorsque nous lisons le pdf. Par conséquent, ajoutez bien la vérification au début de la fonction pdf_to_dict() :
<script></script> <script></script> <script></script> <script></script>
Notez que j'ai déplacé Employee_name = sys.argv[1] vers cette fonction car elle est plus facile à lire (plutôt que si sys.argv[1] n'était pas dans le texte). Après cela, je le passe à get_fixed_values().

Enfin, nous récupérons l'erreur dans la fonction principale :


Et pour conclure, n'oubliez pas de mettre à jour votre fichier README avec les nouvelles instructions.

Le scénario complet


Voici le code complet que vous pouvez utiliser pour jouer avec vos fiches de salaire :


Weekend Coding: Turn PDF Payslips Into a Single CSV Report
https://cupofcode.blog/
J'espère que vous avez apprécié celui-ci ! Intéressé par un autre projet de codage du week-end ? Consultez mon article de blog sur l’envoi automatisé d’e-mails !

Blogging est mon passe-temps, donc j'y consacre volontiers du temps et de l'argent. Si vous avez apprécié cet article de blog, mettre 1 euro dans mon pot à pourboires me le fera savoir :) Merci pour votre soutien ! <script></script> <script></script>

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal