Commit da9e7246 authored by Marco Schmiedel's avatar Marco Schmiedel

fix

parent 92796ceb
{
"fileId": "12a942d7-6f13-4c57-9f5c-1c6fd3baaf21",
"originalPath": "work/manager/OpenAiManager.py",
"currentPath": "work/manager/OpenAiManager.py",
"hash": "a5d54b395c9edf435ee5ec0a2f3637058b54bab058827175487268f4dc07304a",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1745314592645,
"lastFileModificationTimestamp": 1745314218432.6475
}
{
"fileId": "1e5daa93-a29c-4db7-98ac-3457b779f0d1",
"originalPath": "work/manager/S3Manager.py",
"currentPath": "work/manager/S3Manager.py",
"hash": "3626bb3b00d9142ba6d471dfdd2d7a0f54d72afca4b908843c9cf6d483d66754",
"docContent": "<p><br></p>",
"checkedStatus": "done",
"comments": [],
"lastCheckedTimestamp": 1745314595210,
"lastFileModificationTimestamp": 1745314341957.45
}
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
"fileId": "22983490-9c01-4bd1-8649-dfe87c659225", "fileId": "22983490-9c01-4bd1-8649-dfe87c659225",
"originalPath": "work/config/MauiConfig.py", "originalPath": "work/config/MauiConfig.py",
"currentPath": "work/config/MauiConfig.py", "currentPath": "work/config/MauiConfig.py",
"hash": "7749ff881e9fac6aaeffe5274aa9566b2b5692a1ec4e7f47e20b79671a0788a3", "hash": "6e627f3800fd413c6dbde92ad2e274d5e3047af0f906de4d75fc826cc129631e",
"docContent": "<p>In this configuration, you’ll find the credentials required to log in to Freenet-Maui.</p>", "docContent": "<p>In this configuration, you’ll find the credentials required to log in to Freenet-Maui.</p>",
"checkedStatus": "done", "checkedStatus": "todo",
"comments": [ "comments": [
{ {
"commentId": "3bc16f5e-4032-44a8-9012-4b632849ba50", "commentId": "3bc16f5e-4032-44a8-9012-4b632849ba50",
...@@ -12,6 +12,6 @@ ...@@ -12,6 +12,6 @@
"timestamp": 1744614418809 "timestamp": 1744614418809
} }
], ],
"lastCheckedTimestamp": 1744614348029, "lastCheckedTimestamp": 1745314578450,
"lastFileModificationTimestamp": 1744614095522.7373 "lastFileModificationTimestamp": 1745313945182.1555
} }
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
"fileId": "24784b38-54dc-4000-9d2a-f59082ebbc1c", "fileId": "24784b38-54dc-4000-9d2a-f59082ebbc1c",
"originalPath": "work/models/base_base.py", "originalPath": "work/models/base_base.py",
"currentPath": "work/models/base_base.py", "currentPath": "work/models/base_base.py",
"hash": "19b6bd8fd24ccac708053a39f013632721ed03ec35663efd7d7ec222cf0be934", "hash": "a13647e2879a37dfcb76bf95c143fc42b0da2eac67a471884afd5c314dc46f8f",
"docContent": "<p><br></p>", "docContent": "<p><br></p>",
"checkedStatus": "done", "checkedStatus": "done",
"comments": [], "comments": [],
"lastCheckedTimestamp": 1744805211278, "lastCheckedTimestamp": 1745314601207,
"lastFileModificationTimestamp": 1744787773143.9746, "lastFileModificationTimestamp": 1745313922231.5488,
"flaggedForCopy": false "flaggedForCopy": false
} }
...@@ -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": "f9c624640584ecc2400d5053b7b366a6b2305ecdf836221aa95e3e169254af79", "hash": "8eeae892f7c5f5aa1e894ca9ff7b8c66ea2891bc37c0167c404cd6e0cb95f858",
"docContent": "<p><br></p>", "docContent": "<p><br></p>",
"checkedStatus": "done", "checkedStatus": "todo",
"comments": [ "comments": [
{ {
"commentId": "56c5adba-20f4-4524-a894-41f81ab7ca55", "commentId": "56c5adba-20f4-4524-a894-41f81ab7ca55",
...@@ -12,6 +12,6 @@ ...@@ -12,6 +12,6 @@
"timestamp": 1744622354948 "timestamp": 1744622354948
} }
], ],
"lastCheckedTimestamp": 1744624735475, "lastCheckedTimestamp": 1745314583521,
"lastFileModificationTimestamp": 1744624729595.99 "lastFileModificationTimestamp": 1745313973064.8933
} }
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
"fileId": "543b791f-9a45-4b53-906e-c49f79ac95d7", "fileId": "543b791f-9a45-4b53-906e-c49f79ac95d7",
"originalPath": "work/notebooks/ImportCsvToDatabase.ipynb", "originalPath": "work/notebooks/ImportCsvToDatabase.ipynb",
"currentPath": "work/notebooks/ImportCsvToDatabase.ipynb", "currentPath": "work/notebooks/ImportCsvToDatabase.ipynb",
"hash": "6cdb9455f0658ca804b4beb6201bdfa4ae721451c9fa7c99e0bebe9f5a0dbf09", "hash": "c6e6e4c70653fbfc4d43ae89e4f05fc1a8d2804eb2f515ad52c41637bd5b0e14",
"docContent": "<p><br></p>", "docContent": "<p><br></p>",
"checkedStatus": "done", "checkedStatus": "changed",
"comments": [], "comments": [],
"lastCheckedTimestamp": 1744806594117, "lastCheckedTimestamp": 1744806594117,
"lastFileModificationTimestamp": 1744806589225.9287 "lastFileModificationTimestamp": 1745313435812.8953
} }
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
"fileId": "766dc461-001e-4901-8faf-263820ad96cd", "fileId": "766dc461-001e-4901-8faf-263820ad96cd",
"originalPath": "work/manager/MysqlManager.py", "originalPath": "work/manager/MysqlManager.py",
"currentPath": "work/manager/MysqlManager.py", "currentPath": "work/manager/MysqlManager.py",
"hash": "c016d5cb9c9b391d19e41323196715c0e57b5838846232d2bdf761f53293e1b4", "hash": "0506c7ebbfff68bce628902018e66ba50936a52e08aa595cadc8d5324c48d46f",
"docContent": "<p><br></p>", "docContent": "<p><br></p>",
"checkedStatus": "done", "checkedStatus": "done",
"comments": [], "comments": [],
"lastCheckedTimestamp": 1744624751868, "lastCheckedTimestamp": 1745314589383,
"lastFileModificationTimestamp": 1744624748400.9556, "lastFileModificationTimestamp": 1745314105678.9277,
"flaggedForCopy": false "flaggedForCopy": false
} }
{
"fileId": "7a3a246b-fc0e-4c80-b748-96b941efab5c",
"originalPath": "work/config/AWSConfig.py",
"currentPath": "work/config/AWSConfig.py",
"hash": "5a6654cb1cd77f8d531fcc1541d31261ea02c4e8cb126f2cc43a217c9c6920aa",
"docContent": "<p><br></p>",
"checkedStatus": "todo",
"comments": [],
"lastCheckedTimestamp": 1745314580866,
"lastFileModificationTimestamp": 1745311719614.9841
}
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
"fileId": "d470fd53-8f95-47d0-a63b-5851586c0eda", "fileId": "d470fd53-8f95-47d0-a63b-5851586c0eda",
"originalPath": "work/manager/SeleniumManager.py", "originalPath": "work/manager/SeleniumManager.py",
"currentPath": "work/manager/SeleniumManager.py", "currentPath": "work/manager/SeleniumManager.py",
"hash": "be7dde51af30a8ea1f80ea3090f39d2e9a84168cf7d3e15086c16bdbbfde2c26", "hash": "f29307970d124e55c7066e71ddf682f55e043d4f925195bdf320ff9da1311e27",
"docContent": "<p><br></p>", "docContent": "<p><br></p>",
"checkedStatus": "done", "checkedStatus": "done",
"comments": [], "comments": [],
"lastCheckedTimestamp": 1744620379152, "lastCheckedTimestamp": 1745314597597,
"lastFileModificationTimestamp": 1744547804595.8647 "lastFileModificationTimestamp": 1745314531223.019
} }
{
"fileId": "f645f6dc-6831-4020-a6ae-8b5a572eed54",
"originalPath": "work/config/OpenAiConfig.py",
"currentPath": "work/config/OpenAiConfig.py",
"hash": "3da3805934b36b3ab8c21e10d8babe3cc8b6c9acb7b2b1446f782612b76cf2c4",
"docContent": "<p><br></p>",
"checkedStatus": "todo",
"comments": [],
"lastCheckedTimestamp": 1745314586062,
"lastFileModificationTimestamp": 1745313976355.6653
}
AWS_ACCESS_KEY_ID = "AKIATXO2FKVK3BSS3DMT"
AWS_SECRET_ACCESS_KEY = "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"
REGION = "eu-central-1"
BUCKET_NAME = "freenetflyer"
# This variable stores the username for accessing Maui.
MAUI_USERNAME = "28009594-198" MAUI_USERNAME = "28009594-198"
# This variable stores the password for accessing Maui.
MAUI_PASSWORD = "8v#5YeeQyh" MAUI_PASSWORD = "8v#5YeeQyh"
# This variable stores the authentication code (2FA code) for accessing Maui.
MAUI_AUTHCODE = "2D3JJNG3WWGSWRDI5KRW2MZKL3NJEZXJ" MAUI_AUTHCODE = "2D3JJNG3WWGSWRDI5KRW2MZKL3NJEZXJ"
# The MySQL host address.
MYSQL_HOST = "itmax-backoffice-prod-aurora-r3dbcluster-1e8hysitdwijk.cluster-crb5tahpiszg.eu-central-1.rds.amazonaws.com" MYSQL_HOST = "itmax-backoffice-prod-aurora-r3dbcluster-1e8hysitdwijk.cluster-crb5tahpiszg.eu-central-1.rds.amazonaws.com"
# The MySQL username for the connection.
MYSQL_USER = "labor-itmaxmaster" MYSQL_USER = "labor-itmaxmaster"
# The MySQL password for the connection.
MYSQL_PASSWORD = "floz09sx3dTyx144gy" MYSQL_PASSWORD = "floz09sx3dTyx144gy"
# The MySQL database name.
MYSQL_DATABASE = "itmax_tarifs" MYSQL_DATABASE = "itmax_tarifs"
# The MySQL port number.
MYSQL_PORT = 3306 MYSQL_PORT = 3306
# If set to True, the MysqlManager will establish an SSH tunnel when connecting to MySQL.
USE_SSH_TUNNEL = True USE_SSH_TUNNEL = True
# The SSH host address (the remote SSH server to tunnel through).
SSH_HOST = "jumphost.bugsmasher.online" SSH_HOST = "jumphost.bugsmasher.online"
# The SSH port number (default is usually 22).
SSH_PORT = 22 SSH_PORT = 22
# The SSH username for the tunnel.
SSH_USERNAME = "root" SSH_USERNAME = "root"
# The SSH password for the tunnel.
SSH_PASSWORD = "7dHz2xO8ct1143T" SSH_PASSWORD = "7dHz2xO8ct1143T"
\ No newline at end of file
secret = "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"
...@@ -4,15 +4,14 @@ from sqlalchemy.orm import sessionmaker ...@@ -4,15 +4,14 @@ from sqlalchemy.orm import sessionmaker
import config.MysqlConfig as DatabaseConfig import config.MysqlConfig as DatabaseConfig
from sshtunnel import SSHTunnelForwarder from sshtunnel import SSHTunnelForwarder
# We create a new class called MysqlManager. # In dieser Klasse wird die Verwaltung einer MySQL-Verbindung umgesetzt.
class MysqlManager: class MysqlManager:
# The following constructor initializes the MySQL connection. # In diesem Konstruktor werden die Verbindungskonfigurationen aus dem Config-Modul geladen und der SSH-Tunnel bei Bedarf gestartet.
def __init__(self): def __init__(self):
# Instead of reading a configuration file, we import the Python configuration. # In dieser Variablen werden die Konfigurationsdaten für den Datenbankzugriff gesammelt.
# We construct the configuration dictionary using values from the DatabaseConfig module. self.dbConfig = {
self.config = {
"host": DatabaseConfig.MYSQL_HOST, "host": DatabaseConfig.MYSQL_HOST,
"user": DatabaseConfig.MYSQL_USER, "user": DatabaseConfig.MYSQL_USER,
"password": DatabaseConfig.MYSQL_PASSWORD, "password": DatabaseConfig.MYSQL_PASSWORD,
...@@ -20,45 +19,46 @@ class MysqlManager: ...@@ -20,45 +19,46 @@ class MysqlManager:
"port": DatabaseConfig.MYSQL_PORT "port": DatabaseConfig.MYSQL_PORT
} }
# Check if an SSH tunnel should be used. # In dieser Abzweigung wird geprüft, ob ein SSH-Tunnel verwendet werden soll.
if getattr(DatabaseConfig, "USE_SSH_TUNNEL", False): if getattr(DatabaseConfig, "USE_SSH_TUNNEL", False):
# Initialize the SSH tunnel using SSHTunnelForwarder. # In dieser Variablen wird ein SSH-Tunnel erstellt, der den Datenverkehr zu einem lokalen Port umleitet.
self.tunnel = SSHTunnelForwarder( self.sshTunnel = SSHTunnelForwarder(
(DatabaseConfig.SSH_HOST, DatabaseConfig.SSH_PORT), (DatabaseConfig.SSH_HOST, DatabaseConfig.SSH_PORT),
ssh_username=DatabaseConfig.SSH_USERNAME, ssh_username=DatabaseConfig.SSH_USERNAME,
ssh_password=DatabaseConfig.SSH_PASSWORD, ssh_password=DatabaseConfig.SSH_PASSWORD,
remote_bind_address=(self.config["host"], self.config["port"]) remote_bind_address=(self.dbConfig["host"], self.dbConfig["port"])
) )
# Start the SSH tunnel.
self.tunnel.start()
# Set the host to localhost and port to the tunnel's local bind port. # Hier wird der SSH-Tunnel gestartet, damit die Weiterleitung aktiv wird.
host = "127.0.0.1" self.sshTunnel.start()
port = self.tunnel.local_bind_port
# In diesen Variablen werden Host und Port auf die lokalen Tunnel-Daten gesetzt.
dbHost = "127.0.0.1"
dbPort = self.sshTunnel.local_bind_port
else: else:
# No SSH tunnel is used. # In dieser Abzweigung wird kein SSH-Tunnel verwendet.
self.tunnel = None self.sshTunnel = None
host = self.config["host"] dbHost = self.dbConfig["host"]
port = self.config["port"] dbPort = self.dbConfig["port"]
# Construct the MySQL engine using the connection details. # In dieser Variablen wird das SQLAlchemy-Engine-Objekt mit den Verbindungsdaten erzeugt.
engine = create_engine( dbEngine = create_engine(
f"mysql+pymysql://{self.config['user']}:{self.config['password']}@{host}:{port}/{self.config['database']}", f"mysql+pymysql://{self.dbConfig['user']}:{self.dbConfig['password']}@{dbHost}:{dbPort}/{self.dbConfig['database']}",
echo=False echo=False
) )
# Start the database session. # In dieser Variablen wird eine neue SessionFactory erzeugt und eine Session erstellt.
self.session = sessionmaker(bind=engine)() self.dbSession = sessionmaker(bind=dbEngine)()
# "getSession" provides the database connection to other modules. # In dieser Methode wird eine Session-Instanz zurückgegeben, um Datenbankaktionen durchzuführen.
def getSession(self): def getSession(self):
return self.session return self.dbSession
# The following method closes the database connection and stops the SSH tunnel if active. # In dieser Methode wird die bestehende Session geschlossen und der SSH-Tunnel (falls vorhanden) gestoppt.
def close(self): def close(self):
self.session.close() self.dbSession.close()
if self.tunnel: if self.sshTunnel:
self.tunnel.stop() self.sshTunnel.stop()
import sys; sys.path.append("..")
import requests
import json
import re
import time
import config.OpenAiConfig as Config
# In dieser Klasse wird die Kommunikation mit der OpenAI-API verwaltet.
class OpenAiManager:
# In diesem Konstruktor werden die Konfiguration und der API-Key geladen und grundlegende Parameter für die Anfragen gesetzt.
def __init__(self):
# In dieser Variablen wird die Konfiguration abgelegt, wobei der Key aus dem externen Config-Modul stammt.
self.config = {
"secret": Config.secret
}
# In dieser Variablen wird der API-Key aus dem Konfigurationsdictionary entnommen.
self.apiKey = self.config["secret"]
# In dieser Variablen wird die URL für Chat-Completions hinterlegt.
self.openAiUrl = "https://api.openai.com/v1/chat/completions"
# In dieser Variablen werden die Header für die HTTP-Anfragen an die OpenAI-API definiert.
self.requestHeaders = {
"Authorization": f"Bearer {self.apiKey}",
"Content-Type": "application/json"
}
# In dieser Methode wird ein Prompt an die OpenAI-API geschickt, um eine Chat-Antwort zu erhalten.
def chat(self, prompt, model="o1-mini"):
# In dieser Variablen wird das JSON-Objekt für die Anfrage an die OpenAI-API aufgebaut.
requestData = {
"model": model,
"messages": [{"role": "user", "content": prompt}]
}
# In diesem Schritt wird ein POST-Request an die definierte Chat-Completion-URL gesendet.
response = requests.post(self.openAiUrl, headers=self.requestHeaders, json=requestData)
# In dieser Variablen wird die Antwort als Python-Objekt interpretiert.
responseData = response.json()
# In dieser Abzweigung wird geprüft, ob in der Antwort mindestens ein Eintrag unter 'choices' existiert.
if "choices" in responseData:
# Hier wird der Inhalt der ersten Choice zurückgegeben, wenn vorhanden.
return responseData["choices"][0]["message"]["content"]
# In dieser Abzweigung wird None zurückgegeben, falls kein verwertbarer Inhalt existiert.
else:
return None
import sys; sys.path.append("..")
import config.AWSConfig as Config
import boto3
import json
import re
# In dieser Klasse werden die Interaktionen mit AWS S3 verwaltet, wie zum Beispiel das Hochladen von Dateien.
class S3Manager:
# In diesem Konstruktor wird ein S3-Client erzeugt und der Bucketname aus den Konfigurationswerten hinterlegt.
def __init__(self):
self.s3Client = boto3.client(
's3',
aws_access_key_id=Config.AWS_ACCESS_KEY_ID,
aws_secret_access_key=Config.AWS_SECRET_ACCESS_KEY,
region_name=Config.REGION
)
self.bucketName = Config.BUCKET_NAME
# In dieser Methode wird eine Datei in den konfigurierten S3-Bucket hochgeladen, wobei standardmäßig eine ACL für public-read gesetzt wird.
def uploadFile(self, filePath, s3Key, acl="public-read"):
# In diesem try-Block wird versucht, die Datei auf S3 hochzuladen und anschließend der öffentliche Link zurückgegeben.
try:
self.s3Client.upload_file(filePath, self.bucketName, s3Key, ExtraArgs={'ACL': acl})
return self.getPublicUrl(s3Key)
# In dieser Abzweigung wird eine Exception abgefangen, falls das Hochladen fehlschlägt, und None zurückgegeben.
except Exception as e:
print(f"Fehler beim Hochladen der Datei: {e}")
return None
# In dieser Methode wird der öffentliche URL-Link generiert, anhand des Bucketnamens und des S3-Schlüssels.
def getPublicUrl(self, s3Key):
return f"https://{self.bucketName}.s3.amazonaws.com/{s3Key}"
import sys; sys.path.append("..")
from selenium.webdriver.firefox.options import Options from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service from selenium.webdriver.firefox.service import Service
from selenium import webdriver from selenium import webdriver
...@@ -7,145 +8,139 @@ import os ...@@ -7,145 +8,139 @@ import os
import uuid import uuid
import glob import glob
# Diese Klasse verwaltet eine Selenium-Instanz und stellt Methoden zum Abrufen von Webseiten bereit. # In dieser Klasse wird ein Selenium-WebDriver für Firefox verwaltet, um Seiten zu laden und zu steuern.
class SeleniumManager: class SeleniumManager:
# Diese Methode ist der Konstruktor und initialisiert den Firefox-Browser mit den gewünschten Optionen. # In diesem Konstruktor werden die Firefox-Optionen eingerichtet und der WebDriver initialisiert.
def __init__( def __init__(
self, self,
window_size: str = "1920,1080", windowSize: str = "1920,1080",
headless: bool = True, headless: bool = True,
disable_gpu: bool = True, disableGpu: bool = True,
no_sandbox: bool = True, noSandbox: bool = True,
disable_dev_shm_usage: bool = True, disableDevShmUsage: bool = True,
geckodriver_path: str = '/usr/bin/geckodriver' geckodriverPath: str = '/usr/bin/geckodriver'
): ):
# Diese Variable speichert die Firefox-Optionen, die für den Browser gelten. # In dieser Variablen werden die Firefox-Optionen gespeichert.
firefox_options = Options() firefoxOptions = Options()
# Diese Anweisung setzt die Fenstergröße nach den Vorgaben. # Hier wird die Fenstergröße auf den angegebenen Wert festgelegt.
firefox_options.add_argument(f"--window-size={window_size}") firefoxOptions.add_argument(f"--window-size={windowSize}")
# Diese Abfrage fügt die Headless-Option nur hinzu, wenn headless True ist. # In dieser Abzweigung wird geprüft, ob der Browser im Headless-Modus laufen soll.
if headless: if headless:
# Diese Anweisung macht den Browser unsichtbar. firefoxOptions.add_argument("--headless")
firefox_options.add_argument("--headless")
# Diese Anweisung deaktiviert die GPU, wenn disable_gpu True ist, ansonsten wird nichts hinzugefügt. # In dieser Anweisung wird die GPU deaktiviert, wenn das Flag gesetzt ist.
firefox_options.add_argument("--disable-gpu" if disable_gpu else "") firefoxOptions.add_argument("--disable-gpu" if disableGpu else "")
# Diese Anweisung deaktiviert die Sandbox, wenn no_sandbox True ist, ansonsten wird nichts hinzugefügt. # In dieser Anweisung wird die Sandbox deaktiviert, wenn das Flag gesetzt ist.
firefox_options.add_argument("--no-sandbox" if no_sandbox else "") firefoxOptions.add_argument("--no-sandbox" if noSandbox else "")
# Diese Anweisung deaktiviert dev-shm, wenn disable_dev_shm_usage True ist, ansonsten wird nichts hinzugefügt. # In dieser Anweisung wird dev-shm usage deaktiviert, wenn das Flag gesetzt ist.
firefox_options.add_argument("--disable-dev-shm-usage" if disable_dev_shm_usage else "") firefoxOptions.add_argument("--disable-dev-shm-usage" if disableDevShmUsage else "")
# Diese Anweisung setzt den User-Agent auf einen zufällig generierten Wert. # Hier wird ein zufälliger User-Agent erzeugt und in die Firefox-Optionen gesetzt.
firefox_options.set_preference("general.useragent.override", self.getRandomUserAgent()) firefoxOptions.set_preference("general.useragent.override", self.getRandomUserAgent())
# Diese Variable erstellt den Dienst für den Firefox-Treiber unter Verwendung des angegebenen Pfads. # In dieser Variablen wird ein Service-Objekt für den Geckodriver mit dem angegebenen Pfad erzeugt.
service = Service(geckodriver_path) firefoxService = Service(geckodriverPath)
# Diese Variable initialisiert den Firefox-WebDriver mit dem Dienst und den Optionen. # Hier wird der Firefox-WebDriver mit dem Dienst und den Optionen initialisiert.
self.driver = webdriver.Firefox(service=service, options=firefox_options) self.driver = webdriver.Firefox(service=firefoxService, options=firefoxOptions)
# Diese Methode führt eine einfache Anfrage an eine bestimmte URL durch und speichert den HTML-Inhalt im Cache. # In dieser Methode wird eine Seite geladen und der HTML-Inhalt im Cache abgelegt.
def simpleRequest(self, url): def simpleRequest(self, url):
# Diese Anweisung öffnet die angegebene URL mit dem Selenium-Treiber. # Hier wird die gewünschte URL aufgerufen.
self.driver.get(url) self.driver.get(url)
# Diese Variable speichert den HTML-Quellcode der aktuellen Seite. # In dieser Variablen wird der gesamte HTML-Quellcode der aktuell geladenen Seite gespeichert.
html = self.driver.page_source htmlContent = self.driver.page_source
# Diese Variable legt den Pfad für den Cache-Ordner fest, relativ zum Skript. # Hier wird der relative Pfad zum Cache-Verzeichnis ermittelt.
cache_dir = os.path.join(os.path.dirname(__file__), "..", "cache") cacheDir = os.path.join(os.path.dirname(__file__), "..", "cache")
# Diese Abfrage prüft, ob der Cache-Ordner existiert. # In dieser Abzweigung wird das Cache-Verzeichnis erstellt, falls es nicht vorhanden ist.
if not os.path.exists(cache_dir): if not os.path.exists(cacheDir):
# Diese Anweisung erstellt den Cache-Ordner, falls er nicht vorhanden ist. os.makedirs(cacheDir)
os.makedirs(cache_dir)
# Diese Variable generiert einen eindeutigen Dateinamen anhand einer UUID. # Hier wird ein eindeutiger Dateiname auf Basis einer UUID erzeugt.
unique_filename = f"{uuid.uuid4().hex}.html" uniqueFilename = f"{uuid.uuid4().hex}.html"
# Diese Variable kombiniert den Cache-Pfad mit dem eindeutigen Dateinamen. # In dieser Variablen wird der vollständige Pfad für die Cache-Datei erzeugt.
file_path = os.path.join(cache_dir, unique_filename) filePath = os.path.join(cacheDir, uniqueFilename)
# Diese Anweisung öffnet die Datei und schreibt den HTML-Inhalt hinein. # Hier wird die HTML-Datei im Cache-Verzeichnis geschrieben.
with open(file_path, "w", encoding="utf-8") as f: with open(filePath, "w", encoding="utf-8") as f:
f.write(html) f.write(htmlContent)
# Diese Variable sammelt alle HTML-Dateien im Cache-Ordner. # In dieser Variablen werden alle HTML-Dateien im Cache-Verzeichnis gesammelt.
files = glob.glob(os.path.join(cache_dir, "*.html")) htmlFiles = glob.glob(os.path.join(cacheDir, "*.html"))
# Diese Anweisung sortiert die Dateien nach Änderungsdatum, wobei die neueste zuerst steht. # Hier wird die Liste nach Änderungszeit sortiert, absteigend (neueste zuerst).
files.sort(key=os.path.getmtime, reverse=True) htmlFiles.sort(key=os.path.getmtime, reverse=True)
# Diese Schleife löscht alle Dateien außer den 10 neuesten. # In dieser Schleife werden alle Dateien außer den 10 aktuellsten entfernt.
for old_file in files[10:]: for oldFile in htmlFiles[10:]:
# Diese Fehlerbehandlung versucht die Datei zu entfernen und gibt einen Fehler aus, wenn es fehlschlägt.
try: try:
os.remove(old_file) os.remove(oldFile)
except Exception as e: except Exception as e:
print(f"Fehler beim Löschen der Datei {old_file}: {e}") print(f"Fehler beim Löschen der Datei {oldFile}: {e}")
# Diese Anweisung gibt den Selenium-Treiber zurück. # Hier wird der WebDriver (Selenium-Treiber) zurückgegeben, um weiter damit arbeiten zu können.
return self.driver return self.driver
# Diese Methode scrollt die Seite schrittweise, um möglichst alle dynamischen Inhalte zu laden. # In dieser Methode wird die Seite durch mehrmaliges Scrollen bis zum Seitenende geladen, um dynamische Inhalte sichtbar zu machen.
def performScroll(self, scroll_step=600, scroll_pause=0.01, max_tries=10): def performScroll(self, scrollStep=600, scrollPause=0.01, maxTries=10):
# Diese Variable speichert die anfängliche Höhe des Dokuments. # Hier wird die Anfangshöhe des Dokumentbodens ermittelt.
last_height = self.driver.execute_script("return document.body.scrollHeight") lastHeight = self.driver.execute_script("return document.body.scrollHeight")
# Diese Variable zählt, wie oft sich die Höhe nacheinander nicht geändert hat. # In dieser Variablen wird gezählt, wie oft sich die Höhe nicht ändert.
stop_counter = 0 stopCounter = 0
# Diese Schleife wird durchlaufen, um mehrmals zu scrollen und somit alle Inhalte zu erfassen. # In dieser Schleife wird eine bestimmte Anzahl von Scroll-Versuchen durchgeführt.
for _ in range(max_tries): for _ in range(maxTries):
# Diese Schleife scrollt schrittweise durch die Seite. # In dieser Schleife wird schrittweise bis zur aktuellen Höhe gescrollt.
for pos in range(0, last_height, scroll_step): for position in range(0, lastHeight, scrollStep):
# Diese Anweisung scrollt zum jeweiligen Abschnitt. self.driver.execute_script(f"window.scrollTo(0, {position});")
self.driver.execute_script(f"window.scrollTo(0, {pos});") time.sleep(scrollPause)
time.sleep(scroll_pause)
# Diese Anweisung scrollt an das Ende des Dokuments. # Hier wird am Ende des Durchlaufs einmal ganz an das Seitenende gescrollt.
self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(scroll_pause) time.sleep(scrollPause)
# Diese Variable liest die neue Höhe nach dem Scrollen. # In dieser Variablen wird die neue Seitenhöhe ermittelt.
new_height = self.driver.execute_script("return document.body.scrollHeight") newHeight = self.driver.execute_script("return document.body.scrollHeight")
# Diese Abfrage prüft, ob sich die Höhe im Vergleich zum letzten Durchlauf nicht geändert hat. # In dieser Abzweigung wird geprüft, ob sich die Höhe im Vergleich zum letzten Durchlauf nicht verändert hat.
if new_height == last_height: if newHeight == lastHeight:
# Diese Anweisung erhöht den Zähler, wenn keine Änderung festgestellt wird. stopCounter += 1
stop_counter += 1
# Diese Abfrage bricht die Schleife ab, wenn keine Änderung zweimal in Folge festgestellt wurde. # In dieser Abzweigung wird die Schleife beendet, wenn sich die Höhe wiederholt nicht ändert.
if stop_counter >= 2: if stopCounter >= 2:
break break
else: else:
# Diese Anweisung setzt den Zähler zurück, wenn eine Veränderung festgestellt wird. stopCounter = 0
stop_counter = 0
# Diese Anweisung aktualisiert last_height mit der neuen Höhe. # Hier wird die zuletzt bekannte Höhe auf den neuen Wert aktualisiert.
last_height = new_height lastHeight = newHeight
# Diese Anweisung gibt den Selenium-Treiber zurück. # Hier wird der WebDriver zurückgegeben, um die Interaktion fortzusetzen.
return self.driver return self.driver
# Diese Methode schließt die Selenium-Instanz und beendet den Browser. # In dieser Methode wird der Selenium-WebDriver geschlossen und der Browser beendet.
def closeDriver(self): def closeDriver(self):
self.driver.quit() self.driver.quit()
# Diese Methode generiert einen zufälligen User-Agent-String, um die Browsererkennung zu erschweren. # In dieser Methode wird ein zufälliger User-Agent erzeugt, um beim Surfen unterschiedliche Browser zu simulieren.
def getRandomUserAgent(self): def getRandomUserAgent(self):
# Diese Variable enthält typische Betriebssystem-Angaben. # In dieser Liste werden verschiedene Betriebssystem-Angaben für den User-Agent hinterlegt.
operating_systems = [ operatingSystems = [
"Windows NT 10.0; Win64; x64", "Windows NT 10.0; Win64; x64",
"Windows NT 6.1; Win64; x64", "Windows NT 6.1; Win64; x64",
"Macintosh; Intel Mac OS X 10_15_7", "Macintosh; Intel Mac OS X 10_15_7",
...@@ -155,61 +150,61 @@ class SeleniumManager: ...@@ -155,61 +150,61 @@ class SeleniumManager:
"Android 11; Mobile; rv:68.0" "Android 11; Mobile; rv:68.0"
] ]
# Diese Variable listet verschiedene Browsertypen auf. # In dieser Liste werden verschiedene Browsertypen aufgeführt.
browsers = ["chrome", "firefox", "safari", "edge", "opera"] browserTypes = ["chrome", "firefox", "safari", "edge", "opera"]
# Diese Anweisung wählt ein zufälliges Betriebssystem aus. # Hier wird ein Zufallswert für das Betriebssystem ausgewählt.
os_ = random.choice(operating_systems) chosenOs = random.choice(operatingSystems)
# Diese Anweisung wählt einen zufälligen Browser aus. # Hier wird ein Zufallswert für den Browser ausgewählt.
browser = random.choice(browsers) chosenBrowser = random.choice(browserTypes)
# Diese Variablen generieren zufällige Versionsnummern. # Diese Variablen repräsentieren zufällige Versionsnummern, um den User-Agent realistischer zu gestalten.
major_version = random.randint(60, 120) majorVersion = random.randint(60, 120)
minor_version = random.randint(0, 4000) minorVersion = random.randint(0, 4000)
build_version = random.randint(0, 999) buildVersion = random.randint(0, 999)
patch_version = random.randint(0, 99) patchVersion = random.randint(0, 99)
# Diese Abfrage erstellt den User-Agent für Chrome. # In dieser Abzweigung wird ein Chrome-User-Agent generiert.
if browser == "chrome": if chosenBrowser == "chrome":
ua = ( userAgent = (
f"Mozilla/5.0 ({os_}) AppleWebKit/537.36 " f"Mozilla/5.0 ({chosenOs}) AppleWebKit/537.36 "
f"(KHTML, like Gecko) Chrome/{major_version}.{build_version}.{minor_version}.{patch_version} " f"(KHTML, like Gecko) Chrome/{majorVersion}.{buildVersion}.{minorVersion}.{patchVersion} "
f"Safari/537.36" f"Safari/537.36"
) )
# Diese Abfrage erstellt den User-Agent für Firefox. # In dieser Abzweigung wird ein Firefox-User-Agent generiert.
elif browser == "firefox": elif chosenBrowser == "firefox":
rv_version = f"{major_version}.0" rvVersion = f"{majorVersion}.0"
ua = ( userAgent = (
f"Mozilla/5.0 ({os_}; rv:{rv_version}) " f"Mozilla/5.0 ({chosenOs}; rv:{rvVersion}) "
f"Gecko/20100101 Firefox/{rv_version}" f"Gecko/20100101 Firefox/{rvVersion}"
) )
# Diese Abfrage erstellt den User-Agent für Safari. # In dieser Abzweigung wird ein Safari-User-Agent generiert.
elif browser == "safari": elif chosenBrowser == "safari":
safari_version = f"{major_version}.{random.randint(0, 9)}" safariVersion = f"{majorVersion}.{random.randint(0, 9)}"
webkit_version = f"605.1.{random.randint(0, 99)}" webkitVersion = f"605.1.{random.randint(0, 99)}"
ua = ( userAgent = (
f"Mozilla/5.0 ({os_}) AppleWebKit/{webkit_version} " f"Mozilla/5.0 ({chosenOs}) AppleWebKit/{webkitVersion} "
f"(KHTML, like Gecko) Version/{safari_version} Safari/{webkit_version}" f"(KHTML, like Gecko) Version/{safariVersion} Safari/{webkitVersion}"
) )
# Diese Abfrage erstellt den User-Agent für Edge. # In dieser Abzweigung wird ein Edge-User-Agent generiert.
elif browser == "edge": elif chosenBrowser == "edge":
blink_version = f"{major_version}.{build_version}.{minor_version}.{patch_version}" blinkVersion = f"{majorVersion}.{buildVersion}.{minorVersion}.{patchVersion}"
ua = ( userAgent = (
f"Mozilla/5.0 ({os_}) AppleWebKit/537.36 " f"Mozilla/5.0 ({chosenOs}) AppleWebKit/537.36 "
f"(KHTML, like Gecko) Chrome/{blink_version} Safari/537.36 Edg/{blink_version}" f"(KHTML, like Gecko) Chrome/{blinkVersion} Safari/537.36 Edg/{blinkVersion}"
) )
# Diese Abfrage erstellt den User-Agent für Opera. # In dieser Abzweigung wird ein Opera-User-Agent generiert.
else: else:
blink_version = f"{major_version}.{build_version}.{minor_version}.{patch_version}" blinkVersion = f"{majorVersion}.{buildVersion}.{minorVersion}.{patchVersion}"
ua = ( userAgent = (
f"Mozilla/5.0 ({os_}) AppleWebKit/537.36 " f"Mozilla/5.0 ({chosenOs}) AppleWebKit/537.36 "
f"(KHTML, like Gecko) Chrome/{blink_version} Safari/537.36 OPR/{blink_version}" f"(KHTML, like Gecko) Chrome/{blinkVersion} Safari/537.36 OPR/{blinkVersion}"
) )
# Diese Anweisung gibt den generierten User-Agent zurück. # Hier wird der generierte User-Agent zurückgegeben.
return ua return userAgent
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, JSON
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from models._system import Base from models._system import Base
from models.basegroup_bgro import BasegroupBgro from models.basegroup_bgro import BasegroupBgro
...@@ -7,13 +7,16 @@ from models.option_opti import OptionOpti ...@@ -7,13 +7,16 @@ from models.option_opti import OptionOpti
class BaseBase(Base): class BaseBase(Base):
__tablename__ = 'base_base' __tablename__ = 'base_base'
id_base = Column(Integer, primary_key=True, autoincrement=True) id_base = Column(Integer, primary_key=True, autoincrement=True)
basegroup_base = Column(Integer, ForeignKey('basegroup_bgro.id_bgro')) basegroup_base = Column(Integer, ForeignKey("basegroup_bgro.id_bgro"))
provider_base = Column(String(255), nullable=False) provider_base = Column(String(255), nullable=False)
providercode_base = Column(String(255)) providercode_base= Column(String(255))
name_base = Column(String(255), nullable=False) name_base = Column(String(255), nullable=False)
alias_base = Column(String(255)) alias_base = Column(String(255))
network_base = Column(Integer, nullable=False) network_base = Column(Integer, nullable=False)
type_base = Column(Integer, nullable=False) type_base = Column(Integer, nullable=False)
flyerurl_base = Column(String(255))
piburl_base = Column(String(255))
details_base = Column(JSON) # enthält das von GPT extrahierte Tarif‑JSON
created_base = Column(DateTime, nullable=False) created_base = Column(DateTime, nullable=False)
updated_base = Column(DateTime, nullable=False) updated_base = Column(DateTime, nullable=False)
basegroup = relationship("BasegroupBgro", back_populates="bases") basegroup = relationship("BasegroupBgro", back_populates="bases")
......
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "193fb1fb-6ad1-447e-b641-73857be9d9dd",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"INFO: Suche nach PDF-Dateien in '../cache' …\n",
"\n",
"--- Verarbeitung ID: 3243715 ---\n",
"INFO: GPT-Abfrage Versuch 1/3 …\n",
"INFO: JSON in id_base 87 gespeichert.\n",
"\n",
"--- Verarbeitung ID: 3332926 ---\n",
"INFO: details_base bereits gefüllt (id_base=96).\n",
"\n",
"--- Verarbeitung ID: 3337091 ---\n",
"INFO: details_base bereits gefüllt (id_base=103).\n",
"\n",
"--- Verarbeitung ID: 3337095 ---\n",
"INFO: GPT-Abfrage Versuch 1/3 …\n",
"INFO: JSON in id_base 101 gespeichert.\n",
"\n",
"--- Verarbeitung ID: 3380690 ---\n",
"INFO: GPT-Abfrage Versuch 1/3 …\n",
"INFO: JSON in id_base 77 gespeichert.\n",
"\n",
"--- Verarbeitung ID: 3385056 ---\n",
"INFO: GPT-Abfrage Versuch 1/3 …\n"
]
},
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[1], line 230\u001b[0m\n\u001b[1;32m 228\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mINFO: GPT-Abfrage Versuch \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mattempt\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m/3 …\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 229\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 230\u001b[0m raw \u001b[38;5;241m=\u001b[39m \u001b[43mgpt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mchat\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfull_prompt\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mgpt-4o-mini\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 231\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 232\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFEHLER: GPT-Abfrage fehlgeschlagen: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00me\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n",
"File \u001b[0;32m~/Local/Development/Maui/notebooks/../manager/OpenAiManager.py:42\u001b[0m, in \u001b[0;36mOpenAiManager.chat\u001b[0;34m(self, prompt, model)\u001b[0m\n\u001b[1;32m 36\u001b[0m data \u001b[38;5;241m=\u001b[39m {\n\u001b[1;32m 37\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmodel\u001b[39m\u001b[38;5;124m\"\u001b[39m: model,\n\u001b[1;32m 38\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmessages\u001b[39m\u001b[38;5;124m\"\u001b[39m: [{\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrole\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124muser\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcontent\u001b[39m\u001b[38;5;124m\"\u001b[39m: prompt}]\n\u001b[1;32m 39\u001b[0m }\n\u001b[1;32m 41\u001b[0m \u001b[38;5;66;03m# An dieser Stelle wird die Anfrage an die OpenAI-API gesendet.\u001b[39;00m\n\u001b[0;32m---> 42\u001b[0m response \u001b[38;5;241m=\u001b[39m \u001b[43mrequests\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpost\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mheaders\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mjson\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 44\u001b[0m \u001b[38;5;66;03m# Die Antwort wird aus dem JSON-Format in ein Python-Objekt konvertiert.\u001b[39;00m\n\u001b[1;32m 45\u001b[0m result \u001b[38;5;241m=\u001b[39m response\u001b[38;5;241m.\u001b[39mjson()\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/requests/api.py:115\u001b[0m, in \u001b[0;36mpost\u001b[0;34m(url, data, json, **kwargs)\u001b[0m\n\u001b[1;32m 103\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mpost\u001b[39m(url, data\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, json\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 104\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124mr\u001b[39m\u001b[38;5;124;03m\"\"\"Sends a POST request.\u001b[39;00m\n\u001b[1;32m 105\u001b[0m \n\u001b[1;32m 106\u001b[0m \u001b[38;5;124;03m :param url: URL for the new :class:`Request` object.\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 112\u001b[0m \u001b[38;5;124;03m :rtype: requests.Response\u001b[39;00m\n\u001b[1;32m 113\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 115\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mpost\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mjson\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mjson\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/requests/api.py:59\u001b[0m, in \u001b[0;36mrequest\u001b[0;34m(method, url, **kwargs)\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[38;5;66;03m# By using the 'with' statement we are sure the session is closed, thus we\u001b[39;00m\n\u001b[1;32m 56\u001b[0m \u001b[38;5;66;03m# avoid leaving sockets open which can trigger a ResourceWarning in some\u001b[39;00m\n\u001b[1;32m 57\u001b[0m \u001b[38;5;66;03m# cases, and look like a memory leak in others.\u001b[39;00m\n\u001b[1;32m 58\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m sessions\u001b[38;5;241m.\u001b[39mSession() \u001b[38;5;28;01mas\u001b[39;00m session:\n\u001b[0;32m---> 59\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43msession\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/requests/sessions.py:589\u001b[0m, in \u001b[0;36mSession.request\u001b[0;34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001b[0m\n\u001b[1;32m 584\u001b[0m send_kwargs \u001b[38;5;241m=\u001b[39m {\n\u001b[1;32m 585\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtimeout\u001b[39m\u001b[38;5;124m\"\u001b[39m: timeout,\n\u001b[1;32m 586\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mallow_redirects\u001b[39m\u001b[38;5;124m\"\u001b[39m: allow_redirects,\n\u001b[1;32m 587\u001b[0m }\n\u001b[1;32m 588\u001b[0m send_kwargs\u001b[38;5;241m.\u001b[39mupdate(settings)\n\u001b[0;32m--> 589\u001b[0m resp \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mprep\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43msend_kwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 591\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m resp\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/requests/sessions.py:703\u001b[0m, in \u001b[0;36mSession.send\u001b[0;34m(self, request, **kwargs)\u001b[0m\n\u001b[1;32m 700\u001b[0m start \u001b[38;5;241m=\u001b[39m preferred_clock()\n\u001b[1;32m 702\u001b[0m \u001b[38;5;66;03m# Send the request\u001b[39;00m\n\u001b[0;32m--> 703\u001b[0m r \u001b[38;5;241m=\u001b[39m \u001b[43madapter\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 705\u001b[0m \u001b[38;5;66;03m# Total elapsed time of the request (approximately)\u001b[39;00m\n\u001b[1;32m 706\u001b[0m elapsed \u001b[38;5;241m=\u001b[39m preferred_clock() \u001b[38;5;241m-\u001b[39m start\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/requests/adapters.py:667\u001b[0m, in \u001b[0;36mHTTPAdapter.send\u001b[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[1;32m 664\u001b[0m timeout \u001b[38;5;241m=\u001b[39m TimeoutSauce(connect\u001b[38;5;241m=\u001b[39mtimeout, read\u001b[38;5;241m=\u001b[39mtimeout)\n\u001b[1;32m 666\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 667\u001b[0m resp \u001b[38;5;241m=\u001b[39m \u001b[43mconn\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43murlopen\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 668\u001b[0m \u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 669\u001b[0m \u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 670\u001b[0m \u001b[43m \u001b[49m\u001b[43mbody\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 671\u001b[0m \u001b[43m \u001b[49m\u001b[43mheaders\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 672\u001b[0m \u001b[43m \u001b[49m\u001b[43mredirect\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 673\u001b[0m \u001b[43m \u001b[49m\u001b[43massert_same_host\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 674\u001b[0m \u001b[43m \u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 675\u001b[0m \u001b[43m \u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 676\u001b[0m \u001b[43m \u001b[49m\u001b[43mretries\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmax_retries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 677\u001b[0m \u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 678\u001b[0m \u001b[43m \u001b[49m\u001b[43mchunked\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mchunked\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 679\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 681\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (ProtocolError, \u001b[38;5;167;01mOSError\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[1;32m 682\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mConnectionError\u001b[39;00m(err, request\u001b[38;5;241m=\u001b[39mrequest)\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/urllib3/connectionpool.py:703\u001b[0m, in \u001b[0;36mHTTPConnectionPool.urlopen\u001b[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001b[0m\n\u001b[1;32m 700\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_prepare_proxy(conn)\n\u001b[1;32m 702\u001b[0m \u001b[38;5;66;03m# Make the request on the httplib connection object.\u001b[39;00m\n\u001b[0;32m--> 703\u001b[0m httplib_response \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_make_request\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 704\u001b[0m \u001b[43m \u001b[49m\u001b[43mconn\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 705\u001b[0m \u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 706\u001b[0m \u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 707\u001b[0m \u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeout_obj\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 708\u001b[0m \u001b[43m \u001b[49m\u001b[43mbody\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 709\u001b[0m \u001b[43m \u001b[49m\u001b[43mheaders\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 710\u001b[0m \u001b[43m \u001b[49m\u001b[43mchunked\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mchunked\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 711\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 713\u001b[0m \u001b[38;5;66;03m# If we're going to release the connection in ``finally:``, then\u001b[39;00m\n\u001b[1;32m 714\u001b[0m \u001b[38;5;66;03m# the response doesn't need to know about the connection. Otherwise\u001b[39;00m\n\u001b[1;32m 715\u001b[0m \u001b[38;5;66;03m# it will also try to release it and we'll have a double-release\u001b[39;00m\n\u001b[1;32m 716\u001b[0m \u001b[38;5;66;03m# mess.\u001b[39;00m\n\u001b[1;32m 717\u001b[0m response_conn \u001b[38;5;241m=\u001b[39m conn \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m release_conn \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/urllib3/connectionpool.py:449\u001b[0m, in \u001b[0;36mHTTPConnectionPool._make_request\u001b[0;34m(self, conn, method, url, timeout, chunked, **httplib_request_kw)\u001b[0m\n\u001b[1;32m 444\u001b[0m httplib_response \u001b[38;5;241m=\u001b[39m conn\u001b[38;5;241m.\u001b[39mgetresponse()\n\u001b[1;32m 445\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 446\u001b[0m \u001b[38;5;66;03m# Remove the TypeError from the exception chain in\u001b[39;00m\n\u001b[1;32m 447\u001b[0m \u001b[38;5;66;03m# Python 3 (including for exceptions like SystemExit).\u001b[39;00m\n\u001b[1;32m 448\u001b[0m \u001b[38;5;66;03m# Otherwise it looks like a bug in the code.\u001b[39;00m\n\u001b[0;32m--> 449\u001b[0m \u001b[43msix\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mraise_from\u001b[49m\u001b[43m(\u001b[49m\u001b[43me\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 450\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (SocketTimeout, BaseSSLError, SocketError) \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 451\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_raise_timeout(err\u001b[38;5;241m=\u001b[39me, url\u001b[38;5;241m=\u001b[39murl, timeout_value\u001b[38;5;241m=\u001b[39mread_timeout)\n",
"File \u001b[0;32m<string>:3\u001b[0m, in \u001b[0;36mraise_from\u001b[0;34m(value, from_value)\u001b[0m\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/site-packages/urllib3/connectionpool.py:444\u001b[0m, in \u001b[0;36mHTTPConnectionPool._make_request\u001b[0;34m(self, conn, method, url, timeout, chunked, **httplib_request_kw)\u001b[0m\n\u001b[1;32m 441\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m:\n\u001b[1;32m 442\u001b[0m \u001b[38;5;66;03m# Python 3\u001b[39;00m\n\u001b[1;32m 443\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 444\u001b[0m httplib_response \u001b[38;5;241m=\u001b[39m \u001b[43mconn\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgetresponse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 445\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 446\u001b[0m \u001b[38;5;66;03m# Remove the TypeError from the exception chain in\u001b[39;00m\n\u001b[1;32m 447\u001b[0m \u001b[38;5;66;03m# Python 3 (including for exceptions like SystemExit).\u001b[39;00m\n\u001b[1;32m 448\u001b[0m \u001b[38;5;66;03m# Otherwise it looks like a bug in the code.\u001b[39;00m\n\u001b[1;32m 449\u001b[0m six\u001b[38;5;241m.\u001b[39mraise_from(e, \u001b[38;5;28;01mNone\u001b[39;00m)\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/http/client.py:1374\u001b[0m, in \u001b[0;36mHTTPConnection.getresponse\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1372\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1373\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 1374\u001b[0m \u001b[43mresponse\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbegin\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1375\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mConnectionError\u001b[39;00m:\n\u001b[1;32m 1376\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mclose()\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/http/client.py:318\u001b[0m, in \u001b[0;36mHTTPResponse.begin\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 316\u001b[0m \u001b[38;5;66;03m# read until we get a non-100 response\u001b[39;00m\n\u001b[1;32m 317\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m--> 318\u001b[0m version, status, reason \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_read_status\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 319\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m status \u001b[38;5;241m!=\u001b[39m CONTINUE:\n\u001b[1;32m 320\u001b[0m \u001b[38;5;28;01mbreak\u001b[39;00m\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/http/client.py:279\u001b[0m, in \u001b[0;36mHTTPResponse._read_status\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 278\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_read_status\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[0;32m--> 279\u001b[0m line \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mstr\u001b[39m(\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mreadline\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_MAXLINE\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124miso-8859-1\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 280\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(line) \u001b[38;5;241m>\u001b[39m _MAXLINE:\n\u001b[1;32m 281\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m LineTooLong(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mstatus line\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/socket.py:705\u001b[0m, in \u001b[0;36mSocketIO.readinto\u001b[0;34m(self, b)\u001b[0m\n\u001b[1;32m 703\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[1;32m 704\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 705\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_sock\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrecv_into\u001b[49m\u001b[43m(\u001b[49m\u001b[43mb\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 706\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m timeout:\n\u001b[1;32m 707\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_timeout_occurred \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/ssl.py:1274\u001b[0m, in \u001b[0;36mSSLSocket.recv_into\u001b[0;34m(self, buffer, nbytes, flags)\u001b[0m\n\u001b[1;32m 1270\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m flags \u001b[38;5;241m!=\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[1;32m 1271\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[1;32m 1272\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnon-zero flags not allowed in calls to recv_into() on \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m%\u001b[39m\n\u001b[1;32m 1273\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m)\n\u001b[0;32m-> 1274\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnbytes\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbuffer\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1275\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 1276\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28msuper\u001b[39m()\u001b[38;5;241m.\u001b[39mrecv_into(buffer, nbytes, flags)\n",
"File \u001b[0;32m~/anaconda3/lib/python3.10/ssl.py:1130\u001b[0m, in \u001b[0;36mSSLSocket.read\u001b[0;34m(self, len, buffer)\u001b[0m\n\u001b[1;32m 1128\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1129\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m buffer \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m-> 1130\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_sslobj\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mlen\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbuffer\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1131\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 1132\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_sslobj\u001b[38;5;241m.\u001b[39mread(\u001b[38;5;28mlen\u001b[39m)\n",
"\u001b[0;31mKeyboardInterrupt\u001b[0m: "
]
}
],
"source": [
"import sys; sys.path.append(\"..\")\n",
"import os\n",
"import re\n",
"import ast\n",
"import json\n",
"import datetime as _dt\n",
"import traceback\n",
"from typing import Any, Dict, List, Tuple\n",
"from pypdf import PdfReader\n",
"from pypdf.errors import PdfReadError\n",
"from manager.OpenAiManager import OpenAiManager\n",
"from manager.MysqlManager import MysqlManager\n",
"from models._system import Base\n",
"from models.base_base import BaseBase\n",
"from models.deal_deal import DealDeal\n",
"from models.option_opti import OptionOpti\n",
"\n",
"\n",
"# In dieser Variablen wird der vollständige Prompt für die Extraktion definiert.\n",
"promptTemplate: str = (\n",
" \"\"\"\n",
"Du bist eine hochpräzise API zur Extraktion spezifischer Mobilfunktarif-Merkmale aus Dokumentenpaaren. Deine Eingabe besteht immer aus dem extrahierten Text von zwei PDF-Dateien: einem **Produktdetailblatt/Flyer** und einem **Produktinformationsblatt (PIB)**, die gemeinsam *einen* spezifischen Tarif beschreiben.\n",
"\n",
"Deine Aufgabe ist es, beide Dokumente sorgfältig und vergleichend zu analysieren, um die unten definierten **relevanten Tarifbestandteile** mit höchstmöglicher Genauigkeit zu extrahieren. Das Ergebnis **muss ausschließlich** ein einzelnes JSON-Objekt sein, das exakt die vorgegebenen Schlüsselnamen und das flache Key-Value-Format verwendet. Ignoriere irrelevante Informationen wie Anbieteradressen, AGB-Verweise oder allgemeine Marketingtexte.\n",
"\n",
"**Extraktionsanweisungen und Felddefinitionen:**\n",
"\n",
"1. **`tariff_name` (String):** Extrahiere den vollständigen, exakten Namen des Tarifs.\n",
"2. **`marketing_start_date` (String oder null):** Extrahiere das Vermarktungsdatum (aus PIB). Formatiere als `JJJJ-MM-TT` oder `null`.\n",
"3. **`network_operator` (String oder null):** Identifiziere den Netzbetreiber (z.B. \"Telekom\", \"Vodafone\", \"O2\").\n",
"4. **`network_technology` (String oder null):** Identifiziere die primär beworbene/höchste Netztechnologie (z.B. \"5G\", \"LTE\").\n",
"5. **`is_data_only_tariff` (Boolean):** Erfasse, ob es ein reiner Datentarif ist. Kriterien: Telefonie nicht als Flat inkludiert oder explizit als \"nicht möglich\". Prüfe Inklusivleistungen und Preise sorgfältig.\n",
"6. **`inclusive_internet_flat` (Boolean oder null):** Prüfe auf explizite Nennung einer Internet-Flat.\n",
"7. **`inclusive_telephony_flat` (Boolean oder null):** Prüfe auf explizite Nennung einer Telefonie-Flat oder Preis pro Minute von 0,00 €.\n",
"8. **`telephony_price_per_minute_eur_brutto` (Number oder null):** Wenn `inclusive_telephony_flat` false und Telefonie möglich: extrahiere Brutto-Preis pro Minute. Sonst 0.0 (Flat) oder null.\n",
"9. **`telephony_price_per_minute_eur_netto` (Number oder null):** Bei positivem Brutto-Wert: Netto = Brutto / 1.19, auf 4 Nachkommastellen gerundet. Sonst 0.0 oder null.\n",
"10. **`inclusive_sms_flat` (Boolean oder null):** Prüfe auf SMS-Flat oder Preis pro SMS 0,00 €.\n",
"11. **`sms_price_per_unit_eur_brutto` (Number oder null):** Wenn keine SMS-Flat: extrahiere Brutto-Preis pro SMS.\n",
"12. **`sms_price_per_unit_eur_netto` (Number oder null):** Bei positivem Brutto-Wert: Netto = Brutto / 1.19, gerundet auf 4 Nachkommastellen.\n",
"13. **`inclusive_volte_wlan_call` (Boolean oder null):** Prüfe auf VoLTE/WLAN-Call. Setze false bei reinen Datentarifen.\n",
"14. **`data_volume_gb` (Number oder null):** Extrahiere das Datenvolumen in GB.\n",
"15. **`data_download_max_mbps` (Number oder null):** Max. Download in Mbit/s.\n",
"16. **`data_upload_max_mbps` (Number oder null):** Max. Upload in Mbit/s.\n",
"17. **`data_download_throttled_kbps` (Number oder null):** Drossel-Download in kbit/s.\n",
"18. **`data_upload_throttled_kbps` (Number oder null):** Drossel-Upload in kbit/s.\n",
"19. **`data_billing_increment_kb` (Number oder null):** Datentaktung in KB.\n",
"20. **`telephony_billing_increment_seconds` (String oder null):** Telefontaktung, z.B. \"60/60\".\n",
"21. **`pricing_connection_fee_eur_brutto` (Number oder null):** Einmaliger Anschlusspreis (Brutto).\n",
"22. **`pricing_connection_fee_eur_netto` (Number oder null):** Netto-Anschlusspreis = Brutto / 1.19, gerundet auf 4 Nachkommastellen.\n",
"23. **`pricing_monthly_initial_eur_brutto` (Number oder null):** Monatlicher Bruttopreis in der Anfangsphase.\n",
"24. **`pricing_monthly_initial_eur_netto` (Number oder null):** Netto-Betrag derselben Phase; falls nicht angegeben selbst berechnen.\n",
"25. **`pricing_monthly_after_period_eur_brutto` (Number oder null):** Bruttopreis nach Aktionsphase.\n",
"26. **`pricing_monthly_after_period_eur_netto` (Number oder null):** Netto-Preis nach Aktionsphase = Brutto / 1.19.\n",
"27. **`contract_min_duration_months` (Number oder null):** Mindest-Vertragslaufzeit (Monate).\n",
"28. **`contract_cancellation_notice_period_months` (Number oder null):** Kündigungsfrist (Monate).\n",
"\n",
"Werte, die nicht zuverlässig extrahiert werden können, auf `null` setzen.\n",
"Numerische Werte als Number-Typ belassen, Netto immer auf 4 Nachkommastellen.\n",
"\n",
"**WICHTIG:** Antworte **ausschließlich** mit dem JSON-Objekt. Keine Markdown, keine Erklärungen.\n",
"#########\n",
"\"\"\"\n",
")\n",
"\n",
"# In dieser Liste werden die erwarteten Keys festgelegt, die im JSON enthalten sein müssen.\n",
"expectedKeys: List[str] = [\n",
" \"tariff_name\",\n",
" \"marketing_start_date\",\n",
" \"network_operator\",\n",
" \"network_technology\",\n",
" \"is_data_only_tariff\",\n",
" \"inclusive_internet_flat\",\n",
" \"inclusive_telephony_flat\",\n",
" \"telephony_price_per_minute_eur_brutto\",\n",
" \"telephony_price_per_minute_eur_netto\",\n",
" \"inclusive_sms_flat\",\n",
" \"sms_price_per_unit_eur_brutto\",\n",
" \"sms_price_per_unit_eur_netto\",\n",
" \"inclusive_volte_wlan_call\",\n",
" \"data_volume_gb\",\n",
" \"data_download_max_mbps\",\n",
" \"data_upload_max_mbps\",\n",
" \"data_download_throttled_kbps\",\n",
" \"data_upload_throttled_kbps\",\n",
" \"data_billing_increment_kb\",\n",
" \"telephony_billing_increment_seconds\",\n",
" \"pricing_connection_fee_eur_brutto\",\n",
" \"pricing_connection_fee_eur_netto\",\n",
" \"pricing_monthly_initial_eur_brutto\",\n",
" \"pricing_monthly_initial_eur_netto\",\n",
" \"pricing_monthly_after_period_eur_brutto\",\n",
" \"pricing_monthly_after_period_eur_netto\",\n",
" \"contract_min_duration_months\",\n",
" \"contract_cancellation_notice_period_months\",\n",
"]\n",
"\n",
"\n",
"# In dieser Funktion wird eine PDF vollständig eingelesen und der extrahierte Text zurückgegeben.\n",
"def extractTextFromPdf(pdfPath: str) -> str | None:\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob die Datei existiert.\n",
" if not os.path.exists(pdfPath):\n",
" print(f\" INFO: Datei nicht gefunden: {os.path.basename(pdfPath)}\")\n",
" return None\n",
"\n",
" # In dieser Liste wird der Text seitenweise abgelegt.\n",
" pageList: List[str] = []\n",
"\n",
" # In diesem try-Block wird die PDF geöffnet und Seite für Seite gelesen.\n",
" try:\n",
" with open(pdfPath, \"rb\") as fileHandle:\n",
" reader = PdfReader(fileHandle)\n",
" for page in reader.pages:\n",
" extractedText = page.extract_text()\n",
" if extractedText:\n",
" pageList.append(extractedText)\n",
"\n",
" # In dieser Abzweigung wird eine Meldung ausgegeben, wenn kein Text gefunden wurde.\n",
" if not pageList:\n",
" print(f\" INFO: Kein Text in {os.path.basename(pdfPath)}\")\n",
" return None\n",
"\n",
" # Hier wird der zusammengesetzte Text zurückgegeben.\n",
" return \"\\n\".join(pageList).strip()\n",
"\n",
" # In dieser Abzweigung wird ein spezieller Fehler von pypdf abgefangen.\n",
" except PdfReadError as e:\n",
" print(f\" WARNUNG: pypdf konnte '{os.path.basename(pdfPath)}' nicht lesen: {e}\")\n",
" return None\n",
"\n",
" # In dieser Abzweigung werden andere Fehler behandelt.\n",
" except Exception:\n",
" print(f\" FEHLER: Unerwarteter Fehler bei '{os.path.basename(pdfPath)}':\")\n",
" traceback.print_exc(limit=1)\n",
" return None\n",
"\n",
"\n",
"# In dieser Funktion wird ein möglicher Code-Fence aus dem JSON entfernt.\n",
"def stripCodeFence(rawString: str) -> str:\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob der String mit Backticks beginnt, und entfernt diese ggf.\n",
" if rawString.strip().startswith(\"```\"):\n",
" return re.sub(r\"```[\\w]*\", \"\", rawString).strip()\n",
"\n",
" # Hier wird der unveränderte String zurückgegeben, wenn kein Fence vorliegt.\n",
" return rawString\n",
"\n",
"\n",
"# In dieser Funktion werden ggf. vorhandene nachgestellte Kommas entfernt, um das JSON zu bereinigen.\n",
"def removeTrailingCommas(jsonString: str) -> str:\n",
"\n",
" # Hier wird ein Regex angewandt, um Kommas vor schließenden Klammern zu entfernen.\n",
" return re.sub(r\",(\\s*[}\\]])\", r\"\\1\", jsonString)\n",
"\n",
"\n",
"# In dieser Funktion wird eine Zeichenkette in ein Dictionary umgewandelt, wobei verschiedene Reparaturansätze versucht werden.\n",
"def loadJsonSafe(rawString: str) -> Dict[str, Any] | None:\n",
"\n",
" # Hier wird der Code-Fence entfernt und \\r beseitigt.\n",
" cleaned = stripCodeFence(rawString).replace(\"\\r\", \"\")\n",
"\n",
" # In dieser Schleife wird sowohl der Ursprungsstring als auch eine Variante ohne nachgestellte Kommas getestet.\n",
" for candidate in (cleaned, removeTrailingCommas(cleaned)):\n",
"\n",
" # In diesem try-Block wird versucht, den JSON-String zu laden.\n",
" try:\n",
" return json.loads(candidate)\n",
"\n",
" # In dieser Abzweigung wird ein JSONDecodeError ignoriert, da weitere Versuche folgen.\n",
" except json.JSONDecodeError:\n",
" pass\n",
"\n",
" # In diesem try-Block wird ein Python-likes Format versucht (True/False/None).\n",
" try:\n",
" pythonLike = cleaned.replace(\"null\", \"None\").replace(\"true\", \"True\").replace(\"false\", \"False\")\n",
" return ast.literal_eval(pythonLike)\n",
" except Exception:\n",
" return None\n",
"\n",
"\n",
"# In dieser Funktion wird die GPT-Antwort validiert, um sicherzustellen, dass alle erwarteten Felder vorliegen.\n",
"def validateResponse(rawString: str) -> Tuple[bool, Dict[str, Any] | None]:\n",
"\n",
" # Hier wird versucht, den String in ein Dict umzuwandeln.\n",
" data = loadJsonSafe(rawString)\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob eine Konvertierung möglich war.\n",
" if data is None:\n",
" print(\" VALIDATION: Antwort ist kein gültiges/rekonstruierbares JSON.\")\n",
" return False, None\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob das geladene Objekt ein Dictionary ist.\n",
" if not isinstance(data, dict):\n",
" print(\" VALIDATION: JSON ist kein Objekt.\")\n",
" return False, None\n",
"\n",
" # Hier wird eine Liste der fehlenden Keys erstellt.\n",
" missingKeys = [key for key in expectedKeys if key not in data]\n",
"\n",
" # In dieser Abzweigung wird eine Meldung ausgegeben, falls Keys fehlen.\n",
" if missingKeys:\n",
" print(f\" VALIDATION: Fehlende Schlüssel: {', '.join(missingKeys)}\")\n",
" return False, None\n",
"\n",
" # Hier wird das Ergebnis zurückgegeben, wenn alles stimmt.\n",
" return True, data\n",
"\n",
"\n",
"# In diesem Hauptblock wird das Skript ausgeführt.\n",
"if __name__ == \"__main__\":\n",
"\n",
" # In dieser Variablen wird das Cache-Verzeichnis festgelegt.\n",
" cacheDir = \"../cache\"\n",
"\n",
" # Hier wird eine Meldung ausgegeben, dass im Cache-Verzeichnis nach PDF-Dateien gesucht wird.\n",
" print(f\"INFO: Suche nach PDF-Dateien in '{cacheDir}' …\")\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob das Cache-Verzeichnis existiert.\n",
" if not os.path.isdir(cacheDir):\n",
" print(\"FEHLER: Cache-Verzeichnis nicht gefunden.\")\n",
" sys.exit(1)\n",
"\n",
" # In dieser Liste werden alle gefundenen PDF-Dateien gesammelt.\n",
" allFiles = [f for f in os.listdir(cacheDir) if f.lower().endswith(\".pdf\")]\n",
"\n",
" # In dieser Menge werden anhand der Dateinamen die Tarif-IDs gespeichert.\n",
" tariffIds: set[str] = set()\n",
"\n",
" # In dieser Schleife werden die PDF-Dateinamen aufgesplittet, um Flyer- und PIB-Paare zu erkennen.\n",
" for fileName in allFiles:\n",
" nameStem = fileName[:-4].lower()\n",
" if nameStem.endswith(\"_flyer\"):\n",
" tariffIds.add(nameStem[:-6])\n",
" elif nameStem.endswith(\"_pib\"):\n",
" tariffIds.add(nameStem[:-4])\n",
"\n",
" # In dieser Abzweigung wird eine Meldung ausgegeben, falls keine passenden PDFs gefunden wurden.\n",
" if not tariffIds:\n",
" print(\"INFO: Keine passenden PDF-Paare gefunden.\")\n",
" sys.exit(0)\n",
"\n",
" # Hier wird ein OpenAiManager-Objekt erzeugt, um später GPT-Abfragen durchführen zu können.\n",
" gptManager = OpenAiManager()\n",
"\n",
" # Hier wird eine Datenbank-Session geöffnet, um auf BaseBase zuzugreifen.\n",
" dbSession = MysqlManager().getSession()\n",
"\n",
" # In dieser Schleife werden alle ermittelten Tarif-IDs verarbeitet.\n",
" for tariffId in sorted(tariffIds):\n",
"\n",
" # Hier wird eine Konsolenausgabe zur Übersicht generiert.\n",
" print(f\"\\n--- Verarbeitung ID: {tariffId} ---\")\n",
"\n",
" # In dieser Variablen wird versucht, einen passenden BaseBase-Datensatz zu finden.\n",
" baseRecord = dbSession.query(BaseBase).filter_by(providercode_base=tariffId).one_or_none()\n",
"\n",
" # In dieser Abzweigung wird das Vorhandensein eines Datenbankeintrags geprüft.\n",
" if not baseRecord:\n",
" print(\"WARNUNG: Kein BaseBase-Datensatz gefunden – übersprungen.\")\n",
" continue\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob details_base bereits gefüllt ist.\n",
" if baseRecord.details_base:\n",
" print(f\"INFO: details_base bereits gefüllt (id_base={baseRecord.id_base}).\")\n",
" continue\n",
"\n",
" # Hier werden die Dateipfade für Flyer und PIB ermittelt.\n",
" flyerPath = os.path.join(cacheDir, f\"{tariffId}_flyer.pdf\")\n",
" pibPath = os.path.join(cacheDir, f\"{tariffId}_pib.pdf\")\n",
"\n",
" # Hier wird der Text aus beiden PDFs extrahiert.\n",
" flyerText = extractTextFromPdf(flyerPath)\n",
" pibText = extractTextFromPdf(pibPath)\n",
"\n",
" # In dieser Abzweigung wird eine Meldung ausgegeben, wenn keine Texte extrahiert werden konnten.\n",
" if not flyerText or not pibText:\n",
" print(\"INFO: Fehlende Texte – überspringe.\")\n",
" continue\n",
"\n",
" # Hier wird der vollständige Prompt um die extrahierten Texte ergänzt.\n",
" fullPrompt = promptTemplate + \"# Flyer-Text:\\n\" + flyerText + \"\\n\" + \"# PIB-Text:\\n\" + pibText\n",
"\n",
" # In dieser Variablen wird später ein validierter JSON-Payload abgelegt.\n",
" validatedPayload: Dict[str, Any] | None = None\n",
"\n",
" # In dieser Schleife werden bis zu drei Versuche unternommen, eine valide Antwort zu erhalten.\n",
" for attemptIndex in range(1, 4):\n",
" print(f\"INFO: GPT-Abfrage Versuch {attemptIndex}/3 …\")\n",
"\n",
" # In diesem try-Block wird die GPT-Anfrage durchgeführt.\n",
" try:\n",
" rawResponse = gptManager.chat(fullPrompt, model=\"gpt-4o-mini\")\n",
"\n",
" # In dieser Abzweigung wird eine Fehlermeldung ausgegeben, wenn die Abfrage fehlschlägt.\n",
" except Exception as e:\n",
" print(f\"FEHLER: GPT-Abfrage fehlgeschlagen: {e}\")\n",
" rawResponse = \"\"\n",
"\n",
" # Hier wird die Antwort validiert.\n",
" isValid, dataResult = validateResponse(rawResponse)\n",
"\n",
" # In dieser Abzweigung wird die Schleife abgebrochen, falls die Antwort gültig ist.\n",
" if isValid:\n",
" validatedPayload = dataResult\n",
" break\n",
"\n",
" print(\" WARNUNG: Antwort ungültig – retry …\")\n",
"\n",
" # In dieser Abzweigung wird eine Meldung ausgegeben, wenn alle Versuche fehlschlagen.\n",
" if not validatedPayload:\n",
" print(\"FEHLER: Drei ungültige Antworten – überspringe Tarif.\")\n",
" continue\n",
"\n",
" # Hier wird das validierte JSON-Objekt in das Feld details_base geschrieben.\n",
" baseRecord.details_base = validatedPayload\n",
" baseRecord.updated_base = _dt.datetime.now()\n",
" dbSession.commit()\n",
"\n",
" # Hier wird eine Erfolgsmeldung ausgegeben.\n",
" print(f\"INFO: JSON in id_base {baseRecord.id_base} gespeichert.\")\n",
"\n",
" # Hier wird die Datenbank-Session nach Abschluss geschlossen.\n",
" dbSession.close()\n",
"\n",
" # Hier wird ausgegeben, dass die Verarbeitung beendet ist.\n",
" print(\"INFO: Verarbeitung abgeschlossen.\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c13bd0d2-7a85-457a-a186-51a9b924e966",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "99b16a15-2667-4ea5-b7c9-fb450d3a0b9c",
"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
}
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -27,172 +27,17 @@ ...@@ -27,172 +27,17 @@
" <thead>\n", " <thead>\n",
" <tr style=\"text-align: right;\">\n", " <tr style=\"text-align: right;\">\n",
" <th></th>\n", " <th></th>\n",
" <th>id_base</th>\n",
" <th>provider_base</th>\n",
" <th>providercode_base</th>\n",
" <th>name_base</th>\n",
" <th>network_base</th>\n",
" <th>created_base</th>\n",
" <th>updated_base</th>\n",
" </tr>\n", " </tr>\n",
" </thead>\n", " </thead>\n",
" <tbody>\n", " <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>Freenet | Cellway</td>\n",
" <td>3877325</td>\n",
" <td>Allnet Flat 20 GB Telekom (Okt 2024)</td>\n",
" <td>1</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>Freenet | Cellway</td>\n",
" <td>3877349</td>\n",
" <td>Allnet Flat 20 GB Telekom (Okt 2024) mit Smart...</td>\n",
" <td>1</td>\n",
" <td>2025-04-16 14:09:40</td>\n",
" <td>2025-04-16 14:09:40</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3</td>\n",
" <td>Freenet | Cellway</td>\n",
" <td>3877337</td>\n",
" <td>Allnet Flat 20 GB Telekom (Okt 2024) mit Smart...</td>\n",
" <td>1</td>\n",
" <td>2025-04-16 14:09:50</td>\n",
" <td>2025-04-16 14:09:50</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4</td>\n",
" <td>Freenet | Cellway</td>\n",
" <td>3878213</td>\n",
" <td>Allnet Flat 25 GB Telekom (Okt 2024)</td>\n",
" <td>1</td>\n",
" <td>2025-04-16 14:10:01</td>\n",
" <td>2025-04-16 14:10:01</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>5</td>\n",
" <td>Freenet | Cellway</td>\n",
" <td>3878237</td>\n",
" <td>Allnet Flat 25 GB Telekom (Okt 2024) mit Smart...</td>\n",
" <td>1</td>\n",
" <td>2025-04-16 14:10:12</td>\n",
" <td>2025-04-16 14:10:12</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>127</th>\n",
" <td>128</td>\n",
" <td>Freenet | Netzbetreiber</td>\n",
" <td>3782331</td>\n",
" <td>o2 Mobile Unlimited Smart (Jun 2024) mit Smart...</td>\n",
" <td>4</td>\n",
" <td>2025-04-16 14:25:58</td>\n",
" <td>2025-04-16 14:25:58</td>\n",
" </tr>\n",
" <tr>\n",
" <th>128</th>\n",
" <td>129</td>\n",
" <td>Freenet | Netzbetreiber</td>\n",
" <td>3782328</td>\n",
" <td>o2 Mobile Unlimited Smart (Jun 2024) mit Smart...</td>\n",
" <td>4</td>\n",
" <td>2025-04-16 14:26:07</td>\n",
" <td>2025-04-16 14:26:07</td>\n",
" </tr>\n",
" <tr>\n",
" <th>129</th>\n",
" <td>130</td>\n",
" <td>Freenet | Netzbetreiber</td>\n",
" <td>3973295</td>\n",
" <td>o2 Mobile XL (Nov 2024)</td>\n",
" <td>4</td>\n",
" <td>2025-04-16 14:26:13</td>\n",
" <td>2025-04-16 14:26:13</td>\n",
" </tr>\n",
" <tr>\n",
" <th>130</th>\n",
" <td>131</td>\n",
" <td>Freenet | Netzbetreiber</td>\n",
" <td>3973301</td>\n",
" <td>o2 Mobile XL (Nov 2024) mit Smartphone 10</td>\n",
" <td>4</td>\n",
" <td>2025-04-16 14:26:21</td>\n",
" <td>2025-04-16 14:26:21</td>\n",
" </tr>\n",
" <tr>\n",
" <th>131</th>\n",
" <td>132</td>\n",
" <td>Freenet | Netzbetreiber</td>\n",
" <td>3973298</td>\n",
" <td>o2 Mobile XL (Nov 2024) mit Smartphone 5</td>\n",
" <td>4</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" </tr>\n",
" </tbody>\n", " </tbody>\n",
"</table>\n", "</table>\n",
"<p>132 rows × 7 columns</p>\n",
"</div>" "</div>"
], ],
"text/plain": [ "text/plain": [
" id_base provider_base providercode_base \\\n", "Empty DataFrame\n",
"0 1 Freenet | Cellway 3877325 \n", "Columns: []\n",
"1 2 Freenet | Cellway 3877349 \n", "Index: []"
"2 3 Freenet | Cellway 3877337 \n",
"3 4 Freenet | Cellway 3878213 \n",
"4 5 Freenet | Cellway 3878237 \n",
".. ... ... ... \n",
"127 128 Freenet | Netzbetreiber 3782331 \n",
"128 129 Freenet | Netzbetreiber 3782328 \n",
"129 130 Freenet | Netzbetreiber 3973295 \n",
"130 131 Freenet | Netzbetreiber 3973301 \n",
"131 132 Freenet | Netzbetreiber 3973298 \n",
"\n",
" name_base network_base \\\n",
"0 Allnet Flat 20 GB Telekom (Okt 2024) 1 \n",
"1 Allnet Flat 20 GB Telekom (Okt 2024) mit Smart... 1 \n",
"2 Allnet Flat 20 GB Telekom (Okt 2024) mit Smart... 1 \n",
"3 Allnet Flat 25 GB Telekom (Okt 2024) 1 \n",
"4 Allnet Flat 25 GB Telekom (Okt 2024) mit Smart... 1 \n",
".. ... ... \n",
"127 o2 Mobile Unlimited Smart (Jun 2024) mit Smart... 4 \n",
"128 o2 Mobile Unlimited Smart (Jun 2024) mit Smart... 4 \n",
"129 o2 Mobile XL (Nov 2024) 4 \n",
"130 o2 Mobile XL (Nov 2024) mit Smartphone 10 4 \n",
"131 o2 Mobile XL (Nov 2024) mit Smartphone 5 4 \n",
"\n",
" created_base updated_base \n",
"0 2025-04-16 14:09:28 2025-04-16 14:09:28 \n",
"1 2025-04-16 14:09:40 2025-04-16 14:09:40 \n",
"2 2025-04-16 14:09:50 2025-04-16 14:09:50 \n",
"3 2025-04-16 14:10:01 2025-04-16 14:10:01 \n",
"4 2025-04-16 14:10:12 2025-04-16 14:10:12 \n",
".. ... ... \n",
"127 2025-04-16 14:25:58 2025-04-16 14:25:58 \n",
"128 2025-04-16 14:26:07 2025-04-16 14:26:07 \n",
"129 2025-04-16 14:26:13 2025-04-16 14:26:13 \n",
"130 2025-04-16 14:26:21 2025-04-16 14:26:21 \n",
"131 2025-04-16 14:26:22 2025-04-16 14:26:22 \n",
"\n",
"[132 rows x 7 columns]"
] ]
}, },
"metadata": {}, "metadata": {},
...@@ -219,196 +64,17 @@ ...@@ -219,196 +64,17 @@
" <thead>\n", " <thead>\n",
" <tr style=\"text-align: right;\">\n", " <tr style=\"text-align: right;\">\n",
" <th></th>\n", " <th></th>\n",
" <th>id_deal</th>\n",
" <th>base_deal</th>\n",
" <th>providercode_deal</th>\n",
" <th>name_deal</th>\n",
" <th>price_deal</th>\n",
" <th>starts_deal</th>\n",
" <th>stops_deal</th>\n",
" <th>created_deal</th>\n",
" <th>updated_deal</th>\n",
" </tr>\n", " </tr>\n",
" </thead>\n", " </thead>\n",
" <tbody>\n", " <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>1</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>25.20168</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>1</td>\n",
" <td>A3908081</td>\n",
" <td>170 EUR Sonderbonus</td>\n",
" <td>25.20168</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3</td>\n",
" <td>1</td>\n",
" <td>A3908084</td>\n",
" <td>24 x 10 EUR Rabatt auf MGP, 0 EUR Vergütungsve...</td>\n",
" <td>25.20168</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4</td>\n",
" <td>2</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>33.60504</td>\n",
" <td>2025-04-16 14:09:40</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:09:40</td>\n",
" <td>2025-04-16 14:09:40</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>5</td>\n",
" <td>2</td>\n",
" <td>A3908081</td>\n",
" <td>170 EUR Sonderbonus</td>\n",
" <td>33.60504</td>\n",
" <td>2025-04-16 14:09:40</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:09:40</td>\n",
" <td>2025-04-16 14:09:40</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>275</th>\n",
" <td>276</td>\n",
" <td>130</td>\n",
" <td>A3990011</td>\n",
" <td>210 EUR Sonderbonus</td>\n",
" <td>42.00840</td>\n",
" <td>2025-04-16 14:26:13</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:26:13</td>\n",
" <td>2025-04-16 14:26:13</td>\n",
" </tr>\n",
" <tr>\n",
" <th>276</th>\n",
" <td>277</td>\n",
" <td>131</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>50.41176</td>\n",
" <td>2025-04-16 14:26:21</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:26:21</td>\n",
" <td>2025-04-16 14:26:21</td>\n",
" </tr>\n",
" <tr>\n",
" <th>277</th>\n",
" <td>278</td>\n",
" <td>131</td>\n",
" <td>A3990011</td>\n",
" <td>210 EUR Sonderbonus</td>\n",
" <td>50.41176</td>\n",
" <td>2025-04-16 14:26:21</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:26:21</td>\n",
" <td>2025-04-16 14:26:21</td>\n",
" </tr>\n",
" <tr>\n",
" <th>278</th>\n",
" <td>279</td>\n",
" <td>132</td>\n",
" <td></td>\n",
" <td></td>\n",
" <td>46.21008</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" </tr>\n",
" <tr>\n",
" <th>279</th>\n",
" <td>280</td>\n",
" <td>132</td>\n",
" <td>A3990011</td>\n",
" <td>210 EUR Sonderbonus</td>\n",
" <td>46.21008</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" </tr>\n",
" </tbody>\n", " </tbody>\n",
"</table>\n", "</table>\n",
"<p>280 rows × 9 columns</p>\n",
"</div>" "</div>"
], ],
"text/plain": [ "text/plain": [
" id_deal base_deal providercode_deal \\\n", "Empty DataFrame\n",
"0 1 1 \n", "Columns: []\n",
"1 2 1 A3908081 \n", "Index: []"
"2 3 1 A3908084 \n",
"3 4 2 \n",
"4 5 2 A3908081 \n",
".. ... ... ... \n",
"275 276 130 A3990011 \n",
"276 277 131 \n",
"277 278 131 A3990011 \n",
"278 279 132 \n",
"279 280 132 A3990011 \n",
"\n",
" name_deal price_deal \\\n",
"0 25.20168 \n",
"1 170 EUR Sonderbonus 25.20168 \n",
"2 24 x 10 EUR Rabatt auf MGP, 0 EUR Vergütungsve... 25.20168 \n",
"3 33.60504 \n",
"4 170 EUR Sonderbonus 33.60504 \n",
".. ... ... \n",
"275 210 EUR Sonderbonus 42.00840 \n",
"276 50.41176 \n",
"277 210 EUR Sonderbonus 50.41176 \n",
"278 46.21008 \n",
"279 210 EUR Sonderbonus 46.21008 \n",
"\n",
" starts_deal stops_deal created_deal updated_deal \n",
"0 2025-04-16 14:09:28 None 2025-04-16 14:09:28 2025-04-16 14:09:28 \n",
"1 2025-04-16 14:09:28 None 2025-04-16 14:09:28 2025-04-16 14:09:28 \n",
"2 2025-04-16 14:09:28 None 2025-04-16 14:09:28 2025-04-16 14:09:28 \n",
"3 2025-04-16 14:09:40 None 2025-04-16 14:09:40 2025-04-16 14:09:40 \n",
"4 2025-04-16 14:09:40 None 2025-04-16 14:09:40 2025-04-16 14:09:40 \n",
".. ... ... ... ... \n",
"275 2025-04-16 14:26:13 None 2025-04-16 14:26:13 2025-04-16 14:26:13 \n",
"276 2025-04-16 14:26:21 None 2025-04-16 14:26:21 2025-04-16 14:26:21 \n",
"277 2025-04-16 14:26:21 None 2025-04-16 14:26:21 2025-04-16 14:26:21 \n",
"278 2025-04-16 14:26:22 None 2025-04-16 14:26:22 2025-04-16 14:26:22 \n",
"279 2025-04-16 14:26:22 None 2025-04-16 14:26:22 2025-04-16 14:26:22 \n",
"\n",
"[280 rows x 9 columns]"
] ]
}, },
"metadata": {}, "metadata": {},
...@@ -435,208 +101,17 @@ ...@@ -435,208 +101,17 @@
" <thead>\n", " <thead>\n",
" <tr style=\"text-align: right;\">\n", " <tr style=\"text-align: right;\">\n",
" <th></th>\n", " <th></th>\n",
" <th>id_opti</th>\n",
" <th>base_opti</th>\n",
" <th>providercode_opti</th>\n",
" <th>providercategory_opti</th>\n",
" <th>name_opti</th>\n",
" <th>price_opti</th>\n",
" <th>starts_opti</th>\n",
" <th>stops_opti</th>\n",
" <th>created_opti</th>\n",
" <th>updated_opti</th>\n",
" </tr>\n", " </tr>\n",
" </thead>\n", " </thead>\n",
" <tbody>\n", " <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>1</td>\n",
" <td>O3120756</td>\n",
" <td>G3120771</td>\n",
" <td>Aktionsguthaben VP | 120 EUR Aktionsguthaben V...</td>\n",
" <td>0.00000</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>1</td>\n",
" <td>O3132971</td>\n",
" <td>G3120771</td>\n",
" <td>Aktionsguthaben VP | 60 EUR Aktionsguthaben VP...</td>\n",
" <td>0.00000</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3</td>\n",
" <td>1</td>\n",
" <td>O3132980</td>\n",
" <td>G3120771</td>\n",
" <td>Aktionsguthaben VP | 192 EUR Aktionsguthaben V...</td>\n",
" <td>0.00000</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4</td>\n",
" <td>1</td>\n",
" <td>O1176</td>\n",
" <td>G18</td>\n",
" <td>Kein EVN | EVN vollständig</td>\n",
" <td>0.00000</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>5</td>\n",
" <td>1</td>\n",
" <td>O1943</td>\n",
" <td>G60</td>\n",
" <td>T-Mobile@home / EUR 4,95 monatlich | Hausnummer</td>\n",
" <td>0.00000</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" <td>2025-04-16 14:09:28</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4621</th>\n",
" <td>4622</td>\n",
" <td>132</td>\n",
" <td>O3501311</td>\n",
" <td>G3559799</td>\n",
" <td>Cashback Fachhandel | VP: 60 Euro Cashback</td>\n",
" <td>0.00000</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4622</th>\n",
" <td>4623</td>\n",
" <td>132</td>\n",
" <td>G3559799</td>\n",
" <td>G179</td>\n",
" <td>Vertriebspartneraktionen | Cashback Fachhandel</td>\n",
" <td>0.00000</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4623</th>\n",
" <td>4624</td>\n",
" <td>132</td>\n",
" <td>G3120771</td>\n",
" <td>G179</td>\n",
" <td>Vertriebspartneraktionen | Aktionsguthaben VP</td>\n",
" <td>0.00000</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4624</th>\n",
" <td>4625</td>\n",
" <td>132</td>\n",
" <td>O3132977</td>\n",
" <td>G3120771</td>\n",
" <td>Aktionsguthaben VP | 168 EUR Aktionsguthaben V...</td>\n",
" <td>0.00000</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4625</th>\n",
" <td>4626</td>\n",
" <td>132</td>\n",
" <td>O3120740</td>\n",
" <td>G3120771</td>\n",
" <td>Aktionsguthaben VP | 24 EUR Aktionsguthaben VP...</td>\n",
" <td>0.00000</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>None</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" <td>2025-04-16 14:26:22</td>\n",
" </tr>\n",
" </tbody>\n", " </tbody>\n",
"</table>\n", "</table>\n",
"<p>4626 rows × 10 columns</p>\n",
"</div>" "</div>"
], ],
"text/plain": [ "text/plain": [
" id_opti base_opti providercode_opti providercategory_opti \\\n", "Empty DataFrame\n",
"0 1 1 O3120756 G3120771 \n", "Columns: []\n",
"1 2 1 O3132971 G3120771 \n", "Index: []"
"2 3 1 O3132980 G3120771 \n",
"3 4 1 O1176 G18 \n",
"4 5 1 O1943 G60 \n",
"... ... ... ... ... \n",
"4621 4622 132 O3501311 G3559799 \n",
"4622 4623 132 G3559799 G179 \n",
"4623 4624 132 G3120771 G179 \n",
"4624 4625 132 O3132977 G3120771 \n",
"4625 4626 132 O3120740 G3120771 \n",
"\n",
" name_opti price_opti \\\n",
"0 Aktionsguthaben VP | 120 EUR Aktionsguthaben V... 0.00000 \n",
"1 Aktionsguthaben VP | 60 EUR Aktionsguthaben VP... 0.00000 \n",
"2 Aktionsguthaben VP | 192 EUR Aktionsguthaben V... 0.00000 \n",
"3 Kein EVN | EVN vollständig 0.00000 \n",
"4 T-Mobile@home / EUR 4,95 monatlich | Hausnummer 0.00000 \n",
"... ... ... \n",
"4621 Cashback Fachhandel | VP: 60 Euro Cashback 0.00000 \n",
"4622 Vertriebspartneraktionen | Cashback Fachhandel 0.00000 \n",
"4623 Vertriebspartneraktionen | Aktionsguthaben VP 0.00000 \n",
"4624 Aktionsguthaben VP | 168 EUR Aktionsguthaben V... 0.00000 \n",
"4625 Aktionsguthaben VP | 24 EUR Aktionsguthaben VP... 0.00000 \n",
"\n",
" starts_opti stops_opti created_opti updated_opti \n",
"0 2025-04-16 14:09:28 None 2025-04-16 14:09:28 2025-04-16 14:09:28 \n",
"1 2025-04-16 14:09:28 None 2025-04-16 14:09:28 2025-04-16 14:09:28 \n",
"2 2025-04-16 14:09:28 None 2025-04-16 14:09:28 2025-04-16 14:09:28 \n",
"3 2025-04-16 14:09:28 None 2025-04-16 14:09:28 2025-04-16 14:09:28 \n",
"4 2025-04-16 14:09:28 None 2025-04-16 14:09:28 2025-04-16 14:09:28 \n",
"... ... ... ... ... \n",
"4621 2025-04-16 14:26:22 None 2025-04-16 14:26:22 2025-04-16 14:26:22 \n",
"4622 2025-04-16 14:26:22 None 2025-04-16 14:26:22 2025-04-16 14:26:22 \n",
"4623 2025-04-16 14:26:22 None 2025-04-16 14:26:22 2025-04-16 14:26:22 \n",
"4624 2025-04-16 14:26:22 None 2025-04-16 14:26:22 2025-04-16 14:26:22 \n",
"4625 2025-04-16 14:26:22 None 2025-04-16 14:26:22 2025-04-16 14:26:22 \n",
"\n",
"[4626 rows x 10 columns]"
] ]
}, },
"metadata": {}, "metadata": {},
...@@ -644,309 +119,316 @@ ...@@ -644,309 +119,316 @@
} }
], ],
"source": [ "source": [
"# -*- coding: utf-8 -*-\n",
"\n",
"# Fügt das übergeordnete Verzeichnis zum Python-Pfad hinzu, um Modulimporte zu ermöglichen.\n",
"import sys; sys.path.append(\"..\")\n", "import sys; sys.path.append(\"..\")\n",
"\n",
"# Importiert das Modul zur Interaktion mit dem Betriebssystem, z.B. für Pfadoperationen.\n",
"import os\n", "import os\n",
"# Importiert das Modul zur Verarbeitung von CSV-Dateien.\n",
"import csv\n", "import csv\n",
"# Importiert das Modul zur Handhabung von Datum und Zeit.\n",
"import datetime\n", "import datetime\n",
"# Importiert die Pandas-Bibliothek für Datenanalyse und -manipulation, insbesondere DataFrames.\n",
"import pandas as pd\n", "import pandas as pd\n",
"# Importiert die Klasse Decimal für präzise Dezimalarithmetik bei Preisangaben.\n",
"from decimal import Decimal\n", "from decimal import Decimal\n",
"\n",
"# Importiert den benutzerdefinierten Manager zur Verwaltung von MySQL-Datenbankverbindungen.\n",
"from manager.MysqlManager import MysqlManager\n", "from manager.MysqlManager import MysqlManager\n",
"# Importiert die Basisklasse für SQLAlchemy-Modelle (Teil der Datenbankinteraktion).\n",
"from models._system import Base\n", "from models._system import Base\n",
"# Importiert das Datenmodell für die Tabelle 'base_base'.\n",
"from models.base_base import BaseBase\n", "from models.base_base import BaseBase\n",
"# Importiert das Datenmodell für die Tabelle 'deal_deal'.\n",
"from models.deal_deal import DealDeal\n", "from models.deal_deal import DealDeal\n",
"# Importiert das Datenmodell für die Tabelle 'option_opti'.\n",
"from models.option_opti import OptionOpti\n", "from models.option_opti import OptionOpti\n",
"\n", "\n",
"# Konstruiert den Dateipfad zur CSV-Datei, die die Tarifplandaten enthält.\n", "# Hier wird der Pfad zur CSV-Datei mit den Tarifplandaten festgelegt.\n",
"csvFilePath = os.path.join('..', 'cache', 'plans.csv')\n", "csvFilePath = os.path.join('..', 'cache', 'plans.csv')\n",
"\n", "\n",
"# Konstruiert den Dateipfad zur CSV-Datei, die die Kampagnendaten enthält.\n", "# Hier wird der Pfad zur CSV-Datei mit den Kampagnendaten festgelegt.\n",
"csvFileCampaigns = os.path.join('..', 'cache', 'campaigns.csv')\n", "csvFileCampaigns = os.path.join('..', 'cache', 'campaigns.csv')\n",
"\n", "\n",
"# Konstruiert den Dateipfad zur CSV-Datei, die die Optionsdaten enthält.\n", "# Hier wird der Pfad zur CSV-Datei mit den Optionsdaten festgelegt.\n",
"csvFileOptions = os.path.join('..', 'cache', 'options.csv')\n", "csvFileOptions = os.path.join('..', 'cache', 'options.csv')\n",
"\n", "\n",
"# Konstruiert den Dateipfad zur CSV-Datei, die die Kategoriedaten enthält.\n", "# Hier wird der Pfad zur CSV-Datei mit den Kategoriedaten festgelegt.\n",
"csvFileCategories = os.path.join('..', 'cache', 'categorys.csv')\n", "csvFileCategories = os.path.join('..', 'cache', 'categorys.csv')\n",
"\n", "\n",
"# Erstellt eine Instanz des MysqlManagers, um eine Verbindung zur Datenbank herzustellen.\n", "# Hier wird ein Objekt zur Verwaltung der MySQL-Verbindung erzeugt.\n",
"mysqlManager = MysqlManager()\n", "mysqlManager = MysqlManager()\n",
"\n", "\n",
"# Ruft eine neue Datenbank-Session vom MysqlManager ab.\n", "# Hier wird eine neue Datenbank-Session geöffnet, um Abfragen durchzuführen.\n",
"session = mysqlManager.getSession()\n", "dbSession = mysqlManager.getSession()\n",
"\n", "\n",
"# Initialisiert eine leere Liste zur Speicherung der IDs neu erstellter Einträge in 'base_base'.\n", "# Hier wird eine Liste gespeichert, welche die IDs neu erstellter Base-Einträge aufnimmt.\n",
"newBaseIds = []\n", "newBaseIds = []\n",
"# Initialisiert eine leere Liste zur Speicherung der IDs neu erstellter Einträge in 'deal_deal'.\n", "\n",
"# Hier wird eine Liste gespeichert, welche die IDs neu erstellter Deal-Einträge aufnimmt.\n",
"newDealIds = []\n", "newDealIds = []\n",
"# Initialisiert eine leere Liste zur Speicherung der IDs neu erstellter Einträge in 'option_opti'.\n", "\n",
"# Hier wird eine Liste gespeichert, welche die IDs neu erstellter Options-Einträge aufnimmt.\n",
"newOptionIds = []\n", "newOptionIds = []\n",
"\n", "\n",
"# Erstellt ein leeres Wörterbuch (Dictionary), um Kategorie-IDs auf ihre Namen abzubilden.\n", "# Hier wird ein Wörterbuch verwaltet, das Kategorie-IDs auf deren Namen abbildet.\n",
"categoryMap = {}\n", "categoryMap = {}\n",
"\n", "\n",
"# Öffnet die CSV-Datei mit den Kategoriedaten im Lesemodus mit UTF-8-Kodierung.\n", "# Hier wird die CSV-Datei mit Kategoriedaten geöffnet, um diese in das Wörterbuch zu laden.\n",
"with open(csvFileCategories, newline='', encoding='utf-8') as csvCat:\n", "with open(csvFileCategories, newline='', encoding='utf-8') as csvCat:\n",
" # Erstellt einen CSV-DictReader, der jede Zeile als Wörterbuch liest, getrennt durch Semikolon.\n", "\n",
" # Hier wird ein CSV-DictReader erzeugt, der den Inhalt zeilenweise als Wörterbuch interpretiert.\n",
" csvReaderCat = csv.DictReader(csvCat, delimiter=';')\n", " csvReaderCat = csv.DictReader(csvCat, delimiter=';')\n",
"\n", "\n",
" # Iteriert durch jede Zeile (als Wörterbuch repräsentiert) in der Kategorien-CSV-Datei.\n", " # Hier wird jede Zeile durchlaufen, um die Kategorie-ID und den Namen zu übernehmen.\n",
" for catRow in csvReaderCat:\n", " for catRow in csvReaderCat:\n",
"\n", "\n",
" # Extrahiert die Kategorie-ID aus der Spalte 'id', entfernt führende/nachfolgende Leerzeichen und weist sie 'catId' zu.\n", " # Hier wird die Kategorie-ID geholt und Leerzeichen werden entfernt.\n",
" catId = catRow.get('id', '').strip()\n", " catId = catRow.get('id', '').strip()\n",
" # Extrahiert den Kategorienamen aus der Spalte 'name', entfernt Leerzeichen und weist ihn 'catName' zu.\n", "\n",
" # Hier wird der Kategoriename geholt und Leerzeichen werden entfernt.\n",
" catName = catRow.get('name', '').strip()\n", " catName = catRow.get('name', '').strip()\n",
"\n", "\n",
" # Speichert den Kategorienamen im 'categoryMap' mit der Kategorie-ID als Schlüssel.\n", " # Hier wird das Mapping in das Wörterbuch geschrieben, wobei die ID der Schlüssel ist.\n",
" categoryMap[catId] = catName\n", " categoryMap[catId] = catName\n",
"\n", "\n",
"# Öffnet die CSV-Datei mit den Tarifplandaten im Lesemodus mit UTF-8-Kodierung.\n", "# Hier wird die CSV-Datei mit den Tarifplandaten geöffnet, um die Daten in die Datenbank zu übertragen.\n",
"with open(csvFilePath, newline='', encoding='utf-8') as csvFile:\n", "with open(csvFilePath, newline='', encoding='utf-8') as csvFile:\n",
" # Erstellt einen CSV-DictReader für die Tarifplandatei.\n", "\n",
" # Hier wird ein CSV-DictReader erzeugt, um die Tarifplandaten auszulesen.\n",
" csvReader = csv.DictReader(csvFile, delimiter=';')\n", " csvReader = csv.DictReader(csvFile, delimiter=';')\n",
"\n", "\n",
" # Iteriert durch jede Zeile (Tarifplan) in der CSV-Datei.\n", " # Hier werden die Tarifplandaten zeilenweise durchlaufen.\n",
" for row in csvReader:\n", " for row in csvReader:\n",
"\n", "\n",
" # Extrahiert die Plan-ID aus der Spalte 'id' und entfernt Leerzeichen.\n", " # Hier wird die Plan-ID aus der Zeile übernommen und Leerzeichen entfernt.\n",
" csvId = row.get('id', '').strip()\n", " csvId = row.get('id', '').strip()\n",
" # Extrahiert den Provider (Tarifwelt) aus der Spalte 'provider' und entfernt Leerzeichen.\n", "\n",
" provider = row.get('provider', '').strip()\n", " # Hier wird der Providername übernommen und Leerzeichen entfernt.\n",
" # Extrahiert das Netzwerk aus der Spalte 'network' und entfernt Leerzeichen.\n", " providerName = row.get('provider', '').strip()\n",
" network = row.get('network', '').strip()\n", "\n",
" # Extrahiert den Plannamen aus der Spalte 'name' und entfernt Leerzeichen.\n", " # Hier wird das Netzwerk übernommen und Leerzeichen entfernt.\n",
" networkName = row.get('network', '').strip()\n",
"\n",
" # Hier wird der Tarifname übernommen und Leerzeichen entfernt.\n",
" planName = row.get('name', '').strip()\n", " planName = row.get('name', '').strip()\n",
" # Extrahiert den Planpreis als String aus der Spalte 'price' und entfernt Leerzeichen.\n", "\n",
" # Hier wird der Tarifpreis als String übernommen und Leerzeichen entfernt.\n",
" planPriceStr = row.get('price', '').strip()\n", " planPriceStr = row.get('price', '').strip()\n",
"\n", "\n",
" # Kombiniert den festen String \"Freenet | \" mit dem extrahierten Providernamen.\n", " # Hier wird ein zusammengesetzter Providerwert erstellt.\n",
" providerBaseVal = \"Freenet | \" + provider\n", " providerBaseVal = \"Freenet | \" + providerName\n",
"\n", "\n",
" # Sucht in der Datenbanktabelle 'base_base' nach einem vorhandenen Eintrag, der mit dem Provider und der Plan-ID übereinstimmt.\n", " # Hier wird in der Tabelle 'base_base' nach einem bereits vorhandenen Datensatz gesucht, der passt.\n",
" existingBase = session.query(BaseBase).filter_by(\n", " existingBase = dbSession.query(BaseBase).filter_by(\n",
" provider_base=providerBaseVal,\n", " provider_base=providerBaseVal,\n",
" providercode_base=csvId\n", " providercode_base=csvId\n",
" ).first()\n", " ).first()\n",
"\n", "\n",
" # Ermittelt den numerischen Wert für das Netzwerk basierend auf dem extrahierten String.\n", " # Hier wird ein Netzwerk-Integerwert anhand des Netzwerknamens bestimmt.\n",
" # Wenn das Netzwerk 'D1' ist.\n", " if networkName == \"D1\":\n",
" if network == \"D1\":\n",
" # Setzt den Netzwerk-Wert auf 1.\n",
" networkVal = 1\n", " networkVal = 1\n",
" # Wenn das Netzwerk 'D2' ist.\n", " elif networkName == \"D2\":\n",
" elif network == \"D2\":\n",
" # Setzt den Netzwerk-Wert auf 2.\n",
" networkVal = 2\n", " networkVal = 2\n",
" # Wenn das Netzwerk 'O2' ist.\n", " elif networkName == \"O2\":\n",
" elif network == \"O2\":\n",
" # Setzt den Netzwerk-Wert auf 4.\n",
" networkVal = 4\n", " networkVal = 4\n",
" # Für alle anderen oder unbekannten Netzwerke.\n",
" else:\n", " else:\n",
" # Setzt den Netzwerk-Wert auf 0.\n",
" networkVal = 0\n", " networkVal = 0\n",
"\n", "\n",
" # Ermittelt den aktuellen Zeitstempel (Datum und Uhrzeit).\n", " # Hier wird die aktuelle Uhrzeit bestimmt.\n",
" currentTime = datetime.datetime.now()\n", " currentTime = datetime.datetime.now()\n",
"\n", "\n",
" # Prüft, ob bereits ein passender Eintrag in 'base_base' gefunden wurde.\n", " # Hier wird geprüft, ob bereits ein passender Eintrag existiert.\n",
" if existingBase:\n", " if existingBase:\n",
" # Verwendet den gefundenen Datenbankeintrag für spätere Referenzen.\n", "\n",
" # Hier wird der vorhandene Eintrag genutzt, falls er existiert.\n",
" baseRecord = existingBase\n", " baseRecord = existingBase\n",
" # Wenn kein passender Eintrag gefunden wurde.\n", "\n",
" else:\n", " else:\n",
"\n", "\n",
" # Erstellt ein neues BaseBase-Objekt (neuer Datenbankeintrag) mit den Daten aus der CSV.\n", " # Hier wird ein neuer Eintrag in 'base_base' erzeugt, da kein passender vorhanden war.\n",
" baseRecord = BaseBase(\n", " baseRecord = BaseBase(\n",
" provider_base=providerBaseVal, # Zusammengesetzter Providername\n", " provider_base=providerBaseVal,\n",
" providercode_base=csvId, # Plan-ID aus CSV\n", " providercode_base=csvId,\n",
" basegroup_base=1, # Fester Gruppenwert\n", " basegroup_base=1,\n",
" name_base=planName, # Planname aus CSV\n", " name_base=planName,\n",
" network_base=networkVal, # Ermittelter Netzwerk-Wert\n", " network_base=networkVal,\n",
" type_base=0, # Fester Typwert\n", " type_base=0,\n",
" created_base=currentTime, # Zeitstempel der Erstellung\n", " created_base=currentTime,\n",
" updated_base=currentTime # Zeitstempel der letzten Aktualisierung\n", " updated_base=currentTime\n",
" )\n", " )\n",
" # Fügt das neue Objekt zur Datenbank-Session hinzu.\n",
" session.add(baseRecord)\n",
" # Schreibt die Änderungen (neuer Eintrag) in die Datenbank.\n",
" session.commit()\n",
"\n", "\n",
" # Fügt die ID des neu erstellten Base-Eintrags zur Liste 'newBaseIds' hinzu.\n", " # Hier wird das neue Objekt der Session hinzugefügt.\n",
" dbSession.add(baseRecord)\n",
"\n",
" # Hier wird das Objekt in der Datenbank gespeichert.\n",
" dbSession.commit()\n",
"\n",
" # Hier wird die ID des neuen Eintrags gemerkt.\n",
" newBaseIds.append(baseRecord.id_base)\n", " newBaseIds.append(baseRecord.id_base)\n",
"\n", "\n",
" # Definiert eine verschachtelte Funktion zum Erstellen oder Aktualisieren von Deal-Einträgen für den aktuellen Base-Eintrag.\n", " # Hier wird eine Funktion definiert, um Deals zu erstellen oder zu überprüfen.\n",
" def upsertDeal(providerCodeDeal, dealName=\"\"):\n", " def upsertDeal(providerCodeDeal, dealName=\"\"):\n",
"\n", "\n",
" # Sucht in der Datenbanktabelle 'deal_deal' nach einem vorhandenen Deal, der zum aktuellen Base-Eintrag und dem Deal-Providercode passt.\n", " # Hier wird geprüft, ob bereits ein Deal mit dem angegebenen Providercode vorhanden ist.\n",
" existingDeal = session.query(DealDeal).filter_by(\n", " existingDeal = dbSession.query(DealDeal).filter_by(\n",
" base_deal=baseRecord.id_base, # Verknüpfung zum Base-Eintrag\n", " base_deal=baseRecord.id_base,\n",
" providercode_deal=providerCodeDeal # Eindeutiger Code des Deals (kann leer sein)\n", " providercode_deal=providerCodeDeal\n",
" ).first()\n", " ).first()\n",
"\n", "\n",
" # Prüft, ob bereits ein passender Deal existiert.\n", " # Hier wird unterschieden, ob ein Deal gefunden wurde.\n",
" if existingDeal:\n", " if existingDeal:\n",
" # Wenn ja, sind keine weiteren Aktionen für diesen Deal nötig.\n", "\n",
" # Hier ist nichts weiter zu tun, wenn er bereits existiert.\n",
" pass\n", " pass\n",
" # Wenn kein passender Deal existiert.\n", "\n",
" else:\n", " else:\n",
"\n", "\n",
" # Erstellt ein neues DealDeal-Objekt mit den Daten des Deals.\n", " # Hier wird ein neuer Deal erstellt, wenn er noch nicht vorhanden ist.\n",
" newDeal = DealDeal(\n", " newDeal = DealDeal(\n",
" provisiongroup_deal=1, # Fester Gruppenwert\n", " provisiongroup_deal=1,\n",
" base_deal=baseRecord.id_base, # Verknüpfung zum Base-Eintrag\n", " base_deal=baseRecord.id_base,\n",
" providercode_deal=providerCodeDeal,# Providercode des Deals\n", " providercode_deal=providerCodeDeal,\n",
" name_deal=dealName, # Name des Deals (Kampagnenname oder leer)\n", " name_deal=dealName,\n",
" # Konvertiert den Planpreis-String in Decimal, oder setzt 0.00 falls leer.\n",
" price_deal=Decimal(planPriceStr) if planPriceStr else Decimal(\"0.00\"),\n", " price_deal=Decimal(planPriceStr) if planPriceStr else Decimal(\"0.00\"),\n",
" starts_deal=currentTime, # Startzeitpunkt des Deals\n", " starts_deal=currentTime,\n",
" stops_deal=None, # Endzeitpunkt (offen)\n", " stops_deal=None,\n",
" created_deal=currentTime, # Zeitstempel der Erstellung\n", " created_deal=currentTime,\n",
" updated_deal=currentTime # Zeitstempel der letzten Aktualisierung\n", " updated_deal=currentTime\n",
" )\n", " )\n",
" # Fügt das neue Deal-Objekt zur Datenbank-Session hinzu.\n", "\n",
" session.add(newDeal)\n", " # Hier wird das neue Deal-Objekt der Session hinzugefügt.\n",
" # Schreibt den neuen Deal in die Datenbank.\n", " dbSession.add(newDeal)\n",
" session.commit()\n", "\n",
" # Fügt die ID des neuen Deals zur Liste 'newDealIds' hinzu.\n", " # Hier wird das Objekt in der Datenbank gespeichert.\n",
" dbSession.commit()\n",
"\n",
" # Hier wird die ID des neuen Deals gemerkt.\n",
" newDealIds.append(newDeal.id_deal)\n", " newDealIds.append(newDeal.id_deal)\n",
"\n", "\n",
" # Ruft die Funktion `upsertDeal` auf, um einen Basis-Deal ohne spezifischen Providercode oder Namen anzulegen (Standard-Deal).\n", " # Hier wird ein Standard-Deal ohne spezifischen Providercode angelegt.\n",
" upsertDeal(\"\")\n", " upsertDeal(\"\")\n",
"\n", "\n",
" # Öffnet die CSV-Datei mit den Kampagnendaten im Lesemodus mit UTF-8-Kodierung.\n", " # Hier wird die CSV-Datei mit den Kampagnendaten geöffnet, um passende Deals zu erstellen.\n",
" with open(csvFileCampaigns, newline='', encoding='utf-8') as csvFile2:\n", " with open(csvFileCampaigns, newline='', encoding='utf-8') as csvFile2:\n",
" # Erstellt einen CSV-DictReader für die Kampagnendatei.\n", "\n",
" # Hier wird ein CSV-DictReader erzeugt, um die Kampagnen zeilenweise auszulesen.\n",
" csvReader2 = csv.DictReader(csvFile2, delimiter=';')\n", " csvReader2 = csv.DictReader(csvFile2, delimiter=';')\n",
"\n", "\n",
" # Iteriert durch jede Zeile (Kampagne) in der Kampagnen-CSV-Datei.\n", " # Hier werden alle Kampagnen durchlaufen.\n",
" for row2 in csvReader2:\n", " for row2 in csvReader2:\n",
"\n", "\n",
" # Extrahiert die Kampagnen-ID und entfernt Leerzeichen.\n", " # Hier wird die Kampagnen-ID ermittelt und Leerzeichen entfernt.\n",
" campaignId = row2.get('id', '').strip()\n", " campaignId = row2.get('id', '').strip()\n",
" # Extrahiert die zugehörige Plan-ID und entfernt Leerzeichen.\n", "\n",
" # Hier wird die zugehörige Tarif-ID ermittelt und Leerzeichen entfernt.\n",
" campaignPlan = row2.get('plan', '').strip()\n", " campaignPlan = row2.get('plan', '').strip()\n",
" # Extrahiert den Kampagnennamen und entfernt Leerzeichen.\n", "\n",
" # Hier wird der Kampagnenname ermittelt und Leerzeichen entfernt.\n",
" campaignName = row2.get('name', '').strip()\n", " campaignName = row2.get('name', '').strip()\n",
"\n", "\n",
" # Prüft, ob die Plan-ID der Kampagne mit der ID des aktuellen Tarifs übereinstimmt.\n", " # Hier wird geprüft, ob die Kampagne zum aktuellen Tarif gehört.\n",
" if campaignPlan == csvId:\n", " if campaignPlan == csvId:\n",
"\n", "\n",
" # Ruft `upsertDeal` auf, um einen spezifischen Deal für diese Kampagne anzulegen.\n", " # Hier wird ein spezifischer Deal für die Kampagne erzeugt.\n",
" # Der Providercode wird mit \"A\" präfixiert, der Kampagnenname wird übergeben.\n",
" upsertDeal(\"A\" + campaignId, campaignName)\n", " upsertDeal(\"A\" + campaignId, campaignName)\n",
"\n", "\n",
" # Definiert eine verschachtelte Funktion zum Erstellen oder Aktualisieren von Options-Einträgen für den aktuellen Base-Eintrag.\n", " # Hier wird eine Funktion definiert, um Optionen zu erstellen oder zu überprüfen.\n",
" def upsertOption(providerCodeOpti, categoryOpti, optionName, optionPriceStr):\n", " def upsertOption(providerCodeOpti, categoryOpti, optionName, optionPriceStr):\n",
"\n", "\n",
" # Sucht in der Datenbanktabelle 'option_opti' nach einer vorhandenen Option, die zum aktuellen Base-Eintrag und Options-Providercode passt.\n", " # Hier wird geprüft, ob bereits eine Option mit dem angegebenen Providercode vorhanden ist.\n",
" existingOpti = session.query(OptionOpti).filter_by(\n", " existingOpti = dbSession.query(OptionOpti).filter_by(\n",
" base_opti=baseRecord.id_base, # Verknüpfung zum Base-Eintrag\n", " base_opti=baseRecord.id_base,\n",
" providercode_opti=providerCodeOpti # Eindeutiger Code der Option\n", " providercode_opti=providerCodeOpti\n",
" ).first()\n", " ).first()\n",
"\n", "\n",
" # Prüft, ob bereits eine passende Option existiert.\n", " # Hier wird unterschieden, ob eine Option gefunden wurde.\n",
" if existingOpti:\n", " if existingOpti:\n",
" # Wenn ja, sind keine weiteren Aktionen nötig.\n", "\n",
" # Hier ist nichts weiter zu tun, wenn sie bereits existiert.\n",
" pass\n", " pass\n",
" # Wenn keine passende Option existiert.\n", "\n",
" else:\n", " else:\n",
"\n", "\n",
" # Holt den vollständigen Kategorienamen aus dem zuvor erstellten 'categoryMap' anhand der Kategorie-ID.\n", " # Hier wird der Kategoriename aus dem Wörterbuch ermittelt und ggf. ein Standard genommen.\n",
" # Falls die ID nicht gefunden wird, wird \"Unbekannt\" als Standard verwendet.\n",
" categoryFullName = categoryMap.get(categoryOpti, \"Unbekannt\")\n", " categoryFullName = categoryMap.get(categoryOpti, \"Unbekannt\")\n",
" # Kombiniert den Kategorienamen und den Optionsnamen zu einem vollständigen Namen.\n", "\n",
" # Hier wird ein vollständiger Name aus Kategorie und Optionsname generiert.\n",
" fullName = categoryFullName + \" | \" + optionName\n", " fullName = categoryFullName + \" | \" + optionName\n",
"\n", "\n",
" # Erstellt ein neues OptionOpti-Objekt mit den Daten der Option.\n", " # Hier wird eine neue Option erzeugt.\n",
" newOpti = OptionOpti(\n", " newOpti = OptionOpti(\n",
" provisiongroup_opti=1, # Fester Gruppenwert\n", " provisiongroup_opti=1,\n",
" base_opti=baseRecord.id_base, # Verknüpfung zum Base-Eintrag\n", " base_opti=baseRecord.id_base,\n",
" providercode_opti=providerCodeOpti, # Providercode der Option\n", " providercode_opti=providerCodeOpti,\n",
" providercategory_opti=categoryOpti, # Provider-Kategorie-ID\n", " providercategory_opti=categoryOpti,\n",
" name_opti=fullName, # Zusammengesetzter Name\n", " name_opti=fullName,\n",
" alias_opti=None, # Alias (nicht verwendet)\n", " alias_opti=None,\n",
" # Konvertiert den Optionspreis-String in Decimal, oder setzt 0.00 falls leer.\n",
" price_opti=Decimal(optionPriceStr) if optionPriceStr else Decimal(\"0.00\"),\n", " price_opti=Decimal(optionPriceStr) if optionPriceStr else Decimal(\"0.00\"),\n",
" starts_opti=currentTime, # Startzeitpunkt der Option\n", " starts_opti=currentTime,\n",
" stops_opti=None, # Endzeitpunkt (offen)\n", " stops_opti=None,\n",
" provision1_opti=Decimal(\"0.00000\"), # Provisionsfelder (Standardwert)\n", " provision1_opti=Decimal(\"0.00000\"),\n",
" provision2_opti=Decimal(\"0.00000\"),\n", " provision2_opti=Decimal(\"0.00000\"),\n",
" provision3_opti=Decimal(\"0.00000\"),\n", " provision3_opti=Decimal(\"0.00000\"),\n",
" provision4_opti=Decimal(\"0.00000\"),\n", " provision4_opti=Decimal(\"0.00000\"),\n",
" created_opti=currentTime, # Zeitstempel der Erstellung\n", " created_opti=currentTime,\n",
" updated_opti=currentTime # Zeitstempel der letzten Aktualisierung\n", " updated_opti=currentTime\n",
" )\n", " )\n",
" # Fügt das neue Option-Objekt zur Datenbank-Session hinzu.\n", "\n",
" session.add(newOpti)\n", " # Hier wird das neue Option-Objekt der Session hinzugefügt.\n",
" # Schreibt die neue Option in die Datenbank.\n", " dbSession.add(newOpti)\n",
" session.commit()\n", "\n",
" # Fügt die ID der neuen Option zur Liste 'newOptionIds' hinzu.\n", " # Hier wird das Objekt in der Datenbank gespeichert.\n",
" dbSession.commit()\n",
"\n",
" # Hier wird die ID der neuen Option gemerkt.\n",
" newOptionIds.append(newOpti.id_opti)\n", " newOptionIds.append(newOpti.id_opti)\n",
"\n", "\n",
" # Öffnet die CSV-Datei mit den Optionsdaten im Lesemodus mit UTF-8-Kodierung.\n", " # Hier wird die CSV-Datei mit den Optionsdaten geöffnet, um passende Optionen zu erstellen.\n",
" with open(csvFileOptions, newline='', encoding='utf-8') as csvFileOpt:\n", " with open(csvFileOptions, newline='', encoding='utf-8') as csvFileOpt:\n",
" # Erstellt einen CSV-DictReader für die Optionsdatei.\n", "\n",
" # Hier wird ein CSV-DictReader erzeugt, um die Optionsdaten zeilenweise auszulesen.\n",
" csvReaderOpt = csv.DictReader(csvFileOpt, delimiter=';')\n", " csvReaderOpt = csv.DictReader(csvFileOpt, delimiter=';')\n",
"\n", "\n",
" # Iteriert durch jede Zeile (Option) in der Options-CSV-Datei.\n", " # Hier werden alle Optionsdaten durchlaufen.\n",
" for rowOpt in csvReaderOpt:\n", " for rowOpt in csvReaderOpt:\n",
"\n", "\n",
" # Extrahiert die Options-ID und entfernt Leerzeichen.\n", " # Hier wird die Options-ID ermittelt und Leerzeichen entfernt.\n",
" optId = rowOpt.get('id', '').strip()\n", " optId = rowOpt.get('id', '').strip()\n",
" # Extrahiert die zugehörige Plan-ID und entfernt Leerzeichen.\n", "\n",
" # Hier wird die zugehörige Plan-ID ermittelt und Leerzeichen entfernt.\n",
" optPlan = rowOpt.get('plan', '').strip()\n", " optPlan = rowOpt.get('plan', '').strip()\n",
" # Extrahiert die zugehörige Kategorie-ID und entfernt Leerzeichen.\n", "\n",
" # Hier wird die Kategorie-ID ermittelt und Leerzeichen entfernt.\n",
" optCategory = rowOpt.get('category', '').strip()\n", " optCategory = rowOpt.get('category', '').strip()\n",
" # Extrahiert den Optionsnamen und entfernt Leerzeichen.\n", "\n",
" # Hier wird der Optionsname ermittelt und Leerzeichen entfernt.\n",
" optName = rowOpt.get('name', '').strip()\n", " optName = rowOpt.get('name', '').strip()\n",
" # Extrahiert den Optionspreis als String und entfernt Leerzeichen.\n", "\n",
" # Hier wird der Optionspreis als String ermittelt und Leerzeichen entfernt.\n",
" optPriceStr = rowOpt.get('price', '').strip()\n", " optPriceStr = rowOpt.get('price', '').strip()\n",
"\n", "\n",
" # Prüft, ob die Plan-ID der Option mit der ID des aktuellen Tarifs übereinstimmt.\n", " # Hier wird geprüft, ob die Option zum aktuellen Tarif gehört.\n",
" if optPlan == csvId:\n", " if optPlan == csvId:\n",
"\n", "\n",
" # Ruft `upsertOption` auf, um die Option für den aktuellen Tarif in der Datenbank anzulegen oder zu prüfen.\n", " # Hier wird eine neue Option in der Datenbank angelegt oder überprüft.\n",
" upsertOption(optId, optCategory, optName, optPriceStr)\n", " upsertOption(optId, optCategory, optName, optPriceStr)\n",
"\n", "\n",
"# Initialisiert eine leere Liste für die neu angelegten Base-Datensätze.\n", "# Hier wird eine leere Liste für neu angelegte Base-Datensätze definiert.\n",
"newBases = []\n", "newBases = []\n",
"# Prüft, ob IDs für neue Base-Einträge gesammelt wurden.\n", "\n",
"# Hier wird geprüft, ob neue Base-Einträge angelegt wurden.\n",
"if newBaseIds:\n", "if newBaseIds:\n",
" # Frägt die Datenbank ab, um die vollständigen Datensätze für die neuen Base-IDs zu erhalten.\n",
" newBases = session.query(BaseBase).filter(BaseBase.id_base.in_(newBaseIds)).all()\n",
"\n", "\n",
"# Initialisiert eine leere Liste für die neu angelegten Deal-Datensätze.\n", " # Hier werden alle neu angelegten Base-Einträge aus der Datenbank gelesen.\n",
" newBases = dbSession.query(BaseBase).filter(BaseBase.id_base.in_(newBaseIds)).all()\n",
"\n",
"# Hier wird eine leere Liste für neu angelegte Deal-Datensätze definiert.\n",
"newDeals = []\n", "newDeals = []\n",
"# Prüft, ob IDs für neue Deal-Einträge gesammelt wurden.\n", "\n",
"# Hier wird geprüft, ob neue Deal-Einträge angelegt wurden.\n",
"if newDealIds:\n", "if newDealIds:\n",
" # Frägt die Datenbank ab, um die vollständigen Datensätze für die neuen Deal-IDs zu erhalten.\n",
" newDeals = session.query(DealDeal).filter(DealDeal.id_deal.in_(newDealIds)).all()\n",
"\n", "\n",
"# Initialisiert eine leere Liste für die neu angelegten Options-Datensätze.\n", " # Hier werden alle neu angelegten Deals aus der Datenbank gelesen.\n",
" newDeals = dbSession.query(DealDeal).filter(DealDeal.id_deal.in_(newDealIds)).all()\n",
"\n",
"# Hier wird eine leere Liste für neu angelegte Options-Datensätze definiert.\n",
"newOptions = []\n", "newOptions = []\n",
"# Prüft, ob IDs für neue Options-Einträge gesammelt wurden.\n", "\n",
"# Hier wird geprüft, ob neue Options-Einträge angelegt wurden.\n",
"if newOptionIds:\n", "if newOptionIds:\n",
" # Frägt die Datenbank ab, um die vollständigen Datensätze für die neuen Options-IDs zu erhalten.\n",
" newOptions = session.query(OptionOpti).filter(OptionOpti.id_opti.in_(newOptionIds)).all()\n",
"\n", "\n",
"# Bereitet eine Liste von Wörterbüchern für die Erstellung des Base-DataFrames vor.\n", " # Hier werden alle neu angelegten Options-Datensätze aus der Datenbank gelesen.\n",
"# Jedes Wörterbuch repräsentiert eine Zeile mit ausgewählten Spalten des Base-Objekts.\n", " newOptions = dbSession.query(OptionOpti).filter(OptionOpti.id_opti.in_(newOptionIds)).all()\n",
"\n",
"# Hier wird eine Liste von Wörterbüchern erstellt, um sie in ein Pandas DataFrame für Base zu überführen.\n",
"baseData = [{\n", "baseData = [{\n",
" 'id_base': b.id_base,\n", " 'id_base': b.id_base,\n",
" 'provider_base': b.provider_base,\n", " 'provider_base': b.provider_base,\n",
...@@ -955,10 +437,9 @@ ...@@ -955,10 +437,9 @@
" 'network_base': b.network_base,\n", " 'network_base': b.network_base,\n",
" 'created_base': b.created_base,\n", " 'created_base': b.created_base,\n",
" 'updated_base': b.updated_base\n", " 'updated_base': b.updated_base\n",
"} for b in newBases] # List Comprehension iteriert durch die Liste der neuen Base-Objekte.\n", "} for b in newBases]\n",
"\n", "\n",
"# Bereitet eine Liste von Wörterbüchern für die Erstellung des Deal-DataFrames vor.\n", "# Hier wird eine Liste von Wörterbüchern erstellt, um sie in ein Pandas DataFrame für Deals zu überführen.\n",
"# Jedes Wörterbuch repräsentiert eine Zeile mit ausgewählten Spalten des Deal-Objekts.\n",
"dealData = [{\n", "dealData = [{\n",
" 'id_deal': d.id_deal,\n", " 'id_deal': d.id_deal,\n",
" 'base_deal': d.base_deal,\n", " 'base_deal': d.base_deal,\n",
...@@ -969,10 +450,9 @@ ...@@ -969,10 +450,9 @@
" 'stops_deal': d.stops_deal,\n", " 'stops_deal': d.stops_deal,\n",
" 'created_deal': d.created_deal,\n", " 'created_deal': d.created_deal,\n",
" 'updated_deal': d.updated_deal\n", " 'updated_deal': d.updated_deal\n",
"} for d in newDeals] # List Comprehension iteriert durch die Liste der neuen Deal-Objekte.\n", "} for d in newDeals]\n",
"\n", "\n",
"# Bereitet eine Liste von Wörterbüchern für die Erstellung des Options-DataFrames vor.\n", "# Hier wird eine Liste von Wörterbüchern erstellt, um sie in ein Pandas DataFrame für Optionen zu überführen.\n",
"# Jedes Wörterbuch repräsentiert eine Zeile mit ausgewählten Spalten des Option-Objekts.\n",
"optiData = [{\n", "optiData = [{\n",
" 'id_opti': o.id_opti,\n", " 'id_opti': o.id_opti,\n",
" 'base_opti': o.base_opti,\n", " 'base_opti': o.base_opti,\n",
...@@ -984,28 +464,28 @@ ...@@ -984,28 +464,28 @@
" 'stops_opti': o.stops_opti,\n", " 'stops_opti': o.stops_opti,\n",
" 'created_opti': o.created_opti,\n", " 'created_opti': o.created_opti,\n",
" 'updated_opti': o.updated_opti\n", " 'updated_opti': o.updated_opti\n",
"} for o in newOptions] # List Comprehension iteriert durch die Liste der neuen Option-Objekte.\n", "} for o in newOptions]\n",
"\n", "\n",
"# Erstellt ein Pandas DataFrame aus der Liste der Base-Daten.\n", "# Hier wird ein DataFrame für die neu angelegten Base-Einträge erzeugt.\n",
"dfBase = pd.DataFrame(baseData)\n", "dfBase = pd.DataFrame(baseData)\n",
"\n", "\n",
"# Erstellt ein Pandas DataFrame aus der Liste der Deal-Daten.\n", "# Hier wird ein DataFrame für die neu angelegten Deal-Einträge erzeugt.\n",
"dfDeal = pd.DataFrame(dealData)\n", "dfDeal = pd.DataFrame(dealData)\n",
"\n", "\n",
"# Erstellt ein Pandas DataFrame aus der Liste der Options-Daten.\n", "# Hier wird ein DataFrame für die neu angelegten Options-Einträge erzeugt.\n",
"dfOpti = pd.DataFrame(optiData)\n", "dfOpti = pd.DataFrame(optiData)\n",
"\n", "\n",
"# Gibt das DataFrame mit den neu angelegten Base-Einträgen aus (typischerweise in Jupyter Notebooks oder ähnlichen Umgebungen sichtbar).\n", "# Hier wird das DataFrame der Base-Einträge angezeigt.\n",
"display(dfBase)\n", "display(dfBase)\n",
"\n", "\n",
"# Gibt das DataFrame mit den neu angelegten Deal-Einträgen aus.\n", "# Hier wird das DataFrame der Deal-Einträge angezeigt.\n",
"display(dfDeal)\n", "display(dfDeal)\n",
"\n", "\n",
"# Gibt das DataFrame mit den neu angelegten Options-Einträgen aus.\n", "# Hier wird das DataFrame der Options-Einträge angezeigt.\n",
"display(dfOpti)\n", "display(dfOpti)\n",
"\n", "\n",
"# Schließt die Datenbank-Session, um die Verbindung freizugeben.\n", "# Hier wird die Datenbank-Session geschlossen, um Ressourcen freizugeben.\n",
"session.close()" "dbSession.close()\n"
] ]
}, },
{ {
......
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "b627c9fc-c8e6-4a16-b445-de08d76222bb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"--- Verarbeitung ID: 3243715 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3243715_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3243715_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3332926 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3332926_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3332926_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3337091 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3337091_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3337091_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3337095 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3337095_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3337095_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3380690 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3380690_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3380690_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3385056 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3385056_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3385056_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3385059 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3385059_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3385059_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3387917 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3387917_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3387917_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3388975 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3388975_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3388975_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3389002 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3389002_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3389002_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3398803 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3398803_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3398803_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3398807 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3398807_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3398807_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3398811 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3398811_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3398811_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3398815 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3398815_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3398815_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3415206 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3415206_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3415206_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3430251 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3430251_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3430251_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3435067 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3435067_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3435067_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3435071 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3435071_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3435071_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3435075 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3435075_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3435075_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3435079 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3435079_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3435079_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3446028 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3446028_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3446028_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3448928 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3448928_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3448928_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3461557 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3461557_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3461557_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3461561 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3461561_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3461561_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3461565 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3461565_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3461565_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3461569 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3461569_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3461569_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3473445 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3473445_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3473445_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3476568 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3476568_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3476568_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3596218 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3596218_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3596218_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3649995 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3649995_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3649995_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3650007 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3650007_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3650007_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3650019 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3650019_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3650019_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3650031 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3650031_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3650031_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3697996 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3697996_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3697996_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3711205 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3711205_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3711205_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3711328 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3711328_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3711328_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3743289 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3743289_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3743289_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3743301 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3743301_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3743301_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3750083 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3750083_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3750083_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3750710 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3750710_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3750710_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3750758 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3750758_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3750758_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3750806 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3750806_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3750806_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3765006 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3765006_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3765006_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3782151 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3782151_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3782151_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3782322 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3782322_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3782322_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3782325 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3782325_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3782325_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3782328 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3782328_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3782328_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3782331 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3782331_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3782331_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833020 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833020_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833020_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833026 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833026_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833026_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833773 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833773_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833773_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833776 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833776_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833776_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833953 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833953_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833953_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833956 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833956_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833956_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833959 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833959_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833959_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833962 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833962_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833962_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833965 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833965_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833965_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833968 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833968_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833968_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833971 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833971_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833971_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833974 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833974_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833974_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833977 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833977_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833977_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833983 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833983_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833983_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833986 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833986_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833986_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833989 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833989_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833989_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833995 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833995_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833995_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3833998 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3833998_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3833998_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3834001 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3834001_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3834001_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3861200 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3861200_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3861200_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3861206 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3861206_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3861206_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3877175 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3877175_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3877175_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3877193 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3877193_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3877193_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3877208 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3877208_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3877208_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3877211 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3877211_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3877211_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3877289 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3877289_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3877289_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3877301 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3877301_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3877301_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3877313 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3877313_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3877313_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3877325 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3877325_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3877325_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3877337 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3877337_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3877337_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3877349 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3877349_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3877349_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3878165 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3878165_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3878165_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3878177 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3878177_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3878177_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3878189 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3878189_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3878189_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3878213 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3878213_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3878213_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3878225 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3878225_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3878225_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3878237 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3878237_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3878237_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3878261 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3878261_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3878261_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3878273 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3878273_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3878273_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3878285 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3878285_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3878285_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3905336 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3905336_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3905336_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3905348 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3905348_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3905348_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3905360 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3905360_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3905360_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3907523 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3907523_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3907523_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3907559 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3907559_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3907559_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3907595 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3907595_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3907595_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3907631 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3907631_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3907631_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3935061 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3935061_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3935061_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3936264 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3936264_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3936264_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3936300 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3936300_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3936300_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3936336 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3936336_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3936336_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3936372 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3936372_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3936372_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3936408 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3936408_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3936408_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973250 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973250_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973250_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973253 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973253_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973253_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973256 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973256_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973256_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973259 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973259_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973259_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973262 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973262_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973262_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973265 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973265_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973265_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973277 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973277_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973277_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973280 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973280_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973280_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973283 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973283_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973283_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973286 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973286_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973286_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973289 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973289_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973289_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973292 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973292_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973292_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973295 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973295_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973295_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973298 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973298_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973298_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3973301 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3973301_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3973301_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3974111 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3974111_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3974111_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3975845 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3975845_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3975845_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3975848 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3975848_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3975848_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3975851 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3975851_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3975851_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3975854 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3975854_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3975854_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3975860 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3975860_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3975860_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3975866 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3975866_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3975866_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3975869 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3975869_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3975869_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3975872 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3975872_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3975872_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3975875 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3975875_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3975875_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 3975881 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/3975881_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/3975881_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 4069662 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/4069662_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/4069662_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 4069698 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/4069698_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/4069698_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 4069734 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/4069734_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/4069734_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 4069770 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/4069770_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/4069770_pib.pdf\n",
"\n",
"--- Verarbeitung ID: 4069806 ---\n",
"INFO: flyerurl_base gesetzt: https://freenetflyer.s3.amazonaws.com/flyers/4069806_flyer.pdf\n",
"INFO: piburl_base gesetzt: https://freenetflyer.s3.amazonaws.com/pibs/4069806_pib.pdf\n",
"INFO: Upload-Vorgang abgeschlossen.\n"
]
}
],
"source": [
"import sys; sys.path.append(\"..\")\n",
"import os\n",
"from manager.S3Manager import S3Manager\n",
"from manager.MysqlManager import MysqlManager\n",
"from models._system import Base\n",
"from models.base_base import BaseBase\n",
"from models.deal_deal import DealDeal\n",
"from models.option_opti import OptionOpti\n",
"\n",
"# In dieser Variablen wird das Cache-Verzeichnis festgelegt.\n",
"cacheDir = \"../cache\"\n",
"\n",
"# In dieser Variablen wird ein S3Manager-Objekt angelegt, um Dateien auf S3 hochzuladen.\n",
"s3Manager = S3Manager()\n",
"\n",
"# In dieser Variablen wird eine Datenbank-Session vorbereitet, um auf die Datenbank zuzugreifen.\n",
"dbSession = MysqlManager().getSession()\n",
"\n",
"# In dieser Liste werden alle Dateien aus dem Cache-Verzeichnis gesammelt, die auf \".pdf\" enden.\n",
"pdfFiles = [fileName for fileName in os.listdir(cacheDir) if fileName.lower().endswith(\".pdf\")]\n",
"\n",
"# In dieser Menge werden IDs aus den PDF-Dateinamen abgelegt, die einen bestimmten Namenszusatz tragen.\n",
"pdfIdSet = set()\n",
"\n",
"# In dieser Schleife wird jede gefundene PDF-Datei verarbeitet, um anhand des Namens die ID zu extrahieren.\n",
"for fileName in pdfFiles:\n",
"\n",
" # In dieser Variablen wird der Dateiname ohne \".pdf\" ermittelt und in Kleinbuchstaben konvertiert.\n",
" nameStem = fileName[:-4].lower()\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob der Dateiname mit \"_flyer\" endet.\n",
" if nameStem.endswith(\"_flyer\"):\n",
"\n",
" # In dieser Menge wird der Teil ohne \"_flyer\" hinterlegt.\n",
" pdfIdSet.add(nameStem[:-6])\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob der Dateiname mit \"_pib\" endet.\n",
" elif nameStem.endswith(\"_pib\"):\n",
"\n",
" # In dieser Menge wird der Teil ohne \"_pib\" hinterlegt.\n",
" pdfIdSet.add(nameStem[:-4])\n",
"\n",
"# In dieser Abzweigung wird überprüft, ob überhaupt passende PDFs im Verzeichnis gefunden wurden.\n",
"if not pdfIdSet:\n",
"\n",
" # Hier wird eine Meldung ausgegeben, dass keine PDF-Paare gefunden wurden.\n",
" print(f\"INFO: Keine PDF-Paare in '{cacheDir}' gefunden.\")\n",
"\n",
" # Hier wird die Datenbank-Session geschlossen.\n",
" dbSession.close()\n",
"\n",
" # Hier wird das Skript beendet.\n",
" sys.exit(0)\n",
"\n",
"# In dieser Schleife wird über alle gefundenen IDs iteriert und jeweils der Upload-Vorgang durchgeführt.\n",
"for currentId in sorted(pdfIdSet):\n",
"\n",
" # Hier wird eine Meldung ausgegeben, dass die Verarbeitung für diese ID startet.\n",
" print(f\"\\n--- Verarbeitung ID: {currentId} ---\")\n",
"\n",
" # In dieser Variablen wird versucht, einen passenden BaseBase-Eintrag aus der Datenbank zu erhalten.\n",
" baseRecord = dbSession.query(BaseBase).filter_by(providercode_base=currentId).one_or_none()\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob überhaupt ein passender Datensatz vorhanden ist.\n",
" if baseRecord is None:\n",
"\n",
" # Hier wird eine Warnung ausgegeben, wenn kein BaseBase-Eintrag existiert.\n",
" print(f\"WARNUNG: Kein BaseBase-Eintrag für providercode_base='{currentId}'.\")\n",
" continue\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob das Feld flyerurl_base noch nicht belegt ist.\n",
" if baseRecord.flyerurl_base is None:\n",
"\n",
" # In dieser Variablen wird der Dateipfad für den Flyer ermittelt.\n",
" flyerFile = os.path.join(cacheDir, f\"{currentId}_flyer.pdf\")\n",
"\n",
" # In dieser Abzweigung wird überprüft, ob die Flyer-Datei existiert.\n",
" if os.path.exists(flyerFile):\n",
"\n",
" # In dieser Variablen wird der gewünschte Schlüssel für den Upload festgelegt.\n",
" flyerKey = f\"flyers/{currentId}_flyer.pdf\"\n",
"\n",
" # Hier wird der Upload mithilfe des S3-Managers durchgeführt und die URL ermittelt.\n",
" flyerUrl = s3Manager.upload_file(flyerFile, flyerKey)\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob der Upload erfolgreich war und eine URL vorliegt.\n",
" if flyerUrl:\n",
"\n",
" # Hier wird die URL in das Feld flyerurl_base eingetragen.\n",
" baseRecord.flyerurl_base = flyerUrl\n",
"\n",
" # Hier wird das Aktualisierungsdatum neu gesetzt.\n",
" baseRecord.updated_base = __import__('datetime').datetime.now()\n",
"\n",
" # Hier wird der Eintrag in der Datenbank gespeichert.\n",
" dbSession.commit()\n",
"\n",
" # Hier wird eine Meldung ausgegeben, dass das Feld erfolgreich gesetzt wurde.\n",
" print(f\"INFO: flyerurl_base gesetzt: {flyerUrl}\")\n",
"\n",
" else:\n",
"\n",
" # Hier wird eine Fehlermeldung ausgegeben, falls der Upload fehlschlägt.\n",
" print(f\"FEHLER: Flyer-Upload fehlgeschlagen für ID {currentId}\")\n",
"\n",
" else:\n",
"\n",
" # Hier wird eine Meldung ausgegeben, wenn keine passende Flyer-Datei gefunden wurde.\n",
" print(f\"INFO: Flyer-Datei nicht gefunden: {flyerFile}\")\n",
"\n",
" else:\n",
"\n",
" # Hier wird eine Meldung ausgegeben, wenn bereits eine flyerurl_base vorhanden ist.\n",
" print(f\"INFO: flyerurl_base bereits vorhanden: {baseRecord.flyerurl_base}\")\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob das Feld piburl_base noch nicht belegt ist.\n",
" if baseRecord.piburl_base is None:\n",
"\n",
" # In dieser Variablen wird der Dateipfad für das PIB ermittelt.\n",
" pibFile = os.path.join(cacheDir, f\"{currentId}_pib.pdf\")\n",
"\n",
" # In dieser Abzweigung wird überprüft, ob die PIB-Datei existiert.\n",
" if os.path.exists(pibFile):\n",
"\n",
" # In dieser Variablen wird der gewünschte Schlüssel für den Upload festgelegt.\n",
" pibKey = f\"pibs/{currentId}_pib.pdf\"\n",
"\n",
" # Hier wird der Upload mithilfe des S3-Managers durchgeführt und die URL ermittelt.\n",
" pibUrl = s3Manager.upload_file(pibFile, pibKey)\n",
"\n",
" # In dieser Abzweigung wird geprüft, ob der Upload erfolgreich war und eine URL vorliegt.\n",
" if pibUrl:\n",
"\n",
" # Hier wird die URL in das Feld piburl_base eingetragen.\n",
" baseRecord.piburl_base = pibUrl\n",
"\n",
" # Hier wird das Aktualisierungsdatum neu gesetzt.\n",
" baseRecord.updated_base = __import__('datetime').datetime.now()\n",
"\n",
" # Hier wird der Eintrag in der Datenbank gespeichert.\n",
" dbSession.commit()\n",
"\n",
" # Hier wird eine Meldung ausgegeben, dass das Feld erfolgreich gesetzt wurde.\n",
" print(f\"INFO: piburl_base gesetzt: {pibUrl}\")\n",
"\n",
" else:\n",
"\n",
" # Hier wird eine Fehlermeldung ausgegeben, falls der Upload fehlschlägt.\n",
" print(f\"FEHLER: PIB-Upload fehlgeschlagen für ID {currentId}\")\n",
"\n",
" else:\n",
"\n",
" # Hier wird eine Meldung ausgegeben, wenn keine passende PIB-Datei gefunden wurde.\n",
" print(f\"INFO: PIB-Datei nicht gefunden: {pibFile}\")\n",
"\n",
" else:\n",
"\n",
" # Hier wird eine Meldung ausgegeben, wenn bereits eine piburl_base vorhanden ist.\n",
" print(f\"INFO: piburl_base bereits vorhanden: {baseRecord.piburl_base}\")\n",
"\n",
"# Hier wird die Datenbank-Session geschlossen, nachdem alle Upload-Vorgänge abgeschlossen sind.\n",
"dbSession.close()\n",
"\n",
"# Hier wird eine Abschlussmeldung ausgegeben, dass der Upload-Vorgang beendet ist.\n",
"print(\"INFO: Upload-Vorgang abgeschlossen.\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "22f1060a-c69d-402c-95ee-420fd70d4cff",
"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
}
**Aufgabe:**
Du bist eine hochpräzise API zur Extraktion spezifischer Mobilfunktarif-Merkmale aus Dokumentenpaaren. Deine Eingabe besteht immer aus zwei PDF-Dateien: einem **Produktdetailblatt/Flyer** und einem **Produktinformationsblatt (PIB)**, die gemeinsam *einen* spezifischen Tarif beschreiben.
Deine Aufgabe ist es, beide Dokumente sorgfältig und vergleichend zu analysieren, um die unten definierten **relevanten Tarifbestandteile** mit höchstmöglicher Genauigkeit zu extrahieren. Das Ergebnis **muss ausschließlich** ein einzelnes JSON-Objekt sein, das exakt die vorgegebenen Schlüsselnamen und das flache Key-Value-Format verwendet. Ignoriere irrelevante Informationen wie Anbieteradressen, AGB-Verweise oder allgemeine Marketingtexte.
**Extraktionsanweisungen und Felddefinitionen:**
1. **`tariff_name` (String):** Extrahiere den vollständigen, exakten Namen des Tarifs.
2. **`marketing_start_date` (String oder null):** Extrahiere das Vermarktungsdatum (aus PIB). Formatiere als `JJJJ-MM-TT` oder `null`.
3. **`network_operator` (String oder null):** Identifiziere den Netzbetreiber (z.B. "Telekom", "Vodafone", "O2").
4. **`network_technology` (String oder null):** Identifiziere die primär beworbene/höchste Netztechnologie (z.B. "5G", "LTE").
5. **`is_data_only_tariff` (Boolean):** Ermittle präzise, ob es ein reiner Datentarif ist (`true`/`false`). Ein Kriterium ist, wenn Telefonie nicht als Flat inkludiert ist oder explizit als "nicht möglich" gilt. Achte darauf, Tarife mit "Data" im Namen, die dennoch Telefonie/SMS-Flats haben könnten, nicht fälschlicherweise als reine Datentarife zu klassifizieren. Prüfe die Inklusivleistungen und Preise sorgfältig.
6. **`inclusive_internet_flat` (Boolean oder null):** Prüfe auf explizite Nennung einer Internet-Flat (`true`/`false`).
7. **`inclusive_telephony_flat` (Boolean oder null):** Prüfe auf explizite Nennung einer Telefonie-Flat oder Preis pro Minute von 0,00 € (`true`/`false`). Bei `is_data_only_tariff: true` setze auf `false`.
8. **`telephony_price_per_minute_eur_brutto` (Number oder null):** Wenn `inclusive_telephony_flat: false` und Telefonie möglich ist, extrahiere den Brutto-Preis pro Minute. Sonst `0.0` (bei Flat) oder `null` (wenn nicht möglich/gefunden).
9. **`telephony_price_per_minute_eur_netto` (Number oder null):** Wenn `telephony_price_per_minute_eur_brutto` ein Wert größer als 0 ist, berechne den Nettopreis (Bruttopreis / 1.19). Sonst `0.0` (bei Flat) oder `null`. Runde auf 4 Nachkommastellen.
10. **`inclusive_sms_flat` (Boolean oder null):** Prüfe auf explizite Nennung einer SMS-Flat oder Preis pro SMS von 0,00 € (`true`/`false`).
11. **`sms_price_per_unit_eur_brutto` (Number oder null):** Wenn `inclusive_sms_flat: false`, extrahiere den Brutto-Preis pro SMS. Sonst `0.0` (bei Flat) oder `null` (wenn nicht gefunden).
12. **`sms_price_per_unit_eur_netto` (Number oder null):** Wenn `sms_price_per_unit_eur_brutto` ein Wert größer als 0 ist, berechne den Nettopreis (Bruttopreis / 1.19). Sonst `0.0` (bei Flat) oder `null`. Runde auf 4 Nachkommastellen.
13. **`inclusive_volte_wlan_call` (Boolean oder null):** Prüfe auf explizite Nennung von VoLTE/WLAN-Call. Setze `true`, wenn erwähnt und `is_data_only_tariff: false`. Sonst `false`.
14. **`data_volume_gb` (Number oder null):** Extrahiere das Datenvolumen in GB.
15. **`data_download_max_mbps` (Number oder null):** Extrahiere max. Download in Mbit/s.
16. **`data_upload_max_mbps` (Number oder null):** Extrahiere max. Upload in Mbit/s.
17. **`data_download_throttled_kbps` (Number oder null):** Extrahiere Drossel-Download in kbit/s.
18. **`data_upload_throttled_kbps` (Number oder null):** Extrahiere Drossel-Upload in kbit/s.
19. **`data_billing_increment_kb` (Number oder null):** Extrahiere Datentaktung in KB.
20. **`telephony_billing_increment_seconds` (String oder null):** Extrahiere Telefontaktung (z.B. "60/60"). Setze `null` bei Datentarifen.
21. **`pricing_connection_fee_eur_brutto` (Number oder null):** Extrahiere den einmaligen Anschlusspreis (Brutto).
22. **`pricing_connection_fee_eur_netto` (Number oder null):** Berechne den Netto-Anschlusspreis (Bruttopreis / 1.19), falls Brutto vorhanden. Runde auf 4 Nachkommastellen.
23. **`pricing_monthly_initial_eur_brutto` (Number oder null):** Extrahiere den monatlichen Bruttopreis (initiale Periode).
24. **`pricing_monthly_initial_eur_netto` (Number oder null):** Extrahiere den monatlichen Nettopreis (initiale Periode), falls explizit genannt (oft in Klammern). Falls nicht genannt, berechne ihn (Bruttopreis / 1.19). Runde auf 4 Nachkommastellen.
25. **`pricing_monthly_after_period_eur_brutto` (Number oder null):** Extrahiere den monatlichen Bruttopreis nach der initialen Periode.
26. **`pricing_monthly_after_period_eur_netto` (Number oder null):** Berechne den monatlichen Nettopreis nach der initialen Periode (Bruttopreis / 1.19). Runde auf 4 Nachkommastellen.
27. **`contract_min_duration_months` (Number oder null):** Extrahiere die Mindestlaufzeit in Monaten.
28. **`contract_cancellation_notice_period_months` (Number oder null):** Extrahiere die Kündigungsfrist als Zahl der Monate (z.B. `1` aus "1 Monat").
**Wichtige Hinweise:**
* Verwende `null` für Werte, die nicht zuverlässig extrahiert werden können.
* Stelle sicher, dass numerische Werte als Zahlen (Number) im JSON erscheinen.
* Runde berechnete Nettopreise auf 4 Nachkommastellen.
* Die Ausgabe darf **nur das JSON-Objekt** enthalten, ohne jeglichen erläuternden Text davor oder danach.
**Gewünschtes JSON-Ausgabeformat (zur Referenz):**
{
"tariff_name": "...",
"marketing_start_date": "...",
"network_operator": "...",
"network_technology": "...",
"is_data_only_tariff": ...,
"inclusive_internet_flat": ...,
"inclusive_telephony_flat": ...,
"telephony_price_per_minute_eur_brutto": ...,
"telephony_price_per_minute_eur_netto": ...,
"inclusive_sms_flat": ...,
"sms_price_per_unit_eur_brutto": ...,
"sms_price_per_unit_eur_netto": ...,
"inclusive_volte_wlan_call": ...,
"data_volume_gb": ...,
"data_download_max_mbps": ...,
"data_upload_max_mbps": ...,
"data_download_throttled_kbps": ...,
"data_upload_throttled_kbps": ...,
"data_billing_increment_kb": ...,
"telephony_billing_increment_seconds": "...",
"pricing_connection_fee_eur_brutto": ...,
"pricing_connection_fee_eur_netto": ...,
"pricing_monthly_initial_eur_brutto": ...,
"pricing_monthly_initial_eur_netto": ...,
"pricing_monthly_after_period_eur_brutto": ...,
"pricing_monthly_after_period_eur_netto": ...,
"contract_min_duration_months": ...,
"contract_cancellation_notice_period_months": ...
}
\ No newline at end of file
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