From ce8cdc4c1e8ebd87faa932ed25be05f4df893c3a Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Wed, 26 Feb 2025 05:54:44 +0100 Subject: [PATCH] Feat: use fsm to extract lines from pdf --- Extract pdf.ipynb | 1345 +++++++++++++++++++------------- pdf_oralia/extract.py | 89 ++- pdf_oralia/pages/charge.py | 137 ++-- pdf_oralia/pages/locataire.py | 223 ++---- pdf_oralia/pages/patrimoine.py | 70 ++ requirements.txt | 2 + 6 files changed, 1085 insertions(+), 781 deletions(-) diff --git a/Extract pdf.ipynb b/Extract pdf.ipynb index 3680e63..af85c04 100644 --- a/Extract pdf.ipynb +++ b/Extract pdf.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 77, "id": "1ac85f0c", "metadata": {}, "outputs": [], @@ -18,31 +18,36 @@ "import pandas as pd\n", "import numpy as np\n", "import pdfplumber\n", - "from pathlib import Path" + "from pathlib import Path\n", + "from pydantic import BaseModel, field_validator" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 156, "id": "6b246985", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "from pdf_oralia.extract import extract_table_settings, extract_date, extract_building\n", + "from pdf_oralia.pages import locataire, charge, patrimoine" + ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 192, "id": "b80265f1", "metadata": {}, "outputs": [], "source": [ - "pdf_file = Path(\"./pdfs/2022 04 Servient.pdf\")\n", + "pdf_file = Path(\"./datas/pdfs/2023 07 SER MAR BLO.pdf\")\n", + "# pdf_file = Path(\"/home/lafrite/Nextcloud/PLESNA Compta SYSTEM/Histoire/CRG/2024/PDF Oralia/2024 10 Servient.pdf\") \n", "pdf = pdfplumber.open(pdf_file)" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 193, "id": "a62448d8", "metadata": {}, "outputs": [], @@ -61,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 194, "id": "ae9eb950", "metadata": {}, "outputs": [], @@ -71,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 195, "id": "32ef66d7", "metadata": {}, "outputs": [ @@ -81,7 +86,7 @@ "False" ] }, - "execution_count": 5, + "execution_count": 195, "metadata": {}, "output_type": "execute_result" } @@ -90,6 +95,354 @@ "\"SITUATION DES LOCATAIRES\" in p1.extract_text()" ] }, + { + "cell_type": "code", + "execution_count": 196, + "id": "07388312-28e2-43a3-ae42-2c9d06ca0034", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 196, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1.to_image()" + ] + }, + { + "cell_type": "code", + "execution_count": 197, + "id": "0276900b-29be-4e75-b04d-e0d880812e06", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[['', 'RECAPITULATIF DES OPERATIONS', 'Débits', 'Crédits', 'Dont T.V.A.'],\n", + " ['Solde au 31.07.2023', '33 RUE MARC BLOCH', '', '10764.53', ''],\n", + " ['', 'TOTAUX', '', '10764.53', ''],\n", + " ['', '', '', '', ''],\n", + " ['', 'Totaux Généraux', '0.00', '10764.53', ''],\n", + " ['', 'Solde créditeur en Euros au 31.07.2023', '', '10764.53', '']]" + ] + }, + "execution_count": 197, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1.extract_table()" + ] + }, + { + "cell_type": "code", + "execution_count": 198, + "id": "3375b15f-7841-43cf-b47d-4e197e2b36a1", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "execution_count": 198, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p2 = pdf.pages[1]\n", + "p2.to_image()" + ] + }, + { + "cell_type": "code", + "execution_count": 199, + "id": "ebf0a23b-a41b-485d-b68a-d91730234628", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[['Locataires',\n", + " 'Période',\n", + " 'Loyers',\n", + " 'Taxes',\n", + " 'Provisions',\n", + " 'Divers',\n", + " '',\n", + " 'Total',\n", + " 'Réglés',\n", + " 'Impayés'],\n", + " ['Lot 0001 Appartement T3\\nPASQUIER BENJAMIN',\n", + " 'Du 01.07.23 Au 31.07.23',\n", + " '990.00',\n", + " '0.00',\n", + " '47.00',\n", + " '',\n", + " None,\n", + " '1037.00',\n", + " '518.50',\n", + " '518.50'],\n", + " ['Totaux',\n", + " '',\n", + " '990.00',\n", + " '0.00',\n", + " '47.00',\n", + " '0.00',\n", + " None,\n", + " '1037.00',\n", + " '518.50',\n", + " '518.50'],\n", + " ['Lot 0002 Appartement T1\\nLEMOINE YANNICK',\n", + " 'Du 01.07.23 Au 31.07.23',\n", + " '470.64',\n", + " '0.00',\n", + " '27.00',\n", + " '',\n", + " None,\n", + " '497.64',\n", + " '497.64',\n", + " ''],\n", + " ['Totaux',\n", + " '',\n", + " '470.64',\n", + " '0.00',\n", + " '27.00',\n", + " '0.00',\n", + " None,\n", + " '497.64',\n", + " '497.64',\n", + " ''],\n", + " ['Lot 0003 Appartement T1\\nRIBOLZI MAIA',\n", + " 'Solde Antérieur 79.63\\nDu 01.07.23 Au 31.07.23',\n", + " '584.74',\n", + " '0.00',\n", + " '27.00',\n", + " '',\n", + " None,\n", + " '79.63\\n611.74',\n", + " '79.63\\n512.37',\n", + " '99.37'],\n", + " ['Totaux',\n", + " '79.63',\n", + " '584.74',\n", + " '0.00',\n", + " '27.00',\n", + " '0.00',\n", + " None,\n", + " '691.37',\n", + " '592.00',\n", + " '99.37'],\n", + " ['Lot 0004 Appartement T1\\nSAHINOVIC MIROSLAV',\n", + " 'Du 01.07.23 Au 31.07.23',\n", + " '643.73',\n", + " '0.00',\n", + " '28.00',\n", + " '',\n", + " None,\n", + " '671.73',\n", + " '671.73',\n", + " ''],\n", + " ['Totaux',\n", + " '',\n", + " '643.73',\n", + " '0.00',\n", + " '28.00',\n", + " '0.00',\n", + " None,\n", + " '671.73',\n", + " '671.73',\n", + " ''],\n", + " ['Lot 0005 Appartement T2\\nRIGAUDIE AUGUSTE',\n", + " 'Du 01.07.23 Au 30.09.23',\n", + " '923.92',\n", + " '0.00',\n", + " '103.00',\n", + " '',\n", + " None,\n", + " '1026.92',\n", + " '1026.92',\n", + " ''],\n", + " ['Totaux',\n", + " '',\n", + " '923.92',\n", + " '0.00',\n", + " '103.00',\n", + " '0.00',\n", + " None,\n", + " '1026.92',\n", + " '1026.92',\n", + " ''],\n", + " ['Lot 0006 Appartement T1\\nDAHMANI DORIAN\\nJULIAN',\n", + " 'Du 01.07.23 Au 31.07.23',\n", + " '620.00',\n", + " '0.00',\n", + " '28.00',\n", + " '',\n", + " None,\n", + " '648.00',\n", + " '648.00',\n", + " ''],\n", + " ['Totaux',\n", + " '',\n", + " '620.00',\n", + " '0.00',\n", + " '28.00',\n", + " '0.00',\n", + " None,\n", + " '648.00',\n", + " '648.00',\n", + " ''],\n", + " ['Lot 0007 Appartement T1\\nLOPEZ MELODIE MARIE\\nCANDY',\n", + " 'Du 01.07.23 Au 31.07.23',\n", + " '590.00',\n", + " '0.00',\n", + " '22.00',\n", + " '',\n", + " None,\n", + " '612.00',\n", + " '612.00',\n", + " ''],\n", + " ['Totaux',\n", + " '',\n", + " '590.00',\n", + " '0.00',\n", + " '22.00',\n", + " '0.00',\n", + " None,\n", + " '612.00',\n", + " '612.00',\n", + " ''],\n", + " ['Lot 0008 Appartement T1\\nNEVERRE Léa',\n", + " 'Du 01.07.23 Au 31.07.23',\n", + " '585.34',\n", + " '0.00',\n", + " '26.00',\n", + " '',\n", + " None,\n", + " '611.34',\n", + " '611.34',\n", + " ''],\n", + " ['Totaux',\n", + " '',\n", + " '585.34',\n", + " '0.00',\n", + " '26.00',\n", + " '0.00',\n", + " None,\n", + " '611.34',\n", + " '611.34',\n", + " ''],\n", + " ['Lot 0009 Appartement T2\\nNOURY Awatif',\n", + " 'Solde Antérieur 1268.91\\nDu 01.07.23 Au 31.07.23',\n", + " '756.86',\n", + " '0.00',\n", + " '35.00',\n", + " '',\n", + " None,\n", + " '1268.91\\n791.86',\n", + " '800.00\\n0.00',\n", + " '468.91\\n791.86'],\n", + " ['Totaux',\n", + " '1268.91',\n", + " '756.86',\n", + " '0.00',\n", + " '35.00',\n", + " '0.00',\n", + " None,\n", + " '2060.77',\n", + " '800.00',\n", + " '1260.77'],\n", + " ['Lot 0010 Appartement T1\\nMERLE Clement',\n", + " 'Du 01.07.23 Au 31.07.23',\n", + " '552.46',\n", + " '0.00',\n", + " '33.00',\n", + " '',\n", + " None,\n", + " '585.46',\n", + " '585.46',\n", + " ''],\n", + " ['Totaux',\n", + " '',\n", + " '552.46',\n", + " '0.00',\n", + " '33.00',\n", + " '0.00',\n", + " None,\n", + " '585.46',\n", + " '585.46',\n", + " ''],\n", + " ['Lot 0011 Appartement T1\\nPICARD JONATHAN',\n", + " 'Du 01.07.23 Au 31.07.23',\n", + " '415.25',\n", + " '0.00',\n", + " '22.00',\n", + " '',\n", + " None,\n", + " '437.25',\n", + " '437.25',\n", + " ''],\n", + " ['Totaux',\n", + " '',\n", + " '415.25',\n", + " '0.00',\n", + " '22.00',\n", + " '0.00',\n", + " None,\n", + " '437.25',\n", + " '437.25',\n", + " '']]" + ] + }, + "execution_count": 199, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p2.extract_table()" + ] + }, + { + "cell_type": "code", + "execution_count": 200, + "id": "cf1e0bf2-c8e0-480e-b48e-e2a530c0c955", + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "too many values to unpack (expected 4)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[200], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m img \u001b[38;5;241m=\u001b[39m p2\u001b[38;5;241m.\u001b[39mto_image()\n\u001b[0;32m----> 2\u001b[0m \u001b[43mimg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdraw_rects\u001b[49m\u001b[43m(\u001b[49m\u001b[43mp2\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mextract_table\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/.cache/pypoetry/virtualenvs/pdf-oralia-SYkbQIeH-py3.13/lib/python3.13/site-packages/pdfplumber/display.py:290\u001b[0m, in \u001b[0;36mPageImage.draw_rects\u001b[0;34m(self, list_of_rects, fill, stroke, stroke_width)\u001b[0m\n\u001b[1;32m 282\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mdraw_rects\u001b[39m(\n\u001b[1;32m 283\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 284\u001b[0m list_of_rects: Union[List[T_bbox], T_obj_list, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpd.DataFrame\u001b[39m\u001b[38;5;124m\"\u001b[39m],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 287\u001b[0m stroke_width: \u001b[38;5;28mint\u001b[39m \u001b[38;5;241m=\u001b[39m DEFAULT_STROKE_WIDTH,\n\u001b[1;32m 288\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPageImage\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 289\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m x \u001b[38;5;129;01min\u001b[39;00m utils\u001b[38;5;241m.\u001b[39mto_list(list_of_rects):\n\u001b[0;32m--> 290\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdraw_rect\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfill\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mfill\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstroke\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstroke\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstroke_width\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstroke_width\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 291\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\n", + "File \u001b[0;32m~/.cache/pypoetry/virtualenvs/pdf-oralia-SYkbQIeH-py3.13/lib/python3.13/site-packages/pdfplumber/display.py:262\u001b[0m, in \u001b[0;36mPageImage.draw_rect\u001b[0;34m(self, bbox_or_obj, fill, stroke, stroke_width)\u001b[0m\n\u001b[1;32m 259\u001b[0m obj \u001b[38;5;241m=\u001b[39m bbox_or_obj\n\u001b[1;32m 260\u001b[0m bbox \u001b[38;5;241m=\u001b[39m (obj[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mx0\u001b[39m\u001b[38;5;124m\"\u001b[39m], obj[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtop\u001b[39m\u001b[38;5;124m\"\u001b[39m], obj[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mx1\u001b[39m\u001b[38;5;124m\"\u001b[39m], obj[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mbottom\u001b[39m\u001b[38;5;124m\"\u001b[39m])\n\u001b[0;32m--> 262\u001b[0m x0, top, x1, bottom \u001b[38;5;241m=\u001b[39m bbox\n\u001b[1;32m 263\u001b[0m half \u001b[38;5;241m=\u001b[39m stroke_width \u001b[38;5;241m/\u001b[39m \u001b[38;5;241m2\u001b[39m\n\u001b[1;32m 264\u001b[0m x0 \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmin\u001b[39m(x0 \u001b[38;5;241m+\u001b[39m half, (x0 \u001b[38;5;241m+\u001b[39m x1) \u001b[38;5;241m/\u001b[39m \u001b[38;5;241m2\u001b[39m)\n", + "\u001b[0;31mValueError\u001b[0m: too many values to unpack (expected 4)" + ] + } + ], + "source": [ + "img = p2.to_image()\n", + "img.draw_rects(p2.extract_table())" + ] + }, { "cell_type": "markdown", "id": "50dd9c09", @@ -100,556 +453,460 @@ }, { "cell_type": "code", - "execution_count": 6, - "id": "e9c0aefd", + "execution_count": null, + "id": "d4724a2a-9097-4a96-ab72-b28235477985", "metadata": {}, "outputs": [], "source": [ - "def extract_situation_loc(table):\n", - " df = pd.DataFrame(table[1:], columns=table[0])\n", - " rows = []\n", - " for i, row in df[df[\"Locataires\"]==\"Totaux\"].iterrows():\n", - " above_row_loc = df.iloc[i-1][\"Locataires\"]\n", - " up_row = pd.concat([row, \n", - " parse_above_loc(above_row_loc),\n", - " ])\n", + "class LocataireLine(BaseModel):\n", + " mois: int\n", + " annee: int\n", + " immeuble: str\n", + " Lot: str\n", + " Type: str\n", + " Locataire: str\n", + " Loyers: float\n", + " Taxes: float\n", + " Provisions: float\n", + " Divers: float\n", + " Total: float\n", + " Réglés: float\n", + " Impayés: float\n", + " \n", + " @field_validator('Loyers', 'Taxes', 'Provisions', 'Divers', 'Total', 'Réglés', 'Impayés', mode='before')\n", + " def set_default_if_empty(cls, v):\n", + " if v == '':\n", + " return 0\n", + " return v\n", "\n", - " rows.append(up_row)\n", - " df_cleaned = pd.concat(rows, axis=1).T\n", - " df_cleaned.drop([\"Locataires\", \"\", \"Période\"], axis=1, inplace=True)\n", - " return df_cleaned\n", + "class ChargeLine(BaseModel):\n", + " mois: int\n", + " annee: int\n", + " immeuble: str\n", + " lot: str\n", + " Champs: str\n", + " Categorie: str\n", + " Fournisseur: str\n", + " Libellé: str\n", + " Débit: float\n", + " Crédits: float\n", + " Dont_TVA: float\n", + " Locatif: float\n", + " Déductible: float\n", + "\n", + " @field_validator('Débit', 'Crédits', 'Dont_TVA', 'Locatif', 'Déductible', mode='before')\n", + " def set_default_if_empty(cls, v):\n", + " if v == '':\n", + " return 0\n", + " return v\n", + " \n", + "class PatrimoineLine(BaseModel):\n", + " mois: int\n", + " annee: int\n", + " immeuble: str\n", + " Etage: str\n", + " Lot: str\n", + " Type: str\n", + " Locataire: str\n", + " Loyer_annuel: int\n", + " Debut_bail: str\n", + " Fin_bail: str\n", + " Entree: str\n", + " Depart: str\n", + " Revision_bail: str\n", + " Usage: str\n", + " Depot_garantie: float\n", + " \n", + " @field_validator('Loyer_annuel', 'Depot_garantie', mode='before')\n", + " def set_default_if_empty(cls, v):\n", + " if v == '':\n", + " return 0\n", + " return v" + ] + }, + { + "cell_type": "code", + "execution_count": 201, + "id": "f2e22a94", + "metadata": {}, + "outputs": [], + "source": [ + "def pdf_extract_tables_lines(pdf):\n", + " loc_sink = fsm_locataire()\n", + " next(loc_sink) \n", + " charge_sink = fsm_charge()\n", + " next(charge_sink)\n", + " patrimoine_sink = fsm_patrimoine()\n", + " next(patrimoine_sink)\n", + "\n", + " for page in pdf.pages:\n", + " page_text = page.extract_text()\n", + " date = extract_date(page_text)\n", + " additionnal_fields = {\n", + " \"immeuble\": extract_building(page_text),\n", + " \"mois\": date.strftime(\"%m\"),\n", + " \"annee\": date.strftime(\"%Y\"),\n", + " }\n", + "\n", + " for line in page.extract_table(extract_table_settings):\n", + " if locataire.is_it(page_text):\n", + " res = loc_sink.send(line)\n", + " if res:\n", + " res.update(additionnal_fields)\n", + " yield LocataireLine(**res)\n", + " elif charge.is_it(page_text):\n", + " res = charge_sink.send(line)\n", + " if res:\n", + " res.update(additionnal_fields)\n", + " yield ChargeLine(**res)\n", + " \n", + " elif patrimoine.is_it(page_text):\n", + " res = patrimoine_sink.send(line)\n", + " if res:\n", + " res.update(additionnal_fields)\n", + " yield PatrimoineLine(**res)\n", + " else:\n", + " print(\"Unknown page\")" + ] + }, + { + "cell_type": "code", + "execution_count": 202, + "id": "93fbe76d-846e-4adb-a2c0-b858fa9defbd", + "metadata": {}, + "outputs": [], + "source": [ + "HEADER_LOC = ['Locataires', 'Période', 'Loyers', 'Taxes', 'Provisions', 'Divers', '', 'Total', 'Réglés', 'Impayés']\n", + "\n", + "def fsm_locataire():\n", + " current_state = \"new_row\"\n", + " row = {}\n", + " line = yield\n", + " while True:\n", + " if line == HEADER_LOC:\n", + " line = yield\n", + " elif current_state == \"new_row\":\n", + " if line[0] != \"\" and line[0] != \"TOTAUX\":\n", + " row.update(locataire.parse_lot(line[0]))\n", + " current_state = \"add_loc\"\n", + " line = yield\n", + " elif current_state == \"add_loc\":\n", + " if line[0] != \"\":\n", + " row[\"Locataire\"] = line[0]\n", + " current_state = \"add_totaux\"\n", + " line = yield\n", + " elif current_state == \"add_totaux\":\n", + " if line[0] == \"Totaux\":\n", + " row.update({\n", + " \"Loyers\": line[2],\n", + " \"Taxes\": line[3],\n", + " \"Provisions\": line[4],\n", + " \"Divers\": line[5],\n", + " \"Total\": line[7],\n", + " \"Réglés\": line[8],\n", + " \"Impayés\": line[9],\n", + " })\n", + " line = yield row\n", + " row = {}\n", + " current_state = \"new_row\"\n", + " else:\n", + " line = yield\n" + ] + }, + { + "cell_type": "code", + "execution_count": 203, + "id": "a339c6c2-5195-4775-912a-a4a3129cab42", + "metadata": {}, + "outputs": [], + "source": [ + "HEADER_CHARGE = ['', 'RECAPITULATIF DES OPERATIONS', 'Débits', 'Crédits', 'Dont T.V.A.', 'Locatif', 'Déductible']\n", + "\n", + "def fsm_charge():\n", + " current_state = \"total\"\n", + " row = {}\n", + " line = yield\n", + " while True:\n", + " if line == HEADER_CHARGE:\n", + " line = yield\n", + " if current_state == \"total\":\n", + " if line[1].lower().split(\" \")[0] in ['total', 'totaux']:\n", + " current_state = \"new_champs\"\n", + " line = yield\n", + " elif current_state == \"new_champs\":\n", + " if line[0] != \"\":\n", + " current_state = \"new_cat\"\n", + " row.update({\"Champs\": line[0]})\n", + " categorie = line[1]\n", + " line = yield\n", + " elif current_state == \"new_cat\": \n", + " if line[1] != \"\":\n", + " current_state = \"new_line\"\n", + " row.update({\"Categorie\": line[1]})\n", + " elif line[0] != \"\":\n", + " current_state = \"new_line\"\n", + " row.update({\"Categorie\": line[0]})\n", + " line = yield\n", + " elif current_state == \"new_line\":\n", + " if line[1].lower().split(\" \")[0] in ['total', 'totaux']:\n", + " current_state = \"new_champs\"\n", + " line = yield\n", + " elif line[2] != \"\" or line[3] != \"\":\n", + " row.update({\n", + " \"Fournisseur\": line[0] if line[0]!='' else row[\"Fournisseur\"],\n", + " \"Libellé\": line[1],\n", + " \"lot\": charge.get_lot(line[1]),\n", + " \"Débit\": line[2],\n", + " \"Crédits\": line[3],\n", + " \"Dont_TVA\": line[4],\n", + " \"Locatif\": line[5],\n", + " \"Déductible\": line[6] \n", + " })\n", + " line = yield row\n", + " elif line[0] != \"\":\n", + " row.update({\"Fournisseur\": line[0]})\n", + " line = yield\n", + " else:\n", + " line = yield" + ] + }, + { + "cell_type": "code", + "execution_count": 204, + "id": "e2039c34-7998-42eb-9eab-2f51c3e4ee2c", + "metadata": {}, + "outputs": [], + "source": [ + "HEADER_PATRIMOINE = ['Etage', 'Lots', 'Type de lot', 'Nom du Locataire', 'Loyer Annuel', 'Début Bail', 'Fin Bail', 'Entrée', 'Départ', 'Révisé le', 'U', 'Dépôt Gar.']\n", + "\n", + "def fsm_patrimoine():\n", + " current_state = \"new_line\"\n", + " row = {}\n", + " line = yield\n", + " while True:\n", + " if line == HEADER_PATRIMOINE:\n", + " line = yield\n", + " if current_state == \"new_line\":\n", + " if line[0]!='':\n", + " row = {\n", + " \"Etage\": line[0],\n", + " \"Lot\": line[1][-2:],\n", + " \"Type\": line[2],\n", + " \"Locataire\": line[3],\n", + " \"Loyer_annuel\": line[4].replace(\" \", \"\"),\n", + " \"Debut_bail\": line[5],\n", + " \"Fin_bail\": line[6],\n", + " \"Entree\": line[7],\n", + " \"Depart\": line[8],\n", + " \"Revision_bail\": line[9],\n", + " \"Usage\": line[10],\n", + " \"Depot_garantie\": line[11].replace(\" \", \"\")\n", + " }\n", + " line = yield row\n", + " else:\n", + " line = yield" + ] + }, + { + "cell_type": "code", + "execution_count": 205, + "id": "dad54ca3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "mois=7 annee=2023 immeuble='bloch' Lot='01' Type='Appartement T3' Locataire='PASQUIER BENJAMIN' Loyers=990.0 Taxes=0.0 Provisions=47.0 Divers=0.0 Total=1037.0 Réglés=518.5 Impayés=518.5\n", + "mois=7 annee=2023 immeuble='bloch' Lot='02' Type='Appartement T1' Locataire='LEMOINE YANNICK' Loyers=470.64 Taxes=0.0 Provisions=27.0 Divers=0.0 Total=497.64 Réglés=497.64 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='03' Type='Appartement T1' Locataire='RIBOLZI MAIA' Loyers=584.74 Taxes=0.0 Provisions=27.0 Divers=0.0 Total=691.37 Réglés=592.0 Impayés=99.37\n", + "mois=7 annee=2023 immeuble='bloch' Lot='04' Type='Appartement T1' Locataire='SAHINOVIC MIROSLAV' Loyers=643.73 Taxes=0.0 Provisions=28.0 Divers=0.0 Total=671.73 Réglés=671.73 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='05' Type='Appartement T2' Locataire='RIGAUDIE AUGUSTE' Loyers=923.92 Taxes=0.0 Provisions=103.0 Divers=0.0 Total=1026.92 Réglés=1026.92 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='06' Type='Appartement T1' Locataire='DAHMANI DORIAN' Loyers=620.0 Taxes=0.0 Provisions=28.0 Divers=0.0 Total=648.0 Réglés=648.0 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='07' Type='Appartement T1' Locataire='LOPEZ MELODIE MARIE' Loyers=590.0 Taxes=0.0 Provisions=22.0 Divers=0.0 Total=612.0 Réglés=612.0 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='08' Type='Appartement T1' Locataire='NEVERRE Léa' Loyers=585.34 Taxes=0.0 Provisions=26.0 Divers=0.0 Total=611.34 Réglés=611.34 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='09' Type='Appartement T2' Locataire='NOURY Awatif' Loyers=756.86 Taxes=0.0 Provisions=35.0 Divers=0.0 Total=2060.77 Réglés=800.0 Impayés=1260.77\n", + "mois=7 annee=2023 immeuble='bloch' Lot='10' Type='Appartement T1' Locataire='MERLE Clement' Loyers=552.46 Taxes=0.0 Provisions=33.0 Divers=0.0 Total=585.46 Réglés=585.46 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='11' Type='Appartement T1' Locataire='PICARD JONATHAN' Loyers=415.25 Taxes=0.0 Provisions=22.0 Divers=0.0 Total=437.25 Réglés=437.25 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='12' Type='Appartement T1' Locataire='BILLET Annick' Loyers=422.72 Taxes=0.0 Provisions=28.0 Divers=0.0 Total=450.72 Réglés=450.72 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='13' Type='Appartement T2' Locataire='LIGOUT Alessandra' Loyers=733.0 Taxes=0.0 Provisions=42.0 Divers=0.0 Total=802.0 Réglés=775.0 Impayés=27.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='14' Type='Appartement T3' Locataire='NGUYEN Huyen Chi' Loyers=690.49 Taxes=0.0 Provisions=46.0 Divers=0.0 Total=739.49 Réglés=733.49 Impayés=6.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='15' Type='Appartement T1' Locataire='ALVES SOBRINHO Xavier' Loyers=541.6 Taxes=0.0 Provisions=29.0 Divers=0.0 Total=570.6 Réglés=570.6 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='16' Type='Appartement T2' Locataire='MOROUX - LE GUYADER' Loyers=233.35 Taxes=0.0 Provisions=15.17 Divers=0.0 Total=248.52 Réglés=248.52 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='17' Type='Appartement T3' Locataire='LE PEZENNEC Marin' Loyers=1034.99 Taxes=0.0 Provisions=53.0 Divers=0.0 Total=1087.99 Réglés=1087.99 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='18' Type='Appartement T1' Locataire='MAUREL HELOISE' Loyers=550.0 Taxes=0.0 Provisions=20.0 Divers=0.0 Total=570.0 Réglés=570.0 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='18' Type='Appartement T1' Locataire='AUVITY Edouard' Loyers=0.0 Taxes=0.0 Provisions=104.0 Divers=63.29 Total=167.29 Réglés=167.29 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='19' Type='Appartement T1' Locataire='AUVITY Clémence' Loyers=553.39 Taxes=0.0 Provisions=20.0 Divers=0.0 Total=573.39 Réglés=573.39 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Lot='20' Type='Studio' Locataire='BOUAZZA JENNA FAYSA' Loyers=409.92 Taxes=0.0 Provisions=15.0 Divers=0.0 Total=424.92 Réglés=424.92 Impayés=0.0\n", + "Unknown page\n", + "mois=7 annee=2023 immeuble='bloch' lot='*' Champs='DEPENSES LOCATIVES' Categorie='Contrat entreprise nettoyage' Fournisseur='POEZEVARA NETTOYAGE' Libellé='Juin 2023' Débit=530.46 Crédits=0.0 Dont_TVA=88.41 Locatif=530.46 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='bloch' lot='*' Champs='DEPENSES LOCATIVES' Categorie='Contrat entreprise nettoyage' Fournisseur='POEZEVARA NETTOYAGE' Libellé='' Débit=71.1 Crédits=0.0 Dont_TVA=11.85 Locatif=71.1 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='bloch' lot='16' Champs='DEPENSES NON RECUPERABLES /LOT' Categorie=\"Frais d'expert\" Fournisseur='OPERA GROUPE' Libellé='B16 - Diagnostic CARREZ + CREP + DPE +' Débit=288.0 Crédits=0.0 Dont_TVA=48.0 Locatif=0.0 Déductible=288.0\n", + "mois=7 annee=2023 immeuble='bloch' lot='20' Champs='DEPENSES NON RECUPERABLES /LOT' Categorie=\"Frais d'expert\" Fournisseur='PPR' Libellé=\"B20 - Réfection fuite d'eau au niveau\" Débit=192.5 Crédits=0.0 Dont_TVA=17.5 Locatif=0.0 Déductible=192.5\n", + "mois=7 annee=2023 immeuble='bloch' lot='*' Champs='HONORAIRES DE GESTION' Categorie='Honoraires H.T.' Fournisseur='PPR' Libellé='TVA/Honoraires ( 20.00 % )' Débit=126.03 Crédits=0.0 Dont_TVA=126.03 Locatif=0.0 Déductible=126.03\n", + "mois=7 annee=2023 immeuble='bloch' Etage='RC' Lot='01' Type='Appartement T3' Locataire='PASQUIER BENJAMIN' Loyer_annuel=11880 Debut_bail='06/08/2022' Fin_bail='05/08/2025' Entree='06/08/2022' Depart='' Revision_bail='06/08/2022' Usage='H' Depot_garantie=990.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='RC' Lot='02' Type='Appartement T1' Locataire='LEMOINE YANNICK' Loyer_annuel=5845 Debut_bail='05/07/2012' Fin_bail='04/07/2015' Entree='05/07/2012' Depart='' Revision_bail='05/07/2023' Usage='H' Depot_garantie=430.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='RC' Lot='03' Type='Appartement T1' Locataire='RIBOLZI MAIA' Loyer_annuel=7016 Debut_bail='15/02/2022' Fin_bail='14/02/2025' Entree='15/02/2022' Depart='' Revision_bail='15/02/2023' Usage='H' Depot_garantie=565.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='01' Lot='04' Type='Appartement T1' Locataire='SAHINOVIC MIROSLAV' Loyer_annuel=7724 Debut_bail='03/04/2022' Fin_bail='02/04/2025' Entree='03/04/2022' Depart='' Revision_bail='03/04/2023' Usage='H' Depot_garantie=622.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='01' Lot='05' Type='Appartement T2' Locataire='RIGAUDIE AUGUSTE' Loyer_annuel=3695 Debut_bail='30/06/1978' Fin_bail='29/06/1979' Entree='30/06/1978' Depart='' Revision_bail='30/06/1989' Usage='H' Depot_garantie=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='01' Lot='06' Type='Appartement T1' Locataire='DAHMANI DORIAN JULIAN' Loyer_annuel=7440 Debut_bail='27/06/2022' Fin_bail='26/06/2025' Entree='27/06/2022' Depart='' Revision_bail='27/06/2023' Usage='H' Depot_garantie=620.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='01' Lot='07' Type='Appartement T1' Locataire='LOPEZ MELODIE MARIE CANDY' Loyer_annuel=7327 Debut_bail='14/07/2022' Fin_bail='13/07/2025' Entree='14/07/2022' Depart='' Revision_bail='14/07/2023' Usage='H' Depot_garantie=590.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='02' Lot='08' Type='Appartement T1' Locataire='NEVERRE Léa' Loyer_annuel=7024 Debut_bail='19/12/2021' Fin_bail='18/12/2024' Entree='19/12/2021' Depart='' Revision_bail='19/12/2022' Usage='H' Depot_garantie=565.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='02' Lot='09' Type='Appartement T2' Locataire='NOURY Awatif' Loyer_annuel=9082 Debut_bail='01/02/2009' Fin_bail='31/01/2012' Entree='01/02/2009' Depart='' Revision_bail='01/02/2023' Usage='H' Depot_garantie=650.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='02' Lot='10' Type='Appartement T1' Locataire='MERLE Clement' Loyer_annuel=6629 Debut_bail='10/01/2020' Fin_bail='09/01/2023' Entree='10/01/2020' Depart='' Revision_bail='10/01/2023' Usage='H' Depot_garantie=527.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='02' Lot='11' Type='Appartement T1' Locataire='PICARD JONATHAN' Loyer_annuel=4982 Debut_bail='12/03/2016' Fin_bail='11/03/2019' Entree='12/03/2016' Depart='' Revision_bail='12/03/2023' Usage='H' Depot_garantie=379.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='03' Lot='12' Type='Appartement T1' Locataire='BILLET Annick' Loyer_annuel=5072 Debut_bail='01/06/1976' Fin_bail='31/05/1979' Entree='01/06/1976' Depart='' Revision_bail='01/06/1987' Usage='H' Depot_garantie=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='03' Lot='13' Type='Appartement T2' Locataire='LIGOUT Alessandra' Loyer_annuel=9103 Debut_bail='21/07/2022' Fin_bail='20/07/2025' Entree='21/07/2022' Depart='' Revision_bail='21/07/2023' Usage='H' Depot_garantie=733.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='03' Lot='14' Type='Appartement T3' Locataire='NGUYEN Huyen Chi' Loyer_annuel=8285 Debut_bail='02/12/2008' Fin_bail='01/12/2011' Entree='02/12/2008' Depart='' Revision_bail='02/12/2022' Usage='H' Depot_garantie=590.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='04' Lot='15' Type='Appartement T1' Locataire='ALVES SOBRINHO Xavier' Loyer_annuel=6499 Debut_bail='08/04/2021' Fin_bail='07/04/2024' Entree='08/04/2021' Depart='' Revision_bail='08/04/2023' Usage='H' Depot_garantie=515.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='04' Lot='16' Type='Appartement T2' Locataire='** VACANT **' Loyer_annuel=0 Debut_bail='' Fin_bail='' Entree='' Depart='' Revision_bail='' Usage='' Depot_garantie=0.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='04' Lot='' Type='' Locataire='MOROUX - LE GUYADER CECILE CHR' Loyer_annuel=6462 Debut_bail='17/07/2020' Fin_bail='16/07/2023' Entree='17/07/2020' Depart='13/07/2023' Revision_bail='17/07/2023' Usage='H' Depot_garantie=525.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='04' Lot='17' Type='Appartement T3' Locataire='LE PEZENNEC Marin' Loyer_annuel=12419 Debut_bail='22/04/2022' Fin_bail='21/04/2025' Entree='22/04/2022' Depart='' Revision_bail='22/04/2023' Usage='H' Depot_garantie=1000.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='05' Lot='18' Type='Appartement T1' Locataire='MAUREL HELOISE' Loyer_annuel=6600 Debut_bail='03/06/2023' Fin_bail='02/06/2026' Entree='03/06/2023' Depart='' Revision_bail='03/06/2023' Usage='H' Depot_garantie=550.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='05' Lot='19' Type='Appartement T1' Locataire='AUVITY Clémence' Loyer_annuel=6640 Debut_bail='20/08/2021' Fin_bail='19/08/2024' Entree='20/08/2021' Depart='' Revision_bail='20/08/2022' Usage='H' Depot_garantie=540.0\n", + "mois=7 annee=2023 immeuble='bloch' Etage='05' Lot='20' Type='Studio' Locataire='BOUAZZA JENNA FAYSA' Loyer_annuel=5090 Debut_bail='05/07/2021' Fin_bail='04/07/2024' Entree='05/07/2021' Depart='' Revision_bail='05/07/2023' Usage='H' Depot_garantie=400.0\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "mois=7 annee=2023 immeuble='marietton' Lot='10' Type='Appartement T3' Locataire='VESELINOV KALOYAN' Loyers=1024.59 Taxes=0.0 Provisions=58.0 Divers=0.0 Total=1082.59 Réglés=1082.59 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Lot='08' Type='Appartement T3' Locataire='BORSCHNECK' Loyers=857.0 Taxes=0.0 Provisions=68.0 Divers=0.0 Total=925.0 Réglés=925.0 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Lot='01' Type='Loc. Commercial' Locataire='ASSOCIES A2C' Loyers=940.73 Taxes=0.0 Provisions=127.0 Divers=0.0 Total=1067.73 Réglés=1067.73 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Lot='02' Type='Loc. Commercial' Locataire='DETENTE YOLAINE' Loyers=897.12 Taxes=0.0 Provisions=40.0 Divers=0.0 Total=937.12 Réglés=0.0 Impayés=937.12\n", + "mois=7 annee=2023 immeuble='marietton' Lot='03' Type='Studio' Locataire='VIGNARDET MALORIE' Loyers=15.07 Taxes=0.0 Provisions=89.5 Divers=34.71 Total=139.28 Réglés=139.28 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Lot='12' Type='Appartement T3' Locataire='ECHARD Clara' Loyers=882.0 Taxes=0.0 Provisions=53.0 Divers=0.0 Total=935.0 Réglés=935.0 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Lot='13' Type='Appartement T3' Locataire='INCERTI ALESSANDRA' Loyers=570.0 Taxes=0.0 Provisions=38.4 Divers=950.0 Total=1558.4 Réglés=1553.2 Impayés=5.2\n", + "mois=7 annee=2023 immeuble='marietton' Lot='04' Type='Appartement T3' Locataire='DALBAN-MOREYNAS C' Loyers=805.45 Taxes=0.0 Provisions=63.0 Divers=0.0 Total=868.45 Réglés=868.45 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Lot='05' Type='Appartement T3' Locataire='SERRE JULIE' Loyers=677.78 Taxes=0.0 Provisions=66.0 Divers=0.0 Total=743.78 Réglés=743.78 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Lot='06' Type='Appartement T3' Locataire='MINARY ALAIN' Loyers=1137.39 Taxes=0.0 Provisions=47.0 Divers=0.0 Total=1184.39 Réglés=1184.39 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Lot='07' Type='Appartement T3' Locataire='MENASRI ICHEME' Loyers=831.81 Taxes=0.0 Provisions=70.0 Divers=0.0 Total=901.81 Réglés=901.81 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Lot='09' Type='Appartement T3' Locataire='FAURE CALIXTE' Loyers=961.46 Taxes=0.0 Provisions=70.0 Divers=0.0 Total=1031.46 Réglés=1031.46 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Lot='11' Type='Appartement T3' Locataire='CHARLOT ANDREE' Loyers=745.41 Taxes=0.0 Provisions=69.0 Divers=0.0 Total=814.41 Réglés=814.41 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='*' Champs='DEPENSES LOCATIVES' Categorie='Contrat entreprise nettoyage' Fournisseur=\"REMALI BRIL'OR\" Libellé='Juin 2023' Débit=381.96 Crédits=0.0 Dont_TVA=63.66 Locatif=381.96 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='*' Champs='DEPENSES LOCATIVES' Categorie='Contrat entreprise nettoyage' Fournisseur='PICARD SERVICES' Libellé='Vérification, refixation et réglage des' Débit=148.5 Crédits=0.0 Dont_TVA=13.5 Locatif=148.5 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='*' Champs='DEPENSES LOCATIVES' Categorie='Contrat entreprise nettoyage' Fournisseur='PICARD SERVICES' Libellé='Recherche panne sur installation contrôl' Débit=232.1 Crédits=0.0 Dont_TVA=21.1 Locatif=232.1 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='06' Champs='DEPENSES RECUPERABLES PAR LOT' Categorie='Plaques' Fournisseur='PICARD SERVICES REGIES' Libellé='M06 - Mise à jour nom sur interphone' Débit=64.9 Crédits=0.0 Dont_TVA=5.9 Locatif=0.0 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='08' Champs='DEPENSES RECUPERABLES PAR LOT' Categorie='Plaques' Fournisseur='PICARD SERVICES' Libellé='M08 - Mise à jour nom sur interphone' Débit=64.9 Crédits=0.0 Dont_TVA=5.9 Locatif=0.0 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='08' Champs='DEPENSES RECUPERABLES PAR LOT' Categorie='Plaques' Fournisseur='RHONE ENERGIES' Libellé='M08 - Entretien chaudière gaz loc' Débit=154.0 Crédits=0.0 Dont_TVA=14.0 Locatif=0.0 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='12' Champs='DEPENSES RECUPERABLES PAR LOT' Categorie='Plaques' Fournisseur='PICARD SERVICES' Libellé='M12 - Mise à jour interphone locataire' Débit=64.9 Crédits=0.0 Dont_TVA=5.9 Locatif=0.0 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='03' Champs='DEPENSES NON RECUPERABLES /LOT' Categorie=\"Frais d'expert\" Fournisseur='OPERA GROUPE' Libellé='M03 - Diagnostics: CARREZ + AMIANTE +' Débit=288.0 Crédits=0.0 Dont_TVA=48.0 Locatif=0.0 Déductible=288.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='03' Champs='DEPENSES NON RECUPERABLES /LOT' Categorie=\"Frais d'expert\" Fournisseur='OPERA GROUPE' Libellé='M03 - Etat des lieux sortie locataire' Débit=121.2 Crédits=0.0 Dont_TVA=20.2 Locatif=0.0 Déductible=121.2\n", + "mois=7 annee=2023 immeuble='marietton' lot='13' Champs='DEPENSES NON RECUPERABLES /LOT' Categorie=\"Frais d'expert\" Fournisseur='OPERA GROUPE' Libellé='M13 - Diagnostics: DPE + CARREZ + ERP' Débit=168.0 Crédits=0.0 Dont_TVA=28.0 Locatif=0.0 Déductible=168.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='13' Champs='DEPENSES NON RECUPERABLES /LOT' Categorie=\"Frais d'expert\" Fournisseur='OPERA GROUPE' Libellé='M13 - Etat des lieux sortie locataire' Débit=121.2 Crédits=0.0 Dont_TVA=20.2 Locatif=0.0 Déductible=121.2\n", + "mois=7 annee=2023 immeuble='marietton' lot='*' Champs='HONORAIRES DE GESTION' Categorie='Hon. prest. mise en location' Fournisseur='ROSIER MODICA MOTTEROZ SA' Libellé='Hon. prest. mise en location' Débit=1039.4 Crédits=0.0 Dont_TVA=173.23 Locatif=0.0 Déductible=1039.4\n", + "mois=7 annee=2023 immeuble='marietton' lot='*' Champs='HONORAIRES DE GESTION' Categorie='Hon. prest. mise en location' Fournisseur='ROSIER MODICA MOTTEROZ SA' Libellé='Prest. réalisation EDL entrée' Débit=93.0 Crédits=0.0 Dont_TVA=15.5 Locatif=0.0 Déductible=93.0\n", + "mois=7 annee=2023 immeuble='marietton' lot='*' Champs='HONORAIRES DE GESTION' Categorie='Hon. prest. mise en location' Fournisseur='ROSIER MODICA MOTTEROZ SA' Libellé='Honoraires H.T.' Débit=562.36 Crédits=0.0 Dont_TVA=0.0 Locatif=0.0 Déductible=562.36\n", + "mois=7 annee=2023 immeuble='marietton' lot='*' Champs='HONORAIRES DE GESTION' Categorie='Hon. prest. mise en location' Fournisseur='ROSIER MODICA MOTTEROZ SA' Libellé='TVA/Honoraires ( 20.00 % )' Débit=112.47 Crédits=0.0 Dont_TVA=112.47 Locatif=0.0 Déductible=112.47\n", + "mois=7 annee=2023 immeuble='marietton' Etage='5' Lot='10' Type='Appartement T3' Locataire='VESELINOV KALOYAN' Loyer_annuel=12295 Debut_bail='04/01/2022' Fin_bail='03/01/2025' Entree='04/01/2022' Depart='' Revision_bail='04/01/2023' Usage='H' Depot_garantie=990.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='4' Lot='08' Type='Appartement T3' Locataire='BORSCHNECK CLEMENCE' Loyer_annuel=10284 Debut_bail='14/04/2023' Fin_bail='13/04/2026' Entree='14/04/2023' Depart='' Revision_bail='14/04/2023' Usage='H' Depot_garantie=857.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='RC' Lot='01' Type='Loc. Commercial' Locataire='ASSOCIES A2C' Loyer_annuel=11288 Debut_bail='04/04/2012' Fin_bail='03/04/2021' Entree='04/04/2012' Depart='' Revision_bail='04/04/2023' Usage='C' Depot_garantie=837.82\n", + "mois=7 annee=2023 immeuble='marietton' Etage='RC' Lot='02' Type='Loc. Commercial' Locataire='DETENTE YOLAINE' Loyer_annuel=10765 Debut_bail='17/08/2017' Fin_bail='16/08/2023' Entree='17/08/2017' Depart='' Revision_bail='17/08/2022' Usage='C' Depot_garantie=1546.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='RC' Lot='03' Type='Studio' Locataire='** VACANT **' Loyer_annuel=0 Debut_bail='' Fin_bail='' Entree='' Depart='' Revision_bail='' Usage='' Depot_garantie=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='RC' Lot='' Type='' Locataire='VIGNARDET MALORIE' Loyer_annuel=5425 Debut_bail='25/06/2021' Fin_bail='24/06/2024' Entree='25/06/2021' Depart='01/07/2023' Revision_bail='25/06/2023' Usage='H' Depot_garantie=0.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='01' Lot='12' Type='Appartement T3' Locataire='ECHARD Clara' Loyer_annuel=10584 Debut_bail='01/09/2021' Fin_bail='31/08/2024' Entree='01/09/2022' Depart='' Revision_bail='01/09/2022' Usage='H' Depot_garantie=882.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='01' Lot='13' Type='Appartement T3' Locataire='INCERTI ALESSANDRA' Loyer_annuel=11400 Debut_bail='13/07/2023' Fin_bail='12/07/2026' Entree='13/07/2023' Depart='' Revision_bail='13/07/2023' Usage='H' Depot_garantie=950.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='02' Lot='04' Type='Appartement T3' Locataire='DALBAN-MOREYNAS C' Loyer_annuel=9665 Debut_bail='27/09/2019' Fin_bail='26/09/2022' Entree='27/09/2007' Depart='' Revision_bail='27/09/2022' Usage='H' Depot_garantie=1360.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='02' Lot='05' Type='Appartement T3' Locataire='SERRE JULIE' Loyer_annuel=8133 Debut_bail='01/05/2000' Fin_bail='30/04/2003' Entree='01/05/2018' Depart='' Revision_bail='01/05/2023' Usage='H' Depot_garantie=914.69\n", + "mois=7 annee=2023 immeuble='marietton' Etage='03' Lot='06' Type='Appartement T3' Locataire='MINARY ALAIN' Loyer_annuel=13648 Debut_bail='01/04/2022' Fin_bail='31/03/2025' Entree='01/04/2022' Depart='' Revision_bail='01/04/2023' Usage='H' Depot_garantie=1099.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='03' Lot='07' Type='Appartement T3' Locataire='MENASRI ICHEME' Loyer_annuel=9981 Debut_bail='22/12/2009' Fin_bail='21/12/2012' Entree='22/12/2018' Depart='' Revision_bail='22/12/2022' Usage='H' Depot_garantie=720.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='04' Lot='09' Type='Appartement T3' Locataire='FAURE CALIXTE' Loyer_annuel=11537 Debut_bail='11/05/2022' Fin_bail='10/05/2025' Entree='11/05/2022' Depart='' Revision_bail='11/05/2023' Usage='H' Depot_garantie=929.0\n", + "mois=7 annee=2023 immeuble='marietton' Etage='05' Lot='11' Type='Appartement T3' Locataire='CHARLOT ANDREE' Loyer_annuel=8944 Debut_bail='01/10/1995' Fin_bail='30/09/1998' Entree='01/10/1995' Depart='' Revision_bail='01/10/2022' Usage='H' Depot_garantie=792.73\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "Unknown page\n", + "mois=7 annee=2023 immeuble='servient' Lot='01' Type='Loc. Commercial' Locataire='EFFUSION' Loyers=4725.43 Taxes=0.0 Provisions=663.0 Divers=0.0 Total=36006.89 Réglés=1796.0 Impayés=34210.89\n", + "mois=7 annee=2023 immeuble='servient' Lot='02' Type='Loc. Commercial' Locataire='RAS' Loyers=3473.79 Taxes=0.0 Provisions=519.0 Divers=0.0 Total=3992.79 Réglés=3992.79 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='03' Type='Appartement T1' Locataire='LE MENE Guillaume' Loyers=640.0 Taxes=0.0 Provisions=31.0 Divers=0.0 Total=671.0 Réglés=671.0 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='04' Type='Appartement T2' Locataire='ROBERT ELSA' Loyers=496.0 Taxes=0.0 Provisions=24.0 Divers=608.2 Total=1128.2 Réglés=1128.2 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='04' Type='Appartement T2' Locataire='PEJAUDIER Adelaide' Loyers=-267.55 Taxes=0.0 Provisions=102.97 Divers=128.23 Total=-36.35 Réglés=-36.35 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='09' Type='Appartement T1' Locataire='MANNA Baptiste' Loyers=485.23 Taxes=0.0 Provisions=20.0 Divers=0.0 Total=505.23 Réglés=505.23 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='05' Type='Loc. Commercial' Locataire='ATELIERS RENAISSANCE' Loyers=828.51 Taxes=0.0 Provisions=191.0 Divers=0.0 Total=1019.51 Réglés=1019.51 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='06' Type='Appartement T3' Locataire='GUELLIER Muriel' Loyers=869.76 Taxes=0.0 Provisions=86.0 Divers=0.0 Total=955.76 Réglés=955.76 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='07' Type='Appartement T1' Locataire='DUSSOLIER ROMAIN' Loyers=606.36 Taxes=0.0 Provisions=50.0 Divers=0.0 Total=656.36 Réglés=656.36 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='08' Type='Appartement T1' Locataire='BESSON Léa' Loyers=594.79 Taxes=0.0 Provisions=28.0 Divers=0.0 Total=622.79 Réglés=622.79 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='10' Type='Appartement T3' Locataire='FILIPPI Bérengère' Loyers=1230.88 Taxes=0.0 Provisions=87.0 Divers=0.0 Total=1317.88 Réglés=1317.88 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='11' Type='Appartement T1' Locataire='LOINE Anais' Loyers=512.87 Taxes=0.0 Provisions=28.0 Divers=0.0 Total=540.87 Réglés=540.87 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='12' Type='Appartement T1' Locataire='TEOLI Mathilde' Loyers=630.14 Taxes=0.0 Provisions=40.0 Divers=0.0 Total=670.14 Réglés=670.14 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='20' Type='Appartement T3' Locataire='NADAM Jérémie' Loyers=1083.25 Taxes=0.0 Provisions=95.0 Divers=0.0 Total=1178.25 Réglés=1178.25 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='13' Type='Appartement T3' Locataire='HALLAIS Béatrice' Loyers=1024.91 Taxes=0.0 Provisions=91.0 Divers=0.0 Total=1115.91 Réglés=1115.91 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='14' Type='Appartement T3' Locataire='AUDOUIN Alexandre' Loyers=1173.33 Taxes=0.0 Provisions=93.0 Divers=0.0 Total=1266.33 Réglés=1266.33 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='15' Type='Appartement T3' Locataire='CHARVET Rémi' Loyers=958.44 Taxes=0.0 Provisions=103.0 Divers=0.0 Total=1061.44 Réglés=1061.44 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='16' Type='Appartement T3' Locataire='FURIN Jean-Jacques' Loyers=785.14 Taxes=0.0 Provisions=96.0 Divers=0.0 Total=881.14 Réglés=881.14 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='17' Type='Appartement T2' Locataire='BARBIER Estelle' Loyers=822.04 Taxes=0.0 Provisions=65.0 Divers=0.0 Total=887.04 Réglés=887.04 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='18' Type='Appartement T3' Locataire='GUYOT Pierre-Alain' Loyers=414.05 Taxes=0.0 Provisions=70.0 Divers=0.0 Total=484.05 Réglés=484.05 Impayés=0.0\n", + "mois=7 annee=2023 immeuble='servient' Lot='19' Type='Appartement T2' Locataire='TAVARES NORTE Dylan' Loyers=1059.78 Taxes=0.0 Provisions=62.0 Divers=0.0 Total=1208.98 Réglés=1086.0 Impayés=122.98\n", + "mois=7 annee=2023 immeuble='servient' lot='*' Champs='DEPENSES LOCATIVES' Categorie='Nettoyage immeuble' Fournisseur='DIDIER NETTOYAGE' Libellé='Juin 2023' Débit=729.84 Crédits=0.0 Dont_TVA=121.64 Locatif=729.84 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='servient' lot='*' Champs='DEPENSES LOCATIVES' Categorie='Nettoyage immeuble' Fournisseur='TOTALENERGIES' Libellé='Conso 19.03.23-18.05.23 - 96 kWh' Débit=69.22 Crédits=0.0 Dont_TVA=7.24 Locatif=69.22 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='servient' lot='*' Champs='DEPENSES LOCATIVES' Categorie='Nettoyage immeuble' Fournisseur='THYSSENKRUPP ASCENSEURS' Libellé='3ème Trimestre 2023' Débit=371.25 Crédits=0.0 Dont_TVA=33.75 Locatif=371.25 Déductible=0.0\n", + "mois=7 annee=2023 immeuble='servient' lot='03' Champs='DEPENSES NON RECUPERABLES /LOT' Categorie='Travaux plomberie' Fournisseur='PPR' Libellé='S03 - Fourniture et pose joints de douch' Débit=99.0 Crédits=0.0 Dont_TVA=9.0 Locatif=0.0 Déductible=99.0\n", + "mois=7 annee=2023 immeuble='servient' lot='*' Champs='HONORAIRES DE GESTION' Categorie='Hon. prest. mise en location' Fournisseur='ROSIER MODICA MOTTEROZ SA' Libellé='Hon. prest. mise en location' Débit=620.0 Crédits=0.0 Dont_TVA=103.33 Locatif=0.0 Déductible=620.0\n", + "mois=7 annee=2023 immeuble='servient' lot='*' Champs='HONORAIRES DE GESTION' Categorie='Hon. prest. mise en location' Fournisseur='ROSIER MODICA MOTTEROZ SA' Libellé='Prest. réalisation EDL entrée' Débit=143.52 Crédits=0.0 Dont_TVA=23.92 Locatif=0.0 Déductible=143.52\n", + "mois=7 annee=2023 immeuble='servient' lot='*' Champs='HONORAIRES DE GESTION' Categorie='Hon. prest. mise en location' Fournisseur='ROSIER MODICA MOTTEROZ SA' Libellé='Honoraires H.T.' Débit=1090.02 Crédits=0.0 Dont_TVA=0.0 Locatif=0.0 Déductible=1090.02\n", + "mois=7 annee=2023 immeuble='servient' lot='*' Champs='HONORAIRES DE GESTION' Categorie='Hon. prest. mise en location' Fournisseur='ROSIER MODICA MOTTEROZ SA' Libellé='TVA/Honoraires ( 20.00 % )' Débit=218.0 Crédits=0.0 Dont_TVA=218.0 Locatif=0.0 Déductible=218.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='RC' Lot='01' Type='Loc. Commercial' Locataire='EFFUSION' Loyer_annuel=18901 Debut_bail='01/09/2018' Fin_bail='31/08/2027' Entree='01/09/2018' Depart='' Revision_bail='01/09/2022' Usage='C' Depot_garantie=4725.43\n", + "mois=7 annee=2023 immeuble='servient' Etage='RC' Lot='02' Type='Loc. Commercial' Locataire='RAS' Loyer_annuel=13895 Debut_bail='11/05/2015' Fin_bail='10/05/2024' Entree='11/05/2015' Depart='' Revision_bail='11/05/2022' Usage='C' Depot_garantie=3375.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='RC' Lot='03' Type='Appartement T1' Locataire='LE MENE Guillaume' Loyer_annuel=7680 Debut_bail='08/02/2023' Fin_bail='07/02/2026' Entree='08/02/2023' Depart='' Revision_bail='08/02/2023' Usage='H' Depot_garantie=640.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='RC' Lot='04' Type='Appartement T2' Locataire='ROBERT ELSA' Loyer_annuel=7440 Debut_bail='07/07/2023' Fin_bail='06/07/2026' Entree='07/07/2023' Depart='' Revision_bail='07/07/2023' Usage='H' Depot_garantie=620.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='RC' Lot='09' Type='Appartement T1' Locataire='MANNA Baptiste' Loyer_annuel=5822 Debut_bail='05/02/2021' Fin_bail='04/02/2024' Entree='05/02/2021' Depart='' Revision_bail='05/02/2023' Usage='H' Depot_garantie=465.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='01' Lot='05' Type='Loc. Commercial' Locataire='ATELIERS RENAISSANCE' Loyer_annuel=9942 Debut_bail='01/06/2013' Fin_bail='31/05/2022' Entree='01/06/2013' Depart='' Revision_bail='01/06/2022' Usage='C' Depot_garantie=1350.64\n", + "mois=7 annee=2023 immeuble='servient' Etage='01' Lot='06' Type='Appartement T3' Locataire='GUELLIER Muriel' Loyer_annuel=10437 Debut_bail='07/08/2018' Fin_bail='06/08/2021' Entree='07/08/2018' Depart='' Revision_bail='07/08/2022' Usage='H' Depot_garantie=813.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='01' Lot='07' Type='Appartement T1' Locataire='DUSSOLIER ROMAIN' Loyer_annuel=7530 Debut_bail='28/07/2017' Fin_bail='27/07/2020' Entree='28/07/2017' Depart='' Revision_bail='17/07/2023' Usage='H' Depot_garantie=570.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='01' Lot='08' Type='Appartement T1' Locataire='BESSON Léa' Loyer_annuel=7137 Debut_bail='20/01/2021' Fin_bail='19/01/2024' Entree='20/01/2021' Depart='' Revision_bail='20/01/2023' Usage='H' Depot_garantie=570.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='02' Lot='10' Type='Appartement T3' Locataire='FILIPPI Bérengère' Loyer_annuel=14770 Debut_bail='14/08/2020' Fin_bail='13/08/2023' Entree='14/08/2020' Depart='' Revision_bail='14/08/2022' Usage='H' Depot_garantie=1200.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='02' Lot='11' Type='Appartement T1' Locataire='LOINE Anais' Loyer_annuel=6154 Debut_bail='09/08/2020' Fin_bail='08/08/2023' Entree='09/08/2022' Depart='' Revision_bail='09/08/2022' Usage='H' Depot_garantie=500.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='02' Lot='12' Type='Appartement T1' Locataire='TEOLI Mathilde' Loyer_annuel=7561 Debut_bail='26/03/2020' Fin_bail='25/03/2023' Entree='26/03/2020' Depart='' Revision_bail='26/03/2023' Usage='H' Depot_garantie=598.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='02' Lot='20' Type='Appartement T3' Locataire='NADAM Jérémie' Loyer_annuel=12998 Debut_bail='08/10/2014' Fin_bail='07/10/2017' Entree='08/10/2014' Depart='' Revision_bail='08/10/2022' Usage='H' Depot_garantie=998.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='03' Lot='13' Type='Appartement T3' Locataire='HALLAIS Béatrice' Loyer_annuel=12298 Debut_bail='02/12/2014' Fin_bail='01/12/2017' Entree='02/12/2014' Depart='' Revision_bail='02/12/2022' Usage='H' Depot_garantie=957.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='03' Lot='14' Type='Appartement T3' Locataire='AUDOUIN Alexandre' Loyer_annuel=14079 Debut_bail='15/01/2019' Fin_bail='14/01/2022' Entree='15/01/2019' Depart='' Revision_bail='15/01/2023' Usage='H' Depot_garantie=1106.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='04' Lot='15' Type='Appartement T3' Locataire='CHARVET Rémi' Loyer_annuel=11501 Debut_bail='04/01/2016' Fin_bail='03/01/2019' Entree='04/01/2016' Depart='' Revision_bail='04/01/2023' Usage='H' Depot_garantie=881.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='04' Lot='16' Type='Appartement T3' Locataire='FURIN Jean-Jacques' Loyer_annuel=9421 Debut_bail='01/06/1999' Fin_bail='31/05/2002' Entree='01/06/1999' Depart='' Revision_bail='01/06/2023' Usage='H' Depot_garantie=1067.14\n", + "mois=7 annee=2023 immeuble='servient' Etage='05' Lot='17' Type='Appartement T2' Locataire='BARBIER Estelle' Loyer_annuel=9864 Debut_bail='01/08/2019' Fin_bail='31/07/2022' Entree='01/08/2019' Depart='' Revision_bail='01/09/2022' Usage='H' Depot_garantie=785.0\n", + "mois=7 annee=2023 immeuble='servient' Etage='05' Lot='18' Type='Appartement T3' Locataire='GUYOT Pierre-Alain' Loyer_annuel=4968 Debut_bail='01/07/1997' Fin_bail='30/06/2000' Entree='01/07/1997' Depart='' Revision_bail='01/07/2023' Usage='H' Depot_garantie=548.82\n", + "mois=7 annee=2023 immeuble='servient' Etage='05' Lot='19' Type='Appartement T2' Locataire='TAVARES NORTE Dylan' Loyer_annuel=12133 Debut_bail='16/04/2022' Fin_bail='15/04/2025' Entree='16/04/2022' Depart='' Revision_bail='16/04/2023' Usage='H' Depot_garantie=1024.0\n" + ] + } + ], + "source": [ + "for l in pdf_extract_tables_lines(pdf):\n", + " print(l)\n", " " ] }, - { - "cell_type": "code", - "execution_count": 7, - "id": "87e05f50", - "metadata": {}, - "outputs": [], - "source": [ - "def parse_above_loc(content):\n", - " row = {}\n", - " try:\n", - " app, loc = content.split(\"\\n\")\n", - " except ValueError:\n", - " row[\"lot\"] = \"\"\n", - " row[\"type\"] = \"\"\n", - " row[\"locataire\"] = content\n", - " \n", - " else:\n", - " app_ = app.split(\" \")\n", - " row[\"lot\"] = app_[1]\n", - " row[\"type\"] = \" \".join(app_[2:])\n", - " row[\"locataire\"] = loc\n", - " return pd.Series(row)" - ] - }, { "cell_type": "code", "execution_count": null, - "id": "19a1446d", + "id": "84f5b164-04ff-436c-a214-a646a891a831", "metadata": {}, "outputs": [], "source": [] }, - { - "cell_type": "code", - "execution_count": 8, - "id": "8afb23c7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
LoyersTaxesProvisionsDiversTotalRéglésImpayéslottypelocataire
0342.800.00663.000.001005.801005.800001Loc. CommercialEFFUSION
13473.790.00519.0096.794089.584089.580002Loc. CommercialRAS
2597.200.0031.000.00628.60628.600003Appartement T1KALAI Bernard
3596.590.0031.000.00627.59627.590004Appartement T2PEJAUDIER Adelaide
4468.850.0020.000.00981.03485.00496.030009Appartement T1MANNA Baptiste
5745.390.00191.000.00936.39936.390005Loc. CommercialATELIERS RENAISSANCE
6834.550.0081.000.00915.55915.550006Appartement T3GUELLIER MURIEL
7591.690.0050.000.00641.69641.69Lot 0007 Appartement T1\\nDOMINIKIEWICZ\\nMELANIE
8574.710.0028.000.00602.71602.710008Appartement T1BESSON Léa
91201.100.0087.000.001288.101288.100010Appartement T3FILIPPI Bérengère
10500.460.0028.000.00528.46528.460011Appartement T1LOINE Anaïs
\n", - "
" - ], - "text/plain": [ - " Loyers Taxes Provisions Divers Total Réglés Impayés lot \\\n", - "0 342.80 0.00 663.00 0.00 1005.80 1005.80 0001 \n", - "1 3473.79 0.00 519.00 96.79 4089.58 4089.58 0002 \n", - "2 597.20 0.00 31.00 0.00 628.60 628.60 0003 \n", - "3 596.59 0.00 31.00 0.00 627.59 627.59 0004 \n", - "4 468.85 0.00 20.00 0.00 981.03 485.00 496.03 0009 \n", - "5 745.39 0.00 191.00 0.00 936.39 936.39 0005 \n", - "6 834.55 0.00 81.00 0.00 915.55 915.55 0006 \n", - "7 591.69 0.00 50.00 0.00 641.69 641.69 \n", - "8 574.71 0.00 28.00 0.00 602.71 602.71 0008 \n", - "9 1201.10 0.00 87.00 0.00 1288.10 1288.10 0010 \n", - "10 500.46 0.00 28.00 0.00 528.46 528.46 0011 \n", - "\n", - " type locataire \n", - "0 Loc. Commercial EFFUSION \n", - "1 Loc. Commercial RAS \n", - "2 Appartement T1 KALAI Bernard \n", - "3 Appartement T2 PEJAUDIER Adelaide \n", - "4 Appartement T1 MANNA Baptiste \n", - "5 Loc. Commercial ATELIERS RENAISSANCE \n", - "6 Appartement T3 GUELLIER MURIEL \n", - "7 Lot 0007 Appartement T1\\nDOMINIKIEWICZ\\nMELANIE \n", - "8 Appartement T1 BESSON Léa \n", - "9 Appartement T3 FILIPPI Bérengère \n", - "10 Appartement T1 LOINE Anaïs " - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p2 = pdf.pages[1]\n", - "extract_situation_loc(p2.extract_table())" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "0e0ddca7", - "metadata": {}, - "outputs": [], - "source": [ - "charge_table_settings = {\n", - " \"vertical_strategy\": \"lines\",\n", - " \"horizontal_strategy\": \"text\",\n", - "}\n", - "def extract_charge(table):\n", - " df = pd.DataFrame(table[1:], columns=table[0]).replace(\"\", np.nan).dropna(subset=[\"Débits\"])\n", - " drop_index = df[df[\"RECAPITULATIF DES OPERATIONS\"].str.contains(\"TOTAUX\", case=False)].index\n", - " df.drop(drop_index, inplace=True)\n", - " return df" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "b915b220", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
RECAPITULATIF DES OPERATIONSDébitsCréditsDont T.V.A.LocatifDéductible
4DIDIER NETTOYAGEPC - ENTRETIEN IMMEUBLE708.58NaN118.10708.58NaN
6TOTAL DIRECT ENERGIEPC TOTAL DIRECT ENERGIE65.70NaN7.0365.70NaN
7EDFPC EDF DU 17.04.202289.56NaN10.2289.56NaN
9PICARD SERVICEFacture du 11/04/202266.76NaN6.0766.76NaN
15V2C MAINTENANCE6 - remplacement circulateur chudière447.70NaN40.70NaN447.70
20IMI GERANCETAVARES NORTE Dylan93.00NaN15.50NaN93.00
23IMI GERANCETAVARES NORTE Dylan173.58NaN28.93NaN173.58
27IMI GERANCETAVARES NORTE Dylan798.72NaN133.12NaN798.72
29NaNHonoraires H.T.979.20NaNNaNNaN979.20
30NaNTVA/Honoraires ( 20.00 % )195.84NaN195.84NaN195.84
\n", - "
" - ], - "text/plain": [ - " RECAPITULATIF DES OPERATIONS Débits \\\n", - "4 DIDIER NETTOYAGE PC - ENTRETIEN IMMEUBLE 708.58 \n", - "6 TOTAL DIRECT ENERGIE PC TOTAL DIRECT ENERGIE 65.70 \n", - "7 EDF PC EDF DU 17.04.2022 89.56 \n", - "9 PICARD SERVICE Facture du 11/04/2022 66.76 \n", - "15 V2C MAINTENANCE 6 - remplacement circulateur chudière 447.70 \n", - "20 IMI GERANCE TAVARES NORTE Dylan 93.00 \n", - "23 IMI GERANCE TAVARES NORTE Dylan 173.58 \n", - "27 IMI GERANCE TAVARES NORTE Dylan 798.72 \n", - "29 NaN Honoraires H.T. 979.20 \n", - "30 NaN TVA/Honoraires ( 20.00 % ) 195.84 \n", - "\n", - " Crédits Dont T.V.A. Locatif Déductible \n", - "4 NaN 118.10 708.58 NaN \n", - "6 NaN 7.03 65.70 NaN \n", - "7 NaN 10.22 89.56 NaN \n", - "9 NaN 6.07 66.76 NaN \n", - "15 NaN 40.70 NaN 447.70 \n", - "20 NaN 15.50 NaN 93.00 \n", - "23 NaN 28.93 NaN 173.58 \n", - "27 NaN 133.12 NaN 798.72 \n", - "29 NaN NaN NaN 979.20 \n", - "30 NaN 195.84 NaN 195.84 " - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "p4 = pdf.pages[3]\n", - "extract_charge(p4.extract_table(charge_table_settings))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "c7b071fa", - "metadata": {}, - "outputs": [], - "source": [ - "# im = p4.to_image()\n", - "# im.debug_tablefinder(charge_table_settings)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "ebe2881a", - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'openpyxl'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn [12], line 15\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mHONORAIRES\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m page\u001b[38;5;241m.\u001b[39mextract_text():\n\u001b[1;32m 13\u001b[0m df_charge \u001b[38;5;241m=\u001b[39m extract_charge(page\u001b[38;5;241m.\u001b[39mextract_table(charge_table_settings))\n\u001b[0;32m---> 15\u001b[0m df_charge\u001b[38;5;241m.\u001b[39mto_excel(xls_charge, sheet_name\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mCharges\u001b[39m\u001b[38;5;124m\"\u001b[39m, index\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[1;32m 18\u001b[0m df_loc \u001b[38;5;241m=\u001b[39m extract_situation_loc(loc_table)\n\u001b[1;32m 19\u001b[0m df_loc \u001b[38;5;241m=\u001b[39m df_loc\u001b[38;5;241m.\u001b[39massign(\n\u001b[1;32m 20\u001b[0m mois \u001b[38;5;241m=\u001b[39m mois,\n\u001b[1;32m 21\u001b[0m annee \u001b[38;5;241m=\u001b[39m annee\n\u001b[1;32m 22\u001b[0m )\n", - "File \u001b[0;32m~/.venv/plesna/lib/python3.10/site-packages/pandas/util/_decorators.py:211\u001b[0m, in \u001b[0;36mdeprecate_kwarg.._deprecate_kwarg..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 209\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 210\u001b[0m kwargs[new_arg_name] \u001b[38;5;241m=\u001b[39m new_arg_value\n\u001b[0;32m--> 211\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/.venv/plesna/lib/python3.10/site-packages/pandas/util/_decorators.py:211\u001b[0m, in \u001b[0;36mdeprecate_kwarg.._deprecate_kwarg..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 209\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 210\u001b[0m kwargs[new_arg_name] \u001b[38;5;241m=\u001b[39m new_arg_value\n\u001b[0;32m--> 211\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/.venv/plesna/lib/python3.10/site-packages/pandas/core/generic.py:2373\u001b[0m, in \u001b[0;36mNDFrame.to_excel\u001b[0;34m(self, excel_writer, sheet_name, na_rep, float_format, columns, header, index, index_label, startrow, startcol, engine, merge_cells, encoding, inf_rep, verbose, freeze_panes, storage_options)\u001b[0m\n\u001b[1;32m 2360\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mio\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mformats\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mexcel\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m ExcelFormatter\n\u001b[1;32m 2362\u001b[0m formatter \u001b[38;5;241m=\u001b[39m ExcelFormatter(\n\u001b[1;32m 2363\u001b[0m df,\n\u001b[1;32m 2364\u001b[0m na_rep\u001b[38;5;241m=\u001b[39mna_rep,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 2371\u001b[0m inf_rep\u001b[38;5;241m=\u001b[39minf_rep,\n\u001b[1;32m 2372\u001b[0m )\n\u001b[0;32m-> 2373\u001b[0m \u001b[43mformatter\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mwrite\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2374\u001b[0m \u001b[43m \u001b[49m\u001b[43mexcel_writer\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2375\u001b[0m \u001b[43m \u001b[49m\u001b[43msheet_name\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msheet_name\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2376\u001b[0m \u001b[43m \u001b[49m\u001b[43mstartrow\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstartrow\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2377\u001b[0m \u001b[43m \u001b[49m\u001b[43mstartcol\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstartcol\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2378\u001b[0m \u001b[43m \u001b[49m\u001b[43mfreeze_panes\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mfreeze_panes\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2379\u001b[0m \u001b[43m \u001b[49m\u001b[43mengine\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mengine\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2380\u001b[0m \u001b[43m \u001b[49m\u001b[43mstorage_options\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstorage_options\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2381\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/.venv/plesna/lib/python3.10/site-packages/pandas/io/formats/excel.py:911\u001b[0m, in \u001b[0;36mExcelFormatter.write\u001b[0;34m(self, writer, sheet_name, startrow, startcol, freeze_panes, engine, storage_options)\u001b[0m\n\u001b[1;32m 907\u001b[0m need_save \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 908\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 909\u001b[0m \u001b[38;5;66;03m# error: Cannot instantiate abstract class 'ExcelWriter' with abstract\u001b[39;00m\n\u001b[1;32m 910\u001b[0m \u001b[38;5;66;03m# attributes 'engine', 'save', 'supported_extensions' and 'write_cells'\u001b[39;00m\n\u001b[0;32m--> 911\u001b[0m writer \u001b[38;5;241m=\u001b[39m \u001b[43mExcelWriter\u001b[49m\u001b[43m(\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore[abstract]\u001b[39;49;00m\n\u001b[1;32m 912\u001b[0m \u001b[43m \u001b[49m\u001b[43mwriter\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mengine\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mengine\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstorage_options\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstorage_options\u001b[49m\n\u001b[1;32m 913\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 914\u001b[0m need_save \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[1;32m 916\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n", - "File \u001b[0;32m~/.venv/plesna/lib/python3.10/site-packages/pandas/io/excel/_openpyxl.py:56\u001b[0m, in \u001b[0;36mOpenpyxlWriter.__init__\u001b[0;34m(self, path, engine, date_format, datetime_format, mode, storage_options, if_sheet_exists, engine_kwargs, **kwargs)\u001b[0m\n\u001b[1;32m 43\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__init__\u001b[39m(\n\u001b[1;32m 44\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 45\u001b[0m path: FilePath \u001b[38;5;241m|\u001b[39m WriteExcelBuffer \u001b[38;5;241m|\u001b[39m ExcelWriter,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 54\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 55\u001b[0m \u001b[38;5;66;03m# Use the openpyxl module as the Excel writer.\u001b[39;00m\n\u001b[0;32m---> 56\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mopenpyxl\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mworkbook\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Workbook\n\u001b[1;32m 58\u001b[0m engine_kwargs \u001b[38;5;241m=\u001b[39m combine_kwargs(engine_kwargs, kwargs)\n\u001b[1;32m 60\u001b[0m \u001b[38;5;28msuper\u001b[39m()\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__init__\u001b[39m(\n\u001b[1;32m 61\u001b[0m path,\n\u001b[1;32m 62\u001b[0m mode\u001b[38;5;241m=\u001b[39mmode,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 65\u001b[0m engine_kwargs\u001b[38;5;241m=\u001b[39mengine_kwargs,\n\u001b[1;32m 66\u001b[0m )\n", - "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'openpyxl'" - ] - } - ], - "source": [ - "frames = []\n", - "loc_table = []\n", - "for page in pdf.pages:\n", - " situation_loc_line = [l for l in page.extract_text().split(\"\\n\") if \"SITUATION DES LOCATAIRES CRG\" in l]\n", - " if situation_loc_line:\n", - " mois, annee = situation_loc_line[0].split(\" \")[-2:]\n", - " if loc_table:\n", - " loc_table += page.extract_table()[1:]\n", - " else:\n", - " loc_table = page.extract_table()\n", - "\n", - " if \"HONORAIRES\" in page.extract_text():\n", - " df_charge = extract_charge(page.extract_table(charge_table_settings))\n", - "\n", - " df_charge.to_excel(xls_charge, sheet_name=\"Charges\", index=False)\n", - "\n", - "\n", - "df_loc = extract_situation_loc(loc_table)\n", - "df_loc = df_loc.assign(\n", - " mois = mois,\n", - " annee = annee\n", - ")\n", - "df_loc.to_excel(xls_locataire, sheet_name=\"Location\", index=False)\n", - "#df_loc" - ] - }, { "cell_type": "code", "execution_count": null, - "id": "f2e22a94", - "metadata": {}, - "outputs": [], - "source": [ - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dad54ca3", + "id": "dd10a8d6-1fdb-4550-a2d5-1e4fcad61ec2", "metadata": {}, "outputs": [], "source": [] @@ -671,7 +928,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.10" + "version": "3.13.2" } }, "nbformat": 4, diff --git a/pdf_oralia/extract.py b/pdf_oralia/extract.py index 414107f..238fe4d 100644 --- a/pdf_oralia/extract.py +++ b/pdf_oralia/extract.py @@ -1,10 +1,11 @@ import logging from datetime import datetime from pathlib import Path +import pandas as pd import pdfplumber -from pdf_oralia.pages import charge, locataire, patrimoine, recapitulatif +from pdf_oralia.pages import charge, locataire, patrimoine extract_table_settings = { "vertical_strategy": "lines", @@ -32,21 +33,16 @@ def extract_building(page_text, buildings=["bloch", "marietton", "servient"]): raise ValueError("Pas d'immeuble trouvé") -def catch_malformed_table(tables): - if len(tables) == 2: - return tables[0] + tables[1] - return tables[0] +def pdf_extract_tables_lines(pdf): + loc_sink = locataire.fsm() + next(loc_sink) + charge_sink = charge.fsm() + next(charge_sink) + patrimoine_sink = patrimoine.fsm() + next(patrimoine_sink) - -def from_pdf(pdf_file): - """Build dataframes one about charges and another on loc""" - pdf = pdfplumber.open(pdf_file) - recapitulatif_tables = [] - loc_tables = [] - charge_tables = [] - patrimoie_tables = [] - - for page_number, page in enumerate(pdf.pages): + page_number = 1 + for page in pdf.pages: page_text = page.extract_text() date = extract_date(page_text) additionnal_fields = { @@ -55,34 +51,50 @@ def from_pdf(pdf_file): "annee": date.strftime("%Y"), } - if recapitulatif.is_it(page_text): - table = page.extract_tables()[0] - extracted = recapitulatif.extract(table, additionnal_fields) - if extracted: - recapitulatif_tables.append(extracted) + for line in page.extract_table(extract_table_settings): + if locataire.is_it(page_text): + res = loc_sink.send(line) + if res: + res.update(additionnal_fields) + yield locataire.Line(**res) + elif charge.is_it(page_text): + res = charge_sink.send(line) + if res: + res.update(additionnal_fields) + yield charge.Line(**res) - elif locataire.is_it(page_text): - tables = page.extract_tables(extract_table_settings)[1:] - table = catch_malformed_table(tables) - extracted = locataire.extract(table, additionnal_fields) - loc_tables.append(extracted) + elif patrimoine.is_it(page_text): + res = patrimoine_sink.send(line) + if res: + res.update(additionnal_fields) + yield patrimoine.Line(**res) + else: + logging.warning(f"Page {page_number} non reconnu. Page ignorée.") - elif charge.is_it(page_text): - tables = page.extract_tables(extract_table_settings)[1:] - table = catch_malformed_table(tables) - extracted = charge.extract(table, additionnal_fields) - charge_tables.append(extracted) + page_number += 1 - elif patrimoine.is_it(page_text): - pass +def from_pdf(pdf_file): + """Build dataframes one about charges and another on loc""" + pdf = pdfplumber.open(pdf_file) + locataire_lines = [] + charge_lines = [] + patrimoine_lines = [] + for line in pdf_extract_tables_lines(pdf): + if isinstance(line, locataire.Line): + locataire_lines.append(line) + elif isinstance(line, charge.Line): + charge_lines.append(line) + elif isinstance(line, patrimoine.Line): + patrimoine_lines.append(line) else: logging.warning(f"Page {page_number+1} non reconnu. Page ignorée.") - df_charge = charge.table2df(recapitulatif_tables + charge_tables) - df_loc = locataire.table2df(loc_tables) - - return df_charge, df_loc + return ( + pd.DataFrame([c.__dict__ for c in charge_lines]), + pd.DataFrame([c.__dict__ for c in locataire_lines]), + pd.DataFrame([c.__dict__ for c in patrimoine_lines]), + ) def extract_save(pdf_file, dest): @@ -90,10 +102,13 @@ def extract_save(pdf_file, dest): pdf_file = Path(pdf_file) xls_charge = Path(dest) / f"{pdf_file.stem.replace(' ', '_')}_charge.xlsx" xls_locataire = Path(dest) / f"{pdf_file.stem.replace(' ', '_')}_locataire.xlsx" + xls_patrimoine = Path(dest) / f"{pdf_file.stem.replace(' ', '_')}_patrimoine.xlsx" - df_charge, df_loc = from_pdf(pdf_file) + df_charge, df_loc, df_patrimoine = from_pdf(pdf_file) df_charge.to_excel(xls_charge, sheet_name="Charges", index=False) logging.info(f"{xls_charge} saved") df_loc.to_excel(xls_locataire, sheet_name="Location", index=False) logging.info(f"{xls_locataire} saved") + df_patrimoine.to_excel(xls_patrimoine, sheet_name="Patrimoine", index=False) + logging.info(f"{xls_patrimoine} saved") diff --git a/pdf_oralia/pages/charge.py b/pdf_oralia/pages/charge.py index 4ebb6bd..905277c 100644 --- a/pdf_oralia/pages/charge.py +++ b/pdf_oralia/pages/charge.py @@ -1,9 +1,16 @@ import re +from pydantic import BaseModel, field_validator -import numpy as np -import pandas as pd -RECAPITULATIF_DES_OPERATIONS = 1 +HEADER_CHARGE = [ + "", + "RECAPITULATIF DES OPERATIONS", + "Débits", + "Crédits", + "Dont T.V.A.", + "Locatif", + "Déductible", +] DF_TYPES = { "Fournisseur": str, "RECAPITULATIF DES OPERATIONS": str, @@ -17,7 +24,30 @@ DF_TYPES = { "annee": str, "lot": str, } -DEFAULT_FOURNISSEUR = "ROSIER MODICA MOTTEROZ SA" + + +class Line(BaseModel): + mois: int + annee: int + immeuble: str + lot: str + Champs: str + Categorie: str + Fournisseur: str + Libellé: str + Débit: float + Crédits: float + Dont_TVA: float + Locatif: float + Déductible: float + + @field_validator( + "Débit", "Crédits", "Dont_TVA", "Locatif", "Déductible", mode="before" + ) + def set_default_if_empty(cls, v): + if v == "": + return 0 + return v def is_it(page_text): @@ -41,51 +71,54 @@ def get_lot(txt): return "*" -def keep_row(row): - return not any( - [ - word.lower() in row[RECAPITULATIF_DES_OPERATIONS].lower() - for word in ["TOTAL", "TOTAUX", "Solde créditeur", "Solde débiteur"] - ] - ) - - -def extract(table, additionnal_fields: dict = {}): - """Turn table to dictionary with additional fields""" - extracted = [] - header = table[0] - for row in table[1:]: - if keep_row(row): - r = dict() - for i, value in enumerate(row): - if header[i] == "": - r["Fournisseur"] = value - else: - r[header[i]] = value - - for k, v in additionnal_fields.items(): - r[k] = v - - if "honoraire" in row[RECAPITULATIF_DES_OPERATIONS].lower(): - r["Fournisseur"] = DEFAULT_FOURNISSEUR - - extracted.append(r) - - return extracted - - -def table2df(tables): - dfs = [] - for table in tables: - df = ( - pd.DataFrame.from_records(table) - .replace("", np.nan) - .dropna(subset=["Débits", "Crédits"], how="all") - ) - df["Fournisseur"] = df["Fournisseur"].fillna(method="ffill") - dfs.append(df) - df = pd.concat(dfs) - - df["immeuble"] = df["immeuble"].apply(lambda x: x[0].capitalize()) - df["lot"] = df["RECAPITULATIF DES OPERATIONS"].apply(get_lot) - return df.astype(DF_TYPES) +def fsm(): + current_state = "total" + row = {} + line = yield + while True: + if line == HEADER_CHARGE: + line = yield + if current_state == "total": + if line[1].lower().split(" ")[0] in ["total", "totaux"]: + current_state = "new_champs" + line = yield + elif current_state == "new_champs": + if line[0] != "": + current_state = "new_cat_line" + row = {"Champs": line[0], "Categorie": "", "Fournisseur": ""} + line = yield + elif current_state == "new_cat_line": + if line[1].lower().split(" ")[0] in ["total", "totaux"]: + current_state = "new_champs" + line = yield + row = {} + elif line[2] != "" or line[3] != "": + row.update( + { + "Fournisseur": line[0] if line[0] != "" else row["Fournisseur"], + "Libellé": line[1], + "lot": get_lot(line[1]), + "Débit": line[2], + "Crédits": line[3], + "Dont_TVA": line[4], + "Locatif": line[5], + "Déductible": line[6], + } + ) + line = yield row + row = { + "Champs": row["Champs"], + "Categorie": row["Categorie"], + "Fournisseur": row["Fournisseur"], + } + elif line[0] != "" and line[1] == "": + row.update({"Categorie": line[0]}) + line = yield + elif line[1] != "": + row.update({"Categorie": line[1]}) + line = yield + elif line[0] != "": + row.update({"Fournisseur": line[0]}) + line = yield + else: + line = yield diff --git a/pdf_oralia/pages/locataire.py b/pdf_oralia/pages/locataire.py index f1fa92f..95a19fc 100644 --- a/pdf_oralia/pages/locataire.py +++ b/pdf_oralia/pages/locataire.py @@ -1,22 +1,48 @@ -import numpy as np -import pandas as pd +from pydantic import BaseModel, field_validator -DF_TYPES = { - "Locataires": str, - "Période": str, - "Loyers": float, - "Taxes": float, - "Provisions": float, - "Divers": str, - "Total": float, - "Réglés": float, - "Impayés": float, - "immeuble": str, - "mois": str, - "annee": str, - "Lot": str, - "Type": str, -} +HEADER_LOC = [ + "Locataires", + "Période", + "Loyers", + "Taxes", + "Provisions", + "Divers", + "", + "Total", + "Réglés", + "Impayés", +] + + +class Line(BaseModel): + mois: int + annee: int + immeuble: str + Lot: str + Type: str + Locataire: str + Loyers: float + Taxes: float + Provisions: float + Divers: float + Total: float + Réglés: float + Impayés: float + + @field_validator( + "Loyers", + "Taxes", + "Provisions", + "Divers", + "Total", + "Réglés", + "Impayés", + mode="before", + ) + def set_default_if_empty(cls, v): + if v == "": + return 0 + return v def is_it(page_text): @@ -25,142 +51,43 @@ def is_it(page_text): return False -def is_drop(row): - if "totaux" in row[0].lower(): - return True - if not any(row): - return True - return False - - -def extract(table, additionnal_fields: dict = {}): - """Turn table to dictionary with additional fields""" - extracted = [] - header = table[0] - for row in table[1:]: - if not is_drop(row): - r = dict() - for i, value in enumerate(row): - if header[i] != "": - r[header[i]] = value - for k, v in additionnal_fields.items(): - r[k] = v - extracted.append(r) - return extracted - - -def join_row(last, next): - row = {} - for key in last: - if last[key] == next[key]: - row[key] = last[key] - elif last[key] and next[key]: - row[key] = f"{last[key]}\n{next[key]}" - elif last[key]: - row[key] = last[key] - elif next[key]: - row[key] = next[key] - else: - row[key] = "" - return row - - -def join_tables(tables): - joined = tables[0] - - for t in tables[1:]: - last_row = joined[-1] - if "totaux" not in last_row["Locataires"].lower(): - first_row = t[0] - joined_row = join_row(last_row, first_row) - joined = joined[:-1] + [joined_row] + t[1:] - else: - joined += t - - return joined - - def parse_lot(string): words = string.split(" ") return {"Lot": "{:02d}".format(int(words[1])), "Type": " ".join(words[2:])} -def clean_type(string): - if "appartement" in string.lower(): - return string[-2:] - return string - - -def join_row(table): - joined = [] - for row in table: - if row["Locataires"].startswith("Lot"): - row.update(parse_lot(row["Locataires"])) - row["Locataires"] = "" - joined.append(row) - elif row["Locataires"] == "Rappel de Loyer": - last_row = joined[-1] - row.update( - { - "Lot": last_row["Lot"], - "Type": last_row["Type"], - "Locataires": last_row["Locataires"], - "Divers": "Rappel de Loyer", - } - ) - joined.append(row) - - elif row["Locataires"]: - last_row = joined.pop() - row_name = row["Locataires"].replace("\n", " ") - row.update({k: v for k, v in last_row.items() if v}) - row["Locataires"] = last_row["Locataires"] + " " + row_name - joined.append(row) - - else: - if row["Période"].startswith("Solde"): - last_row = joined.pop() +def fsm(): + current_state = "new_row" + row = {} + line = yield + while True: + if line == HEADER_LOC: + line = yield + elif current_state == "new_row": + if line[0] != "" and line[0] != "TOTAUX": + row.update(parse_lot(line[0])) + current_state = "add_loc" + line = yield + elif current_state == "add_loc": + if line[0] != "": + row["Locataire"] = line[0] + current_state = "add_totaux" + line = yield + elif current_state == "add_totaux": + if line[0] == "Totaux": row.update( { - "Lot": last_row["Lot"], - "Type": last_row["Type"], - "Locataires": last_row["Locataires"], + "Loyers": line[2], + "Taxes": line[3], + "Provisions": line[4], + "Divers": line[5], + "Total": line[7], + "Réglés": line[8], + "Impayés": line[9], } ) - joined.append(row) - - elif row["Période"].startswith("Du"): - last_row = joined[-1] - row.update( - { - "Lot": last_row["Lot"], - "Type": last_row["Type"], - "Locataires": last_row["Locataires"], - } - ) - joined.append(row) - - return joined - - -def flat_tables(tables): - tables_flat = [] - for table in tables: - tables_flat.extend(table) - return tables_flat - - -def table2df(tables): - tables = flat_tables(tables) - joined = join_row(tables) - df = pd.DataFrame.from_records(joined) - - df["immeuble"] = df["immeuble"].apply(lambda x: x[0].capitalize()) - df["Type"] = df["Type"].apply(clean_type) - - numeric_cols = [k for k, v in DF_TYPES.items() if v == float] - df[numeric_cols] = df[numeric_cols].replace("", np.nan) - - df = df.drop(df[(df["Locataires"] == "") & (df["Période"] == "")].index) - - return df.astype(DF_TYPES) + line = yield row + row = {} + current_state = "new_row" + else: + line = yield diff --git a/pdf_oralia/pages/patrimoine.py b/pdf_oralia/pages/patrimoine.py index 3541ea7..c2e3b66 100644 --- a/pdf_oralia/pages/patrimoine.py +++ b/pdf_oralia/pages/patrimoine.py @@ -1,4 +1,74 @@ +from pydantic import BaseModel, field_validator + +HEADER_PATRIMOINE = [ + "Etage", + "Lots", + "Type de lot", + "Nom du Locataire", + "Loyer Annuel", + "Début Bail", + "Fin Bail", + "Entrée", + "Départ", + "Révisé le", + "U", + "Dépôt Gar.", +] + + +class Line(BaseModel): + mois: int + annee: int + immeuble: str + Etage: str + Lot: str + Type: str + Locataire: str + Loyer_annuel: int + Debut_bail: str + Fin_bail: str + Entree: str + Depart: str + Revision_bail: str + Usage: str + Depot_garantie: float + + @field_validator("Loyer_annuel", "Depot_garantie", mode="before") + def set_default_if_empty(cls, v): + if v == "": + return 0 + return v + + def is_it(page_text): if "VOTRE PATRIMOINE" in page_text: return True return False + + +def fsm(): + current_state = "new_line" + row = {} + line = yield + while True: + if line == HEADER_PATRIMOINE: + line = yield + if current_state == "new_line": + if line[0] != "": + row = { + "Etage": line[0], + "Lot": line[1][-2:] if line[1] != "" else row["Lot"], + "Type": line[2] if line[2] != "" else row["Type"], + "Locataire": line[3], + "Loyer_annuel": line[4].replace(" ", ""), + "Debut_bail": line[5], + "Fin_bail": line[6], + "Entree": line[7], + "Depart": line[8], + "Revision_bail": line[9], + "Usage": line[10], + "Depot_garantie": line[11].replace(" ", ""), + } + line = yield row + else: + line = yield diff --git a/requirements.txt b/requirements.txt index 2bf5e9e..d7e814f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ pdfplumber numpy pandas +click +openpyxl