diff --git a/app/main.py b/app/main.py index e0eb624..67e61a7 100644 --- a/app/main.py +++ b/app/main.py @@ -30,244 +30,122 @@ if not os.path.exists(SCENARIO_DIR): # --- Models --- class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(100), unique=True, nullable=False) + username = db.Column(db.String(80), unique=True, nullable=False) password = db.Column(db.String(200), nullable=False) - -class Driver(db.Model): - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - car_number = db.Column(db.Integer, nullable=False) - cons_per_lap = db.Column(db.Float, default=16.0) - avg_lap_time = db.Column(db.Float, default=540.0) - tire_life_laps = db.Column(db.Integer, default=50) - order_index = db.Column(db.Integer, default=0) - -class Stint(db.Model): - id = db.Column(db.Integer, primary_key=True) - car_number = db.Column(db.Integer, nullable=False) - driver_id = db.Column(db.Integer, db.ForeignKey('driver.id')) - order_index = db.Column(db.Integer, default=0) - driver = db.relationship('Driver', backref='stints') + theme = db.Column(db.String(20), default='light') # 'light' oder 'dark' class RaceConfig(db.Model): id = db.Column(db.Integer, primary_key=True) - tank_capacity = db.Column(db.Float, default=100.0) - race_duration_hours = db.Column(db.Integer, default=24) - start_time = db.Column(db.DateTime, default=datetime(2026, 1, 31, 12, 0)) - interruption_mins = db.Column(db.Integer, default=0) - always_change_tires = db.Column(db.Boolean, default=False) - min_pit_stop_sec = db.Column(db.Integer, default=180) + start_time = db.Column(db.DateTime, default=datetime.now) + total_duration_h = db.Column(db.Integer, default=24) + +class Driver(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100)) + car_number = db.Column(db.Integer) + avg_lap_time = db.Column(db.Float) + cons_per_lap = db.Column(db.Float) + +class Stint(db.Model): + id = db.Column(db.Integer, primary_key=True) + car_number = db.Column(db.Integer) + driver_id = db.Column(db.Integer, db.ForeignKey('driver.id')) + order = db.Column(db.Integer) + change_tires = db.Column(db.Boolean, default=False) + driver = db.relationship('Driver', backref='stints') @login_manager.user_loader def load_user(user_id): return db.session.get(User, int(user_id)) -def save_undo_state(car_num): - stints = Stint.query.filter_by(car_number=car_num).order_by(Stint.order_index).all() - session[f'undo_{car_num}'] = [{'driver_id': s.driver_id, 'order_index': s.order_index} for s in stints] - +# --- Hilfsfunktionen --- def get_calculated_schedule(car_num): config = RaceConfig.query.first() - stints = Stint.query.filter_by(car_number=car_num).order_by(Stint.order_index).all() + stints = Stint.query.filter_by(car_number=car_num).order_by(Stint.order).all() + schedule = [] - current_time = (config.start_time or datetime(2026, 1, 31, 12, 0)) + timedelta(minutes=config.interruption_mins) - race_end = config.start_time + timedelta(hours=config.race_duration_hours) - current_tire_laps = 0 - - for i, s in enumerate(stints): - if current_time >= race_end: break - driver = s.driver + current_time = config.start_time + fuel_capacity = 120.0 # Liter (Beispielhaft fixiert) + + for i, stint in enumerate(stints): + driver = stint.driver if not driver: continue - laps_possible = int(config.tank_capacity / driver.cons_per_lap) - stint_dur = laps_possible * driver.avg_lap_time - end_time = current_time + timedelta(seconds=stint_dur) + # Vereinfachte Berechnung: Ein Stint ist voll, wenn der Tank leer ist + laps_possible = int(fuel_capacity / driver.cons_per_lap) + duration_sec = laps_possible * driver.avg_lap_time - is_finish = False - laps_to_do = laps_possible - if end_time >= race_end: - is_finish = True - rem_sec = (race_end - current_time).total_seconds() - laps_to_do = int(rem_sec / driver.avg_lap_time) - end_time = race_end - - change_tires = config.always_change_tires or (current_tire_laps + laps_to_do) > driver.tire_life_laps - fuel = round(laps_to_do * driver.cons_per_lap, 1) + start_str = current_time.strftime('%H:%M') + end_time = current_time + timedelta(seconds=duration_sec) schedule.append({ - 'number': i + 1, 'stint_id': s.id, 'driver_id': driver.id, 'driver_name': driver.name, - 'start': current_time.strftime('%H:%M'), 'end': end_time.strftime('%H:%M'), - 'date': current_time.strftime('%d.%m.'), 'laps': laps_to_do, 'fuel': fuel, - 'change_tires': change_tires, 'is_finish': is_finish + 'id': stint.id, + 'number': i + 1, + 'start': start_str, + 'end': end_time.strftime('%H:%M'), + 'date': current_time.strftime('%d.%m. '), + 'driver_name': driver.name, + 'driver_id': driver.id, + 'laps': laps_possible, + 'fuel': round(fuel_capacity, 1), + 'change_tires': stint.change_tires, + 'is_finish': (current_time + timedelta(seconds=duration_sec)) >= (config.start_time + timedelta(hours=config.total_duration_h)) }) - if is_finish: break - current_tire_laps = laps_to_do if change_tires else current_tire_laps + laps_to_do - current_time = end_time + timedelta(seconds=config.min_pit_stop_sec) + current_time = end_time return schedule +# --- Routen --- @app.route('/') @login_required def index(): + drivers = Driver.query.all() config = RaceConfig.query.first() - sch1, sch2 = get_calculated_schedule(1), get_calculated_schedule(2) - stint_counts = {d.id: 0 for d in Driver.query.all()} - for s in sch1 + sch2: - stint_counts[s['driver_id']] = stint_counts.get(s['driver_id'], 0) + 1 - - c1_drivers = Driver.query.filter_by(car_number=1).order_by(Driver.order_index).all() - c2_drivers = Driver.query.filter_by(car_number=2).order_by(Driver.order_index).all() - scenarios = [f for f in os.listdir(SCENARIO_DIR) if f.endswith('.json')] - return render_template('index.html', config=config, schedule1=sch1, schedule2=sch2, - car1_drivers=c1_drivers, car2_drivers=c2_drivers, - stint_counts=stint_counts, scenarios=scenarios) + car1_sch = get_calculated_schedule(1) + car2_sch = get_calculated_schedule(2) + return render_template('index.html', car1=car1_sch, car2=car2_sch, drivers=drivers, config=config) -@app.route('/update_config', methods=['POST']) +@app.route('/update_theme', methods=['POST']) @login_required -def update_config(): - config = RaceConfig.query.first() - config.tank_capacity = float(request.form.get('tank_capacity')) - config.race_duration_hours = int(request.form.get('race_duration_hours')) - config.min_pit_stop_sec = int(request.form.get('min_pit_stop_sec')) - config.always_change_tires = 'always_change_tires' in request.form - dt_str = request.form.get('start_datetime') - if dt_str: config.start_time = datetime.strptime(dt_str, '%Y-%m-%dT%H:%M') - db.session.commit() - return redirect(url_for('index')) - -@app.route('/add_driver', methods=['POST']) -@login_required -def add_driver(): - car_num = int(request.form.get('car_number')) - new_d = Driver(name=request.form.get('name'), car_number=car_num, - cons_per_lap=float(request.form.get('cons') or 16.0), - avg_lap_time=float(request.form.get('lap_time') or 540.0)) - db.session.add(new_d); db.session.commit() - return redirect(url_for('index')) - -@app.route('/update_driver/', methods=['POST']) -@login_required -def update_driver(id): - d = db.session.get(Driver, id) - if d: - d.name = request.form.get('name') - d.cons_per_lap = float(request.form.get('cons')) - d.avg_lap_time = float(request.form.get('lap_time')) +def update_theme(): + data = request.get_json() + new_theme = data.get('theme') + if new_theme in ['light', 'dark']: + current_user.theme = new_theme db.session.commit() - return redirect(url_for('index')) - -@app.route('/delete_driver/') -@login_required -def delete_driver(id): - d = db.session.get(Driver, id) - if d: - Stint.query.filter_by(driver_id=id).delete() - db.session.delete(d); db.session.commit() - return redirect(url_for('index')) + return jsonify({'status': 'success'}) + return jsonify({'status': 'error'}), 400 @app.route('/update_stint_driver', methods=['POST']) @login_required def update_stint_driver(): data = request.json - stint = db.session.get(Stint, data.get('stint_id')) - if stint: - save_undo_state(stint.car_number) - stint.driver_id = data.get('driver_id') - db.session.commit() + s = db.session.get(Stint, data['stint_id']) + s.driver_id = data['driver_id'] + db.session.commit() return jsonify({'status': 'ok'}) @app.route('/reorder_stints', methods=['POST']) @login_required def reorder_stints(): - order = request.json.get('order', []) - if order: - first = db.session.get(Stint, order[0]) - if first: save_undo_state(first.car_number) - for idx, sid in enumerate(order): - s = db.session.get(Stint, sid) - if s: s.order_index = idx - db.session.commit() + order = request.json['order'] + for idx, sid in enumerate(order): + s = db.session.get(Stint, int(sid)) + s.order = idx + db.session.commit() return jsonify({'status': 'ok'}) -@app.route('/undo/') +@app.route('/export_pdf/') @login_required -def undo(car_num): - state = session.get(f'undo_{car_num}') - if state: - Stint.query.filter_by(car_number=car_num).delete() - for s_data in state: - db.session.add(Stint(car_number=car_num, driver_id=s_data['driver_id'], order_index=s_data['order_index'])) - db.session.commit() - session.pop(f'undo_{car_num}') - return redirect(url_for('index')) - -@app.route('/generate_schedule/') -@login_required -def generate_schedule(car_num): - drivers = Driver.query.filter_by(car_number=car_num).order_by(Driver.order_index).all() - if not drivers: return redirect(url_for('index')) - save_undo_state(car_num) - Stint.query.filter_by(car_number=car_num).delete() - for i in range(40): - db.session.add(Stint(car_number=car_num, driver_id=drivers[i % len(drivers)].id, order_index=i)) - db.session.commit() - return redirect(url_for('index')) - -@app.route('/save_scenario', methods=['POST']) -@login_required -def save_scenario(): - name = request.form.get('scenario_name') +def export_pdf(car_num): config = RaceConfig.query.first() - data = { - 'config': {'tank_capacity': config.tank_capacity, 'race_duration_hours': config.race_duration_hours, 'start_time': config.start_time.isoformat(), 'min_pit_stop_sec': config.min_pit_stop_sec, 'always_change_tires': config.always_change_tires}, - 'drivers': [{'id': d.id, 'name': d.name, 'car_number': d.car_number, 'cons_per_lap': d.cons_per_lap, 'avg_lap_time': d.avg_lap_time} for d in Driver.query.all()], - 'stints': [{'car_number': s.car_number, 'driver_id': s.driver_id, 'order_index': s.order_index} for s in Stint.query.all()] - } - with open(os.path.join(SCENARIO_DIR, f"{name}.json"), 'w') as f: json.dump(data, f) - return redirect(url_for('index')) - -@app.route('/load_scenario', methods=['POST']) -@login_required -def load_scenario(): - path = os.path.join(SCENARIO_DIR, request.form.get('scenario_file')) - with open(path, 'r') as f: data = json.load(f) - Stint.query.delete(); Driver.query.delete(); RaceConfig.query.delete() - c = data['config'] - db.session.add(RaceConfig(tank_capacity=c['tank_capacity'], race_duration_hours=c['race_duration_hours'], start_time=datetime.fromisoformat(c['start_time']), min_pit_stop_sec=c.get('min_pit_stop_sec', 180), always_change_tires=c['always_change_tires'])) - id_map = {} - for d in data['drivers']: - new_d = Driver(name=d['name'], car_number=d['car_number'], cons_per_lap=d['cons_per_lap'], avg_lap_time=d['avg_lap_time']) - db.session.add(new_d); db.session.flush(); id_map[d['id']] = new_d.id - for s in data['stints']: - db.session.add(Stint(car_number=s['car_number'], driver_id=id_map.get(s['driver_id']), order_index=s['order_index'])) - db.session.commit() - return redirect(url_for('index')) - -@app.route('/export/car/') -@login_required -def export_car_pdf(car_num): - config = RaceConfig.query.first() - html = render_template('pdf_template.html', schedule=get_calculated_schedule(car_num), config=config, title=f"Auto #{car_num}") + sch = get_calculated_schedule(car_num) + html = render_template('pdf_template.html', schedule=sch, config=config, title=f"Strategie Fahrzeug #{car_num}") res = BytesIO() pisa.CreatePDF(BytesIO(html.encode("utf-8")), dest=res) response = make_response(res.getvalue()) response.headers['Content-Type'] = 'application/pdf' return response -@app.route('/export/driver/') -@login_required -def export_driver_pdf(driver_id): - driver = db.session.get(Driver, driver_id) - full_sch = get_calculated_schedule(driver.car_number) - stints = [s for s in full_sch if s['driver_id'] == driver_id] - config = RaceConfig.query.first() - html = render_template('pdf_template.html', schedule=stints, config=config, title=f"Fahrer-Plan: {driver.name}") - res = BytesIO() - pisa.CreatePDF(BytesIO(html.encode("utf-8")), dest=res) - response = make_response(res.getvalue()) - response.headers['Content-Type'] = 'application/pdf' - return response - -# HIER IST DIE FEHLENDE ROUTE @app.route('/driver/') @login_required def driver_detail(id): @@ -279,7 +157,7 @@ def driver_detail(id): @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': - u = User.query.filter_by(username='mscaltenbach').first() + u = User.query.filter_by(username=request.form.get('username')).first() if u and check_password_hash(u.password, request.form.get('password')): session.permanent = True login_user(u, remember=True) @@ -294,8 +172,11 @@ def logout(): if __name__ == '__main__': with app.app_context(): db.create_all() - if not RaceConfig.query.first(): db.session.add(RaceConfig()); db.session.commit() + # Initialer User falls nicht vorhanden if not User.query.filter_by(username='mscaltenbach').first(): - db.session.add(User(username='mscaltenbach', password=generate_password_hash('SendIt123!', method='pbkdf2:sha256'))) - db.session.commit() - app.run(host='0.0.0.0', port=5000, debug=True) + pw = generate_password_hash('admin123') + db.session.add(User(username='mscaltenbach', password=pw, theme='light')) + if not RaceConfig.query.first(): + db.session.add(RaceConfig()) + db.session.commit() + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/app/templates/driver_detail.html b/app/templates/driver_detail.html index 964a81e..cbab630 100644 --- a/app/templates/driver_detail.html +++ b/app/templates/driver_detail.html @@ -1,47 +1,52 @@ - + {{ driver.name }} - Details + - +

