Formatierung durch black, extract-Funktion bereinigt
parent
77d169633e
commit
fd8bfa3952
|
|
@ -5,8 +5,6 @@ from dotenv import load_dotenv
|
||||||
from controller import register_routes
|
from controller import register_routes
|
||||||
from model.database import init_db
|
from model.database import init_db
|
||||||
from controller.socketIO import socketio
|
from controller.socketIO import socketio
|
||||||
from controller.kennzahlen import kennzahlen_bp
|
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
@ -23,15 +21,11 @@ init_db(app)
|
||||||
register_routes(app)
|
register_routes(app)
|
||||||
|
|
||||||
|
|
||||||
# Register blueprints
|
|
||||||
app.register_blueprint(kennzahlen_bp)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/health")
|
@app.route("/health")
|
||||||
def health_check():
|
def health_check():
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
# für Docker wichtig: host='0.0.0.0'
|
# Für Docker wichtig: host='0.0.0.0'
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
socketio.run(app, debug=True, host="0.0.0.0", port=5050)
|
socketio.run(app, debug=True, host="0.0.0.0", port=5050)
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,36 @@ from werkzeug.utils import secure_filename
|
||||||
from model.database import db
|
from model.database import db
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
spacy_controller = Blueprint("spacy", __name__, url_prefix="/api/spacy")
|
spacy_controller = Blueprint("spacy", __name__, url_prefix="/api/spacy")
|
||||||
|
|
||||||
|
SPACY_TRAINING_URL = os.getenv("SPACY_TRAINING_URL", "http://spacy:5052/train")
|
||||||
|
training_running_flag_path = os.path.join("spacy_training", "training_running.json")
|
||||||
|
|
||||||
|
|
||||||
|
@spacy_controller.route("/train", methods=["POST"])
|
||||||
|
def trigger_training():
|
||||||
|
try:
|
||||||
|
with open(training_running_flag_path, "w") as f:
|
||||||
|
json.dump({"running": True}, f)
|
||||||
|
|
||||||
|
response = requests.post(SPACY_TRAINING_URL, timeout=600)
|
||||||
|
if response.ok:
|
||||||
|
return jsonify({"message": "Training erfolgreich angestoßen."}), 200
|
||||||
|
else:
|
||||||
|
return (
|
||||||
|
jsonify({"error": "Training fehlgeschlagen", "details": response.text}),
|
||||||
|
500,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return (
|
||||||
|
jsonify(
|
||||||
|
{"error": "Fehler beim Senden an Trainingsservice", "details": str(e)}
|
||||||
|
),
|
||||||
|
500,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@spacy_controller.route("/", methods=["GET"])
|
@spacy_controller.route("/", methods=["GET"])
|
||||||
def get_all_files():
|
def get_all_files():
|
||||||
|
|
@ -33,7 +59,6 @@ def download_file(id):
|
||||||
|
|
||||||
@spacy_controller.route("/", methods=["POST"])
|
@spacy_controller.route("/", methods=["POST"])
|
||||||
def upload_file():
|
def upload_file():
|
||||||
print(request)
|
|
||||||
if "file" not in request.files:
|
if "file" not in request.files:
|
||||||
return jsonify({"error": "No file part in the request"}), 400
|
return jsonify({"error": "No file part in the request"}), 400
|
||||||
|
|
||||||
|
|
@ -41,17 +66,13 @@ def upload_file():
|
||||||
if uploaded_file.filename == "":
|
if uploaded_file.filename == "":
|
||||||
return jsonify({"error": "No selected file"}), 400
|
return jsonify({"error": "No selected file"}), 400
|
||||||
|
|
||||||
# Read file data once
|
|
||||||
file_data = uploaded_file.read()
|
file_data = uploaded_file.read()
|
||||||
try:
|
try:
|
||||||
if uploaded_file:
|
fileName = uploaded_file.filename or ""
|
||||||
fileName = uploaded_file.filename or ""
|
new_file = SpacyModel(filename=secure_filename(fileName), file=file_data)
|
||||||
new_file = SpacyModel(filename=secure_filename(fileName), file=file_data)
|
db.session.add(new_file)
|
||||||
|
db.session.commit()
|
||||||
db.session.add(new_file)
|
return jsonify(new_file.to_dict()), 201
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return jsonify(new_file.to_dict()), 201
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return jsonify({"error": "Invalid file format. Only PDF files are accepted"}), 400
|
return jsonify({"error": "Invalid file format. Only PDF files are accepted"}), 400
|
||||||
|
|
@ -65,14 +86,9 @@ def update_file(id):
|
||||||
uploaded_file = request.files["file"]
|
uploaded_file = request.files["file"]
|
||||||
if uploaded_file.filename != "":
|
if uploaded_file.filename != "":
|
||||||
file.filename = uploaded_file.filename
|
file.filename = uploaded_file.filename
|
||||||
|
|
||||||
# Read file data once
|
|
||||||
file_data = uploaded_file.read()
|
file_data = uploaded_file.read()
|
||||||
try:
|
try:
|
||||||
if (
|
if puremagic.from_string(file_data, mime=True) == "application/pdf":
|
||||||
uploaded_file
|
|
||||||
and puremagic.from_string(file_data, mime=True) == "application/pdf"
|
|
||||||
):
|
|
||||||
file.file = file_data
|
file.file = file_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
@ -81,7 +97,6 @@ def update_file(id):
|
||||||
file.kpi = request.form.get("kpi")
|
file.kpi = request.form.get("kpi")
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify(file.to_dict()), 200
|
return jsonify(file.to_dict()), 200
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -90,7 +105,6 @@ def delete_file(id):
|
||||||
file = SpacyModel.query.get_or_404(id)
|
file = SpacyModel.query.get_or_404(id)
|
||||||
db.session.delete(file)
|
db.session.delete(file)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify({"message": f"File {id} deleted successfully"}), 200
|
return jsonify({"message": f"File {id} deleted successfully"}), 200
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -110,7 +124,6 @@ def append_training_entry():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
@ -128,3 +141,16 @@ def append_training_entry():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] Fehler beim Schreiben: {e}")
|
print(f"[ERROR] Fehler beim Schreiben: {e}")
|
||||||
return jsonify({"error": "Interner Fehler beim Schreiben."}), 500
|
return jsonify({"error": "Interner Fehler beim Schreiben."}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@spacy_controller.route("/train-status", methods=["GET"])
|
||||||
|
def training_status():
|
||||||
|
try:
|
||||||
|
if os.path.exists(training_running_flag_path):
|
||||||
|
with open(training_running_flag_path, "r") as f:
|
||||||
|
status = json.load(f)
|
||||||
|
return jsonify(status), 200
|
||||||
|
else:
|
||||||
|
return jsonify({"running": False}), 200
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": "Fehler beim Statuscheck", "details": str(e)}), 500
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . /app
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
CMD ["python", "extractExxeta.py"]
|
||||||
|
|
@ -11,6 +11,8 @@ COPY requirements.txt /app
|
||||||
|
|
||||||
RUN pip install --upgrade pip
|
RUN pip install --upgrade pip
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
RUN pip install flask-cors
|
||||||
|
|
||||||
|
|
||||||
RUN python -m spacy download en_core_web_sm
|
RUN python -m spacy download en_core_web_sm
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
from flask import Flask, request, jsonify
|
from flask import Flask, request, jsonify
|
||||||
from extractSpacy import extract
|
from extractSpacy import extract, load_model
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
training_status = {"running": False}
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
@ -66,7 +71,7 @@ def append_training_entry():
|
||||||
else:
|
else:
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
# Optional: Duplikate prüfen
|
# Duplikate prüfen
|
||||||
if entry in data:
|
if entry in data:
|
||||||
return jsonify({"message": "Eintrag existiert bereits."}), 200
|
return jsonify({"message": "Eintrag existiert bereits."}), 200
|
||||||
|
|
||||||
|
|
@ -80,5 +85,59 @@ def append_training_entry():
|
||||||
return jsonify({"error": "Interner Fehler beim Schreiben."}), 500
|
return jsonify({"error": "Interner Fehler beim Schreiben."}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/train", methods=["POST"])
|
||||||
|
def trigger_training():
|
||||||
|
from threading import Thread
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
def run_training():
|
||||||
|
training_status["running"] = True
|
||||||
|
try:
|
||||||
|
if os.path.exists("output/model-last"):
|
||||||
|
shutil.copytree(
|
||||||
|
"output/model-last", "output/model-backup", dirs_exist_ok=True
|
||||||
|
)
|
||||||
|
subprocess.run(["python", "spacy_training/ner_trainer.py"], check=True)
|
||||||
|
load_model()
|
||||||
|
except Exception as e:
|
||||||
|
print("Training failed:", e)
|
||||||
|
training_status["running"] = False
|
||||||
|
|
||||||
|
Thread(target=run_training).start()
|
||||||
|
return jsonify({"message": "Training gestartet"}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/train-status", methods=["GET"])
|
||||||
|
def get_training_status():
|
||||||
|
return jsonify(training_status), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/reload-model", methods=["POST"])
|
||||||
|
def reload_model():
|
||||||
|
try:
|
||||||
|
load_model()
|
||||||
|
return jsonify({"message": "Modell wurde erfolgreich neu geladen."}), 200
|
||||||
|
except Exception as e:
|
||||||
|
return (
|
||||||
|
jsonify({"error": "Fehler beim Neuladen des Modells", "details": str(e)}),
|
||||||
|
500,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_training():
|
||||||
|
training_status["running"] = True
|
||||||
|
try:
|
||||||
|
if os.path.exists("output/model-last"):
|
||||||
|
shutil.copytree(
|
||||||
|
"output/model-last", "output/model-backup", dirs_exist_ok=True
|
||||||
|
)
|
||||||
|
subprocess.run(["python", "spacy_training/ner_trainer.py"], check=True)
|
||||||
|
load_model() # ⬅ Modell nach dem Training direkt neu laden
|
||||||
|
except Exception as e:
|
||||||
|
print("Training failed:", e)
|
||||||
|
training_status["running"] = False
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=5052, debug=True)
|
app.run(host="0.0.0.0", port=5052, debug=True)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,25 @@ import spacy
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
model_path = os.path.join(current_dir, "spacy_training/output/model-last")
|
model_path = os.path.join(current_dir, "spacy_training/output/model-last")
|
||||||
nlp = spacy.load(model_path)
|
|
||||||
|
|
||||||
|
# Globales NLP-Modell
|
||||||
|
nlp = None
|
||||||
|
|
||||||
|
|
||||||
|
def load_model():
|
||||||
|
global nlp
|
||||||
|
print("[INFO] Lade SpaCy-Modell aus spacy_training/output/model-last ...")
|
||||||
|
nlp = spacy.load("spacy_training/output/model-last")
|
||||||
|
print("[INFO] Modell erfolgreich geladen.")
|
||||||
|
|
||||||
|
|
||||||
|
# Initial einmal laden
|
||||||
|
load_model()
|
||||||
|
|
||||||
|
|
||||||
def extract(pages_json):
|
def extract(pages_json):
|
||||||
results = []
|
results = []
|
||||||
|
|
@ -19,10 +35,6 @@ def extract(pages_json):
|
||||||
|
|
||||||
spacy_result = nlp(text)
|
spacy_result = nlp(text)
|
||||||
for ent in spacy_result.ents:
|
for ent in spacy_result.ents:
|
||||||
results.append({
|
results.append({"label": ent.label_, "entity": ent.text, "page": page_num})
|
||||||
"label": ent.label_,
|
|
||||||
"entity": ent.text,
|
|
||||||
"page": page_num
|
|
||||||
})
|
|
||||||
|
|
||||||
return json.dumps(results, indent=2, ensure_ascii=False)
|
return json.dumps(results, indent=2, ensure_ascii=False)
|
||||||
|
|
@ -4,3 +4,4 @@ transformers==4.35.2
|
||||||
torch
|
torch
|
||||||
flask
|
flask
|
||||||
requests
|
requests
|
||||||
|
flask-cors
|
||||||
|
|
@ -1648,5 +1648,175 @@
|
||||||
"NEUEKENNZAHL"
|
"NEUEKENNZAHL"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "fhfhfh56",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
6,
|
||||||
|
8,
|
||||||
|
"TEST545"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "fhfhfh56",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
6,
|
||||||
|
8,
|
||||||
|
"TEST345"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "sdgds45",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
"TEST243"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "4t4r3",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
"TEST243"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "sdgds45",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
"DGTDDTFHZ"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "gjufzj45",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
"DGTDDTFHZ"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "irr beträgt 43",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
12,
|
||||||
|
14,
|
||||||
|
"TEST3243"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "irr beträgt 43",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
12,
|
||||||
|
14,
|
||||||
|
"IRR"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Rendite besträgt 5 %",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
17,
|
||||||
|
20,
|
||||||
|
"RENDITE"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "RenditeX besträgt 5 %",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
18,
|
||||||
|
21,
|
||||||
|
"RENDITE_X"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "gtg3ahz8",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
"ERTRETT"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "wffwee 45",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
7,
|
||||||
|
9,
|
||||||
|
"TEST45"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "efwwef 45",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
7,
|
||||||
|
9,
|
||||||
|
"TEST12"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "wfwefwe34",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
7,
|
||||||
|
9,
|
||||||
|
"TEST232"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "fwefbmj34",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
7,
|
||||||
|
9,
|
||||||
|
"TEST223"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "asdas45",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
5,
|
||||||
|
7,
|
||||||
|
"TEST122"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "ewefw4",
|
||||||
|
"entities": [
|
||||||
|
[
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
"TEST3434"
|
||||||
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -1,21 +1,33 @@
|
||||||
import spacy
|
import spacy
|
||||||
from spacy.training.example import Example
|
from spacy.training.example import Example
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def load_data(file_path):
|
def load_data(file_path):
|
||||||
with open(file_path, "r", encoding="utf8") as f:
|
with open(file_path, "r", encoding="utf8") as f:
|
||||||
raw = json.load(f)
|
raw = json.load(f)
|
||||||
TRAIN_DATA = []
|
return [
|
||||||
for entry in raw:
|
(
|
||||||
text = entry["text"]
|
entry["text"],
|
||||||
entities = [(start, end, label) for start, end, label in entry["entities"]]
|
{
|
||||||
TRAIN_DATA.append((text, {"entities": entities}))
|
"entities": [
|
||||||
return TRAIN_DATA
|
(start, end, label) for start, end, label in entry["entities"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for entry in raw
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
TRAIN_DATA = load_data("annotation_data.json")
|
# Stelle sicher, dass der "output"-Ordner existiert
|
||||||
|
os.makedirs("output", exist_ok=True)
|
||||||
|
|
||||||
|
TRAIN_DATA = load_data(os.path.join("spacy_training", "annotation_data.json"))
|
||||||
|
|
||||||
nlp = spacy.blank("de")
|
nlp = spacy.blank("de")
|
||||||
ner = nlp.add_pipe("ner")
|
ner = nlp.add_pipe("ner")
|
||||||
ner.add_label("KENNZAHL")
|
ner.add_label("KENNZAHL")
|
||||||
|
|
@ -26,9 +38,43 @@ def main():
|
||||||
example = Example.from_dict(nlp.make_doc(text), annotations)
|
example = Example.from_dict(nlp.make_doc(text), annotations)
|
||||||
nlp.update([example], drop=0.2, sgd=optimizer)
|
nlp.update([example], drop=0.2, sgd=optimizer)
|
||||||
|
|
||||||
nlp.to_disk("output/model-last")
|
temp_model_dir = "output/temp-model"
|
||||||
|
final_model_dir = "output/model-last"
|
||||||
|
backup_dir = "output/model-backup"
|
||||||
|
|
||||||
# nlp.to_disk("model/") # Speichert das Modell
|
try:
|
||||||
|
# Vorheriges temporäres Verzeichnis entfernen
|
||||||
|
if os.path.exists(temp_model_dir):
|
||||||
|
shutil.rmtree(temp_model_dir)
|
||||||
|
|
||||||
|
# Modell zunächst in temp speichern
|
||||||
|
nlp.to_disk(temp_model_dir)
|
||||||
|
|
||||||
|
# Backup der letzten Version (falls vorhanden)
|
||||||
|
if os.path.exists(final_model_dir):
|
||||||
|
if os.path.exists(backup_dir):
|
||||||
|
shutil.rmtree(backup_dir)
|
||||||
|
shutil.copytree(final_model_dir, backup_dir)
|
||||||
|
|
||||||
|
shutil.rmtree(final_model_dir)
|
||||||
|
|
||||||
|
# Modell verschieben
|
||||||
|
shutil.move(temp_model_dir, final_model_dir)
|
||||||
|
print("[INFO] Training abgeschlossen und Modell gespeichert.")
|
||||||
|
|
||||||
|
nlp.to_disk("spacy_training/output/model-last")
|
||||||
|
|
||||||
|
# Training beendet – Status auf False setzen
|
||||||
|
with open("spacy_training/training_running.json", "w") as f:
|
||||||
|
json.dump({"running": False}, f)
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[FEHLER] Während des Trainings ist ein Fehler aufgetreten: {e}")
|
||||||
|
if os.path.exists(temp_model_dir):
|
||||||
|
shutil.rmtree(temp_model_dir)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
[paths]
|
[paths]
|
||||||
train = "./data/train.spacy"
|
train = null
|
||||||
dev = "./data/train.spacy"
|
dev = null
|
||||||
vectors = null
|
vectors = null
|
||||||
init_tok2vec = null
|
init_tok2vec = null
|
||||||
|
|
||||||
[system]
|
[system]
|
||||||
gpu_allocator = null
|
|
||||||
seed = 0
|
seed = 0
|
||||||
|
gpu_allocator = null
|
||||||
|
|
||||||
[nlp]
|
[nlp]
|
||||||
lang = "de"
|
lang = "de"
|
||||||
pipeline = ["tok2vec","ner"]
|
pipeline = ["ner"]
|
||||||
batch_size = 1000
|
|
||||||
disabled = []
|
disabled = []
|
||||||
before_creation = null
|
before_creation = null
|
||||||
after_creation = null
|
after_creation = null
|
||||||
after_pipeline_creation = null
|
after_pipeline_creation = null
|
||||||
|
batch_size = 1000
|
||||||
tokenizer = {"@tokenizers":"spacy.Tokenizer.v1"}
|
tokenizer = {"@tokenizers":"spacy.Tokenizer.v1"}
|
||||||
vectors = {"@vectors":"spacy.Vectors.v1"}
|
vectors = {"@vectors":"spacy.Vectors.v1"}
|
||||||
|
|
||||||
|
|
@ -38,51 +38,34 @@ use_upper = true
|
||||||
nO = null
|
nO = null
|
||||||
|
|
||||||
[components.ner.model.tok2vec]
|
[components.ner.model.tok2vec]
|
||||||
@architectures = "spacy.Tok2VecListener.v1"
|
@architectures = "spacy.HashEmbedCNN.v2"
|
||||||
width = ${components.tok2vec.model.encode.width}
|
pretrained_vectors = null
|
||||||
upstream = "*"
|
|
||||||
|
|
||||||
[components.tok2vec]
|
|
||||||
factory = "tok2vec"
|
|
||||||
|
|
||||||
[components.tok2vec.model]
|
|
||||||
@architectures = "spacy.Tok2Vec.v2"
|
|
||||||
|
|
||||||
[components.tok2vec.model.embed]
|
|
||||||
@architectures = "spacy.MultiHashEmbed.v2"
|
|
||||||
width = ${components.tok2vec.model.encode.width}
|
|
||||||
attrs = ["NORM","PREFIX","SUFFIX","SHAPE"]
|
|
||||||
rows = [5000,1000,2500,2500]
|
|
||||||
include_static_vectors = false
|
|
||||||
|
|
||||||
[components.tok2vec.model.encode]
|
|
||||||
@architectures = "spacy.MaxoutWindowEncoder.v2"
|
|
||||||
width = 96
|
width = 96
|
||||||
depth = 4
|
depth = 4
|
||||||
|
embed_size = 2000
|
||||||
window_size = 1
|
window_size = 1
|
||||||
maxout_pieces = 3
|
maxout_pieces = 3
|
||||||
|
subword_features = true
|
||||||
|
|
||||||
[corpora]
|
[corpora]
|
||||||
|
|
||||||
[corpora.dev]
|
[corpora.dev]
|
||||||
@readers = "spacy.Corpus.v1"
|
@readers = "spacy.Corpus.v1"
|
||||||
path = ${paths.dev}
|
path = ${paths.dev}
|
||||||
max_length = 0
|
|
||||||
gold_preproc = false
|
gold_preproc = false
|
||||||
|
max_length = 0
|
||||||
limit = 0
|
limit = 0
|
||||||
augmenter = null
|
augmenter = null
|
||||||
|
|
||||||
[corpora.train]
|
[corpora.train]
|
||||||
@readers = "spacy.Corpus.v1"
|
@readers = "spacy.Corpus.v1"
|
||||||
path = ${paths.train}
|
path = ${paths.train}
|
||||||
max_length = 0
|
|
||||||
gold_preproc = false
|
gold_preproc = false
|
||||||
|
max_length = 0
|
||||||
limit = 0
|
limit = 0
|
||||||
augmenter = null
|
augmenter = null
|
||||||
|
|
||||||
[training]
|
[training]
|
||||||
dev_corpus = "corpora.dev"
|
|
||||||
train_corpus = "corpora.train"
|
|
||||||
seed = ${system.seed}
|
seed = ${system.seed}
|
||||||
gpu_allocator = ${system.gpu_allocator}
|
gpu_allocator = ${system.gpu_allocator}
|
||||||
dropout = 0.1
|
dropout = 0.1
|
||||||
|
|
@ -93,6 +76,8 @@ max_steps = 20000
|
||||||
eval_frequency = 200
|
eval_frequency = 200
|
||||||
frozen_components = []
|
frozen_components = []
|
||||||
annotating_components = []
|
annotating_components = []
|
||||||
|
dev_corpus = "corpora.dev"
|
||||||
|
train_corpus = "corpora.train"
|
||||||
before_to_disk = null
|
before_to_disk = null
|
||||||
before_update = null
|
before_update = null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,84 +17,29 @@
|
||||||
"mode":"default"
|
"mode":"default"
|
||||||
},
|
},
|
||||||
"labels":{
|
"labels":{
|
||||||
"tok2vec":[
|
|
||||||
|
|
||||||
],
|
|
||||||
"ner":[
|
"ner":[
|
||||||
"AUSSCH\u00dcTTUNGSRENDITE",
|
"AUSSCH\u00dcTTUNGSRENDITE",
|
||||||
|
"IRR",
|
||||||
|
"KENNZAHL",
|
||||||
"LAUFZEIT",
|
"LAUFZEIT",
|
||||||
"L\u00c4NDERALLOKATION",
|
"L\u00c4NDERALLOKATION",
|
||||||
"MANAGMENTGEB\u00dcHREN",
|
"MANAGMENTGEB\u00dcHREN",
|
||||||
"RENDITE",
|
"RENDITE",
|
||||||
|
"RENDITE_X",
|
||||||
"RISIKOPROFIL",
|
"RISIKOPROFIL",
|
||||||
"SEKTORENALLOKATION",
|
"SEKTORENALLOKATION",
|
||||||
|
"TEST3243",
|
||||||
"ZIELAUSSCH\u00dcTTUNG",
|
"ZIELAUSSCH\u00dcTTUNG",
|
||||||
"ZIELRENDITE"
|
"ZIELRENDITE"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"pipeline":[
|
"pipeline":[
|
||||||
"tok2vec",
|
|
||||||
"ner"
|
"ner"
|
||||||
],
|
],
|
||||||
"components":[
|
"components":[
|
||||||
"tok2vec",
|
|
||||||
"ner"
|
"ner"
|
||||||
],
|
],
|
||||||
"disabled":[
|
"disabled":[
|
||||||
|
|
||||||
],
|
]
|
||||||
"performance":{
|
|
||||||
"ents_f":0.9608938547,
|
|
||||||
"ents_p":1.0,
|
|
||||||
"ents_r":0.9247311828,
|
|
||||||
"ents_per_type":{
|
|
||||||
"RISIKOPROFIL":{
|
|
||||||
"p":1.0,
|
|
||||||
"r":1.0,
|
|
||||||
"f":1.0
|
|
||||||
},
|
|
||||||
"AUSSCH\u00dcTTUNGSRENDITE":{
|
|
||||||
"p":1.0,
|
|
||||||
"r":0.5925925926,
|
|
||||||
"f":0.7441860465
|
|
||||||
},
|
|
||||||
"LAUFZEIT":{
|
|
||||||
"p":1.0,
|
|
||||||
"r":1.0,
|
|
||||||
"f":1.0
|
|
||||||
},
|
|
||||||
"RENDITE":{
|
|
||||||
"p":1.0,
|
|
||||||
"r":1.0,
|
|
||||||
"f":1.0
|
|
||||||
},
|
|
||||||
"L\u00c4NDERALLOKATION":{
|
|
||||||
"p":1.0,
|
|
||||||
"r":0.8965517241,
|
|
||||||
"f":0.9454545455
|
|
||||||
},
|
|
||||||
"ZIELRENDITE":{
|
|
||||||
"p":1.0,
|
|
||||||
"r":1.0,
|
|
||||||
"f":1.0
|
|
||||||
},
|
|
||||||
"ZIELAUSSCH\u00dcTTUNG":{
|
|
||||||
"p":1.0,
|
|
||||||
"r":1.0,
|
|
||||||
"f":1.0
|
|
||||||
},
|
|
||||||
"MANAGMENTGEB\u00dcHREN":{
|
|
||||||
"p":1.0,
|
|
||||||
"r":1.0,
|
|
||||||
"f":1.0
|
|
||||||
},
|
|
||||||
"SEKTORENALLOKATION":{
|
|
||||||
"p":1.0,
|
|
||||||
"r":1.0,
|
|
||||||
"f":1.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tok2vec_loss":33.6051129291,
|
|
||||||
"ner_loss":740.5764770508
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
|
@ -1 +1 @@
|
||||||
‚¥movesÚL{"0":{},"1":{"RISIKOPROFIL":161,"L\u00c4NDERALLOKATION":161,"RENDITE":91,"AUSSCH\u00dcTTUNGSRENDITE":68,"LAUFZEIT":38,"ZIELRENDITE":12,"SEKTORENALLOKATION":12,"MANAGMENTGEB\u00dcHREN":8,"ZIELAUSSCH\u00dcTTUNG":2},"2":{"RISIKOPROFIL":161,"L\u00c4NDERALLOKATION":161,"RENDITE":91,"AUSSCH\u00dcTTUNGSRENDITE":68,"LAUFZEIT":38,"ZIELRENDITE":12,"SEKTORENALLOKATION":12,"MANAGMENTGEB\u00dcHREN":8,"ZIELAUSSCH\u00dcTTUNG":2},"3":{"RISIKOPROFIL":161,"L\u00c4NDERALLOKATION":161,"RENDITE":91,"AUSSCH\u00dcTTUNGSRENDITE":68,"LAUFZEIT":38,"ZIELRENDITE":12,"SEKTORENALLOKATION":12,"MANAGMENTGEB\u00dcHREN":8,"ZIELAUSSCH\u00dcTTUNG":2},"4":{"RISIKOPROFIL":161,"L\u00c4NDERALLOKATION":161,"RENDITE":91,"AUSSCH\u00dcTTUNGSRENDITE":68,"LAUFZEIT":38,"ZIELRENDITE":12,"SEKTORENALLOKATION":12,"MANAGMENTGEB\u00dcHREN":8,"ZIELAUSSCH\u00dcTTUNG":2,"":1},"5":{"":1}}£cfg<66>§neg_keyÀ
|
‚¥movesÚ,{"0":{},"1":{"KENNZAHL":-1,"RISIKOPROFIL":-2,"AUSSCH\u00dcTTUNGSRENDITE":-3,"LAUFZEIT":-4,"RENDITE":-5,"L\u00c4NDERALLOKATION":-6,"ZIELRENDITE":-7,"ZIELAUSSCH\u00dcTTUNG":-8,"MANAGMENTGEB\u00dcHREN":-9,"SEKTORENALLOKATION":-10,"TEST3243":-11,"IRR":-12,"RENDITE_X":-13},"2":{"KENNZAHL":-1,"RISIKOPROFIL":-2,"AUSSCH\u00dcTTUNGSRENDITE":-3,"LAUFZEIT":-4,"RENDITE":-5,"L\u00c4NDERALLOKATION":-6,"ZIELRENDITE":-7,"ZIELAUSSCH\u00dcTTUNG":-8,"MANAGMENTGEB\u00dcHREN":-9,"SEKTORENALLOKATION":-10,"TEST3243":-11,"IRR":-12,"RENDITE_X":-13},"3":{"KENNZAHL":-1,"RISIKOPROFIL":-2,"AUSSCH\u00dcTTUNGSRENDITE":-3,"LAUFZEIT":-4,"RENDITE":-5,"L\u00c4NDERALLOKATION":-6,"ZIELRENDITE":-7,"ZIELAUSSCH\u00dcTTUNG":-8,"MANAGMENTGEB\u00dcHREN":-9,"SEKTORENALLOKATION":-10,"TEST3243":-11,"IRR":-12,"RENDITE_X":-13},"4":{"":1,"KENNZAHL":-1,"RISIKOPROFIL":-2,"AUSSCH\u00dcTTUNGSRENDITE":-3,"LAUFZEIT":-4,"RENDITE":-5,"L\u00c4NDERALLOKATION":-6,"ZIELRENDITE":-7,"ZIELAUSSCH\u00dcTTUNG":-8,"MANAGMENTGEB\u00dcHREN":-9,"SEKTORENALLOKATION":-10,"TEST3243":-11,"IRR":-12,"RENDITE_X":-13},"5":{"":1}}£cfg<66>§neg_keyÀ
|
||||||
|
|
@ -52,9 +52,7 @@
|
||||||
"*",
|
"*",
|
||||||
"+",
|
"+",
|
||||||
"+/D",
|
"+/D",
|
||||||
"+/d",
|
|
||||||
"+AU",
|
"+AU",
|
||||||
"+au",
|
|
||||||
",",
|
",",
|
||||||
",00",
|
",00",
|
||||||
",03",
|
",03",
|
||||||
|
|
@ -141,9 +139,6 @@
|
||||||
"/d,dd",
|
"/d,dd",
|
||||||
"/ddd%/ddd%/ddd",
|
"/ddd%/ddd%/ddd",
|
||||||
"/fk",
|
"/fk",
|
||||||
"/xx",
|
|
||||||
"/xxx",
|
|
||||||
"/xxxx+",
|
|
||||||
"0",
|
"0",
|
||||||
"0%+",
|
"0%+",
|
||||||
"0,0",
|
"0,0",
|
||||||
|
|
@ -278,8 +273,11 @@
|
||||||
"4,91",
|
"4,91",
|
||||||
"40",
|
"40",
|
||||||
"400",
|
"400",
|
||||||
|
"43",
|
||||||
"45",
|
"45",
|
||||||
"491",
|
"491",
|
||||||
|
"4r3",
|
||||||
|
"4t4r3",
|
||||||
"5",
|
"5",
|
||||||
"5%+",
|
"5%+",
|
||||||
"5,0",
|
"5,0",
|
||||||
|
|
@ -310,6 +308,7 @@
|
||||||
"67",
|
"67",
|
||||||
"7",
|
"7",
|
||||||
"7,1",
|
"7,1",
|
||||||
|
"7,2",
|
||||||
"7,5",
|
"7,5",
|
||||||
"7,5%+",
|
"7,5%+",
|
||||||
"7,50",
|
"7,50",
|
||||||
|
|
@ -648,7 +647,6 @@
|
||||||
"E.",
|
"E.",
|
||||||
"EAN",
|
"EAN",
|
||||||
"ECLF",
|
"ECLF",
|
||||||
"EIT",
|
|
||||||
"EM",
|
"EM",
|
||||||
"ERD",
|
"ERD",
|
||||||
"ESG-",
|
"ESG-",
|
||||||
|
|
@ -683,7 +681,6 @@
|
||||||
"F",
|
"F",
|
||||||
"F.",
|
"F.",
|
||||||
"FDR",
|
"FDR",
|
||||||
"FIL",
|
|
||||||
"FR",
|
"FR",
|
||||||
"FRANCE",
|
"FRANCE",
|
||||||
"FUND",
|
"FUND",
|
||||||
|
|
@ -794,11 +791,9 @@
|
||||||
"III.",
|
"III.",
|
||||||
"INK",
|
"INK",
|
||||||
"INREV",
|
"INREV",
|
||||||
"ION",
|
|
||||||
"IRR",
|
"IRR",
|
||||||
"IRR6.5",
|
"IRR6.5",
|
||||||
"IT",
|
"IT",
|
||||||
"ITE",
|
|
||||||
"IUM",
|
"IUM",
|
||||||
"IV",
|
"IV",
|
||||||
"IV.",
|
"IV.",
|
||||||
|
|
@ -866,6 +861,7 @@
|
||||||
"K.",
|
"K.",
|
||||||
"K.O.",
|
"K.O.",
|
||||||
"KAGB",
|
"KAGB",
|
||||||
|
"KENNZAHL",
|
||||||
"KINGDOM",
|
"KINGDOM",
|
||||||
"KVG",
|
"KVG",
|
||||||
"Kapitalstruktur",
|
"Kapitalstruktur",
|
||||||
|
|
@ -1072,8 +1068,8 @@
|
||||||
"R.",
|
"R.",
|
||||||
"R.I.P.",
|
"R.I.P.",
|
||||||
"RE",
|
"RE",
|
||||||
"REN",
|
|
||||||
"RENDITE",
|
"RENDITE",
|
||||||
|
"RENDITE_X",
|
||||||
"REV",
|
"REV",
|
||||||
"REWE",
|
"REWE",
|
||||||
"RISIKOPROFIL",
|
"RISIKOPROFIL",
|
||||||
|
|
@ -1088,8 +1084,10 @@
|
||||||
"Redaktion",
|
"Redaktion",
|
||||||
"Region",
|
"Region",
|
||||||
"Regionen",
|
"Regionen",
|
||||||
|
"Rendite",
|
||||||
"Rendite-",
|
"Rendite-",
|
||||||
"Rendite-Risiko-Profil",
|
"Rendite-Risiko-Profil",
|
||||||
|
"RenditeX",
|
||||||
"Renovierungen",
|
"Renovierungen",
|
||||||
"Rents",
|
"Rents",
|
||||||
"Residential",
|
"Residential",
|
||||||
|
|
@ -1169,6 +1167,7 @@
|
||||||
"T",
|
"T",
|
||||||
"T.",
|
"T.",
|
||||||
"TED",
|
"TED",
|
||||||
|
"TEST3243",
|
||||||
"Tag",
|
"Tag",
|
||||||
"Target",
|
"Target",
|
||||||
"Target-IRR",
|
"Target-IRR",
|
||||||
|
|
@ -1197,7 +1196,6 @@
|
||||||
"U.S.S.",
|
"U.S.S.",
|
||||||
"UK",
|
"UK",
|
||||||
"UND",
|
"UND",
|
||||||
"UNG",
|
|
||||||
"UNITED",
|
"UNITED",
|
||||||
"USt",
|
"USt",
|
||||||
"Univ",
|
"Univ",
|
||||||
|
|
@ -1310,6 +1308,7 @@
|
||||||
"Xxxxx-Xxxxx-Xxxxx",
|
"Xxxxx-Xxxxx-Xxxxx",
|
||||||
"Xxxxx-xxx",
|
"Xxxxx-xxx",
|
||||||
"Xxxxx-xxxx",
|
"Xxxxx-xxxx",
|
||||||
|
"XxxxxX",
|
||||||
"Xxxxx\u0308xx",
|
"Xxxxx\u0308xx",
|
||||||
"Xxxxx\u0308xxx-Xxxxx",
|
"Xxxxx\u0308xxx-Xxxxx",
|
||||||
"Xxxxx\u0308xxxx",
|
"Xxxxx\u0308xxxx",
|
||||||
|
|
@ -1410,14 +1409,12 @@
|
||||||
"advantage",
|
"advantage",
|
||||||
"ae",
|
"ae",
|
||||||
"aft",
|
"aft",
|
||||||
"agb",
|
|
||||||
"age",
|
"age",
|
||||||
"agreements",
|
"agreements",
|
||||||
"aha",
|
"aha",
|
||||||
"ahe",
|
"ahe",
|
||||||
"ahl",
|
"ahl",
|
||||||
"ahr",
|
"ahr",
|
||||||
"aif",
|
|
||||||
"ail",
|
"ail",
|
||||||
"aiming",
|
"aiming",
|
||||||
"ain",
|
"ain",
|
||||||
|
|
@ -1429,7 +1426,6 @@
|
||||||
"al.",
|
"al.",
|
||||||
"ald",
|
"ald",
|
||||||
"ale",
|
"ale",
|
||||||
"alf",
|
|
||||||
"all",
|
"all",
|
||||||
"allg",
|
"allg",
|
||||||
"allg.",
|
"allg.",
|
||||||
|
|
@ -1440,7 +1436,6 @@
|
||||||
"allokationsprofil",
|
"allokationsprofil",
|
||||||
"als",
|
"als",
|
||||||
"also",
|
"also",
|
||||||
"alt",
|
|
||||||
"alternative",
|
"alternative",
|
||||||
"aly",
|
"aly",
|
||||||
"am.",
|
"am.",
|
||||||
|
|
@ -1535,7 +1530,6 @@
|
||||||
"aussch\u00fcttungsrandite",
|
"aussch\u00fcttungsrandite",
|
||||||
"aussch\u00fcttungsrendite",
|
"aussch\u00fcttungsrendite",
|
||||||
"aussch\u00fcttungsrendites",
|
"aussch\u00fcttungsrendites",
|
||||||
"aut",
|
|
||||||
"ave",
|
"ave",
|
||||||
"ax.",
|
"ax.",
|
||||||
"b",
|
"b",
|
||||||
|
|
@ -1565,9 +1559,11 @@
|
||||||
"berlin",
|
"berlin",
|
||||||
"bestandsentwicklung",
|
"bestandsentwicklung",
|
||||||
"bestandsentwicklungen",
|
"bestandsentwicklungen",
|
||||||
|
"bestr\u00e4gt",
|
||||||
"betr",
|
"betr",
|
||||||
"betr.",
|
"betr.",
|
||||||
"betreute",
|
"betreute",
|
||||||
|
"betr\u00e4gt",
|
||||||
"bev\u00f6lkerungsprognose",
|
"bev\u00f6lkerungsprognose",
|
||||||
"beziehungsweise",
|
"beziehungsweise",
|
||||||
"bez\u00fcglich",
|
"bez\u00fcglich",
|
||||||
|
|
@ -1643,7 +1639,6 @@
|
||||||
"cl.",
|
"cl.",
|
||||||
"class",
|
"class",
|
||||||
"cle",
|
"cle",
|
||||||
"clf",
|
|
||||||
"closed",
|
"closed",
|
||||||
"closing",
|
"closing",
|
||||||
"closings",
|
"closings",
|
||||||
|
|
@ -1663,7 +1658,6 @@
|
||||||
"construction",
|
"construction",
|
||||||
"contract",
|
"contract",
|
||||||
"contracts",
|
"contracts",
|
||||||
"cor",
|
|
||||||
"core",
|
"core",
|
||||||
"core+",
|
"core+",
|
||||||
"core+/d",
|
"core+/d",
|
||||||
|
|
@ -1672,7 +1666,6 @@
|
||||||
"could",
|
"could",
|
||||||
"country",
|
"country",
|
||||||
"creation",
|
"creation",
|
||||||
"csp",
|
|
||||||
"csu",
|
"csu",
|
||||||
"cts",
|
"cts",
|
||||||
"currency",
|
"currency",
|
||||||
|
|
@ -1756,7 +1749,6 @@
|
||||||
"dipl.",
|
"dipl.",
|
||||||
"dipl.-ing",
|
"dipl.-ing",
|
||||||
"dipl.-ing.",
|
"dipl.-ing.",
|
||||||
"dis",
|
|
||||||
"discretionary",
|
"discretionary",
|
||||||
"distributions",
|
"distributions",
|
||||||
"diversification",
|
"diversification",
|
||||||
|
|
@ -1767,7 +1759,6 @@
|
||||||
"dle",
|
"dle",
|
||||||
"do",
|
"do",
|
||||||
"do.",
|
"do.",
|
||||||
"dom",
|
|
||||||
"domicile",
|
"domicile",
|
||||||
"domiciled",
|
"domiciled",
|
||||||
"don",
|
"don",
|
||||||
|
|
@ -1783,7 +1774,7 @@
|
||||||
"durchschnittlich",
|
"durchschnittlich",
|
||||||
"du\u2019s",
|
"du\u2019s",
|
||||||
"dv.",
|
"dv.",
|
||||||
"dxxx.\u20ac",
|
"dxdxd",
|
||||||
"dy",
|
"dy",
|
||||||
"d\u00e4nemark",
|
"d\u00e4nemark",
|
||||||
"d\u2019",
|
"d\u2019",
|
||||||
|
|
@ -1877,7 +1868,6 @@
|
||||||
"er.",
|
"er.",
|
||||||
"erb",
|
"erb",
|
||||||
"erbbaurechte",
|
"erbbaurechte",
|
||||||
"erd",
|
|
||||||
"ere",
|
"ere",
|
||||||
"erfolgten",
|
"erfolgten",
|
||||||
"erg",
|
"erg",
|
||||||
|
|
@ -1940,7 +1930,6 @@
|
||||||
"fam",
|
"fam",
|
||||||
"fam.",
|
"fam.",
|
||||||
"favour",
|
"favour",
|
||||||
"fdr",
|
|
||||||
"feb",
|
"feb",
|
||||||
"feb.",
|
"feb.",
|
||||||
"fee",
|
"fee",
|
||||||
|
|
@ -1950,6 +1939,7 @@
|
||||||
"festgelegt",
|
"festgelegt",
|
||||||
"festgelegter",
|
"festgelegter",
|
||||||
"ff",
|
"ff",
|
||||||
|
"fhfhfh56",
|
||||||
"fierce",
|
"fierce",
|
||||||
"fil",
|
"fil",
|
||||||
"financially",
|
"financially",
|
||||||
|
|
@ -2050,6 +2040,7 @@
|
||||||
"ght",
|
"ght",
|
||||||
"gic",
|
"gic",
|
||||||
"gie",
|
"gie",
|
||||||
|
"gjufzj45",
|
||||||
"gl.",
|
"gl.",
|
||||||
"global",
|
"global",
|
||||||
"globale",
|
"globale",
|
||||||
|
|
@ -2071,6 +2062,7 @@
|
||||||
"h.",
|
"h.",
|
||||||
"h.c",
|
"h.c",
|
||||||
"h.c.",
|
"h.c.",
|
||||||
|
"h56",
|
||||||
"haltedauer",
|
"haltedauer",
|
||||||
"halten",
|
"halten",
|
||||||
"halten-strategie",
|
"halten-strategie",
|
||||||
|
|
@ -2270,6 +2262,7 @@
|
||||||
"ize",
|
"ize",
|
||||||
"j",
|
"j",
|
||||||
"j.",
|
"j.",
|
||||||
|
"j45",
|
||||||
"ja",
|
"ja",
|
||||||
"jahr",
|
"jahr",
|
||||||
"jahre",
|
"jahre",
|
||||||
|
|
@ -2393,7 +2386,6 @@
|
||||||
"lto",
|
"lto",
|
||||||
"ltv",
|
"ltv",
|
||||||
"ltv-ziel",
|
"ltv-ziel",
|
||||||
"lty",
|
|
||||||
"lu",
|
"lu",
|
||||||
"lub",
|
"lub",
|
||||||
"lue",
|
"lue",
|
||||||
|
|
@ -2427,7 +2419,6 @@
|
||||||
"management",
|
"management",
|
||||||
"manager",
|
"manager",
|
||||||
"manager-defined",
|
"manager-defined",
|
||||||
"managmentgeb\u00fchren",
|
|
||||||
"mandate",
|
"mandate",
|
||||||
"mandates",
|
"mandates",
|
||||||
"market",
|
"market",
|
||||||
|
|
@ -2439,7 +2430,6 @@
|
||||||
"maximal",
|
"maximal",
|
||||||
"maximaler",
|
"maximaler",
|
||||||
"mbH",
|
"mbH",
|
||||||
"mbh",
|
|
||||||
"means",
|
"means",
|
||||||
"medizin",
|
"medizin",
|
||||||
"medizinnahe",
|
"medizinnahe",
|
||||||
|
|
@ -2662,8 +2652,6 @@
|
||||||
"partners",
|
"partners",
|
||||||
"partnership",
|
"partnership",
|
||||||
"pattern",
|
"pattern",
|
||||||
"pci",
|
|
||||||
"pco",
|
|
||||||
"ped",
|
"ped",
|
||||||
"pen",
|
"pen",
|
||||||
"per",
|
"per",
|
||||||
|
|
@ -2719,7 +2707,6 @@
|
||||||
"q.",
|
"q.",
|
||||||
"q.e.d",
|
"q.e.d",
|
||||||
"q.e.d.",
|
"q.e.d.",
|
||||||
"qin",
|
|
||||||
"quality",
|
"quality",
|
||||||
"quarterly",
|
"quarterly",
|
||||||
"quota",
|
"quota",
|
||||||
|
|
@ -2759,6 +2746,7 @@
|
||||||
"rendite",
|
"rendite",
|
||||||
"rendite-",
|
"rendite-",
|
||||||
"rendite-risiko-profil",
|
"rendite-risiko-profil",
|
||||||
|
"renditex",
|
||||||
"renegotiation",
|
"renegotiation",
|
||||||
"renovierungen",
|
"renovierungen",
|
||||||
"rent",
|
"rent",
|
||||||
|
|
@ -2773,7 +2761,6 @@
|
||||||
"retailinvestitionsvolumen",
|
"retailinvestitionsvolumen",
|
||||||
"return",
|
"return",
|
||||||
"returns",
|
"returns",
|
||||||
"rev",
|
|
||||||
"reversion",
|
"reversion",
|
||||||
"rewe",
|
"rewe",
|
||||||
"rge",
|
"rge",
|
||||||
|
|
@ -2800,12 +2787,10 @@
|
||||||
"rop",
|
"rop",
|
||||||
"rotterdam",
|
"rotterdam",
|
||||||
"rr.",
|
"rr.",
|
||||||
"rre",
|
|
||||||
"rs.",
|
"rs.",
|
||||||
"rsg",
|
"rsg",
|
||||||
"rst",
|
"rst",
|
||||||
"rte",
|
"rte",
|
||||||
"rtt",
|
|
||||||
"rz.",
|
"rz.",
|
||||||
"r\u00f6m",
|
"r\u00f6m",
|
||||||
"r\u00f6m.",
|
"r\u00f6m.",
|
||||||
|
|
@ -2818,6 +2803,7 @@
|
||||||
"s.o",
|
"s.o",
|
||||||
"s.o.",
|
"s.o.",
|
||||||
"s.w",
|
"s.w",
|
||||||
|
"s45",
|
||||||
"sa",
|
"sa",
|
||||||
"sa.",
|
"sa.",
|
||||||
"sale",
|
"sale",
|
||||||
|
|
@ -2828,6 +2814,7 @@
|
||||||
"scs",
|
"scs",
|
||||||
"scsp",
|
"scsp",
|
||||||
"sd.",
|
"sd.",
|
||||||
|
"sdgds45",
|
||||||
"sector",
|
"sector",
|
||||||
"sectors",
|
"sectors",
|
||||||
"sed",
|
"sed",
|
||||||
|
|
@ -2835,7 +2822,6 @@
|
||||||
"segment",
|
"segment",
|
||||||
"sektor",
|
"sektor",
|
||||||
"sektoraler",
|
"sektoraler",
|
||||||
"sektorenallokation",
|
|
||||||
"selection",
|
"selection",
|
||||||
"sen",
|
"sen",
|
||||||
"sen.",
|
"sen.",
|
||||||
|
|
@ -2849,7 +2835,6 @@
|
||||||
"set",
|
"set",
|
||||||
"sf.",
|
"sf.",
|
||||||
"sfdr",
|
"sfdr",
|
||||||
"sg-",
|
|
||||||
"sg.",
|
"sg.",
|
||||||
"short-term",
|
"short-term",
|
||||||
"sicav-raif",
|
"sicav-raif",
|
||||||
|
|
@ -2935,6 +2920,7 @@
|
||||||
"tc.",
|
"tc.",
|
||||||
"td.",
|
"td.",
|
||||||
"te-",
|
"te-",
|
||||||
|
"teX",
|
||||||
"ted",
|
"ted",
|
||||||
"tee",
|
"tee",
|
||||||
"teflimmobilfe)-",
|
"teflimmobilfe)-",
|
||||||
|
|
@ -3128,9 +3114,6 @@
|
||||||
"worldwide",
|
"worldwide",
|
||||||
"x",
|
"x",
|
||||||
"x'",
|
"x'",
|
||||||
"x+xx",
|
|
||||||
"x+xxx",
|
|
||||||
"x-xxxx",
|
|
||||||
"x.",
|
"x.",
|
||||||
"x.X",
|
"x.X",
|
||||||
"x.X.",
|
"x.X.",
|
||||||
|
|
@ -3157,38 +3140,23 @@
|
||||||
"xemoours",
|
"xemoours",
|
||||||
"xit",
|
"xit",
|
||||||
"xx",
|
"xx",
|
||||||
"xx-xxxx",
|
|
||||||
"xx.",
|
"xx.",
|
||||||
"xx.x",
|
"xx.x",
|
||||||
"xxXxx",
|
"xxXxx",
|
||||||
"xxx",
|
"xxx",
|
||||||
"xxx-",
|
|
||||||
"xxx-Xxxxx",
|
"xxx-Xxxxx",
|
||||||
"xxx-xxxx",
|
"xxx-xxxx",
|
||||||
"xxx.",
|
"xxx.",
|
||||||
"xxxd.d",
|
|
||||||
"xxxx",
|
"xxxx",
|
||||||
"xxxx)-",
|
|
||||||
"xxxx)/xxxx",
|
|
||||||
"xxxx+",
|
"xxxx+",
|
||||||
"xxxx+/x",
|
|
||||||
"xxxx+/xxxx",
|
|
||||||
"xxxx,dd",
|
|
||||||
"xxxx-",
|
|
||||||
"xxxx-xx",
|
"xxxx-xx",
|
||||||
"xxxx-xx-xxxx",
|
|
||||||
"xxxx-xxx",
|
"xxxx-xxx",
|
||||||
"xxxx-xxxx",
|
"xxxx-xxxx",
|
||||||
"xxxx-xxxx-xxx",
|
|
||||||
"xxxx-xxxx-xxxx",
|
|
||||||
"xxxx.",
|
"xxxx.",
|
||||||
"xxxx\u0308xx",
|
"xxxxdd",
|
||||||
"xxxx\u0308xxx-xxxx",
|
|
||||||
"xxxx\u0308xxxx",
|
|
||||||
"xxxx\u2019x",
|
"xxxx\u2019x",
|
||||||
"xxx\u2019x",
|
"xxx\u2019x",
|
||||||
"xx\u0308x",
|
"xx\u0308x",
|
||||||
"xx\u0308xxxx",
|
|
||||||
"xx\u2019x",
|
"xx\u2019x",
|
||||||
"x\u0308xxx",
|
"x\u0308xxx",
|
||||||
"x\u0308xxxx",
|
"x\u0308xxxx",
|
||||||
|
|
@ -3224,7 +3192,6 @@
|
||||||
"zielallokation",
|
"zielallokation",
|
||||||
"zielanlagestrategie",
|
"zielanlagestrategie",
|
||||||
"zielausschu\u0308ttung",
|
"zielausschu\u0308ttung",
|
||||||
"zielaussch\u00fcttung",
|
|
||||||
"zielmarkts",
|
"zielmarkts",
|
||||||
"zielm\u00e4rkte",
|
"zielm\u00e4rkte",
|
||||||
"zielobjekte",
|
"zielobjekte",
|
||||||
|
|
@ -3279,6 +3246,7 @@
|
||||||
"\u00e4",
|
"\u00e4",
|
||||||
"\u00e4.",
|
"\u00e4.",
|
||||||
"\u00e4gl",
|
"\u00e4gl",
|
||||||
|
"\u00e4gt",
|
||||||
"\u00e4r.",
|
"\u00e4r.",
|
||||||
"\u00e4rzteh\u00e4user",
|
"\u00e4rzteh\u00e4user",
|
||||||
"\u00e4rzteh\u00e4usern",
|
"\u00e4rzteh\u00e4usern",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"running": false}
|
||||||
|
|
@ -74,10 +74,6 @@ services:
|
||||||
- COORDINATOR_URL=http://coordinator:5000
|
- COORDINATOR_URL=http://coordinator:5000
|
||||||
ports:
|
ports:
|
||||||
- 5053:5000
|
- 5053:5000
|
||||||
depends_on:
|
|
||||||
- coordinator
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
validate:
|
validate:
|
||||||
build:
|
build:
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import { Box, Typography, Button, Paper, TextField, FormControlLabel,
|
import {
|
||||||
Checkbox, Select, MenuItem, FormControl, InputLabel, Divider, CircularProgress } from "@mui/material";
|
Box, Typography, Button, Paper, TextField, FormControlLabel,
|
||||||
|
Checkbox, Select, MenuItem, FormControl, InputLabel, Divider, CircularProgress
|
||||||
|
} from "@mui/material";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import type { Kennzahl } from "../types/kpi";
|
import type { Kennzahl } from "../types/kpi";
|
||||||
import { typeDisplayMapping } from "../types/kpi";
|
import { typeDisplayMapping } from "../types/kpi";
|
||||||
// import { saveAs } from "file-saver";
|
import Snackbar from "@mui/material/Snackbar";
|
||||||
|
import MuiAlert from "@mui/material/Alert";
|
||||||
|
|
||||||
|
|
||||||
interface KPIFormProps {
|
interface KPIFormProps {
|
||||||
mode: 'add' | 'edit';
|
mode: 'add' | 'edit';
|
||||||
|
|
@ -11,6 +15,7 @@ interface KPIFormProps {
|
||||||
onSave: (data: Partial<Kennzahl>) => Promise<void>;
|
onSave: (data: Partial<Kennzahl>) => Promise<void>;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
resetTrigger?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyKPI: Partial<Kennzahl> = {
|
const emptyKPI: Partial<Kennzahl> = {
|
||||||
|
|
@ -21,83 +26,132 @@ const emptyKPI: Partial<Kennzahl> = {
|
||||||
translation: '',
|
translation: '',
|
||||||
example: '',
|
example: '',
|
||||||
active: true,
|
active: true,
|
||||||
exampleText: '',
|
examples: [{ sentence: '', value: '' }],
|
||||||
markedValue: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function KPIForm({ mode, initialData, onSave, onCancel, loading = false }: KPIFormProps) {
|
|
||||||
|
export function KPIForm({ mode, initialData, onSave, onCancel, loading = false, resetTrigger }: KPIFormProps) {
|
||||||
const [formData, setFormData] = useState<Partial<Kennzahl>>(emptyKPI);
|
const [formData, setFormData] = useState<Partial<Kennzahl>>(emptyKPI);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||||
|
const [snackbarMessage, setSnackbarMessage] = useState("");
|
||||||
|
const [snackbarSeverity, setSnackbarSeverity] = useState<'success' | 'error' | 'info'>("success");
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode === 'edit' && initialData) {
|
if (mode === 'edit' && initialData) {
|
||||||
setFormData(initialData);
|
setFormData(initialData);
|
||||||
} else {
|
} else if (mode === 'add') {
|
||||||
setFormData(emptyKPI);
|
setFormData(emptyKPI);
|
||||||
}
|
}
|
||||||
}, [mode, initialData]);
|
}, [mode, initialData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mode === 'add') {
|
||||||
|
setFormData(emptyKPI);
|
||||||
|
}
|
||||||
|
}, [resetTrigger]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!formData.name?.trim()) {
|
if (!formData.name?.trim()) {
|
||||||
alert('Name ist erforderlich');
|
setSnackbarMessage("Name ist erforderlich");
|
||||||
|
setSnackbarSeverity("error");
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.exampleText?.trim()) {
|
if (!formData.examples || formData.examples.length === 0) {
|
||||||
alert('Beispielsatz ist erforderlich');
|
setSnackbarMessage("Mindestens ein Beispielsatz ist erforderlich");
|
||||||
|
setSnackbarSeverity("error");
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.markedValue?.trim()) {
|
for (const ex of formData.examples) {
|
||||||
alert('Bezeichneter Wert im Satz ist erforderlich');
|
if (!ex.sentence?.trim() || !ex.value?.trim()) {
|
||||||
return;
|
setSnackbarMessage('Alle Beispielsätze müssen vollständig sein.');
|
||||||
|
setSnackbarSeverity("error");
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
try {
|
try {
|
||||||
const spacyEntry = generateSpacyEntry(formData);
|
const spacyEntries = generateSpacyEntries(formData);
|
||||||
|
|
||||||
//in localStorage merken
|
// Für jeden einzelnen Beispielsatz:
|
||||||
const stored = localStorage.getItem("spacyData");
|
for (const entry of spacyEntries) {
|
||||||
const existingData = stored ? JSON.parse(stored) : [];
|
// im localStorage speichern (zum Debuggen oder Vorschau)
|
||||||
const updated = [...existingData, spacyEntry];
|
const stored = localStorage.getItem("spacyData");
|
||||||
localStorage.setItem("spacyData", JSON.stringify(updated));
|
const existingData = stored ? JSON.parse(stored) : [];
|
||||||
|
const updated = [...existingData, entry];
|
||||||
|
localStorage.setItem("spacyData", JSON.stringify(updated));
|
||||||
|
|
||||||
// an Flask senden
|
// POST Request an das Flask-Backend
|
||||||
const response = await fetch("http://localhost:5050/api/spacy/append-training-entry", {
|
const response = await fetch("http://localhost:5050/api/spacy/append-training-entry", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
body: JSON.stringify(spacyEntry)
|
body: JSON.stringify(entry)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || "Fehler beim Aufruf von append-training-entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("SpaCy-Eintrag gespeichert:", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dann in die DB speichern
|
||||||
|
await onSave({
|
||||||
|
name: formData.name!,
|
||||||
|
description: formData.description || '',
|
||||||
|
mandatory: formData.mandatory ?? false,
|
||||||
|
type: formData.type || 'string',
|
||||||
|
translation: formData.translation || '',
|
||||||
|
example: formData.example || '',
|
||||||
|
position: formData.position ?? 0,
|
||||||
|
active: formData.active ?? true,
|
||||||
|
examples: [{ sentence: '', value: '' }]
|
||||||
});
|
});
|
||||||
|
// Formular zurücksetzen:
|
||||||
|
setFormData(emptyKPI);
|
||||||
|
|
||||||
|
|
||||||
const data = await response.json();
|
setSnackbarMessage("Beispielsätze gespeichert. Jetzt auf ‚Neu trainieren‘ klicken oder weitere Kennzahlen hinzufügen.");
|
||||||
console.log("Response von /append-training-entry:", data);
|
setSnackbarSeverity("success");
|
||||||
|
setSnackbarOpen(true);
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error || "Fehler beim Aufruf von append-training-entry");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Fehler vom Backend: " + response.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
// anschließend in der Datenbank speichern
|
|
||||||
await onSave(formData);
|
|
||||||
|
|
||||||
alert("SpaCy-Eintrag erfolgreich gespeichert!");
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
alert(e.message || "Fehler beim Erzeugen des Trainingsbeispiels.");
|
// Prüfe auf 409-Fehler
|
||||||
|
if (e?.message?.includes("409") || e?.response?.status === 409) {
|
||||||
|
setSnackbarMessage("Diese Kennzahl existiert bereits. Sie können sie unter ‚Konfiguration‘ bearbeiten.");
|
||||||
|
setSnackbarSeverity("info");
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
} else {
|
||||||
|
setSnackbarMessage(e.message || "Fehler beim Speichern.");
|
||||||
|
setSnackbarSeverity("error");
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
}
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
|
setFormData(emptyKPI);
|
||||||
onCancel();
|
onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -105,6 +159,24 @@ export function KPIForm({ mode, initialData, onSave, onCancel, loading = false }
|
||||||
setFormData(prev => ({ ...prev, [field]: value }));
|
setFormData(prev => ({ ...prev, [field]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateExample = (index: number, field: 'sentence' | 'value', value: string) => {
|
||||||
|
const newExamples = [...(formData.examples || [])];
|
||||||
|
newExamples[index][field] = value;
|
||||||
|
updateField('examples', newExamples);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addExample = () => {
|
||||||
|
const newExamples = [...(formData.examples || []), { sentence: '', value: '' }];
|
||||||
|
updateField('examples', newExamples);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeExample = (index: number) => {
|
||||||
|
const newExamples = [...(formData.examples || [])];
|
||||||
|
newExamples.splice(index, 1);
|
||||||
|
updateField('examples', newExamples);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
|
@ -123,218 +195,221 @@ export function KPIForm({ mode, initialData, onSave, onCancel, loading = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<>
|
||||||
elevation={2}
|
<Paper
|
||||||
sx={{
|
elevation={2}
|
||||||
width: "90%",
|
sx={{
|
||||||
maxWidth: 800,
|
width: "90%",
|
||||||
p: 4,
|
maxWidth: 800,
|
||||||
borderRadius: 2,
|
p: 4,
|
||||||
backgroundColor: "white"
|
borderRadius: 2,
|
||||||
}}
|
backgroundColor: "white"
|
||||||
>
|
}}
|
||||||
<Box mb={4}>
|
>
|
||||||
<Typography variant="h6" fontWeight="bold" mb={2}>
|
<Box mb={4}>
|
||||||
Kennzahl
|
<Typography variant="h6" fontWeight="bold" mb={2}>
|
||||||
</Typography>
|
Kennzahl
|
||||||
<TextField
|
</Typography>
|
||||||
fullWidth
|
<TextField
|
||||||
label="Name *"
|
fullWidth
|
||||||
value={formData.name || ''}
|
label="Name *"
|
||||||
onChange={(e) => updateField('name', e.target.value)}
|
placeholder="z. B. IRR"
|
||||||
sx={{ mb: 2 }}
|
value={formData.name || ''}
|
||||||
required
|
onChange={(e) => updateField('name', e.target.value)}
|
||||||
error={!formData.name?.trim()}
|
sx={{ mb: 2 }}
|
||||||
helperText={!formData.name?.trim() ? 'Name ist erforderlich' : ''}
|
required
|
||||||
/>
|
error={!formData.name?.trim()}
|
||||||
</Box>
|
helperText={!formData.name?.trim() ? 'Name ist erforderlich' : ''}
|
||||||
|
|
||||||
<Divider sx={{ my: 3 }} />
|
|
||||||
|
|
||||||
<Box mb={4}>
|
|
||||||
<Typography variant="h6" fontWeight="bold" mb={2}>
|
|
||||||
Beispielsatz
|
|
||||||
</Typography>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
rows={3}
|
|
||||||
label="Beispielsatz"
|
|
||||||
required
|
|
||||||
value={formData.exampleText || ''}
|
|
||||||
onChange={(e) => updateField('exampleText', e.target.value)}
|
|
||||||
error={!formData.exampleText?.trim()}
|
|
||||||
helperText={
|
|
||||||
!formData.exampleText?.trim()
|
|
||||||
? "Beispielsatz ist erforderlich"
|
|
||||||
: "Ein vollständiger Satz, in dem der markierte Begriff vorkommt"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
sx={{ mt: 2 }}
|
|
||||||
label="Bezeichneter Wert im Satz *"
|
|
||||||
value={formData.markedValue || ''}
|
|
||||||
onChange={(e) => updateField('markedValue', e.target.value)}
|
|
||||||
error={!formData.markedValue?.trim()}
|
|
||||||
helperText={
|
|
||||||
!formData.markedValue?.trim()
|
|
||||||
? "Markierter Begriff ist erforderlich"
|
|
||||||
: "Nur der Begriff, der im Satz markiert werden soll (z. B. Core/Core+)"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
<Box mt={3}>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={formData.mandatory || false}
|
|
||||||
onChange={(e) => updateField('mandatory', e.target.checked)}
|
|
||||||
sx={{ color: '#383838' }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Erforderlich"
|
|
||||||
/>
|
/>
|
||||||
<Typography variant="body2" color="text.secondary" ml={4}>
|
</Box>
|
||||||
Die Kennzahl erlaubt keine leeren Werte
|
|
||||||
|
<Divider sx={{ my: 3 }} />
|
||||||
|
|
||||||
|
<Box mb={4}>
|
||||||
|
<Typography variant="h6" fontWeight="bold" mb={2}>
|
||||||
|
Format: {typeDisplayMapping[formData.type as keyof typeof typeDisplayMapping] || formData.type}
|
||||||
|
</Typography>
|
||||||
|
<FormControl fullWidth sx={{ mb: 2 }}>
|
||||||
|
<InputLabel>Typ</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={formData.type || 'string'}
|
||||||
|
label="Typ"
|
||||||
|
onChange={(e) => updateField('type', e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value="string">Text</MenuItem>
|
||||||
|
<MenuItem value="number">Zahl</MenuItem>
|
||||||
|
<MenuItem value="date">Datum</MenuItem>
|
||||||
|
<MenuItem value="boolean">Ja/Nein</MenuItem>
|
||||||
|
<MenuItem value="array">Liste (mehrfach)</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{mode === 'add' && (
|
||||||
|
<>
|
||||||
|
<Divider sx={{ my: 3 }} />
|
||||||
|
<Box mb={4}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={formData.active !== false}
|
||||||
|
onChange={(e) => updateField('active', e.target.checked)}
|
||||||
|
sx={{ color: '#383838' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Aktiv"
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" color="text.secondary" ml={4}>
|
||||||
|
Die Kennzahl ist aktiv und wird angezeigt
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box mt={3}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={formData.mandatory || false}
|
||||||
|
onChange={(e) => updateField('mandatory', e.target.checked)}
|
||||||
|
sx={{ color: '#383838' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Erforderlich"
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" color="text.secondary" ml={4}>
|
||||||
|
Die Kennzahl erlaubt keine leeren Werte
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Divider sx={{ my: 3 }} />
|
||||||
|
|
||||||
|
{/* Hinweistext vor Beispielsätzen */}
|
||||||
|
<Box mb={2} p={2} sx={{ backgroundColor: '#fff8e1', border: '1px solid #ffe082', borderRadius: 2 }}>
|
||||||
|
<Typography variant="body1" sx={{ fontWeight: 'bold', mb: 1 }}>
|
||||||
|
Hinweis zur Trainingsqualität
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Damit das System neue Kennzahlen zuverlässig erkennen kann, empfehlen wir <strong>mindestens 5 Beispielsätze</strong> zu erstellen – je mehr, desto besser.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" mt={1}>
|
||||||
|
<strong>Wichtig:</strong> Neue Kennzahlen werden erst in PDF-Dokumenten erkannt, wenn Sie den Button <em>"Neu trainieren"</em> auf der Konfigurationsseite ausführen.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" mt={1}>
|
||||||
|
<strong>Tipp:</strong> Sie können jederzeit weitere Beispielsätze hinzufügen oder vorhandene in der Kennzahlenverwaltung bearbeiten.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Divider sx={{ my: 3 }} />
|
<Box mb={4}>
|
||||||
|
|
||||||
<Box mb={4}>
|
<Typography variant="h6" fontWeight="bold" mb={2}>
|
||||||
<Typography variant="h6" fontWeight="bold" mb={2}>
|
Beispielsätze
|
||||||
Format: {typeDisplayMapping[formData.type as keyof typeof typeDisplayMapping] || formData.type}
|
</Typography>
|
||||||
</Typography>
|
{(formData.examples || []).map((ex, idx) => (
|
||||||
<FormControl fullWidth sx={{ mb: 2 }}>
|
<Box key={idx} sx={{ mb: 2, border: '1px solid #ccc', p: 2, borderRadius: 1 }}>
|
||||||
<InputLabel>Typ</InputLabel>
|
<TextField
|
||||||
<Select
|
fullWidth
|
||||||
value={formData.type || 'string'}
|
multiline
|
||||||
label="Typ"
|
label={`Beispielsatz ${idx + 1}`}
|
||||||
onChange={(e) => updateField('type', e.target.value)}
|
placeholder="z. B. Die IRR beträgt 7,8 %"
|
||||||
|
value={ex.sentence}
|
||||||
|
onChange={(e) => updateExample(idx, 'sentence', e.target.value)}
|
||||||
|
required
|
||||||
|
sx={{ mb: 1 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Bezeichneter Wert im Satz"
|
||||||
|
placeholder="z. B. 7,8 %"
|
||||||
|
value={ex.value}
|
||||||
|
onChange={(e) => updateExample(idx, 'value', e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{(formData.examples?.length || 0) > 1 && (
|
||||||
|
<Button onClick={() => removeExample(idx)} sx={{ mt: 1 }} color="error">
|
||||||
|
Entfernen
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Button variant="outlined" onClick={addExample}>
|
||||||
|
+ Beispielsatz hinzufügen
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display="flex" justifyContent="flex-end" gap={2} mt={4}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleCancel}
|
||||||
|
disabled={isSaving}
|
||||||
|
sx={{
|
||||||
|
borderColor: "#383838",
|
||||||
|
color: "#383838",
|
||||||
|
"&:hover": { borderColor: "#2e2e2e", backgroundColor: "#f5f5f5" }
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value="string">Text</MenuItem>
|
Abbrechen
|
||||||
<MenuItem value="number">Zahl</MenuItem>
|
</Button>
|
||||||
<MenuItem value="date">Datum</MenuItem>
|
<Button
|
||||||
<MenuItem value="boolean">Ja/Nein</MenuItem>
|
variant="contained"
|
||||||
<MenuItem value="array">Liste (mehrfach)</MenuItem>
|
onClick={handleSave}
|
||||||
</Select>
|
disabled={isSaving || !formData.name?.trim()}
|
||||||
</FormControl>
|
sx={{
|
||||||
</Box>
|
backgroundColor: "#383838",
|
||||||
|
"&:hover": { backgroundColor: "#2e2e2e" },
|
||||||
<Divider sx={{ my: 3 }} />
|
}}
|
||||||
|
>
|
||||||
<Box mb={4}>
|
{isSaving ? (
|
||||||
<Typography variant="h6" fontWeight="bold" mb={2}>
|
<>
|
||||||
Synonyme & Übersetzungen
|
<CircularProgress size={16} sx={{ mr: 1, color: 'white' }} />
|
||||||
</Typography>
|
{mode === 'add' ? 'Hinzufügen...' : 'Speichern...'}
|
||||||
<TextField
|
</>
|
||||||
fullWidth
|
) : (
|
||||||
label="Übersetzung"
|
mode === 'add' ? 'Hinzufügen' : 'Speichern'
|
||||||
value={formData.translation || ''}
|
)}
|
||||||
onChange={(e) => updateField('translation', e.target.value)}
|
</Button>
|
||||||
helperText="z.B. Englische Übersetzung der Kennzahl"
|
</Box>
|
||||||
/>
|
</Paper>
|
||||||
</Box>
|
<Snackbar
|
||||||
|
open={snackbarOpen}
|
||||||
<Divider sx={{ my: 3 }} />
|
autoHideDuration={5000}
|
||||||
|
onClose={() => setSnackbarOpen(false)}
|
||||||
<Box mb={4}>
|
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||||
<Typography variant="h6" fontWeight="bold" mb={2}>
|
>
|
||||||
Beispiele von Kennzahl
|
<MuiAlert
|
||||||
</Typography>
|
elevation={6}
|
||||||
<TextField
|
variant="filled"
|
||||||
fullWidth
|
onClose={() => setSnackbarOpen(false)}
|
||||||
multiline
|
severity={snackbarSeverity}
|
||||||
rows={2}
|
sx={{ width: '100%', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
|
||||||
label="Beispiel"
|
|
||||||
value={formData.example || ''}
|
|
||||||
onChange={(e) => updateField('example', e.target.value)}
|
|
||||||
helperText="Beispielwerte für diese Kennzahl"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{mode === 'add' && (
|
|
||||||
<>
|
|
||||||
<Divider sx={{ my: 3 }} />
|
|
||||||
<Box mb={4}>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={formData.active !== false}
|
|
||||||
onChange={(e) => updateField('active', e.target.checked)}
|
|
||||||
sx={{ color: '#383838' }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Aktiv"
|
|
||||||
/>
|
|
||||||
<Typography variant="body2" color="text.secondary" ml={4}>
|
|
||||||
Die Kennzahl ist aktiv und wird angezeigt
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box display="flex" justifyContent="flex-end" gap={2} mt={4}>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
onClick={handleCancel}
|
|
||||||
disabled={isSaving}
|
|
||||||
sx={{
|
|
||||||
borderColor: "#383838",
|
|
||||||
color: "#383838",
|
|
||||||
"&:hover": { borderColor: "#2e2e2e", backgroundColor: "#f5f5f5" }
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Abbrechen
|
<span>{snackbarMessage}</span>
|
||||||
</Button>
|
<Button color="inherit" size="small" onClick={() => setSnackbarOpen(false)}>
|
||||||
<Button
|
OK
|
||||||
variant="contained"
|
</Button>
|
||||||
onClick={handleSave}
|
</MuiAlert>
|
||||||
disabled={isSaving || !formData.name?.trim()}
|
|
||||||
sx={{
|
</Snackbar>
|
||||||
backgroundColor: "#383838",
|
</>
|
||||||
"&:hover": { backgroundColor: "#2e2e2e" },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isSaving ? (
|
|
||||||
<>
|
|
||||||
<CircularProgress size={16} sx={{ mr: 1, color: 'white' }} />
|
|
||||||
{mode === 'add' ? 'Hinzufügen...' : 'Speichern...'}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
mode === 'add' ? 'Hinzufügen' : 'Speichern'
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSpacyEntry(formData: Partial<Kennzahl>) {
|
|
||||||
const text = formData.exampleText?.trim() || "";
|
|
||||||
const value = formData.markedValue?.trim() || "";
|
|
||||||
const label = formData.name?.trim().toUpperCase() || "";
|
|
||||||
|
|
||||||
const start = text.indexOf(value);
|
function generateSpacyEntries(formData: Partial<Kennzahl>) {
|
||||||
if (start === -1) {
|
const label = formData.name?.trim().toUpperCase() || "";
|
||||||
throw new Error("Bezeichneter Begriff wurde im Satz nicht gefunden.");
|
return (formData.examples || []).map(({ sentence, value }) => {
|
||||||
}
|
const start = sentence.indexOf(value);
|
||||||
|
if (start === -1) {
|
||||||
return {
|
throw new Error(`"${value}" nicht gefunden in Satz: "${sentence}"`);
|
||||||
text,
|
}
|
||||||
entities: [[start, start + value.length, label]],
|
return {
|
||||||
};
|
text: sentence,
|
||||||
|
entities: [[start, start + value.length, label]]
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// function appendAndDownload(newEntry: any, existing: any[] = []) {
|
|
||||||
// const updated = [...existing, newEntry];
|
|
||||||
// const blob = new Blob([JSON.stringify(updated, null, 2)], {
|
|
||||||
// type: "application/json",
|
|
||||||
// });
|
|
||||||
// saveAs(blob, "..\project\backend\spacy-service\spacy_training\annotation_data.json");
|
|
||||||
// }
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { KPIForm } from "../components/KPIForm";
|
||||||
import type { Kennzahl } from "../types/kpi";
|
import type { Kennzahl } from "../types/kpi";
|
||||||
import { API_HOST } from "../util/api";
|
import { API_HOST } from "../util/api";
|
||||||
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/config-add")({
|
export const Route = createFileRoute("/config-add")({
|
||||||
component: ConfigAddPage,
|
component: ConfigAddPage,
|
||||||
validateSearch: (search: Record<string, unknown>): { from?: string } => {
|
validateSearch: (search: Record<string, unknown>): { from?: string } => {
|
||||||
|
|
@ -47,19 +48,28 @@ function ConfigAddPage() {
|
||||||
body: JSON.stringify(kpiData),
|
body: JSON.stringify(kpiData),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate({ to: "/config" });
|
navigate({
|
||||||
|
to: "/config",
|
||||||
|
search: { success: "true", ...(from ? { from } : {}) },
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating KPI:', error);
|
console.error('Error creating KPI:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
navigate({ to: "/config" });
|
navigate({
|
||||||
|
to: "/config",
|
||||||
|
search: from ? { from } : undefined,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -83,7 +93,7 @@ function ConfigAddPage() {
|
||||||
>
|
>
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<IconButton onClick={handleBack}>
|
<IconButton onClick={handleBack}>
|
||||||
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }}/>
|
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h5" fontWeight="bold" ml={3}>
|
<Typography variant="h5" fontWeight="bold" ml={3}>
|
||||||
Neue Kennzahl hinzufügen
|
Neue Kennzahl hinzufügen
|
||||||
|
|
@ -93,6 +103,7 @@ function ConfigAddPage() {
|
||||||
|
|
||||||
<KPIForm
|
<KPIForm
|
||||||
mode="add"
|
mode="add"
|
||||||
|
key={Date.now()}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||||
import { Box, Typography, IconButton, Button, CircularProgress, Paper, Divider
|
import {
|
||||||
|
Box, Typography, IconButton, Button, CircularProgress, Paper, Divider
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
@ -38,6 +39,7 @@ function KPIDetailPage() {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(`${API_HOST}/api/kpi_setting/${kpiId}`);
|
const response = await fetch(`${API_HOST}/api/kpi_setting/${kpiId}`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (response.status === 404) {
|
if (response.status === 404) {
|
||||||
setError('KPI not found');
|
setError('KPI not found');
|
||||||
|
|
@ -72,7 +74,6 @@ function KPIDetailPage() {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedKennzahl = await response.json();
|
const updatedKennzahl = await response.json();
|
||||||
setKennzahl(updatedKennzahl);
|
setKennzahl(updatedKennzahl);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
|
|
@ -82,6 +83,7 @@ function KPIDetailPage() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
};
|
};
|
||||||
|
|
@ -153,7 +155,7 @@ function KPIDetailPage() {
|
||||||
>
|
>
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<IconButton onClick={handleBack}>
|
<IconButton onClick={handleBack}>
|
||||||
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }}/>
|
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h5" fontWeight="bold" ml={3}>
|
<Typography variant="h5" fontWeight="bold" ml={3}>
|
||||||
Detailansicht
|
Detailansicht
|
||||||
|
|
@ -192,18 +194,13 @@ function KPIDetailPage() {
|
||||||
<Divider sx={{ my: 3 }} />
|
<Divider sx={{ my: 3 }} />
|
||||||
|
|
||||||
<Box mb={4}>
|
<Box mb={4}>
|
||||||
<Typography variant="h6" fontWeight="bold" mb={2}>
|
<Typography variant="h6" fontWeight="bold" mb={1}>
|
||||||
Beschreibung
|
Erforderlich:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography variant="body1" sx={{ mb: 2, fontSize: 16 }}>
|
||||||
{kennzahl.description || "Zurzeit ist die Beschreibung der Kennzahl leer. Klicken Sie auf den Bearbeiten-Button, um die Beschreibung zu ergänzen."}
|
{kennzahl.mandatory ? 'Ja' : 'Nein'}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box mt={2}>
|
|
||||||
<Typography variant="body2" color="text.secondary">
|
|
||||||
<strong>Erforderlich:</strong> {kennzahl.mandatory ? 'Ja' : 'Nein'}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider sx={{ my: 3 }} />
|
<Divider sx={{ my: 3 }} />
|
||||||
|
|
@ -216,28 +213,6 @@ function KPIDetailPage() {
|
||||||
{typeDisplayMapping[kennzahl.type] || kennzahl.type}
|
{typeDisplayMapping[kennzahl.type] || kennzahl.type}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider sx={{ my: 3 }} />
|
|
||||||
|
|
||||||
<Box mb={4}>
|
|
||||||
<Typography variant="h6" fontWeight="bold" mb={2}>
|
|
||||||
Synonyme & Übersetzungen
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body1" color="text.secondary">
|
|
||||||
{kennzahl.translation || "Zurzeit gibt es keine Einträge für Synonyme und Übersetzungen der Kennzahl. Klicken Sie auf den Bearbeiten-Button, um die Liste zu ergänzen."}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Divider sx={{ my: 3 }} />
|
|
||||||
|
|
||||||
<Box mb={4}>
|
|
||||||
<Typography variant="h6" fontWeight="bold" mb={2}>
|
|
||||||
Beispiele von Kennzahl
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body1" color="text.secondary">
|
|
||||||
{kennzahl.example || "Zurzeit gibt es keine Beispiele der Kennzahl. Klicken Sie auf den Bearbeiten-Button, um die Liste zu ergänzen."}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
@ -264,7 +239,7 @@ function KPIDetailPage() {
|
||||||
>
|
>
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<IconButton onClick={handleBack}>
|
<IconButton onClick={handleBack}>
|
||||||
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }}/>
|
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h5" fontWeight="bold" ml={3}>
|
<Typography variant="h5" fontWeight="bold" ml={3}>
|
||||||
Kennzahl bearbeiten
|
Kennzahl bearbeiten
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,65 @@ import { Box, Button, IconButton, Typography } from "@mui/material";
|
||||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { ConfigTable } from "../components/ConfigTable";
|
import { ConfigTable } from "../components/ConfigTable";
|
||||||
|
import { API_HOST } from "../util/api";
|
||||||
|
import Snackbar from "@mui/material/Snackbar";
|
||||||
|
import MuiAlert from "@mui/material/Alert";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/config")({
|
export const Route = createFileRoute("/config")({
|
||||||
component: ConfigPage,
|
component: ConfigPage,
|
||||||
validateSearch: (search: Record<string, unknown>): { from?: string } => {
|
validateSearch: (search: Record<string, unknown>): { from?: string; success?: string } => {
|
||||||
const from = typeof search.from === "string" ? search.from : undefined;
|
return {
|
||||||
return { from };
|
from: typeof search.from === "string" ? search.from : undefined,
|
||||||
|
success: typeof search.success === "string" ? search.success : undefined
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function ConfigPage() {
|
function ConfigPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { from } = Route.useSearch();
|
const { from, success } = Route.useSearch();
|
||||||
|
const [snackbarOpen, setSnackbarOpen] = useState(success === "true");
|
||||||
|
const [snackbarMessage, setSnackbarMessage] = useState<string>("Beispielsätze gespeichert. Jetzt auf ‚Neu trainieren‘ klicken oder zuerst weitere Kennzahlen hinzufügen.");
|
||||||
|
const [trainingRunning, setTrainingRunning] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (success === "true") {
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate({
|
||||||
|
to: "/config",
|
||||||
|
search: from ? { from } : undefined,
|
||||||
|
replace: true
|
||||||
|
});
|
||||||
|
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}, [success]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkInitialTrainingStatus = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_HOST}/api/spacy/train-status`);
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.running) {
|
||||||
|
setTrainingRunning(true);
|
||||||
|
pollTrainingStatus();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Initiale Trainingsstatus-Abfrage fehlgeschlagen", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkInitialTrainingStatus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleAddNewKPI = () => {
|
const handleAddNewKPI = () => {
|
||||||
navigate({
|
navigate({
|
||||||
|
|
@ -31,46 +78,128 @@ function ConfigPage() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTriggerTraining = () => {
|
||||||
|
setTrainingRunning(true);
|
||||||
|
setSnackbarMessage("Training wurde gestartet.");
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
|
||||||
|
fetch(`${API_HOST}/api/spacy/train`, {
|
||||||
|
method: "POST",
|
||||||
|
}).catch(err => {
|
||||||
|
setSnackbarMessage("Fehler beim Starten des Trainings.");
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
pollTrainingStatus(); // Starte Überwachung
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const pollTrainingStatus = () => {
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_HOST}/api/spacy/train-status`);
|
||||||
|
const data = await res.json();
|
||||||
|
console.log("Trainingsstatus:", data); //Debug-Ausgabe
|
||||||
|
if (!data.running) {
|
||||||
|
clearInterval(interval);
|
||||||
|
console.log("Training abgeschlossen – Snackbar wird ausgelöst");
|
||||||
|
setSnackbarMessage("Training abgeschlossen!");
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
|
||||||
|
setTrainingRunning(false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Polling-Fehler:", err);
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<>
|
||||||
minHeight="100vh"
|
|
||||||
width="100vw"
|
|
||||||
bgcolor="white"
|
|
||||||
display="flex"
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="center"
|
|
||||||
pt={3}
|
|
||||||
pb={4}
|
|
||||||
>
|
|
||||||
<Box
|
<Box
|
||||||
width="100%"
|
minHeight="100vh"
|
||||||
|
width="100vw"
|
||||||
|
bgcolor="white"
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="space-between"
|
flexDirection="column"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
px={4}
|
pt={3}
|
||||||
|
pb={4}
|
||||||
>
|
>
|
||||||
<Box display="flex" alignItems="center">
|
<Box
|
||||||
<IconButton onClick={handleBack}>
|
width="100%"
|
||||||
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }}/>
|
display="flex"
|
||||||
</IconButton>
|
justifyContent="space-between"
|
||||||
<Typography variant="h5" fontWeight="bold" ml={3}>
|
alignItems="center"
|
||||||
Konfiguration der Kennzahlen
|
px={4}
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleAddNewKPI}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: "#383838",
|
|
||||||
"&:hover": { backgroundColor: "#2e2e2e" },
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Neue Kennzahl hinzufügen
|
{/* Linke Seite: Zurück & Titel */}
|
||||||
</Button>
|
<Box display="flex" alignItems="center">
|
||||||
|
<IconButton onClick={handleBack}>
|
||||||
|
<ArrowBackIcon fontSize="large" sx={{ color: '#383838' }} />
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="h5" fontWeight="bold" ml={3}>
|
||||||
|
Konfiguration der Kennzahlen
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Rechte Seite: Buttons */}
|
||||||
|
<Box display="flex" gap={2}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleTriggerTraining}
|
||||||
|
disabled={trainingRunning}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#383838",
|
||||||
|
"&:hover": { backgroundColor: "#2e2e2e" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{trainingRunning ? (
|
||||||
|
<CircularProgress size={24} sx={{ color: "white" }} />
|
||||||
|
) : (
|
||||||
|
"Neu trainieren"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleAddNewKPI}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#383838",
|
||||||
|
"&:hover": { backgroundColor: "#2e2e2e" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Neue Kennzahl hinzufügen
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Tabelle */}
|
||||||
|
<Box sx={{ width: "100%", mt: 4, display: "flex", justifyContent: "center" }}>
|
||||||
|
<ConfigTable from={from} />
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ width: "100%", mt: 4, display: "flex", justifyContent: "center" }}>
|
|
||||||
<ConfigTable from={from} />
|
{/* Snackbar */}
|
||||||
</Box>
|
<Snackbar
|
||||||
</Box>
|
open={snackbarOpen}
|
||||||
|
autoHideDuration={4000}
|
||||||
|
onClose={() => setSnackbarOpen(false)}
|
||||||
|
anchorOrigin={{ vertical: "top", horizontal: "center" }}
|
||||||
|
>
|
||||||
|
<MuiAlert
|
||||||
|
elevation={6}
|
||||||
|
variant="filled"
|
||||||
|
onClose={() => setSnackbarOpen(false)}
|
||||||
|
severity="success"
|
||||||
|
sx={{ width: "100%" }}
|
||||||
|
>
|
||||||
|
{snackbarMessage}
|
||||||
|
</MuiAlert>
|
||||||
|
</Snackbar>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,10 @@ export interface Kennzahl {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
exampleText?: string;
|
exampleText?: string;
|
||||||
markedValue?: string;
|
markedValue?: string;
|
||||||
|
examples?: {
|
||||||
|
sentence: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const typeDisplayMapping: Record<string, string> = {
|
export const typeDisplayMapping: Record<string, string> = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue