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
This script receives a directory with payslip PDFs and returns a CSV file with the desired data |
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
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
On commence ?
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 :
Nous lirons également les fichiers PDF dans le script principal, il est donc préférable de déplacer cette fonction là-bas.
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 :
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 :
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).
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
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
Après avoir extrait la catégorie et le code, il nous reste :
PENSION G 150.00 587.49
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 :
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
Au départ, je pensais devoir renommer les fichiers (Payslip1.pdf -> Payslip01.pdf), mais il existe une meilleure solution :
É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 :
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.
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 :
Vous pouvez faciliter la lecture en téléchargeant l'extension VS RainbowCSV (ou tout parallèle d'un autre IDE)
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 :
Écrire : pas besoin d'écrire dans report.json - nous pouvons supprimer cette section.
Lire : Passez json_payslips directement à la fonction json_to_csv() :
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
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
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.
Enfin, nous récupérons l'erreur dans la fonction principale :
Voici le code complet que vous pouvez utiliser pour jouer avec vos fiches de salaire :
https://cupofcode.blog/ |
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!