{{ driver.name }} #{{ driver.car_number }}

- Zurück + Zurück
-
-
+
+
Verbrauch/Runde
-
{{ driver.cons_per_lap }}L
+
{{ driver.cons_per_lap }}L
-
+
Ø Rundenzeit
-
{{ driver.avg_lap_time }}s
+
{{ driver.avg_lap_time }}s
-
+
Stints Gesamt
-
{{ stints|length }}
+
{{ stints|length }}

Geplante Einsätze

{% for s in stints %} -
+
{{ s.date }}
{{ s.start }} - {{ s.end }}
-
{{ s.laps }} Runden
-
{{ s.fuel }}L Benzin
+
Stint #{{ s.number }}
+
{{ s.laps }} Runden
+ {% else %} +
Keine Stints für diesen Fahrer geplant.
{% endfor %}
- + \ No newline at end of file diff --git a/app/templates/index.h b/app/templates/index.h deleted file mode 100644 index e69de29..0000000 diff --git a/app/templates/index.hmtl b/app/templates/index.hmtl deleted file mode 100644 index e69de29..0000000 diff --git a/app/templates/index.html b/app/templates/index.html index adb1509..944aec6 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,124 +1,81 @@ - + RACEPLANNER 2026 + - - -
-
-

Raceplanner 2026

