Commit 3028c354 authored by Marco Schmiedel's avatar Marco Schmiedel

Add Provs

parent fb66c66f
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
"fileId": "38b9eebe-955e-4052-a0f6-29c69b1242b3", "fileId": "38b9eebe-955e-4052-a0f6-29c69b1242b3",
"originalPath": "work/config/MysqlConfig.py", "originalPath": "work/config/MysqlConfig.py",
"currentPath": "work/config/MysqlConfig.py", "currentPath": "work/config/MysqlConfig.py",
"hash": "a6de5ed4cb42c8208047b4466357bde2952886430465dbff6097f02d8e886d7c", "hash": "d8958dba0bf7c100587dabf6ff576e0a1905a0aed4980a6c64ff6254f3671e5a",
"docContent": "<p><br></p>", "docContent": "<p><br></p>",
"checkedStatus": "done", "checkedStatus": "changed",
"comments": [ "comments": [
{ {
"commentId": "56c5adba-20f4-4524-a894-41f81ab7ca55", "commentId": "56c5adba-20f4-4524-a894-41f81ab7ca55",
...@@ -13,5 +13,5 @@ ...@@ -13,5 +13,5 @@
} }
], ],
"lastCheckedTimestamp": 1747123619645, "lastCheckedTimestamp": 1747123619645,
"lastFileModificationTimestamp": 1747073230250.2017 "lastFileModificationTimestamp": 1747135280562.6484
} }
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
"fileId": "58307c8c-416a-4c24-adc9-7ed6324d1f8a", "fileId": "58307c8c-416a-4c24-adc9-7ed6324d1f8a",
"originalPath": "work/manager/WebManager.py", "originalPath": "work/manager/WebManager.py",
"currentPath": "work/manager/WebManager.py", "currentPath": "work/manager/WebManager.py",
"hash": "5a987f9d37c2d083c9a04ea0a0c4739a4fbd6c2e6c98877a0f64fadb45717a1c", "hash": "a0a065203ba1c85cb820f87483fae6404cdacc88c703f135b42bcee608e09bad",
"docContent": "<p><br></p>", "docContent": "<p><br></p>",
"checkedStatus": "done", "checkedStatus": "done",
"comments": [], "comments": [],
"lastCheckedTimestamp": 1747070585799, "lastCheckedTimestamp": 1748952745993,
"lastFileModificationTimestamp": 1747070580506.2974 "lastFileModificationTimestamp": 1748948322859.7175
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"fileId": "647ff9a8-a56f-486e-ba2a-8ff77e4514d4", "fileId": "647ff9a8-a56f-486e-ba2a-8ff77e4514d4",
"originalPath": "work/Dockerfile", "originalPath": "work/Dockerfile",
"currentPath": "work/Dockerfile", "currentPath": "work/Dockerfile",
"hash": "ca6a37e37aff3fff276f8020d9860b94c6556181bbb65a3fe1c62f3868f7f0b3", "hash": "7d986675138b37a5ad1f01c7a7201d1aa409f2b4557afe5f2fd590ebde55e3ce",
"docContent": "<p><br></p>", "docContent": "<p><br></p>",
"checkedStatus": "done", "checkedStatus": "done",
"comments": [ "comments": [
...@@ -12,6 +12,6 @@ ...@@ -12,6 +12,6 @@
"timestamp": 1746693591017 "timestamp": 1746693591017
} }
], ],
"lastCheckedTimestamp": 1747069787674, "lastCheckedTimestamp": 1748952742075,
"lastFileModificationTimestamp": 1747069781547.9219 "lastFileModificationTimestamp": 1748952688396.303
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"fileId": "986eeb57-8634-4f40-a4ea-a2eae9d87e71", "fileId": "986eeb57-8634-4f40-a4ea-a2eae9d87e71",
"originalPath": "work/readme.md", "originalPath": "work/readme.md",
"currentPath": "work/readme.md", "currentPath": "work/readme.md",
"hash": "4c28da167e1e7e214e5f1861d6bbd6bdd8de8883d759fbc349e5fc5beaa2b6ad", "hash": "4d543d075a22bd2b13008dcc7a1cf518a3df29bbf1b1ab516eb69a2df778aaf9",
"docContent": "<p><br></p>", "docContent": "<p><br></p>",
"checkedStatus": "done", "checkedStatus": "done",
"comments": [ "comments": [
...@@ -17,6 +17,6 @@ ...@@ -17,6 +17,6 @@
"timestamp": 1747069658074 "timestamp": 1747069658074
} }
], ],
"lastCheckedTimestamp": 1747125739662, "lastCheckedTimestamp": 1748952738060,
"lastFileModificationTimestamp": 1747123960177.0483 "lastFileModificationTimestamp": 1748952727853.0393
} }
{
"fileId": "ec1eb8b3-9fc0-4593-ac37-e51f7c643f1d",
"originalPath": "work/routes/FreenetUploadRouter.py",
"currentPath": "work/routes/FreenetUploadRouter.py",
"hash": "d101612a37ead90042f21152be5b3d1ff924ea26d2e4e16b459aa5a623d321b1",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1748953027802,
"lastFileModificationTimestamp": 1748952926381.2961
}
...@@ -95,6 +95,9 @@ RUN pip3 install --break-system-packages json5 ...@@ -95,6 +95,9 @@ RUN pip3 install --break-system-packages json5
# pyotp is installed to generate and verify one-time passwords. # pyotp is installed to generate and verify one-time passwords.
RUN pip3 install --break-system-packages pyotp RUN pip3 install --break-system-packages pyotp
# openpyxl is for reading excel files.
RUN pip3 install --break-system-packages openpyxl
# sshtunnel is installed to create SSH tunnels from Python. # sshtunnel is installed to create SSH tunnels from Python.
RUN pip3 install --break-system-packages sshtunnel RUN pip3 install --break-system-packages sshtunnel
......
...@@ -6,6 +6,7 @@ from models.token_toke import TokenToke ...@@ -6,6 +6,7 @@ from models.token_toke import TokenToke
from routes.HealtCheckRouter import blueprint as health_router from routes.HealtCheckRouter import blueprint as health_router
from routes.BaseRouter import blueprint as tarifs_router from routes.BaseRouter import blueprint as tarifs_router
from routes.EeccxRouter import blueprint as eeccx_router from routes.EeccxRouter import blueprint as eeccx_router
from routes.FreenetUploadRouter import blueprint as upload_router
# This class bundles blueprint registration so that all route collections are attached to the Flask application. # This class bundles blueprint registration so that all route collections are attached to the Flask application.
class WebManager: class WebManager:
...@@ -17,7 +18,7 @@ class WebManager: ...@@ -17,7 +18,7 @@ class WebManager:
# This helper method iterates over all blueprints and registers each of them on the Flask application. # This helper method iterates over all blueprints and registers each of them on the Flask application.
def _register_blueprints(self) -> None: def _register_blueprints(self) -> None:
for bp in (health_router, tarifs_router, eeccx_router): for bp in (health_router, tarifs_router, eeccx_router, upload_router):
self.app.register_blueprint(bp) self.app.register_blueprint(bp)
......
{
"cells": [
{
"cell_type": "code",
"execution_count": 14,
"id": "531d8b07-8f2f-4ef6-b92a-d4bf3364e166",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"⚠️ D3264371: kein passender Deal\n",
"⚠️ C3428798: kein passender Deal\n",
"⚠️ A3960710: kein passender Deal\n",
"⚠️ A3960719: kein passender Deal\n",
"⚠️ A3960722: kein passender Deal\n",
"⚠️ A3960725: kein passender Deal\n",
"✅ A3908075: 3 Deal(s) → provision2_deal = 80\n",
"✅ A3908285: 6 Deal(s) → provision2_deal = 50\n",
"✅ A3908078: 3 Deal(s) → provision2_deal = 0\n",
"✅ A3908288: 6 Deal(s) → provision2_deal = -30\n",
"✅ A3908291: 6 Deal(s) → provision2_deal = 5\n",
"✅ A3908300: 6 Deal(s) → provision2_deal = -40\n",
"⚠️ A3960713: kein passender Deal\n",
"⚠️ A3960728: kein passender Deal\n",
"⚠️ A3960731: kein passender Deal\n",
"⚠️ A3960734: kein passender Deal\n",
"✅ A3908081: 3 Deal(s) → provision2_deal = 170\n",
"✅ A3908306: 6 Deal(s) → provision2_deal = 140\n",
"✅ A3908084: 3 Deal(s) → provision2_deal = 0\n",
"✅ A3908321: 6 Deal(s) → provision2_deal = -30\n",
"✅ A3908345: 6 Deal(s) → provision2_deal = 80\n",
"✅ A3908363: 6 Deal(s) → provision2_deal = 20\n",
"✅ A3908087: 3 Deal(s) → provision2_deal = 230\n",
"✅ A3908375: 6 Deal(s) → provision2_deal = 200\n",
"✅ A3908090: 3 Deal(s) → provision2_deal = 30\n",
"✅ A3908378: 6 Deal(s) → provision2_deal = 0\n",
"✅ A3908384: 6 Deal(s) → provision2_deal = 130\n",
"✅ A3908390: 6 Deal(s) → provision2_deal = 60\n",
"✅ A3908093: 3 Deal(s) → provision2_deal = 255\n",
"✅ A3908399: 6 Deal(s) → provision2_deal = 225\n",
"✅ A3908402: 6 Deal(s) → provision2_deal = 145\n",
"✅ A3908405: 6 Deal(s) → provision2_deal = 65\n",
"✅ A3908096: 3 Deal(s) → provision2_deal = 275\n",
"✅ A3908411: 6 Deal(s) → provision2_deal = 245\n",
"✅ A3908099: 3 Deal(s) → provision2_deal = -25\n",
"✅ A3908417: 6 Deal(s) → provision2_deal = -55\n",
"✅ A3908426: 6 Deal(s) → provision2_deal = 155\n",
"✅ A3908435: 6 Deal(s) → provision2_deal = 65\n",
"✅ A3908120: 3 Deal(s) → provision2_deal = 280\n",
"✅ A3908444: 6 Deal(s) → provision2_deal = 250\n",
"✅ A3908447: 6 Deal(s) → provision2_deal = 150\n",
"✅ A3908450: 6 Deal(s) → provision2_deal = 50\n",
"⚠️ A4117956: kein passender Deal\n",
"⚠️ A4117980: kein passender Deal\n",
"⚠️ A4117983: kein passender Deal\n",
"✅ A4117959: 1 Deal(s) → provision2_deal = 150\n",
"✅ A4117986: 2 Deal(s) → provision2_deal = 120\n",
"✅ A4117989: 2 Deal(s) → provision2_deal = 60\n",
"✅ A4117962: 1 Deal(s) → provision2_deal = 165\n",
"✅ A4117992: 2 Deal(s) → provision2_deal = 135\n",
"✅ A4117965: 1 Deal(s) → provision2_deal = -35\n",
"✅ A4117995: 2 Deal(s) → provision2_deal = -65\n",
"✅ A4117998: 2 Deal(s) → provision2_deal = 55\n",
"✅ A4117968: 1 Deal(s) → provision2_deal = 230\n",
"✅ A4118001: 2 Deal(s) → provision2_deal = 200\n",
"✅ A4117971: 1 Deal(s) → provision2_deal = 30\n",
"✅ A4118004: 2 Deal(s) → provision2_deal = 0\n",
"✅ A4118007: 2 Deal(s) → provision2_deal = 100\n",
"✅ A4117974: 1 Deal(s) → provision2_deal = 275\n",
"✅ A4118010: 2 Deal(s) → provision2_deal = 245\n",
"✅ A4118013: 2 Deal(s) → provision2_deal = 125\n",
"✅ A4117977: 1 Deal(s) → provision2_deal = 420\n",
"✅ A4118016: 2 Deal(s) → provision2_deal = 390\n",
"✅ A4118019: 2 Deal(s) → provision2_deal = 220\n",
"✅ A3934322: 1 Deal(s) → provision2_deal = 170\n",
"✅ A3934346: 2 Deal(s) → provision2_deal = 140\n",
"✅ A3934325: 1 Deal(s) → provision2_deal = 200\n",
"✅ A3934349: 2 Deal(s) → provision2_deal = 170\n",
"✅ A3934328: 1 Deal(s) → provision2_deal = 170\n",
"✅ A3934352: 2 Deal(s) → provision2_deal = 140\n",
"✅ A3934331: 1 Deal(s) → provision2_deal = 430\n",
"✅ A3934355: 2 Deal(s) → provision2_deal = 400\n",
"✅ A3934334: 1 Deal(s) → provision2_deal = -235\n",
"✅ A3934358: 2 Deal(s) → provision2_deal = -265\n",
"✅ A3934337: 1 Deal(s) → provision2_deal = -300\n",
"✅ A3934361: 2 Deal(s) → provision2_deal = -330\n",
"✅ A3934340: 1 Deal(s) → provision2_deal = -335\n",
"✅ A3934364: 2 Deal(s) → provision2_deal = -365\n",
"✅ A3934343: 1 Deal(s) → provision2_deal = -275\n",
"✅ A3934367: 2 Deal(s) → provision2_deal = -305\n",
"⚠️ A4061755: kein passender Deal\n",
"⚠️ A4061758: kein passender Deal\n",
"⚠️ A4061761: kein passender Deal\n",
"⚠️ A4061764: kein passender Deal\n",
"⚠️ A4061767: kein passender Deal\n",
"⚠️ A4061770: kein passender Deal\n",
"⚠️ A4035005: kein passender Deal\n",
"⚠️ A4035014: kein passender Deal\n",
"⚠️ A4035008: kein passender Deal\n",
"⚠️ A4035017: kein passender Deal\n",
"⚠️ A4035011: kein passender Deal\n",
"⚠️ A4035020: kein passender Deal\n",
"⚠️ A4035023: kein passender Deal\n",
"⚠️ A4035026: kein passender Deal\n",
"⚠️ A4035029: kein passender Deal\n",
"⚠️ A4035032: kein passender Deal\n",
"⚠️ A4035035: kein passender Deal\n",
"⚠️ A4035038: kein passender Deal\n",
"⚠️ A4038964: kein passender Deal\n",
"⚠️ A4039090: kein passender Deal\n",
"⚠️ A4039093: kein passender Deal\n",
"⚠️ A4039096: kein passender Deal\n",
"⚠️ A4038982: kein passender Deal\n",
"⚠️ A4039099: kein passender Deal\n",
"⚠️ A4039102: kein passender Deal\n",
"⚠️ A4039105: kein passender Deal\n",
"⚠️ A4038991: kein passender Deal\n",
"⚠️ A4039108: kein passender Deal\n",
"⚠️ A4039111: kein passender Deal\n",
"⚠️ A4039114: kein passender Deal\n",
"⚠️ A4035041: kein passender Deal\n",
"⚠️ A4035047: kein passender Deal\n",
"⚠️ A4035044: kein passender Deal\n",
"⚠️ A4035050: kein passender Deal\n",
"⚠️ A4035053: kein passender Deal\n",
"⚠️ A4035056: kein passender Deal\n",
"⚠️ A4039016: kein passender Deal\n",
"⚠️ A4039117: kein passender Deal\n",
"⚠️ A4039120: kein passender Deal\n",
"⚠️ A4039123: kein passender Deal\n",
"⚠️ A4039019: kein passender Deal\n",
"⚠️ A4039126: kein passender Deal\n",
"⚠️ A4039129: kein passender Deal\n",
"⚠️ A4039132: kein passender Deal\n",
"⚠️ A4039022: kein passender Deal\n",
"⚠️ A4039135: kein passender Deal\n",
"⚠️ A4039138: kein passender Deal\n",
"⚠️ A4039144: kein passender Deal\n",
"⚠️ A4039025: kein passender Deal\n",
"⚠️ A4039159: kein passender Deal\n",
"⚠️ A4039162: kein passender Deal\n",
"⚠️ A4039165: kein passender Deal\n",
"⚠️ A4160413: kein passender Deal\n",
"⚠️ A4160422: kein passender Deal\n",
"⚠️ A4160416: kein passender Deal\n",
"⚠️ A4160437: kein passender Deal\n",
"⚠️ A4160440: kein passender Deal\n",
"⚠️ A4160443: kein passender Deal\n",
"✅ A3631476: 1 Deal(s) → provision2_deal = 120\n",
"✅ A3631488: 2 Deal(s) → provision2_deal = 90\n",
"✅ A3631479: 1 Deal(s) → provision2_deal = 150\n",
"✅ A3631491: 1 Deal(s) → provision2_deal = 120\n",
"✅ A3631482: 1 Deal(s) → provision2_deal = 190\n",
"✅ A3631494: 2 Deal(s) → provision2_deal = 160\n",
"✅ A3631485: 1 Deal(s) → provision2_deal = 240\n",
"✅ A3631497: 2 Deal(s) → provision2_deal = 210\n",
"✅ A3481976: 1 Deal(s) → provision2_deal = 120\n",
"✅ A3482258: 2 Deal(s) → provision2_deal = 90\n",
"✅ A3482261: 2 Deal(s) → provision2_deal = 30\n",
"✅ A3482264: 2 Deal(s) → provision2_deal = -30\n",
"✅ A3481979: 1 Deal(s) → provision2_deal = 180\n",
"✅ A3482267: 2 Deal(s) → provision2_deal = 150\n",
"✅ A3656020: 1 Deal(s) → provision2_deal = -220\n",
"✅ A3656023: 2 Deal(s) → provision2_deal = -250\n",
"✅ A3482270: 2 Deal(s) → provision2_deal = 70\n",
"✅ A3482273: 2 Deal(s) → provision2_deal = -10\n",
"✅ A3481982: 1 Deal(s) → provision2_deal = 250\n",
"✅ A3482276: 2 Deal(s) → provision2_deal = 220\n",
"✅ A3482279: 2 Deal(s) → provision2_deal = 120\n",
"✅ A3482282: 2 Deal(s) → provision2_deal = 20\n",
"✅ A3481985: 1 Deal(s) → provision2_deal = 290\n",
"✅ A3482285: 2 Deal(s) → provision2_deal = 260\n",
"✅ A3482288: 2 Deal(s) → provision2_deal = 140\n",
"✅ A3482291: 2 Deal(s) → provision2_deal = 20\n",
"✅ A3481988: 1 Deal(s) → provision2_deal = 250\n",
"✅ A3482294: 2 Deal(s) → provision2_deal = 220\n",
"✅ A3482297: 2 Deal(s) → provision2_deal = 60\n",
"✅ A3482300: 2 Deal(s) → provision2_deal = -100\n",
"⚠️ A3481991: kein passender Deal\n",
"⚠️ A3481994: kein passender Deal\n",
"✅ A3782757: 6 Deal(s) → provision2_deal = 100\n",
"✅ A3782760: 6 Deal(s) → provision2_deal = 20\n",
"✅ A3782763: 6 Deal(s) → provision2_deal = 10\n",
"✅ A3823782: 6 Deal(s) → provision2_deal = 40\n",
"✅ A3832597: 6 Deal(s) → provision2_deal = 10\n",
"✅ A3782766: 8 Deal(s) → provision2_deal = 100\n",
"✅ A3823785: 8 Deal(s) → provision2_deal = 30\n",
"✅ A3832600: 8 Deal(s) → provision2_deal = -10\n",
"✅ A3782769: 6 Deal(s) → provision2_deal = 140\n",
"✅ A3823788: 6 Deal(s) → provision2_deal = 60\n",
"✅ A3832603: 6 Deal(s) → provision2_deal = 10\n",
"✅ A3782772: 6 Deal(s) → provision2_deal = 180\n",
"✅ A3782775: 6 Deal(s) → provision2_deal = 180\n",
"✅ A3989987: 1 Deal(s) → provision2_deal = 220\n",
"✅ A3989990: 1 Deal(s) → provision2_deal = 250\n",
"✅ A3989993: 1 Deal(s) → provision2_deal = 280\n",
"✅ A3989996: 1 Deal(s) → provision2_deal = 320\n",
"⚠️ A4160446: kein passender Deal\n",
"✅ A3989999: 6 Deal(s) → provision2_deal = 20\n",
"✅ A3990002: 6 Deal(s) → provision2_deal = 100\n",
"✅ A3990005: 6 Deal(s) → provision2_deal = 80\n",
"✅ A3990008: 6 Deal(s) → provision2_deal = 220\n",
"✅ A3990011: 6 Deal(s) → provision2_deal = 210\n",
"✅ A4069383: 6 Deal(s) → provision2_deal = 320\n",
"✅ A4069386: 6 Deal(s) → provision2_deal = 60\n",
"✅ A3974983: 2 Deal(s) → provision2_deal = 140\n",
"⚠️ A3782862: kein passender Deal\n",
"⚠️ A3782865: kein passender Deal\n",
"⚠️ A3782868: kein passender Deal\n",
"⚠️ A3907124: kein passender Deal\n",
"⚠️ A3907127: kein passender Deal\n",
"✅ A3834235: 1 Deal(s) → provision2_deal = 10\n",
"✅ A3834238: 1 Deal(s) → provision2_deal = 20\n",
"✅ A3422747: 2 Deal(s) → provision2_deal = 55\n",
"✅ A3422748: 4 Deal(s) → provision2_deal = 25\n",
"⚠️ D3398586: kein passender Deal\n",
"✅ A3429133: 4 Deal(s) → provision2_deal = -30\n",
"✅ A3429134: 4 Deal(s) → provision2_deal = -30\n",
"✅ A3429132: 2 Deal(s) → provision2_deal = 50\n",
"✅ A3429135: 4 Deal(s) → provision2_deal = 20\n",
"✅ A3398581: 2 Deal(s) → provision2_deal = 75\n",
"✅ A3398587: 4 Deal(s) → provision2_deal = 45\n",
"✅ A3398589: 4 Deal(s) → provision2_deal = 5\n",
"✅ A3398590: 4 Deal(s) → provision2_deal = -35\n",
"✅ A3398582: 2 Deal(s) → provision2_deal = 155\n",
"✅ A3398583: 4 Deal(s) → provision2_deal = 125\n",
"✅ A3398606: 4 Deal(s) → provision2_deal = 65\n",
"✅ A3398608: 4 Deal(s) → provision2_deal = 5\n",
"✅ A3482006: 2 Deal(s) → provision2_deal = 70\n",
"✅ A3482303: 4 Deal(s) → provision2_deal = 40\n",
"⚠️ A3482009: kein passender Deal\n",
"⚠️ A4022555: kein passender Deal\n",
"⚠️ A4022558: kein passender Deal\n",
"⚠️ A4022561: kein passender Deal\n",
"⚠️ A4022564: kein passender Deal\n",
"✅ A3974989: 1 Deal(s) → provision2_deal = 225\n",
"⚠️ A3907943: kein passender Deal\n",
"⚠️ A3907946: kein passender Deal\n",
"⚠️ A4069340: kein passender Deal\n",
"⚠️ A3782871: kein passender Deal\n",
"⚠️ A3782874: kein passender Deal\n",
"⚠️ A3782877: kein passender Deal\n",
"⚠️ A3782880: kein passender Deal\n",
"⚠️ A4139231: kein passender Deal\n",
"⚠️ A3990014: kein passender Deal\n",
"⚠️ A3975887: kein passender Deal\n",
"⚠️ A4118022: kein passender Deal\n",
"⚠️ A4118025: kein passender Deal\n",
"⚠️ A4118028: kein passender Deal\n",
"⚠️ A4118034: kein passender Deal\n",
"⚠️ A4118037: kein passender Deal\n",
"⚠️ A4118040: kein passender Deal\n",
"⚠️ A4118043: kein passender Deal\n",
"⚠️ A4118046: kein passender Deal\n",
"⚠️ A3908000: kein passender Deal\n",
"⚠️ A3908006: kein passender Deal\n",
"⚠️ A3934046: kein passender Deal\n",
"⚠️ A3934049: kein passender Deal\n",
"⚠️ A3908009: kein passender Deal\n",
"⚠️ A3908012: kein passender Deal\n",
"⚠️ A3908018: kein passender Deal\n",
"⚠️ A3908030: kein passender Deal\n",
"⚠️ A3908057: kein passender Deal\n",
"⚠️ A3908063: kein passender Deal\n",
"⚠️ A3908036: kein passender Deal\n",
"⚠️ A3908039: kein passender Deal\n",
"⚠️ A3908042: kein passender Deal\n",
"⚠️ A3908045: kein passender Deal\n",
"⚠️ A3908048: kein passender Deal\n",
"⚠️ A3908051: kein passender Deal\n",
"⚠️ A4061815: kein passender Deal\n",
"⚠️ A4061818: kein passender Deal\n",
"⚠️ A4044706: kein passender Deal\n",
"⚠️ A4044712: kein passender Deal\n",
"⚠️ A4140102: kein passender Deal\n",
"⚠️ A4140105: kein passender Deal\n",
"⚠️ A4069280: kein passender Deal\n",
"⚠️ A4069283: kein passender Deal\n",
"⚠️ A4069286: kein passender Deal\n",
"⚠️ A4069289: kein passender Deal\n",
"⚠️ A3832594: kein passender Deal\n",
"⚠️ A4033241: kein passender Deal\n",
"⚠️ A4140874: kein passender Deal\n",
"⚠️ A4140904: kein passender Deal\n",
"⚠️ A4140922: kein passender Deal\n",
"⚠️ A4118049: kein passender Deal\n",
"⚠️ A4118052: kein passender Deal\n",
"⚠️ A4140952: kein passender Deal\n",
"⚠️ A4140961: kein passender Deal\n",
"⚠️ A4118058: kein passender Deal\n",
"⚠️ A4140964: kein passender Deal\n",
"⚠️ A4118061: kein passender Deal\n",
"⚠️ A3936824: kein passender Deal\n",
"⚠️ A3936827: kein passender Deal\n",
"⚠️ A3848188: kein passender Deal\n",
"⚠️ A3833905: kein passender Deal\n",
"⚠️ A3833908: kein passender Deal\n",
"⚠️ A4117731: kein passender Deal\n",
"⚠️ A4117719: kein passender Deal\n",
"⚠️ A4117722: kein passender Deal\n",
"⚠️ A4117725: kein passender Deal\n",
"⚠️ A4117728: kein passender Deal\n",
"⚠️ A3848287: kein passender Deal\n",
"⚠️ A4022552: kein passender Deal\n",
"⚠️ A3908627: kein passender Deal\n",
"⚠️ A4065368: kein passender Deal\n",
"⚠️ A3908630: kein passender Deal\n",
"⚠️ A4069304: kein passender Deal\n",
"⚠️ A4069307: kein passender Deal\n",
"✅ A3908633: 6 Deal(s) → provision2_deal = -115\n",
"✅ A3833923: 2 Deal(s) → provision2_deal = -25\n",
"✅ A3833926: 2 Deal(s) → provision2_deal = 15\n",
"⚠️ A3833932: kein passender Deal\n",
"⚠️ A3908672: kein passender Deal\n",
"⚠️ A3908636: kein passender Deal\n",
"⚠️ A4065371: kein passender Deal\n",
"⚠️ A4140054: kein passender Deal\n",
"⚠️ A4140060: kein passender Deal\n",
"⚠️ A3398788: kein passender Deal\n",
"⚠️ A4139243: kein passender Deal\n",
"⚠️ A4069310: kein passender Deal\n",
"⚠️ A4069322: kein passender Deal\n",
"⚠️ A4069313: kein passender Deal\n",
"⚠️ A4069325: kein passender Deal\n",
"⚠️ A4069316: kein passender Deal\n",
"⚠️ A4069328: kein passender Deal\n",
"⚠️ A4069319: kein passender Deal\n",
"⚠️ A4069331: kein passender Deal\n",
"⚠️ A3908642: kein passender Deal\n",
"⚠️ A3908975: kein passender Deal\n",
"⚠️ A3908678: kein passender Deal\n",
"⚠️ A4117848: kein passender Deal\n",
"⚠️ A4117851: kein passender Deal\n",
"⚠️ A4139966: kein passender Deal\n",
"⚠️ A3877214: kein passender Deal\n",
"⚠️ A4022624: kein passender Deal\n",
"⚠️ A3957911: kein passender Deal\n",
"⚠️ A3957914: kein passender Deal\n",
"⚠️ A3908675: kein passender Deal\n",
"⚠️ A3908654: kein passender Deal\n",
"⚠️ A3936710: kein passender Deal\n",
"⚠️ A3908657: kein passender Deal\n",
"⚠️ A4035059: kein passender Deal\n",
"⚠️ A4116009: kein passender Deal\n",
"⚠️ A4116012: kein passender Deal\n",
"⚠️ A4116015: kein passender Deal\n",
"⚠️ A3989270: kein passender Deal\n",
"⚠️ A4034720: kein passender Deal\n",
"⚠️ A3908660: kein passender Deal\n",
"⚠️ A3908663: kein passender Deal\n",
"⚠️ A3908666: kein passender Deal\n",
"⚠️ A3908669: kein passender Deal\n",
"⚠️ A3990026: kein passender Deal\n",
"⚠️ A3990029: kein passender Deal\n",
"⚠️ A3990032: kein passender Deal\n",
"⚠️ A4038943: kein passender Deal\n",
"⚠️ A3833398: kein passender Deal\n",
"\n",
"Ergebnis: 144 Codes aktualisiert, 203 ohne Treffer\n"
]
}
],
"source": [
"from __future__ import annotations\n",
"import sys, re\n",
"from pathlib import Path\n",
"import pandas as pd\n",
"\n",
"# Diese Variable enthält das Basisverzeichnis für die Ausführung.\n",
"BASE_DIR = Path(__file__).resolve().parent.parent if \"__file__\" in globals() else Path.cwd().parent\n",
"\n",
"# Diese Variable legt den Pfad zur Excel-Datei fest.\n",
"EXCEL_PATH = BASE_DIR / \"cache\" / \"fachhandel.xlsx\"\n",
"\n",
"# Diese Variable definiert den auszulesenden Spaltenbereich der Excel-Datei.\n",
"SLICE = slice(7, 13)\n",
"\n",
"# Diese Variable stellt ein Muster für gültige Codes bereit.\n",
"CODE_RE = re.compile(r\"^[A-Z]\\d{7}$\")\n",
"\n",
"# Diese Variable stellt ein Muster für Zahlen bereit.\n",
"NUM_RE = re.compile(r\"^\\d+(,\\d+)?$\")\n",
"\n",
"# Diese Funktion wandelt einen Text in eine Ganzzahl um.\n",
"def to_int(txt: str) -> int:\n",
"\n",
" # Diese Anweisung entfernt überflüssige Leerzeichen.\n",
" txt = str(txt).strip()\n",
"\n",
" # Diese Abfrage ersetzt leere oder spezielle Platzhalter durch 0.\n",
" if txt in {\"\", \"-\", \"–\"}:\n",
" return 0\n",
"\n",
" # Diese Anweisung gibt den konvertierten Wert zurück oder 0, falls das Muster nicht passt.\n",
" return int(float(txt.replace(\",\", \".\"))) if NUM_RE.match(txt) else 0\n",
"\n",
"# Diese Funktion liest die Excel-Datei ein und gibt ein DataFrame mit gültigen Datensätzen zurück.\n",
"def parse_excel(path: Path) -> pd.DataFrame:\n",
"\n",
" # Diese Anweisung liest die Excel-Datei ohne Header ein und bereinigt leere Werte.\n",
" df = pd.read_excel(path, header=None, dtype=str).fillna(\"\").iloc[:, SLICE]\n",
"\n",
" # Diese Variable sammelt die ermittelten Datensätze in Form von Dictionaries.\n",
" records: list[dict[str, int | str]] = []\n",
"\n",
" # Diese Schleife durchläuft jede Zeile des DataFrame.\n",
" for row in df.itertuples(index=False):\n",
"\n",
" # Diese Anweisung fasst die Zelleninhalte in eine Liste zusammen und entfernt Leerzeichen.\n",
" r = [str(c).strip() for c in row]\n",
"\n",
" # Diese Abfrage überprüft, ob die Zeile gültige Einträge besitzt und ob mindestens ein Code vorhanden ist.\n",
" if not (len(r) == 6 and all(t in {\"\", \"-\", \"–\"} or NUM_RE.match(t) or CODE_RE.match(t) for t in r)\n",
" and (CODE_RE.match(r[2]) or CODE_RE.match(r[5]))):\n",
" continue\n",
"\n",
" # Diese Zuweisungen ordnen die Zellinhalte den Variablen zu.\n",
" verz_mc, bonus_mc, code_mc, verz_sp, bonus_sp, code_sp = r\n",
"\n",
" # Diese Abfrage fügt den Datensatz zur Liste hinzu, falls ein MC-Code vorliegt.\n",
" if CODE_RE.match(code_mc):\n",
" records.append({\"code\": code_mc, \"value\": to_int(bonus_mc) - to_int(verz_mc), \"source\": \"MC\"})\n",
"\n",
" # Diese Abfrage fügt den Datensatz zur Liste hinzu, falls ein SP-Code vorliegt.\n",
" if CODE_RE.match(code_sp):\n",
" records.append({\"code\": code_sp, \"value\": to_int(bonus_sp) - to_int(verz_sp), \"source\": \"SP\"})\n",
"\n",
" # Diese Anweisung gibt das erstellte DataFrame mit den gültigen Datensätzen zurück.\n",
" return pd.DataFrame(records)\n",
"\n",
"# Diese Anweisung erweitert den Modulpfad um das Projekt-Root.\n",
"sys.path.append(\"..\")\n",
"\n",
"from models.base_base import BaseBase # noqa: F401\n",
"from models.option_opti import OptionOpti # noqa: F401\n",
"from models.provisiongroup_pgro import ProvisiongroupPgro # noqa: F401\n",
"from models.deal_deal import DealDeal\n",
"from manager.MysqlManager import MysqlManager\n",
"from sqlalchemy.orm import Session\n",
"\n",
"# Diese Funktion aktualisiert anhand der DataFrame-Daten die Deals in der Datenbank.\n",
"def update_deals(df: pd.DataFrame) -> None:\n",
"\n",
" # Diese Anweisung erzeugt eine neue Session für den Datenbankzugriff.\n",
" session: Session = MysqlManager().getSession()\n",
" try:\n",
"\n",
" # Diese Variablen protokollieren die Anzahl erfolgreicher sowie nicht gefundener Deals.\n",
" hits = 0\n",
" misses = 0\n",
"\n",
" # Diese Schleife durchläuft alle Zeilen des DataFrame, um jede Code-Value-Kombination abzuarbeiten.\n",
" for code, value in df[[\"code\", \"value\"]].itertuples(index=False):\n",
"\n",
" # Diese Anweisung sucht nach passenden Deals in der Datenbank.\n",
" deals = session.query(DealDeal).filter_by(providercode_deal=code).all()\n",
"\n",
" # Diese Abfrage prüft, ob mindestens ein Deal gefunden wurde.\n",
" if deals:\n",
"\n",
" # Diese Schleife aktualisiert den Wert für provision2_deal bei jedem gefundenen Deal.\n",
" for d in deals:\n",
" if d.provision2_deal != value:\n",
" d.provision2_deal = value\n",
"\n",
" # Diese Anweisung erhöht den Zähler der erfolgreichen Treffer.\n",
" hits += 1\n",
" print(f\"✅ {code}: {len(deals)} Deal(s) → provision2_deal = {value}\")\n",
" else:\n",
"\n",
" # Diese Anweisung erhöht den Zähler der nicht gefundenen Deals.\n",
" misses += 1\n",
" print(f\"⚠️ {code}: kein passender Deal\")\n",
"\n",
" # Diese Anweisung bestätigt alle Änderungen in der Datenbank.\n",
" session.commit()\n",
" print(f\"\\nErgebnis: {hits} Codes aktualisiert, {misses} ohne Treffer\")\n",
"\n",
" except Exception as exc:\n",
"\n",
" # Diese Anweisung macht alle Änderungen rückgängig, wenn ein Fehler auftritt.\n",
" session.rollback()\n",
" print(f\"\\n❌ Fehler: {exc} – alle Änderungen zurückgerollt\")\n",
" raise\n",
" finally:\n",
"\n",
" # Diese Anweisung schließt die Session in jedem Fall.\n",
" session.close()\n",
"\n",
"# Diese Funktion bildet den Einstiegspunkt für die Programmausführung.\n",
"def main() -> None:\n",
"\n",
" # Diese Abfrage prüft, ob die Excel-Datei existiert.\n",
" if not EXCEL_PATH.is_file():\n",
" sys.exit(f\"Excel nicht gefunden: {EXCEL_PATH}\")\n",
"\n",
" # Diese Anweisung ruft die parse_excel-Funktion auf und speichert die Ergebnisse.\n",
" result_df = parse_excel(EXCEL_PATH)\n",
"\n",
" # Diese Anweisung passt die Anzeigeoptionen für DataFrames an.\n",
" pd.set_option(\"display.max_rows\", None, \"display.max_columns\", None, \"display.width\", None)\n",
"\n",
" # Diese Anweisung gibt einen Hinweis aus, dass die extrahierten Codes angezeigt werden.\n",
" #print(\"\\nExtracted codes:\")\n",
" #print(result_df)\n",
"\n",
" # Diese Abfrage prüft, ob das DataFrame leer ist.\n",
" if result_df.empty:\n",
" print(\"\\nKeine gültigen Aktionscodes gefunden – Ende.\")\n",
" return\n",
"\n",
" # Diese Anweisung ruft die Funktion auf, die die Deals in der Datenbank aktualisiert.\n",
" update_deals(result_df)\n",
"\n",
"# Diese Abfrage stellt sicher, dass main() nur ausgeführt wird, wenn das Skript direkt aufgerufen wird.\n",
"if __name__ == \"__main__\":\n",
" main()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2bf9926c-ea6d-406e-9a8c-c94b9ee0e2e8",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "531d8b07-8f2f-4ef6-b92a-d4bf3364e166",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ Datenbankmodule (DealDeal, BaseBase, MysqlManager, Session) erfolgreich importiert.\n",
"ℹ️ Info: ID-Feld 1 (Spalte J) wird als 'ID_J_Raw' eingelesen (Original-Header: 'Variant-ID MC').\n",
"ℹ️ Info: ID-Feld 2 (Spalte L) wird als 'ID_L_Raw' eingelesen (Original-Header: 'Unnamed: 11').\n",
"ℹ️ Info: ID-Feld 3 (Spalte N) wird als 'ID_N_Raw' eingelesen (Original-Header: 'Unnamed: 13').\n",
"ℹ️ Info: Grundvergütung (Spalte P) wird als 'Grundvergütung_Raw' eingelesen (Original-Header: 'Grund-\n",
"Vergütung1,5').\n",
"ℹ️ Info: Aktionsvergütung (Spalte Q) wird als 'Aktionsvergütung_Raw' eingelesen (Original-Header: 'Aktions-\n",
"vergütung1)').\n",
"ℹ️ Info: Punktewert (Spalte V) wird als 'Punktewert_Raw' eingelesen (Original-Header: 'Punktewert3').\n",
"✅ DataFrame initial geladen mit 173 Zeilen und Spalten: ['ID_J_Raw', 'ID_L_Raw', 'ID_N_Raw', 'Grundvergütung_Raw', 'Aktionsvergütung_Raw', 'Punktewert_Raw']\n",
"\n",
"\n",
"--- Transformiertes DataFrame (bereit für DB-Update) (erste 5 Zeilen) ---\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>id</th>\n",
" <th>provision1</th>\n",
" <th>provision3</th>\n",
" <th>provision4</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>3958043</td>\n",
" <td>50</td>\n",
" <td>80</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>3958055</td>\n",
" <td>130</td>\n",
" <td>80</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3958067</td>\n",
" <td>210</td>\n",
" <td>80</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>3877289</td>\n",
" <td>70</td>\n",
" <td>80</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>3877301</td>\n",
" <td>150</td>\n",
" <td>80</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" id provision1 provision3 provision4\n",
"0 3958043 50 80 0.0\n",
"1 3958055 130 80 0.0\n",
"2 3958067 210 80 0.0\n",
"3 3877289 70 80 0.0\n",
"4 3877301 150 80 0.0"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"⚠️ Code '3958043' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958055' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958067' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3877289': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877301': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877313': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '3958079' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958091' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958103' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3877325': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877337': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877349': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878213': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878225': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878237': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878165': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878177': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878189': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3905336': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3905348': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3905360': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878261': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878273': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878285': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '4045357' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045369' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045387' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045399' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045411' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034315' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034327' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034339' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034351' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034363' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034375' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038254' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038266' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038278' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038290' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038302' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038314' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038326' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038338' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038350' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034387' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034399' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034411' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038362' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038374' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038386' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038434' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038446' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038458' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038398' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038410' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038422' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038470' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038482' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038494' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4141231' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4141243' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4141255' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161818' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161830' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161842' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161854' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161866' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161878' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3833773': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833776': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833953': 2 Base-Eintrag/Einträge; 12 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833956': 2 Base-Eintrag/Einträge; 12 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833959': 2 Base-Eintrag/Einträge; 12 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833962': 2 Base-Eintrag/Einträge; 8 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833965': 2 Base-Eintrag/Einträge; 8 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833968': 2 Base-Eintrag/Einträge; 8 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833971': 2 Base-Eintrag/Einträge; 8 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833974': 2 Base-Eintrag/Einträge; 8 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833977': 2 Base-Eintrag/Einträge; 8 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833983': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833986': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833989': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833995': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3833998': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3834001': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '3833170' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3833173' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3833182' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3833185' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3833188' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3975845': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3975848': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3975854': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3975860': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '4141264' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3975866': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3975869': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3975875': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3975881': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '3058649' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '2970863' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '2970859' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3436262' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3436266' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4022570' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4022573' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4022576' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4022579' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3974111': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3398803': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435067': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3398807': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435071': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3398811': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435075': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3398815': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435079': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3446028': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461557': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461561': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461565': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461569': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3332926': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '3350064' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3832549' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3832585' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3832573' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3832561' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3337095': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3337091': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3448928': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '4069040' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '4069662': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069698': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907523': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069734': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907559': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069770': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907595': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069806': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907631': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936264': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936300': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936336': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936372': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936408': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750083': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750710': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750758': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750806': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3935061': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750806': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '3974749' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3973250': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973253': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973256': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973259': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973262': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973265': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973277': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973280': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973283': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973286': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973289': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973292': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973295': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973298': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3973301': 2 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3782322': 2 Base-Eintrag/Einträge; 6 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3782328': 2 Base-Eintrag/Einträge; 6 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3782331': 2 Base-Eintrag/Einträge; 6 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '4117887' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4117890' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958046' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958058' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958070' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3877292': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877304': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877316': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '3958082' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958094' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958106' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3877328': 1 Base-Eintrag/Einträge; 6 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877340': 1 Base-Eintrag/Einträge; 6 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877352': 1 Base-Eintrag/Einträge; 6 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878216': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878228': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878240': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878168': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878180': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878192': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3905339': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3905351': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3905363': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878264': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878276': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878288': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '4045357' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045372' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045390' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045402' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045414' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034318' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034330' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034342' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034354' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034366' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034378' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038257' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038269' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038281' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038293' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038305' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038317' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038329' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038341' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038353' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034390' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034402' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034414' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038365' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038377' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038389' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038437' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038449' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038461' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038401' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038413' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038425' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038473' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038485' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038497' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4141234' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4141246' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4141258' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161821' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161833' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161845' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161857' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161869' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161881' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3058650' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '2970864' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '2970860' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3436263' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3436267' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3398804': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435068': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3398808': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435072': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3398812': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435076': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3398816': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435080': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3446029': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461558': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461562': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461566': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461570': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '3832552' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3832588' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3832576' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3832564' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3337096': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3337092': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3448929': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '4069043' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '4069665': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069701': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907526': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069737': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907562': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069773': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907598': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069809': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907634': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936267': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936303': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936339': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936375': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936411': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750086': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750713': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750761': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750809': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750809': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '3958049' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958061' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958073' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3877295': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877307': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877319': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '3958085' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958097' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3958109' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3877331': 1 Base-Eintrag/Einträge; 6 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877343': 1 Base-Eintrag/Einträge; 6 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3877355': 1 Base-Eintrag/Einträge; 6 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878219': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878231': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878243': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878171': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878183': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878195': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3905342': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3905354': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3905366': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878267': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878279': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3878291': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '4045363' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045375' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045393' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045405' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4045417' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034321' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034333' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034345' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034357' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034369' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034381' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038260' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038272' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038284' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038296' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038308' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038320' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038332' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038344' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038356' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034393' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034405' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4034417' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038368' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038380' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038392' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038440' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038452' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038464' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038404' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038416' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038428' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038476' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038488' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4038500' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4141237' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4141249' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4141261' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161824' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161836' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161848' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161860' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161872' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '4161884' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3058651' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '2970865' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '2970861' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3436264' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3436268' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3398805': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435069': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3398809': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435073': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3398813': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435077': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3398817': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3435081': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3446030': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461559': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461563': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461567': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3461571': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '3832555' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3832591' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3832579' nicht in 'base_base' (providercode_base) gefunden.\n",
"⚠️ Code '3832567' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '3337097': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3337093': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3448930': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"⚠️ Code '4069046' nicht in 'base_base' (providercode_base) gefunden.\n",
"ℹ️ Code '4069668': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069704': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907529': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069740': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907565': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069776': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907601': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '4069812': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3907637': 1 Base-Eintrag/Einträge; 3 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936270': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936306': 1 Base-Eintrag/Einträge; 5 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936342': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936378': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3936414': 1 Base-Eintrag/Einträge; 4 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750089': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750716': 1 Base-Eintrag/Einträge; 1 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750764': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750812': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"ℹ️ Code '3750812': 1 Base-Eintrag/Einträge; 2 Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\n",
"\n",
"ℹ️ Keine Änderungen an Deal-Datensätzen vorgenommen, kein Commit notwendig.\n",
"\n",
"--- Zusammenfassung Datenbank-Update ---\n",
"Verarbeitete Provider-Codes (aus Datei): 399\n",
"Davon in 'base_base' gefunden: 205\n",
"Davon NICHT in 'base_base' gefunden: 194\n",
"Geprüfte Deal-Datensätze (via base_deal): 701\n",
"Aktualisierte Deal-Datensätze: 0\n",
"Datenbank-Session geschlossen.\n"
]
}
],
"source": [
"from pathlib import Path\n",
"import pandas as pd\n",
"import traceback\n",
"\n",
"try:\n",
" from IPython.display import display\n",
"except ImportError:\n",
" display = print\n",
"\n",
"import sys\n",
"\n",
"if \"__file__\" in globals():\n",
" PROJECT_ROOT_FOR_IMPORTS = Path(__file__).resolve().parent.parent\n",
"else:\n",
" if (Path.cwd() / \"cache\").is_dir():\n",
" PROJECT_ROOT_FOR_IMPORTS = Path.cwd()\n",
" else:\n",
" PROJECT_ROOT_FOR_IMPORTS = Path.cwd().parent\n",
"sys.path.append(str(PROJECT_ROOT_FOR_IMPORTS))\n",
"\n",
"MODELS_IMPORTED = False\n",
"try:\n",
" from models.deal_deal import DealDeal\n",
" from models.base_base import BaseBase\n",
" from manager.MysqlManager import MysqlManager\n",
" from sqlalchemy.orm import Session\n",
" MODELS_IMPORTED = True\n",
" print(\"✅ Datenbankmodule (DealDeal, BaseBase, MysqlManager, Session) erfolgreich importiert.\")\n",
"except ImportError as e:\n",
" print(f\"⚠️ Warnung: Datenbankmodule konnten nicht importiert werden: {e}\")\n",
" # print(f\" sys.path geprüft: {sys.path}\") # Für Debugging bei Bedarf einkommentieren\n",
" # print(f\" Angenommener Projekt-Root für Importe: {PROJECT_ROOT_FOR_IMPORTS}\")\n",
" print(\" Die Datenbankaktualisierung wird übersprungen.\")\n",
"\n",
"# normalize_col_name und add_column_by_name_or_position sind nicht mehr notwendig\n",
"\n",
"def can_be_int(value: object) -> bool:\n",
" \"\"\"Prüft, ob ein Wert als Integer interpretierbar ist (z.B. '123', 123.0).\"\"\"\n",
" if pd.isna(value): return False\n",
" try:\n",
" float_val = float(value)\n",
" return float_val == int(float_val)\n",
" except (ValueError, TypeError): return False\n",
"\n",
"def load_freenet_tarife_data() -> pd.DataFrame | None:\n",
" \"\"\"\n",
" Lädt spezifische Spalten aus dem Tabellenblatt 'freenet-Tarife' der Excel-Datei\n",
" 'fachhandel3.xlsx' (gemäß letzter Referenz A) ausschließlich über feste Spaltenpositionen.\n",
" \"\"\"\n",
" PROJECT_ROOT_DIR = None\n",
" if \"__file__\" in globals():\n",
" PROJECT_ROOT_DIR = Path(__file__).resolve().parent.parent\n",
" else:\n",
" cwd = Path.cwd()\n",
" if (cwd / \"cache\" / \"fachhandel3.xlsx\").is_file(): PROJECT_ROOT_DIR = cwd\n",
" elif (cwd.parent / \"cache\" / \"fachhandel3.xlsx\").is_file(): PROJECT_ROOT_DIR = cwd.parent\n",
" else: PROJECT_ROOT_DIR = cwd.parent\n",
"\n",
" EXCEL_PATH = PROJECT_ROOT_DIR / \"cache\" / \"fachhandel3.xlsx\"\n",
" SHEET_NAME = \"freenet-Tarife\"\n",
" HEADER_ROW_INDEX = 6 # Header ist in Zeile 7 (0-indiziert = 6)\n",
"\n",
" # Definierte 0-basierte Spaltenpositionen\n",
" # ID-Felder: J (9), L (11), N (13)\n",
" # Grundvergütung: P (15)\n",
" # Aktionsvergütung: Q (16)\n",
" # Punktewert (für provision3): V (21)\n",
" COL_POS_ID_J = 9\n",
" COL_POS_ID_L = 11\n",
" COL_POS_ID_N = 13\n",
" COL_POS_GV = 15\n",
" COL_POS_AV = 16\n",
" COL_POS_PW = 21 # Für Punktewert\n",
"\n",
" try:\n",
" if not EXCEL_PATH.is_file():\n",
" print(f\"❌ Excel-Datei nicht gefunden: {EXCEL_PATH}\"); return None\n",
"\n",
" # Zuerst die Header-Zeile lesen, um die originalen Spaltennamen an den Positionen zu erhalten\n",
" df_full_header = pd.read_excel(EXCEL_PATH, sheet_name=SHEET_NAME, header=HEADER_ROW_INDEX, nrows=0)\n",
" all_original_columns = df_full_header.columns.tolist()\n",
"\n",
" columns_to_select_by_original_name = []\n",
" internal_rename_map = {} # Mappt originalen Excel-Headernamen auf unsere internen Rohnamen\n",
"\n",
" # Hilfsfunktion zum Zuordnen\n",
" def map_pos_to_internal_name(pos, internal_raw_name, description):\n",
" if pos < len(all_original_columns):\n",
" original_header_name = all_original_columns[pos]\n",
" columns_to_select_by_original_name.append(original_header_name)\n",
" internal_rename_map[original_header_name] = internal_raw_name\n",
" print(f\"ℹ️ Info: {description} (Spalte {chr(ord('A')+pos)}) wird als '{internal_raw_name}' eingelesen (Original-Header: '{original_header_name}').\")\n",
" else:\n",
" print(f\"⚠️ Warnung: {description} (erwartet an Spaltenposition {pos} / {chr(ord('A')+pos)}) ist außerhalb des Bereichs der Excel-Datei ({len(all_original_columns)} Spalten).\")\n",
" \n",
" map_pos_to_internal_name(COL_POS_ID_J, \"ID_J_Raw\", \"ID-Feld 1\")\n",
" map_pos_to_internal_name(COL_POS_ID_L, \"ID_L_Raw\", \"ID-Feld 2\")\n",
" map_pos_to_internal_name(COL_POS_ID_N, \"ID_N_Raw\", \"ID-Feld 3\")\n",
" map_pos_to_internal_name(COL_POS_GV, \"Grundvergütung_Raw\", \"Grundvergütung\")\n",
" map_pos_to_internal_name(COL_POS_AV, \"Aktionsvergütung_Raw\", \"Aktionsvergütung\")\n",
" map_pos_to_internal_name(COL_POS_PW, \"Punktewert_Raw\", \"Punktewert\")\n",
"\n",
" if not columns_to_select_by_original_name:\n",
" print(\"❌ Keine der benötigten Spalten konnte anhand der Positionen in der Excel-Datei gefunden werden.\"); return None\n",
" \n",
" # Entferne Duplikate, falls eine Position versehentlich mehrfach zugewiesen wurde oder gleiche Originalnamen hat\n",
" columns_to_select_by_original_name = sorted(list(set(columns_to_select_by_original_name)))\n",
"\n",
"\n",
" df = pd.read_excel(EXCEL_PATH, sheet_name=SHEET_NAME, header=HEADER_ROW_INDEX, \n",
" usecols=columns_to_select_by_original_name, dtype=str) # Alles als String lesen für initiale Konsistenz\n",
" df.rename(columns=internal_rename_map, inplace=True)\n",
" \n",
" # Sicherstellen, dass alle erwarteten internen Rohnamen als Spalten existieren, ggf. mit Warnung\n",
" expected_raw_cols = [\"ID_J_Raw\", \"ID_L_Raw\", \"ID_N_Raw\", \"Grundvergütung_Raw\", \"Aktionsvergütung_Raw\", \"Punktewert_Raw\"]\n",
" for col in expected_raw_cols:\n",
" if col not in df.columns:\n",
" print(f\"⚠️ Warnung nach Einlesen: Die intern benannte Spalte '{col}' fehlt im DataFrame. Dies kann passieren, wenn die Excel-Position ungültig war.\")\n",
"\n",
"\n",
" df.dropna(how='all', inplace=True) # Leere Zeilen entfernen\n",
" df.reset_index(drop=True, inplace=True)\n",
" \n",
" return df\n",
"\n",
" except FileNotFoundError: print(f\"❌ Fehler: Excel nicht gefunden: {EXCEL_PATH}\"); return None\n",
" except Exception as e: print(f\"❌ Unerwarteter Fehler beim Laden der Excel-Datei: {e}\"); print(traceback.format_exc()); return None\n",
"\n",
"\n",
"def update_deals_in_database(df_input: pd.DataFrame):\n",
" if not MODELS_IMPORTED:\n",
" print(\"❌ DB-Update nicht möglich: Module nicht geladen.\"); return\n",
"\n",
" required_cols = ['id', 'provision1', 'provision3', 'provision4']\n",
" if not all(col in df_input.columns for col in required_cols):\n",
" missing = [col for col in required_cols if col not in df_input.columns]\n",
" print(f\"❌ DB-Update abgebrochen: Fehlende Spalten im DataFrame für DB-Update: {missing}\"); return\n",
" \n",
" session: Session = MysqlManager().getSession()\n",
" provider_codes_processed = 0\n",
" base_records_found_count = 0\n",
" base_records_not_found_count = 0\n",
" total_deal_records_checked = 0\n",
" total_deal_records_updated = 0\n",
"\n",
" try:\n",
" for row in df_input.itertuples(index=False):\n",
" provider_codes_processed += 1\n",
" current_provider_code_from_df = str(row.id)\n",
" val_provision1 = row.provision1\n",
" val_provision3 = row.provision3\n",
" val_provision4 = row.provision4\n",
"\n",
" base_records = session.query(BaseBase).filter_by(providercode_base=current_provider_code_from_df).all()\n",
"\n",
" if base_records:\n",
" base_records_found_count += 1\n",
" deals_found_for_this_pc = 0\n",
" deals_updated_for_this_pc = 0\n",
" \n",
" for base_record in base_records:\n",
" current_id_base = base_record.id_base\n",
" deals_to_update = session.query(DealDeal).filter_by(base_deal=current_id_base).all()\n",
" deals_found_for_this_pc += len(deals_to_update)\n",
" \n",
" for deal_obj in deals_to_update:\n",
" changed_in_this_obj = False\n",
" if deal_obj.provision1_deal != val_provision1:\n",
" deal_obj.provision1_deal = val_provision1\n",
" changed_in_this_obj = True\n",
" \n",
" if hasattr(deal_obj, 'provision3_deal'):\n",
" if deal_obj.provision3_deal != val_provision3:\n",
" deal_obj.provision3_deal = val_provision3\n",
" changed_in_this_obj = True\n",
" elif val_provision3 != 0: # Nur warnen, wenn versucht wird, einen Wert != 0 zu schreiben\n",
" # Annahme: id_deal ist der Primärschlüssel von DealDeal für die Logausgabe\n",
" deal_id_for_log = getattr(deal_obj, 'id_deal', 'N/A')\n",
" print(f\" ⚠️ Deal (ID: {deal_id_for_log}) hat kein Feld 'provision3_deal'. Wert {val_provision3} nicht gesetzt.\")\n",
"\n",
" if deal_obj.provision4_deal != val_provision4:\n",
" deal_obj.provision4_deal = val_provision4\n",
" changed_in_this_obj = True\n",
" \n",
" if changed_in_this_obj:\n",
" deals_updated_for_this_pc += 1\n",
" \n",
" total_deal_records_checked += deals_found_for_this_pc\n",
" total_deal_records_updated += deals_updated_for_this_pc\n",
"\n",
" if deals_found_for_this_pc > 0:\n",
" if deals_updated_for_this_pc > 0:\n",
" print(f\"✅ Code '{current_provider_code_from_df}': {len(base_records)} Base-Eintrag/Einträge; {deals_updated_for_this_pc} von {deals_found_for_this_pc} Deal(s) aktualisiert (P1,P3,P4).\")\n",
" else:\n",
" print(f\"ℹ️ Code '{current_provider_code_from_df}': {len(base_records)} Base-Eintrag/Einträge; {deals_found_for_this_pc} Deal(s) gefunden, keine Updates bei P1/P3/P4 nötig.\")\n",
" else:\n",
" print(f\"ℹ️ Code '{current_provider_code_from_df}': {len(base_records)} Base-Eintrag/Einträge gefunden, aber keine zugehörigen Deals.\")\n",
" else:\n",
" base_records_not_found_count += 1\n",
" print(f\"⚠️ Code '{current_provider_code_from_df}' nicht in 'base_base' (providercode_base) gefunden.\")\n",
" \n",
" if total_deal_records_updated > 0:\n",
" session.commit()\n",
" print(f\"\\n✅ Datenbank-Commit erfolgreich.\")\n",
" else:\n",
" print(\"\\nℹ️ Keine Änderungen an Deal-Datensätzen vorgenommen, kein Commit notwendig.\")\n",
"\n",
" print(f\"\\n--- Zusammenfassung Datenbank-Update ---\")\n",
" print(f\"Verarbeitete Provider-Codes (aus Datei): {provider_codes_processed}\")\n",
" print(f\"Davon in 'base_base' gefunden: {base_records_found_count}\")\n",
" print(f\"Davon NICHT in 'base_base' gefunden: {base_records_not_found_count}\")\n",
" print(f\"Geprüfte Deal-Datensätze (via base_deal): {total_deal_records_checked}\")\n",
" print(f\"Aktualisierte Deal-Datensätze: {total_deal_records_updated}\")\n",
"\n",
" except Exception as e:\n",
" session.rollback()\n",
" print(f\"\\n❌ Fehler beim DB-Update: {e} – Änderungen zurückgerollt.\")\n",
" print(traceback.format_exc())\n",
" finally:\n",
" if MODELS_IMPORTED and session: session.close(); print(\"Datenbank-Session geschlossen.\")\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" freenet_tarife_df_original = load_freenet_tarife_data()\n",
" df_transformed = pd.DataFrame()\n",
" \n",
" if freenet_tarife_df_original is not None and not freenet_tarife_df_original.empty:\n",
" print(f\"✅ DataFrame initial geladen mit {freenet_tarife_df_original.shape[0]} Zeilen und Spalten: {freenet_tarife_df_original.columns.tolist()}\")\n",
" df_to_transform = freenet_tarife_df_original.copy()\n",
"\n",
" # Provisionen P1 und P4 erstellen und NaNs füllen\n",
" provision_map = {\n",
" \"Grundvergütung_Raw\": \"provision1\",\n",
" \"Aktionsvergütung_Raw\": \"provision4\"\n",
" }\n",
" for raw_col, final_col in provision_map.items():\n",
" if raw_col in df_to_transform.columns:\n",
" df_to_transform[final_col] = pd.to_numeric(df_to_transform[raw_col], errors='coerce').fillna(0)\n",
" if raw_col != final_col: # Nur löschen, wenn es eine separate Rohspalte war\n",
" df_to_transform.drop(columns=[raw_col], inplace=True, errors='ignore')\n",
" else:\n",
" print(f\"⚠️ Warnung: Rohspalte '{raw_col}' für '{final_col}' nicht gefunden. '{final_col}' wird mit 0 initialisiert.\")\n",
" df_to_transform[final_col] = 0\n",
" \n",
" # Provision P3 erstellen\n",
" if \"Punktewert_Raw\" in df_to_transform.columns:\n",
" punktewert_numeric = pd.to_numeric(df_to_transform[\"Punktewert_Raw\"], errors='coerce')\n",
" df_to_transform['provision3'] = punktewert_numeric.apply(lambda x: 80 if pd.notna(x) and x > 1 else 0)\n",
" df_to_transform.drop(columns=[\"Punktewert_Raw\"], inplace=True, errors='ignore') # Rohspalte entfernen\n",
" else:\n",
" print(\"⚠️ Warnung: 'Punktewert_Raw' nicht gefunden. 'provision3' wird auf 0 gesetzt.\")\n",
" df_to_transform['provision3'] = 0\n",
" \n",
" # ID-Spalten für Melt vorbereiten (die jetzt _Raw heißen)\n",
" id_raw_cols_to_melt = []\n",
" for raw_id_col_candidate in [\"ID_J_Raw\", \"ID_L_Raw\", \"ID_N_Raw\"]:\n",
" if raw_id_col_candidate in df_to_transform.columns:\n",
" id_raw_cols_to_melt.append(raw_id_col_candidate)\n",
"\n",
" if not id_raw_cols_to_melt:\n",
" print(\"❌ Keine Roh-ID-Spalten (J, L, N) zum Umformatieren gefunden.\")\n",
" else:\n",
" id_vars_for_melt = [col for col in df_to_transform.columns if col not in id_raw_cols_to_melt]\n",
" \n",
" df_transformed = pd.melt(df_to_transform, id_vars=id_vars_for_melt, value_vars=id_raw_cols_to_melt,\n",
" var_name=\"original_id_source_column\", value_name=\"id\")\n",
" df_transformed.drop(columns=[\"original_id_source_column\"], inplace=True)\n",
" # Auch die ursprünglichen _Raw ID-Spalten werden durch melt entfernt, falls sie nicht in id_vars waren\n",
" \n",
" df_transformed.dropna(subset=['id'], inplace=True)\n",
"\n",
" if 'id' in df_transformed.columns:\n",
" df_transformed['id'] = df_transformed['id'].astype(str).str.strip()\n",
" df_transformed = df_transformed[df_transformed['id'] != '']\n",
" if not df_transformed.empty:\n",
" is_int_mask = df_transformed['id'].apply(can_be_int)\n",
" df_transformed = df_transformed[is_int_mask]\n",
" if not df_transformed.empty and 'id' in df_transformed.columns:\n",
" try: df_transformed['id'] = df_transformed['id'].astype(float).astype(int)\n",
" except ValueError as e: print(f\"⚠️ Warnung bei Konvertierung 'id' zu int: {e}.\")\n",
" \n",
" desired_order = ['id']\n",
" for prov_col in ['provision1', 'provision3', 'provision4']: # Reihenfolge sicherstellen\n",
" if prov_col in df_transformed.columns:\n",
" desired_order.append(prov_col)\n",
" \n",
" remaining_cols = [col for col in df_transformed.columns if col not in desired_order]\n",
" final_columns_order = desired_order + remaining_cols\n",
" if not df_transformed.empty:\n",
" df_transformed = df_transformed.reindex(columns=final_columns_order, copy=False)\n",
"\n",
" print(\"\\n\\n--- Transformiertes DataFrame (bereit für DB-Update) (erste 5 Zeilen) ---\")\n",
" display(df_transformed.head())\n",
" \n",
" if df_transformed.empty:\n",
" print(\"⚠️ Transformiertes DataFrame ist leer. Kein Datenbank-Update.\")\n",
" else:\n",
" update_deals_in_database(df_transformed)\n",
" else:\n",
" print(\"\\nGeladenes DataFrame ist leer oder None. Keine Transformation oder DB-Update.\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2bf9926c-ea6d-406e-9a8c-c94b9ee0e2e8",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "59561050-6403-44d1-82c0-47778a97bde6",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
...@@ -7,6 +7,7 @@ https://s3.eu-central-1.amazonaws.com/monosnap.bugsmasher.online/marcoschmiedel/ ...@@ -7,6 +7,7 @@ https://s3.eu-central-1.amazonaws.com/monosnap.bugsmasher.online/marcoschmiedel/
https://s3.eu-central-1.amazonaws.com/monosnap.bugsmasher.online/marcoschmiedel/2025-05-09.m4v https://s3.eu-central-1.amazonaws.com/monosnap.bugsmasher.online/marcoschmiedel/2025-05-09.m4v
https://s3.eu-central-1.amazonaws.com/monosnap.bugsmasher.online/marcoschmiedel/2025-05-13+API-Tutorial.mp4 https://s3.eu-central-1.amazonaws.com/monosnap.bugsmasher.online/marcoschmiedel/2025-05-13+API-Tutorial.mp4
https://s3.eu-central-1.amazonaws.com/monosnap.bugsmasher.online/marcoschmiedel/2024-05-13.m4v https://s3.eu-central-1.amazonaws.com/monosnap.bugsmasher.online/marcoschmiedel/2024-05-13.m4v
https://s3.eu-central-1.amazonaws.com/monosnap.bugsmasher.online/marcoschmiedel/2025-06-03.m4v
## JupyterLab ## JupyterLab
......
# --- Imports ---
from flask import Blueprint, request, render_template_string
import pandas as pd
import re
from pathlib import Path
import traceback
import sys
import io # Für das Abfangen von print-Ausgaben
# --- Logging Utility ---
# This helper returns a consistently formatted log entry for UI display and console output.
def _log(level: str, stage: str, message: str) -> str:
ICONS = {"INFO": "ℹ️", "SUCCESS": "✅", "WARNING": "⚠️", "ERROR": "❌"}
icon = ICONS.get(level.upper(), "ℹ️")
return f"{icon} [{stage}] {message}"
# --- Globale Konfiguration & Initialisierung ---
# The Blueprint registers the upload route inside the containing Flask application.
blueprint = Blueprint(__name__.rsplit(".", 1)[-1], __name__)
# The import of IPython.display.display is optional for nicer debugging inside notebooks.
try:
from IPython.display import display
except ImportError:
display = print
# This block establishes the project root so that internal modules can be imported reliably.
if "__file__" in globals():
PROJECT_ROOT_FOR_IMPORTS = Path(__file__).resolve().parent.parent
else:
if (Path.cwd() / "cache").is_dir() and (Path.cwd() / "models").is_dir():
PROJECT_ROOT_FOR_IMPORTS = Path.cwd()
else:
PROJECT_ROOT_FOR_IMPORTS = Path.cwd().parent
sys.path.append(str(PROJECT_ROOT_FOR_IMPORTS))
# These placeholders are overwritten if the database models can be imported successfully.
MODELS_IMPORTED = False
DealDeal, BaseBase, MysqlManager, Session = None, None, None, None
# The following try-except attempts to load the SQLAlchemy models and a helper for database sessions.
try:
from models.deal_deal import DealDeal
from models.base_base import BaseBase
from manager.MysqlManager import MysqlManager
from sqlalchemy.orm import Session
MODELS_IMPORTED = True
print(_log("SUCCESS", "Startup", "Database modules imported successfully."))
# A failure to import modules results in a warning and disables database operations for the session.
except ImportError as e:
print(_log("WARNING", "Startup", f"Database modules could not be imported: {e}"))
print(_log("INFO", "Startup", f"sys.path = {sys.path}"))
print(_log("INFO", "Startup", f'Assumed project root: "{PROJECT_ROOT_FOR_IMPORTS}"'))
print(_log("INFO", "Startup", "Database operations will be skipped."))
# --- Reguläre Ausdrücke und Hilfsfunktionen ---
# This regex matches a provider code in reference B files.
CODE_RE_B = re.compile(r"^[A-Z]\d{7}$")
# This regex matches numeric strings that may contain a comma as a decimal separator.
NUM_RE_B = re.compile(r"^\d+(,\d+)?$")
# The helper converts a numeric string from reference B into an integer representation.
def to_int_ref_b(txt: str) -> int:
txt = str(txt).strip()
if txt in {"", "-", "–"}:
return 0
return int(float(txt.replace(",", "."))) if NUM_RE_B.match(txt) else 0
# The helper validates whether a value can safely be converted to an integer.
def can_be_int_ref_a(value: object) -> bool:
if pd.isna(value):
return False
try:
float_val = float(value)
return float_val == int(float_val)
except (ValueError, TypeError):
return False
# The helper heuristically detects whether the uploaded file is of reference A type.
def is_referenz_a_file(file_stream) -> bool:
try:
file_stream.seek(0)
xls = pd.ExcelFile(file_stream)
if "freenet-Tarife" not in xls.sheet_names:
return False
file_stream.seek(0)
df_header = pd.read_excel(file_stream, sheet_name="freenet-Tarife", header=6, nrows=0)
return df_header.shape[1] >= 22
except Exception:
return False
# The helper heuristically detects whether the uploaded file is of reference B type.
def is_referenz_b_file(file_stream) -> bool:
try:
file_stream.seek(0)
df_sample = pd.read_excel(file_stream, sheet_name=0, header=None, nrows=20, dtype=str)
if df_sample.shape[1] < 13:
return False
df_sliced_sample = df_sample.iloc[:, 7:13]
if df_sliced_sample.shape[1] != 6:
return False
match_count = 0
rows_to_check = min(len(df_sliced_sample), 10)
if rows_to_check == 0:
return False
for i in range(rows_to_check):
row_values = [str(c).strip() for c in df_sliced_sample.iloc[i].tolist()]
if len(row_values) == 6:
if CODE_RE_B.match(row_values[2]) or CODE_RE_B.match(row_values[5]):
match_count += 1
return match_count > 0
except Exception:
return False
# --- Reference A Verarbeitung ---
# The loader reads the relevant columns from the reference A Excel file and logs any inconsistencies.
def load_data_ref_a(file_stream) -> tuple[pd.DataFrame | None, list[str]]:
SHEET_NAME = "freenet-Tarife"
HEADER_ROW_INDEX = 6
COL_POS_ID_J, COL_POS_ID_L, COL_POS_ID_N = 9, 11, 13
COL_POS_GV, COL_POS_AV, COL_POS_PW = 15, 16, 21
log_ref_a = []
# This nested helper maps column positions to internal names while collecting warnings.
def map_pos(all_columns, pos, internal_name, desc):
if pos < len(all_columns):
orig_header = all_columns[pos]
columns_to_select_by_original_name.append(orig_header)
internal_rename_map[orig_header] = internal_name
else:
log_ref_a.append(_log("WARNING", "RefA-Load", f"{desc} (expected pos {pos}) out of range ({len(all_columns)} columns)."))
try:
file_stream.seek(0)
df_full_header = pd.read_excel(file_stream, sheet_name=SHEET_NAME, header=HEADER_ROW_INDEX, nrows=0)
all_original_columns = df_full_header.columns.tolist()
columns_to_select_by_original_name: list[str] = []
internal_rename_map: dict[str, str] = {}
map_pos(all_original_columns, COL_POS_ID_J, "ID_J_Raw", "ID1")
map_pos(all_original_columns, COL_POS_ID_L, "ID_L_Raw", "ID2")
map_pos(all_original_columns, COL_POS_ID_N, "ID_N_Raw", "ID3")
map_pos(all_original_columns, COL_POS_GV, "Grundvergütung_Raw", "GV")
map_pos(all_original_columns, COL_POS_AV, "Aktionsvergütung_Raw", "AV")
map_pos(all_original_columns, COL_POS_PW, "Punktewert_Raw", "PW")
# An early exit occurs if no usable columns were found.
if not columns_to_select_by_original_name:
log_ref_a.append(_log("ERROR", "RefA-Load", "No columns found by position."))
return None, log_ref_a
# The loader reads the selected columns and applies the internal rename map.
columns_to_select_by_original_name = sorted(set(columns_to_select_by_original_name))
file_stream.seek(0)
df = pd.read_excel(
file_stream,
sheet_name=SHEET_NAME,
header=HEADER_ROW_INDEX,
usecols=columns_to_select_by_original_name,
dtype=str,
)
df.rename(columns=internal_rename_map, inplace=True)
df.dropna(how="all", inplace=True)
df.reset_index(drop=True, inplace=True)
log_ref_a.append(
_log(
"SUCCESS",
"RefA-Load",
f"DataFrame loaded with {df.shape[0]} rows. Columns: {df.columns.tolist()}",
)
)
return df, log_ref_a
# The catch-all handles any unexpected loader failure.
except Exception as e:
log_ref_a.append(_log("ERROR", "RefA-Load", str(e)))
print(traceback.format_exc())
return None, log_ref_a
# The transformer converts raw numeric columns and reshapes IDs into a consolidated format.
def transform_data_ref_a(df_loaded: pd.DataFrame) -> tuple[pd.DataFrame | None, list[str]]:
log_ref_a: list[str] = []
# An empty or missing DataFrame causes an early exit.
if df_loaded is None or df_loaded.empty:
log_ref_a.append(_log("INFO", "RefA-Transform", "Input DataFrame is empty."))
return pd.DataFrame(), log_ref_a
df = df_loaded.copy()
# This loop converts three raw numeric columns into integer provisions.
for raw_col, final_col in {
"Grundvergütung_Raw": "provision1",
"Aktionsvergütung_Raw": "provision4",
}.items():
if raw_col in df.columns:
df[final_col] = pd.to_numeric(df[raw_col], errors="coerce").fillna(0)
if raw_col != final_col:
df.drop(columns=[raw_col], inplace=True, errors="ignore")
else:
df[final_col] = 0
log_ref_a.append(
_log("WARNING", "RefA-Transform", f"Raw column '{raw_col}' missing; set '{final_col}' to 0.")
)
# This branch creates a third provision based on the points value if present.
if "Punktewert_Raw" in df.columns:
pw_num = pd.to_numeric(df["Punktewert_Raw"], errors="coerce")
df["provision3"] = pw_num.apply(lambda x: 80 if pd.notna(x) and x > 1 else 0)
df.drop(columns=["Punktewert_Raw"], inplace=True, errors="ignore")
# The else branch handles the absence of the points column.
else:
df["provision3"] = 0
log_ref_a.append(
_log("WARNING", "RefA-Transform", "'Punktewert_Raw' missing; set 'provision3' to 0.")
)
id_raw_cols = [c for c in ["ID_J_Raw", "ID_L_Raw", "ID_N_Raw"] if c in df.columns]
# An early exit is triggered if none of the expected ID columns are present.
if not id_raw_cols:
log_ref_a.append(_log("ERROR", "RefA-Transform", "No raw ID columns available for melt operation."))
return pd.DataFrame(), log_ref_a
# The melt reshapes each ID column into a single 'id' column, preserving additional data.
id_vars = [c for c in df.columns if c not in id_raw_cols]
df_melted = pd.melt(df, id_vars=id_vars, value_vars=id_raw_cols, var_name="_src", value_name="id")
df_melted.drop(columns=["_src"], inplace=True)
df_melted.dropna(subset=["id"], inplace=True)
# This block validates and normalizes the ID column to integer type.
if "id" in df_melted.columns:
df_melted["id"] = df_melted["id"].astype(str).str.strip()
df_melted = df_melted[df_melted["id"] != ""]
if not df_melted.empty:
df_melted = df_melted[df_melted["id"].apply(can_be_int_ref_a)]
if not df_melted.empty:
try:
df_melted["id"] = df_melted["id"].astype(float).astype(int)
except ValueError:
log_ref_a.append(
_log("WARNING", "RefA-Transform", "Failed to cast 'id' column to int.")
)
# This line reorders the most important columns to the beginning of the DataFrame.
order = ["id"] + [p for p in ["provision1", "provision3", "provision4"] if p in df_melted.columns]
final_cols = order + [c for c in df_melted.columns if c not in order]
if not df_melted.empty:
df_melted = df_melted.reindex(columns=final_cols, copy=False)
log_ref_a.append(
_log("SUCCESS", "RefA-Transform", f"Transformation completed with {len(df_melted)} records ready for DB.")
)
return df_melted, log_ref_a
# The updater synchronizes transformed reference A data into the transactional database.
def update_db_ref_a(df_input: pd.DataFrame, log_ref_a_outer: list[str]) -> list[str]:
log_ref_a = log_ref_a_outer
# A missing model import prevents any database interaction.
if not MODELS_IMPORTED:
log_ref_a.append(_log("ERROR", "RefA-DB", "Database modules are not available."))
return log_ref_a
# An empty DataFrame leads to an informational exit.
if df_input.empty:
log_ref_a.append(_log("INFO", "RefA-DB", "Input DataFrame is empty."))
return log_ref_a
req_cols = ["id", "provision1", "provision3", "provision4"]
# Ensuring all required columns exist before proceeding.
if not all(c in df_input.columns for c in req_cols):
missing = [c for c in req_cols if c not in df_input.columns]
log_ref_a.append(_log("ERROR", "RefA-DB", f"Missing columns: {missing}"))
return log_ref_a
session: Session = MysqlManager().getSession()
processed = found_base = not_found_base = checked_deals = updated_deals = 0
# The outer try-except captures any unexpected database errors.
try:
for row_idx, row in df_input.iterrows():
processed += 1
current_provider_code_to_query = ""
try:
id_as_int = int(float(str(row.id)))
current_provider_code_to_query = str(id_as_int)
except ValueError:
log_ref_a.append(
_log(
"WARNING",
"RefA-DB",
f"ID '{row.id}' (row {row_idx}) could not be converted to int. Skipping.",
)
)
continue
p1, p3, p4 = row.provision1, row.provision3, row.provision4
base_recs = session.query(BaseBase).filter_by(providercode_base=current_provider_code_to_query).all()
# This branch handles the existence of at least one matching base record.
if base_recs:
found_base += 1
deals_found_for_pc = deals_updated_for_pc = 0
for br in base_recs:
deals = session.query(DealDeal).filter_by(base_deal=br.id_base).all()
deals_found_for_pc += len(deals)
# Each deal is compared and updated if any provision differs.
for deal_obj in deals:
changed = False
if deal_obj.provision1_deal != p1:
deal_obj.provision1_deal = p1
changed = True
if hasattr(deal_obj, "provision3_deal"):
if deal_obj.provision3_deal != p3:
deal_obj.provision3_deal = p3
changed = True
elif p3 != 0:
log_ref_a.append(
_log(
"WARNING",
"RefA-DB",
f"Deal {getattr(deal_obj, 'id_deal', 'N/A')} has no provision3_deal (value {p3}).",
)
)
if deal_obj.provision4_deal != p4:
deal_obj.provision4_deal = p4
changed = True
if changed:
deals_updated_for_pc += 1
checked_deals += deals_found_for_pc
updated_deals += deals_updated_for_pc
# This log message summarizes the outcome for the current provider code.
prefix = f"Code '{current_provider_code_to_query}'"
status = "SUCCESS" if deals_updated_for_pc else "INFO"
log_ref_a.append(
_log(
status,
"RefA-DB",
f"{prefix}: {deals_updated_for_pc}/{deals_found_for_pc} deals {'updated' if deals_updated_for_pc else 'checked (no change)'}.",
)
)
# The else branch records the absence of a matching base record.
else:
not_found_base += 1
log_ref_a.append(
_log("WARNING", "RefA-DB", f"Code '{current_provider_code_to_query}' not found in base_base.")
)
# This commit persists updates only if any deal was modified.
if updated_deals > 0:
session.commit()
log_ref_a.append(_log("SUCCESS", "RefA-DB", "Commit successful."))
# The else branch indicates no changes were made.
else:
log_ref_a.append(_log("INFO", "RefA-DB", "No DB changes, commit skipped."))
# A rollback ensures consistency on any unhandled exception.
except Exception as e:
session.rollback()
log_ref_a.append(_log("ERROR", "RefA-DB", str(e)))
print(traceback.format_exc())
finally:
if MODELS_IMPORTED and session:
session.close()
log_ref_a.append(
_log(
"INFO",
"RefA-DB",
f"Summary: processed={processed}, base_found={found_base}, base_missing={not_found_base}, deals_checked={checked_deals}, deals_updated={updated_deals}",
)
)
return log_ref_a
# The orchestrator performs the end-to-end processing for reference A files.
def process_referenz_a_file(file_stream) -> list[str]:
all_logs = [_log("INFO", "RefA", "Processing started.")]
df_loaded, logs_load = load_data_ref_a(file_stream)
all_logs.extend(logs_load)
# An early exit occurs if the initial load failed or produced no rows.
if df_loaded is None or df_loaded.empty:
all_logs.append(_log("INFO", "RefA", "Processing aborted after loader stage."))
return all_logs
df_transformed, logs_transform = transform_data_ref_a(df_loaded)
all_logs.extend(logs_transform)
# A transformed DataFrame with no rows stops further processing.
if df_transformed.empty:
all_logs.append(_log("INFO", "RefA", "Processing aborted after transform stage (no records)."))
return all_logs
logs_db = update_db_ref_a(df_transformed, [])
all_logs.extend(logs_db)
all_logs.append(_log("INFO", "RefA", "Processing completed."))
return all_logs
# --- Reference B Verarbeitung ---
# The parser extracts value differences from reference B Excel files.
def parse_excel_ref_b(file_stream) -> tuple[pd.DataFrame | None, list[str]]:
log_ref_b: list[str] = []
records: list[dict[str, object]] = []
try:
file_stream.seek(0)
df = pd.read_excel(file_stream, sheet_name=0, header=None, dtype=str).fillna("").iloc[:, 7:13]
# An immediate failure is reported if the expected six columns are not present.
if df.shape[1] != 6:
log_ref_b.append(_log("ERROR", "RefB-Parse", "Incorrect column count after slice."))
return pd.DataFrame(), log_ref_b
for row_tuple in df.itertuples(index=False):
r = [str(c).strip() for c in row_tuple]
if not (
len(r) == 6
and all(
t in {"", "-", "–"} or NUM_RE_B.match(t) or CODE_RE_B.match(t) for t in r
)
and (CODE_RE_B.match(r[2]) or CODE_RE_B.match(r[5]))
):
continue
vmc, bmc, cmc, vsp, bsp, csp = r
if CODE_RE_B.match(cmc):
records.append(
{
"code": cmc,
"value": to_int_ref_b(bmc) - to_int_ref_b(vmc),
"source": "MC",
}
)
if CODE_RE_B.match(csp):
records.append(
{
"code": csp,
"value": to_int_ref_b(bsp) - to_int_ref_b(vsp),
"source": "SP",
}
)
result_df = pd.DataFrame(records)
log_ref_b.append(_log("SUCCESS", "RefB-Parse", f"{len(result_df)} valid entries parsed."))
return result_df, log_ref_b
except Exception as e:
log_ref_b.append(_log("ERROR", "RefB-Parse", str(e)))
print(traceback.format_exc())
return pd.DataFrame(), log_ref_b
# The updater applies reference B values to the database.
def update_db_ref_b(df_input: pd.DataFrame, log_ref_b_outer: list[str]) -> list[str]:
log_ref_b = log_ref_b_outer
# The absence of model imports is treated as a critical error.
if not MODELS_IMPORTED:
log_ref_b.append(_log("ERROR", "RefB-DB", "Database modules are not available."))
return log_ref_b
# An empty DataFrame short-circuits the update logic.
if df_input.empty:
log_ref_b.append(_log("INFO", "RefB-DB", "Input DataFrame is empty."))
return log_ref_b
session: Session = MysqlManager().getSession()
hits = misses = updated_count = 0
try:
for _, row in df_input.iterrows():
code, value = row["code"], row["value"]
deals = session.query(DealDeal).filter_by(providercode_deal=code).all()
# This branch logs a successful hit and possible updates.
if deals:
hits += 1
current_deal_updated = False
for d_obj in deals:
if hasattr(d_obj, "provision2_deal") and d_obj.provision2_deal != value:
d_obj.provision2_deal = value
current_deal_updated = True
elif not hasattr(d_obj, "provision2_deal"):
log_ref_b.append(
_log("WARNING", "RefB-DB", f"Deal for code {code} lacks provision2_deal.")
)
if current_deal_updated:
updated_count += 1
log_ref_b.append(
_log("SUCCESS", "RefB-DB", f"{code} ({len(deals)} deals) -> P2={value}")
)
# An informational log is omitted when no changes are required.
# The else branch records a miss against the provided code.
else:
misses += 1
log_ref_b.append(_log("WARNING", "RefB-DB", f"{code}: no matching deal"))
# This commit finalizes updates when any deal was modified.
if updated_count > 0:
session.commit()
log_ref_b.append(_log("SUCCESS", "RefB-DB", "Commit successful."))
# The else branch signifies no updates were necessary.
else:
log_ref_b.append(_log("INFO", "RefB-DB", "No DB changes, commit skipped."))
except Exception as e:
session.rollback()
log_ref_b.append(_log("ERROR", "RefB-DB", str(e)))
print(traceback.format_exc())
finally:
if MODELS_IMPORTED and session:
session.close()
log_ref_b.append(
_log(
"INFO",
"RefB-DB",
f"Summary: hits={hits} (updated={updated_count}), misses={misses}",
)
)
return log_ref_b
# The orchestrator performs the end-to-end processing for reference B files.
def process_referenz_b_file(file_stream) -> list[str]:
all_logs = [_log("INFO", "RefB", "Processing started.")]
df_parsed, logs_parse = parse_excel_ref_b(file_stream)
all_logs.extend(logs_parse)
# An early exit is triggered if parsing failed or produced no records.
if df_parsed.empty and not logs_parse:
all_logs.append(_log("INFO", "RefB", "Processing aborted after parse stage."))
return all_logs
if df_parsed.empty:
all_logs.append(_log("INFO", "RefB", "No data found after parsing; DB update skipped."))
return all_logs
logs_db = update_db_ref_b(df_parsed, [])
all_logs.extend(logs_db)
all_logs.append(_log("INFO", "RefB", "Processing completed."))
return all_logs
# --- Front-End HTML Template ---
# The upload page provides file selection and displays the processing log.
UPLOAD_FORM_HTML = """
<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Freenet Upload</title><style>body{font-family:Arial,sans-serif;margin:20px;background-color:#f9f9f9;color:#333}.container{max-width:700px;margin:auto;background-color:#fff;padding:30px;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1)}h1{color:#28a745;text-align:center;margin-bottom:20px}input[type=file]{display:block;width:calc(100% - 22px);padding:10px;margin-bottom:20px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box}input[type=submit]{display:block;width:100%;padding:12px;background-color:#28a745;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:16px;transition:background-color .3s ease}input[type=submit]:hover{background-color:#1e7e34}.message-box{margin-top:25px;padding:15px;border-radius:4px;text-align:left;max-height:400px;overflow-y:auto;border:1px solid #ddd;background-color:#fdfdfd}.message-box p{margin:0 0 5px 0;white-space:pre-wrap;word-wrap:break-word;font-size:0.9em;}.message-box hr{margin:10px 0;border:0;border-top:1px solid #eee}.info{background-color:#e7f3fe;border-left:6px solid #2196F3;color:#0d47a1}.error{background-color:#ffebee;border-left:6px solid #f44336;color:#c62828}.unknown{background-color:#fff3e0;border-left:6px solid #ffa726;color:#e65100}</style></head>
<body><div class="container"><h1>Freenet Upload</h1>
<form method="post" enctype="multipart/form-data">
<input type="file" name="excel_file" id="excel_file" accept=".xlsx,.xls" required><input type="submit" value="Save"></form>
{% if feedback_messages %} <div class="message-box {{ message_class }}"><h3>Verarbeitungs-Log:</h3> {% for msg in feedback_messages %} <p>{{ msg | safe }}</p> {% endfor %} </div> {% endif %}
</div></body></html>
"""
# --- Flask Route ---
# The route handles GET for the upload form and POST for file processing.
@blueprint.route("/freenet-upload", methods=["GET", "POST"])
def freenet_upload_route():
feedback_messages: list[str] = []
message_class = "info"
# This branch covers the POST request that processes the uploaded file.
if request.method == "POST":
if "excel_file" not in request.files or request.files["excel_file"].filename == "":
feedback_messages.append("Fehler: Keine Datei ausgewählt.")
message_class = "error"
else:
file = request.files["excel_file"]
file_stream_content = io.BytesIO()
file.save(file_stream_content)
file_stream_content.seek(0)
try:
is_a = is_referenz_a_file(file_stream_content)
is_b = is_referenz_b_file(file_stream_content)
# This branch processes a reference A file.
if is_a:
file_stream_content.seek(0)
logs = process_referenz_a_file(file_stream_content)
feedback_messages.extend(logs)
message_class = (
"info"
if not any("❌" in log or "⚠️" in log for log in logs)
else "error"
)
# The elif handles a reference B file.
elif is_b:
file_stream_content.seek(0)
logs = process_referenz_b_file(file_stream_content)
feedback_messages.extend(logs)
message_class = (
"info"
if not any("❌" in log or "⚠️" in log for log in logs)
else "error"
)
# The else branch indicates an unrecognized file structure.
else:
feedback_messages.append("Struktur unbekannt: Datei entspricht weder Referenz A noch B.")
message_class = "unknown"
# Any unhandled exception bubbles up as a critical error.
except Exception as e:
feedback_messages.append(f"❌ Schwerwiegender Fehler bei Dateiverarbeitung: {str(e)}")
message_class = "error"
print(traceback.format_exc())
# A fallback message ensures that the user always receives some form of feedback.
if not feedback_messages:
feedback_messages.append("Verarbeitung abgeschlossen. Keine spezifischen Logs.")
return render_template_string(UPLOAD_FORM_HTML, feedback_messages=feedback_messages, message_class=message_class)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment