diff --git a/delfland/ppo-insight/Dockerfile b/delfland/ppo-insight/Dockerfile new file mode 100644 index 0000000..54a5937 --- /dev/null +++ b/delfland/ppo-insight/Dockerfile @@ -0,0 +1,36 @@ +# Gebruik een officiële Python-image als basis +FROM python:3.9-slim + +RUN apt-get update && apt-get -y install cron nano procps libmariadb-dev +# Werkdirectory instellen +WORKDIR /app + +# Kopieer je Python-bestanden naar de container +COPY . /app + +# Kopieer de requirements.txt naar de container +COPY requirements.txt . +# Kopieer de requirements.txt naar de container +COPY .env . + +# Installeer de vereiste Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Installeer cron en zorg ervoor dat de cron-demon actief is +RUN apt-get update && apt-get install -y cron + +# Voeg het cron-script toe +COPY cronfile.energie /etc/cron.d/cronfile.energie +# Zorg ervoor dat het cron-script uitvoerbaar is +RUN chmod 0644 /etc/cron.d/cronfile.energie + +# Voeg een cronjob toe die de Python-script dagelijks uitvoert +RUN crontab /etc/cron.d/cronfile.energie + + +# Voeg start.sh toe +COPY start.sh /start.sh +RUN chmod +x /start.sh + +# Gebruik start.sh om zowel cron als currentprice.py te starten +CMD ["/start.sh"] \ No newline at end of file diff --git a/delfland/ppo-insight/database.py b/delfland/ppo-insight/database.py new file mode 100644 index 0000000..fb30e3a --- /dev/null +++ b/delfland/ppo-insight/database.py @@ -0,0 +1,18 @@ +import mariadb +import os +from dotenv import load_dotenv + +# Configuratie laden uit .env-bestand +load_dotenv() + + +def get_db_connection(): + """Maakt een verbinding met de MariaDB-database en retourneert de cursor en verbinding.""" + conn = mariadb.connect( + host=os.getenv("DB_HOST"), + user=os.getenv("DB_USER"), + password=os.getenv("DB_PASSWORD"), + database=os.getenv("DB_NAME"), + port=int(os.getenv("DB_PORT")) + ) + return conn, conn.cursor() diff --git a/delfland/ppo-insight/docker-compose.yml b/delfland/ppo-insight/docker-compose.yml new file mode 100644 index 0000000..4aa4dc9 --- /dev/null +++ b/delfland/ppo-insight/docker-compose.yml @@ -0,0 +1,15 @@ +# docker compose -f energyprices_docker-compose.yml up -d --build + +version: "3.3" +services: + delflanddev: + restart: always + container_name: delflanddev + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/Amsterdam + build: + dockerfile: ./Dockerfile + env_file: + - .env diff --git a/delfland/ppo-insight/ppo_insight.py b/delfland/ppo-insight/ppo_insight.py new file mode 100644 index 0000000..9fe3639 --- /dev/null +++ b/delfland/ppo-insight/ppo_insight.py @@ -0,0 +1,226 @@ +import os +import pandas as pd +from flask import Flask, request, render_template, redirect, url_for, flash +from werkzeug.utils import secure_filename +from dotenv import load_dotenv +from database import get_db_connection +from flask import Response +import io +from datetime import datetime + +# Configuratie laden +env_path = "./.env" +load_dotenv(env_path) + +UPLOAD_FOLDER = os.getenv("UPLOAD_FOLDER") +ALLOWED_EXTENSIONS = {"xlsx"} + +app = Flask(__name__) +app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER +app.secret_key = "supersecretkey" + + +# Controleer of bestand toegestaan is +def allowed_file(filename): + return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS + + +# Route voor de uploadpagina +@app.route("/", methods=["GET", "POST"]) +def upload_file(): + if request.method == "POST": + if "file" not in request.files: + flash("Geen bestand geselecteerd", "danger") + return redirect(request.url) + + file = request.files["file"] + if file.filename == "": + flash("Geen bestand geselecteerd", "danger") + return redirect(request.url) + + if file and allowed_file(file.filename): + filename = secure_filename(file.filename) + filepath = os.path.join(app.config["UPLOAD_FOLDER"], filename) + file.save(filepath) + flash("Bestand geüpload en verwerkt! Bekijk de resultaten", "success") + process_excel(filepath) + return redirect(url_for("upload_file")) + + return render_template("index.html") + + +@app.route("/extrapolate", methods=["GET", "POST"]) +def show_result(): + x = request.form.get("x", 1, type=int) + result = getextrapolation(x) + + # Geef de resultaten door aan het HTML-template + return render_template("resultaat.html", result=result, x=x) + + +@app.route("/download", methods=["GET"]) +def download_excel(): + x = request.args.get("x", 1, type=int) + result = getextrapolation(x) + + df = pd.DataFrame(result) + + # Excel-bestand in-memory genereren + output = io.BytesIO() + with pd.ExcelWriter(output, engine="openpyxl") as writer: + df.to_excel(writer, index=False, sheet_name=f"Extrapolatie_{x}jaar") + + output.seek(0) + + # Dynamische bestandsnaam + filename = f"extrapolatie_{x}jaar.xlsx" + + return Response( + output, + mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + headers={"Content-Disposition": f"attachment; filename={filename}"} + ) + + +def getextrapolation(extrapolateYears): + conn, cursor = get_db_connection() + + # Haal de originele data op zonder extrapolatie + cursor.execute(""" + SELECT + Object_code, + Object_omschrijving, + Afdeling_object, + PO_Code, + Omschrijving_PO, + Vervaldatum, + Frequentie, + UOM, + PO_schema_Niet_gebruikt, + Cluster, + Locatie, + Locatie_omschrijving, + Klasse_object, + Categorie + FROM onderhoud + """) + + # Haal de kolomnamen op + columns = [desc[0] for desc in cursor.description] + + # Haal de ruwe data op + rows = cursor.fetchall() + + # Sluit de cursor en de verbinding + cursor.close() + conn.close() + + raw_data = [dict(zip(columns, row)) for row in rows] + + # Huidige datum als pd.Timestamp (zonder tijd) + today = pd.Timestamp.today().normalize() + + result = [] + for row in raw_data: + vervaldatum = row.get("Vervaldatum") + + # Converteer de Vervaldatum correct naar pd.Timestamp (of None bij fouten) + vervaldatum = pd.to_datetime(vervaldatum, errors="coerce") + + # Controleer of de datum geldig is en in het verleden ligt + if pd.notna(vervaldatum) and vervaldatum < today: + vervaldatum = today # Zet op vandaag + + for i in range(extrapolateYears + 1): + new_row = row.copy() + + if pd.notna(vervaldatum): # Controleer of het een geldige datum is + new_row["Vervaldatum"] = ( + vervaldatum + pd.DateOffset(years=i)).strftime("%Y-%m-%d") + else: + # Indien geen geldige datum, zet als None + new_row["Vervaldatum"] = None + + result.append(new_row) + + return result + + +def process_exceld(filepath): + df = pd.read_excel(filepath, engine="openpyxl") + print(df.columns) + df.columns = df.columns.str.strip() + # for _, row in df.iterrows(): + # print(row["Object_omschrijving"]) + + +# Verwerk het Excel-bestand en sla op in MariaDB +def process_excel(filepath): + df = pd.read_excel(filepath, engine="openpyxl") + df.columns = df.columns.str.strip() + # Databaseverbinding maken + conn, cursor = get_db_connection() + + # Zorg dat de tabel bestaat + cursor.execute( + """ + CREATE TABLE IF NOT EXISTS onderhoud ( + id INT AUTO_INCREMENT PRIMARY KEY, + Object_code VARCHAR(50), + Object_omschrijving TEXT, + Afdeling_object VARCHAR(100), + PO_Code VARCHAR(50), + Omschrijving_PO TEXT, + Vervaldatum DATE, + Frequentie INT, + UOM TEXT, + PO_schema_Niet_gebruikt TEXT, + Cluster TEXT, + Locatie TEXT, + Locatie_omschrijving TEXT, + Klasse_object TEXT, + Categorie TEXT + ) + """ + ) + + # Data in de database invoegen + for _, row in df.iterrows(): + for column in row.index: + if pd.isna(row[column]): + row[column] = "" + + cursor.execute( + """ + INSERT INTO onderhoud + (Object_code, Object_omschrijving, Afdeling_object, PO_Code, Omschrijving_PO, Vervaldatum, Frequentie, + UOM, PO_schema_Niet_gebruikt, Cluster, Locatie, Locatie_omschrijving, Klasse_object, Categorie) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + row["Object code"], + row["Object omschrijving"], + row["Afdeling object"], + row["PO Code"], + row["Omschrijving PO"], + row["Vervaldatum"], + row["Frequentie"], + row["UOM"], + row["PO-schema Niet gebruikt"], + row["Cluster"], + row["Locatie"], + row["Locatie omschrijving"], + row["Klasse object"], + row["Categorie"], + ), + ) + + conn.commit() + cursor.close() + conn.close() + + +if __name__ == "__main__": + os.makedirs(UPLOAD_FOLDER, exist_ok=True) + app.run(debug=True) diff --git a/delfland/ppo-insight/requirements.txt b/delfland/ppo-insight/requirements.txt new file mode 100644 index 0000000..1e81f8d --- /dev/null +++ b/delfland/ppo-insight/requirements.txt @@ -0,0 +1,6 @@ +flask +python-dotenv +pandas +mariadb +openpyxl +werkzeug diff --git a/delfland/ppo-insight/start.sh b/delfland/ppo-insight/start.sh new file mode 100644 index 0000000..7e3056c --- /dev/null +++ b/delfland/ppo-insight/start.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Start de cron-demon +cron + +# Start het Python-script currentprice.py +python /app/ppo_insight.py & + +# Wacht voor altijd zodat de container draaiende blijft +tail -f /dev/null diff --git a/delfland/ppo-insight/templates/index.html b/delfland/ppo-insight/templates/index.html new file mode 100644 index 0000000..f20eccd --- /dev/null +++ b/delfland/ppo-insight/templates/index.html @@ -0,0 +1,45 @@ + + + + + + Bestand Uploaden + + + + +
+

Upload een bestand

+ + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + + {% endif %} + {% endwith %} + + +
+
+ + +
+ +
+
+ + + + + + + diff --git a/delfland/ppo-insight/templates/resultaat.html b/delfland/ppo-insight/templates/resultaat.html new file mode 100644 index 0000000..b4c93c7 --- /dev/null +++ b/delfland/ppo-insight/templates/resultaat.html @@ -0,0 +1,88 @@ + + + + + + Resultaat + + + + + + + +
+

Resultaten van de query

+ + + Terug naar upload + + +
+
+ + + +
+
+ + + Download als Excel ({{ x }} jaar) + + + + + + + + + + + + + + + + + + + + + + + + {% for row in result %} + + + + + + + + + + + + + + + + + {% endfor %} + +
Object_codeObject_omschrijvingAfdeling_objectPO_CodeOmschrijving_POVervaldatumFrequentieUOMPO_schema_Niet_gebruiktClusterLocatieLocatie_omschrijvingKlasse_objectCategorie
{{ row['Object_code'] }}{{ row['Object_omschrijving'] }}{{ row['Afdeling_object'] }}{{ row['PO_Code'] }}{{ row['Omschrijving_PO'] }}{{ row['Vervaldatum'] }}{{ row['Frequentie'] }}{{ row['UOM'] }}{{ row['PO_schema_Niet_gebruikt'] }}{{ row['Cluster'] }}{{ row['Locatie'] }}{{ row['Locatie_omschrijving'] }}{{ row['Klasse_object'] }}{{ row['Categorie'] }}
+ +
+ + + + + + + diff --git a/delfland/ppo-insight/uploads/Preventief_onderhoud_WK_dump.xlsx b/delfland/ppo-insight/uploads/Preventief_onderhoud_WK_dump.xlsx new file mode 100644 index 0000000..91d41f2 Binary files /dev/null and b/delfland/ppo-insight/uploads/Preventief_onderhoud_WK_dump.xlsx differ