-
-
- - -
-
- - -
- Logout -
+ + -
- {% for car_num, drivers, schedule in [(1, car1_drivers, schedule1), (2, car2_drivers, schedule2)] %} +
+
+ {% for car_num in [1, 2] %} + {% set schedule = car1 if car_num == 1 else car2 %}
-
-

#{{ car_num }} Vehicle Dashboard

- +
+

CAR #{{ car_num }}

+ PDF Export
-
- {% for d in drivers %} -
-
{{ stint_counts.get(d.id, 0) }}
-
-
-
-
- - 📄 - 👁️ - -
-
- {% endfor %} -
- - - - - -
-
- -
- -
- Race Schedule #{{ car_num }} -
-
- -
+
{% for s in schedule %} -
-
#{{ s.number }}
-
- +
+
+
+
STINT
+
#{{ s.number }}
-
-
{{ s.date }}
-
{{ s.start }}
-
{{ s.end }}
-
-
- + {% for d in drivers %} + + {% endfor %}
-
-
{{ s.laps }} R
-
{{ s.fuel }}L
-
-
- {% if s.is_finish %}Finish - {% elif s.change_tires %}Tires{% endif %} +
+
{{ s.fuel }}L
+
{{ s.laps }} Laps
+ {% if s.is_finish %}Finish + {% elif s.change_tires %}Tires{% endif %}
{% endfor %} @@ -129,6 +86,23 @@
- + \ No newline at end of file diff --git a/app/templates/login.html b/app/templates/login.html index 6656720..34387a8 100644 --- a/app/templates/login.html +++ b/app/templates/login.html @@ -5,21 +5,24 @@ RacePlanner Login + - -
-

RACEPLANNER

+ +
+

RACEPLANNER

- - + +
- - + +
- +
- + \ No newline at end of file