all
BIN
Silvester2021.pdf
Normal file
890
silvester.py
Normal file
@ -0,0 +1,890 @@
|
||||
# %%
|
||||
from gurobipy import *
|
||||
import json
|
||||
import random
|
||||
|
||||
|
||||
MEN = ["Herbert", "Christoph", "Martin", "Christian", "Marlon"]
|
||||
WOMEN = ["Gaby", "Teresa", "Iris", "Anne-Christin", "Jenni", "Birgit", "Johanna"]
|
||||
NAMES = MEN + WOMEN
|
||||
|
||||
PAIRS_2018 = [
|
||||
("Martin", "Birgit"),
|
||||
("Birgit", "Teresa"),
|
||||
("Teresa", "Herbert"),
|
||||
("Herbert", "Peter"),
|
||||
("Peter", "Iris"),
|
||||
("Iris", "Gaby"),
|
||||
("Gaby", "Christoph"),
|
||||
("Christoph", "Martin"),
|
||||
("Christian", "Anne-Christin"),
|
||||
("Anne-Christin", "Jenni"),
|
||||
]
|
||||
PAIRS_2019 = [
|
||||
("Peter", "Herbert"),
|
||||
("Herbert", "Gaby"),
|
||||
("Gaby", "Christian"),
|
||||
("Christian", "Renate"),
|
||||
("Renate", "Anne-Christin"),
|
||||
("Anne-Christin", "Martin"),
|
||||
("Martin", "Iris"),
|
||||
("Iris", "Teresa"),
|
||||
("Teresa", "Jenni"),
|
||||
("Jenni", "Christoph"),
|
||||
("Christoph", "Birgit"),
|
||||
("Birgit", "Peter"),
|
||||
]
|
||||
PAIRS_2021 = [
|
||||
("Peter", "Teresa"),
|
||||
("Teresa", "Christian"),
|
||||
("Christian", "Birgit"),
|
||||
("Birgit", "Jenni"),
|
||||
("Jenni", "Iris"),
|
||||
("Iris", "Herbert"),
|
||||
("Herbert", "Martin"),
|
||||
("Martin", "Christoph"),
|
||||
("Christoph", "Gaby"),
|
||||
("Gaby", "Anne-Christin"),
|
||||
("Anne-Christin", "Peter"),
|
||||
]
|
||||
PAIRS_2022 = [
|
||||
("Peter", "Christian"),
|
||||
("Christian", "Herbert"),
|
||||
("Herbert", "Anne-Christin"),
|
||||
("Anne-Christin", "Birgit"),
|
||||
("Birgit", "Iris"),
|
||||
("Iris", "Christoph"),
|
||||
("Christoph", "Teresa"),
|
||||
("Teresa", "Gaby"),
|
||||
("Gaby", "Martin"),
|
||||
("Martin", "Jenni"),
|
||||
("Jenni", "Peter"),
|
||||
]
|
||||
PAIRS_2022 = [
|
||||
("Peter", "Christian"),
|
||||
("Christian", "Herbert"),
|
||||
("Herbert", "Anne-Christin"),
|
||||
("Anne-Christin", "Birgit"),
|
||||
("Birgit", "Iris"),
|
||||
("Iris", "Christoph"),
|
||||
("Christoph", "Teresa"),
|
||||
("Teresa", "Gaby"),
|
||||
("Gaby", "Martin"),
|
||||
("Martin", "Jenni"),
|
||||
("Jenni", "Peter"),
|
||||
]
|
||||
PAIRS_2023 = [
|
||||
("Herbert", "Jenni"),
|
||||
("Christoph", "Herbert"),
|
||||
("Martin", "Johanna"),
|
||||
("Christian", "Christoph"),
|
||||
("Gaby", "Birgit"),
|
||||
("Teresa", "Anne-Christin"),
|
||||
("Iris", "Christian"),
|
||||
("Anne-Christin", "Iris"),
|
||||
("Jenni", "Gaby"),
|
||||
("Birgit", "Martin"),
|
||||
("Johanna", "Teresa"),
|
||||
]
|
||||
|
||||
PAIRS_2024 = [
|
||||
("Johanna", "Herbert"),
|
||||
("Herbert", "Teresa"),
|
||||
("Teresa", "Iris"),
|
||||
("Iris", "Martin"),
|
||||
("Martin", "Anne-Christin"),
|
||||
("Anne-Christin", "Jenni"),
|
||||
("Jenni", "Christoph"),
|
||||
("Christoph", "Birgit"),
|
||||
("Birgit", "Marlon"),
|
||||
("Marlon", "Gaby"),
|
||||
("Gaby", "Christian"),
|
||||
("Christian", "Johanna"),
|
||||
]
|
||||
|
||||
|
||||
SEX_PAIRS = [
|
||||
("Herbert", "Birgit"),
|
||||
("Anne-Christin", "Christoph"),
|
||||
("Christian", "Jenni"),
|
||||
("Iris", "Jenni"),
|
||||
("Iris", "Christian"),
|
||||
# ("Teresa", "Martin"),
|
||||
# ("Gaby", "Peter"),
|
||||
("Johanna", "Christoph"),
|
||||
("Johanna", "Anne-Christin"),
|
||||
("Marlon", "Christoph"),
|
||||
("Marlon", "Anne-Christin"),
|
||||
("Marlon", "Johanna"),
|
||||
|
||||
]
|
||||
|
||||
|
||||
# %%
|
||||
|
||||
m = Model("Silvester")
|
||||
|
||||
x = {}
|
||||
y = {}
|
||||
|
||||
for i in NAMES:
|
||||
for j in NAMES:
|
||||
x[i, j] = m.addVar(vtype=GRB.BINARY)
|
||||
if i == j:
|
||||
x[i, j].ub = 0
|
||||
|
||||
sex_penalty = m.addVar(vtype=GRB.CONTINUOUS)
|
||||
old_penalty_2018 = m.addVar(vtype=GRB.CONTINUOUS)
|
||||
old_penalty_2019 = m.addVar(vtype=GRB.CONTINUOUS)
|
||||
old_penalty_2021 = m.addVar(vtype=GRB.CONTINUOUS)
|
||||
old_penalty_2022 = m.addVar(vtype=GRB.CONTINUOUS)
|
||||
old_penalty_2023 = m.addVar(vtype=GRB.CONTINUOUS)
|
||||
old_penalty_2024 = m.addVar(vtype=GRB.CONTINUOUS)
|
||||
old_penalty_2024_reverse = m.addVar(vtype=GRB.CONTINUOUS)
|
||||
pair_penalty = m.addVar(vtype=GRB.CONTINUOUS)
|
||||
|
||||
m.update()
|
||||
print("Finished Variable set-up")
|
||||
|
||||
|
||||
# kein gegenseitiges Beschenken
|
||||
m.addConstrs(x[i, j] + x[j, i] <= 1 for i in NAMES for j in NAMES)
|
||||
|
||||
# falls ein Teilnehmer im nächsten Jahr ausfällt
|
||||
m.addConstrs(
|
||||
x[i, j] + x[j, k] + x[k, i] <= 2 for i in NAMES for j in NAMES for k in NAMES
|
||||
)
|
||||
|
||||
|
||||
# add constraints
|
||||
m.addConstr(
|
||||
quicksum(x[p] + x[p[1], p[0]] for p in SEX_PAIRS if p[0] in NAMES and p[1] in NAMES)
|
||||
<= pair_penalty
|
||||
)
|
||||
m.addConstr(
|
||||
quicksum(x[i, j] for i in MEN for j in MEN)
|
||||
+ quicksum(x[i, j] for i in WOMEN for j in WOMEN)
|
||||
<= sex_penalty
|
||||
)
|
||||
|
||||
m.addConstr(
|
||||
quicksum(x[p[0], p[1]] for p in PAIRS_2018 if p[0] in NAMES and p[1] in NAMES)
|
||||
<= old_penalty_2018
|
||||
)
|
||||
m.addConstr(
|
||||
quicksum(x[p[0], p[1]] for p in PAIRS_2019 if p[0] in NAMES and p[1] in NAMES)
|
||||
<= old_penalty_2019
|
||||
)
|
||||
m.addConstr(
|
||||
quicksum(x[p[0], p[1]] for p in PAIRS_2021 if p[0] in NAMES and p[1] in NAMES)
|
||||
<= old_penalty_2021
|
||||
)
|
||||
m.addConstr(
|
||||
quicksum(x[p[0], p[1]] for p in PAIRS_2022 if p[0] in NAMES and p[1] in NAMES)
|
||||
<= old_penalty_2022
|
||||
)
|
||||
m.addConstr(
|
||||
quicksum(x[p[0], p[1]] for p in PAIRS_2023 if p[0] in NAMES and p[1] in NAMES)
|
||||
<= old_penalty_2023
|
||||
)
|
||||
m.addConstr(
|
||||
quicksum(x[p[0], p[1]] for p in PAIRS_2024 if p[0] in NAMES and p[1] in NAMES)
|
||||
<= old_penalty_2024
|
||||
)
|
||||
|
||||
# kein zurueckschenken
|
||||
# m.addConstr(
|
||||
# quicksum(x[p[1], p[0]] for p in PAIRS_2018 if p[0] in NAMES and p[1] in NAMES)
|
||||
# <= old_penalty_2018
|
||||
# )
|
||||
# m.addConstr(
|
||||
# quicksum(x[p[1], p[0]] for p in PAIRS_2019 if p[0] in NAMES and p[1] in NAMES)
|
||||
# <= old_penalty_2019
|
||||
# )
|
||||
# m.addConstr(
|
||||
# quicksum(x[p[1], p[0]] for p in PAIRS_2021 if p[0] in NAMES and p[1] in NAMES)
|
||||
# <= old_penalty_2021
|
||||
# )
|
||||
# m.addConstr(
|
||||
# quicksum(x[p[1], p[0]] for p in PAIRS_2022 if p[0] in NAMES and p[1] in NAMES)
|
||||
# <= old_penalty_2022
|
||||
# )
|
||||
# m.addConstr(
|
||||
# quicksum(x[p[1], p[0]] for p in PAIRS_2023 if p[0] in NAMES and p[1] in NAMES)
|
||||
# <= old_penalty_2023
|
||||
# )
|
||||
m.addConstr(
|
||||
quicksum(x[p[1], p[0]] for p in PAIRS_2024 if p[0] in NAMES and p[1] in NAMES)
|
||||
<= old_penalty_2024_reverse
|
||||
)
|
||||
# m.addConstr(x['Teresa','Christoph'] == 0)
|
||||
|
||||
|
||||
|
||||
# MARLON
|
||||
# allowed_names = ['Birgit', 'Gaby', 'Herbert']
|
||||
# m.addConstr(quicksum(x["Johanna", name] for name in allowed_names) == 1)
|
||||
# m.addConstr(quicksum(x["Marlon", name] for name in allowed_names) == 1)
|
||||
|
||||
|
||||
for i in NAMES:
|
||||
m.addConstr(quicksum(x[i, j] for j in NAMES) == 1)
|
||||
m.addConstr(quicksum(x[j, i] for j in NAMES) == 1)
|
||||
|
||||
# Set objective
|
||||
m.modelSense = GRB.MINIMIZE
|
||||
m.setObjective(
|
||||
0.01 * quicksum(random.uniform(0, 1) * x[key] for key in x.keys())
|
||||
+ 10
|
||||
* (
|
||||
# 5 * sex_penalty
|
||||
+ 100 * pair_penalty
|
||||
+ 5000 * old_penalty_2024
|
||||
+ 100 * old_penalty_2024_reverse
|
||||
+ 1000 * old_penalty_2023
|
||||
+ 500 * old_penalty_2022
|
||||
+ 100 * old_penalty_2021
|
||||
+ 50 * old_penalty_2019
|
||||
+ old_penalty_2018
|
||||
)
|
||||
)
|
||||
|
||||
print("Finished Contraint set-up")
|
||||
|
||||
def generate_html_report(PAIRS, c, violations_data, obj_data):
|
||||
"""Generate a beautiful HTML report for the solution"""
|
||||
from datetime import datetime
|
||||
|
||||
current_time = datetime.now().strftime('%d.%m.%Y um %H:%M:%S')
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Silvester Wichteln 2025 - Lösung #{c}</title>
|
||||
<style>
|
||||
* {{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}}
|
||||
body {{
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}}
|
||||
.container {{
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
overflow: hidden;
|
||||
}}
|
||||
header {{
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}}
|
||||
h1 {{
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
}}
|
||||
.subtitle {{
|
||||
font-size: 1.2em;
|
||||
opacity: 0.9;
|
||||
}}
|
||||
.content {{
|
||||
padding: 40px;
|
||||
}}
|
||||
section {{
|
||||
margin-bottom: 40px;
|
||||
}}
|
||||
h2 {{
|
||||
color: #667eea;
|
||||
border-bottom: 3px solid #667eea;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.8em;
|
||||
}}
|
||||
.pairs-grid {{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}}
|
||||
.pair-card {{
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
transition: transform 0.3s;
|
||||
}}
|
||||
.pair-card:hover {{
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}}
|
||||
.pair-arrow {{
|
||||
font-size: 1.5em;
|
||||
color: #667eea;
|
||||
margin: 5px 0;
|
||||
}}
|
||||
.history-table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}}
|
||||
.history-table th, .history-table td {{
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}}
|
||||
.history-table th {{
|
||||
background: #667eea;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.history-table tr:hover {{
|
||||
background: #f5f7fa;
|
||||
}}
|
||||
.violation-section {{
|
||||
background: #fff5f5;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 5px solid #fc8181;
|
||||
}}
|
||||
.violation-section.no-violations {{
|
||||
background: #f0fff4;
|
||||
border-left-color: #68d391;
|
||||
}}
|
||||
.violation-item {{
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
}}
|
||||
.violation-badge {{
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.badge-error {{
|
||||
background: #fc8181;
|
||||
color: white;
|
||||
}}
|
||||
.badge-success {{
|
||||
background: #68d391;
|
||||
color: white;
|
||||
}}
|
||||
.objective-grid {{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}}
|
||||
.objective-card {{
|
||||
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}}
|
||||
.objective-label {{
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
}}
|
||||
.objective-value {{
|
||||
font-size: 1.5em;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.summary-box {{
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
margin-top: 20px;
|
||||
}}
|
||||
.summary-valid {{
|
||||
background: #68d391;
|
||||
color: white;
|
||||
}}
|
||||
.summary-invalid {{
|
||||
background: #fc8181;
|
||||
color: white;
|
||||
}}
|
||||
.year-badge {{
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.8em;
|
||||
margin-right: 5px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}}
|
||||
@media print {{
|
||||
body {{
|
||||
background: white;
|
||||
}}
|
||||
.container {{
|
||||
box-shadow: none;
|
||||
}}
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>🎁 Silvester Wichteln 2025</h1>
|
||||
<div class="subtitle">Lösung #{c} - Generiert am {current_time}</div>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<section>
|
||||
<h2>Geschenkpaare 2025</h2>
|
||||
<div class="pairs-grid">
|
||||
"""
|
||||
|
||||
for giver, receiver in PAIRS:
|
||||
html += f""" <div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">{giver}</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">{receiver}</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
html += """ </div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Historie pro Person</h2>
|
||||
<table class="history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Person</th>
|
||||
<th>2018</th>
|
||||
<th>2019</th>
|
||||
<th>2021</th>
|
||||
<th>2022</th>
|
||||
<th>2023</th>
|
||||
<th>2024</th>
|
||||
<th>2025</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
"""
|
||||
|
||||
for n in NAMES:
|
||||
hist_2018 = [t[1] for t in PAIRS_2018 if t[0] == n]
|
||||
hist_2019 = [t[1] for t in PAIRS_2019 if t[0] == n]
|
||||
hist_2021 = [t[1] for t in PAIRS_2021 if t[0] == n]
|
||||
hist_2022 = [t[1] for t in PAIRS_2022 if t[0] == n]
|
||||
hist_2023 = [t[1] for t in PAIRS_2023 if t[0] == n]
|
||||
hist_2024 = [t[1] for t in PAIRS_2024 if t[0] == n]
|
||||
hist_2025 = [t[1] for t in PAIRS if t[0] == n]
|
||||
|
||||
html += f""" <tr>
|
||||
<td><strong>{n}</strong></td>
|
||||
<td>{', '.join(hist_2018) if hist_2018 else '-'}</td>
|
||||
<td>{', '.join(hist_2019) if hist_2019 else '-'}</td>
|
||||
<td>{', '.join(hist_2021) if hist_2021 else '-'}</td>
|
||||
<td>{', '.join(hist_2022) if hist_2022 else '-'}</td>
|
||||
<td>{', '.join(hist_2023) if hist_2023 else '-'}</td>
|
||||
<td>{', '.join(hist_2024) if hist_2024 else '-'}</td>
|
||||
<td><strong style="color: #667eea;">{', '.join(hist_2025) if hist_2025 else '-'}</strong></td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
html += """ </tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Verletzungsprüfung</h2>
|
||||
"""
|
||||
|
||||
violations_found = violations_data.get('violations_found', False)
|
||||
violation_class = "" if violations_found else "no-violations"
|
||||
|
||||
html += f""" <div class="violation-section {violation_class}">
|
||||
"""
|
||||
|
||||
# Sex pair violations
|
||||
sex_pair_violations = violations_data.get('sex_pair_violations', [])
|
||||
if sex_pair_violations:
|
||||
html += """ <div class="violation-item">
|
||||
<span class="violation-badge badge-error">❌ VERLETZUNG</span>
|
||||
<strong>Verbotene Paare:</strong>
|
||||
<ul style="margin-top: 10px; margin-left: 20px;">
|
||||
"""
|
||||
for giver, receiver in sex_pair_violations:
|
||||
html += f" <li>{giver} → {receiver}</li>\n"
|
||||
html += """ </ul>
|
||||
</div>
|
||||
"""
|
||||
else:
|
||||
html += """ <div class="violation-item">
|
||||
<span class="violation-badge badge-success">✓ OK</span>
|
||||
<strong>Verbotene Paare:</strong> Keine Verletzungen
|
||||
</div>
|
||||
"""
|
||||
|
||||
# Previous year violations
|
||||
prev_year_violations = violations_data.get('prev_year_violations', [])
|
||||
if prev_year_violations:
|
||||
html += """ <div class="violation-item">
|
||||
<span class="violation-badge badge-error">❌ VERLETZUNG</span>
|
||||
<strong>Vorjahrespaare:</strong>
|
||||
<ul style="margin-top: 10px; margin-left: 20px;">
|
||||
"""
|
||||
for year, giver, receiver, direction in prev_year_violations:
|
||||
html += f" <li><span class='year-badge'>{year}</span> {giver} → {receiver} ({direction})</li>\n"
|
||||
html += """ </ul>
|
||||
</div>
|
||||
"""
|
||||
else:
|
||||
html += """ <div class="violation-item">
|
||||
<span class="violation-badge badge-success">✓ OK</span>
|
||||
<strong>Vorjahrespaare:</strong> Keine Verletzungen
|
||||
</div>
|
||||
"""
|
||||
|
||||
# Mutual violations
|
||||
mutual_violations = violations_data.get('mutual_violations', [])
|
||||
if mutual_violations:
|
||||
html += """ <div class="violation-item">
|
||||
<span class="violation-badge badge-error">❌ VERLETZUNG</span>
|
||||
<strong>Gegenseitiges Beschenken:</strong>
|
||||
<ul style="margin-top: 10px; margin-left: 20px;">
|
||||
"""
|
||||
for g1, r1, g2, r2 in mutual_violations:
|
||||
html += f" <li>{g1} ↔ {r1}</li>\n"
|
||||
html += """ </ul>
|
||||
</div>
|
||||
"""
|
||||
else:
|
||||
html += """ <div class="violation-item">
|
||||
<span class="violation-badge badge-success">✓ OK</span>
|
||||
<strong>Gegenseitiges Beschenken:</strong> Keine Verletzungen
|
||||
</div>
|
||||
"""
|
||||
|
||||
# Assignment violations
|
||||
assignment_issues = violations_data.get('assignment_issues', {})
|
||||
if assignment_issues.get('missing_givers') or assignment_issues.get('missing_receivers') or \
|
||||
assignment_issues.get('duplicate_givers') or assignment_issues.get('duplicate_receivers'):
|
||||
html += """ <div class="violation-item">
|
||||
<span class="violation-badge badge-error">❌ VERLETZUNG</span>
|
||||
<strong>Zuordnungsfehler:</strong>
|
||||
<ul style="margin-top: 10px; margin-left: 20px;">
|
||||
"""
|
||||
if assignment_issues.get('missing_givers'):
|
||||
html += f" <li>Fehlende Geber: {', '.join(assignment_issues['missing_givers'])}</li>\n"
|
||||
if assignment_issues.get('missing_receivers'):
|
||||
html += f" <li>Fehlende Empfänger: {', '.join(assignment_issues['missing_receivers'])}</li>\n"
|
||||
if assignment_issues.get('duplicate_givers'):
|
||||
html += f" <li>Doppelte Geber: {', '.join(set(assignment_issues['duplicate_givers']))}</li>\n"
|
||||
if assignment_issues.get('duplicate_receivers'):
|
||||
html += f" <li>Doppelte Empfänger: {', '.join(set(assignment_issues['duplicate_receivers']))}</li>\n"
|
||||
html += """ </ul>
|
||||
</div>
|
||||
"""
|
||||
else:
|
||||
html += """ <div class="violation-item">
|
||||
<span class="violation-badge badge-success">✓ OK</span>
|
||||
<strong>Zuordnungen:</strong> Alle Teilnehmer geben und erhalten genau einmal
|
||||
</div>
|
||||
"""
|
||||
|
||||
html += f""" </div>
|
||||
|
||||
<div class="summary-box {'summary-valid' if not violations_found else 'summary-invalid'}">
|
||||
{'✓ KEINE VERLETZUNGEN - LÖSUNG IST GÜLTIG' if not violations_found else '⚠️ VERLETZUNGEN ERKANNT'}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Zielfunktionswerte</h2>
|
||||
<div class="objective-grid">
|
||||
"""
|
||||
|
||||
for label, value, weighted in obj_data:
|
||||
html += f""" <div class="objective-card">
|
||||
<div class="objective-label">{label}</div>
|
||||
<div class="objective-value">{value:.4f}</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ {weighted:.2f}</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
html += f""" </div>
|
||||
<div style="text-align: center; margin-top: 20px; padding: 20px; background: #f5f7fa; border-radius: 10px;">
|
||||
<strong style="font-size: 1.3em;">Gesamter Zielfunktionswert: {obj_data[0][1]:.4f}</strong>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
return html
|
||||
|
||||
|
||||
objVal = 0
|
||||
c = 0
|
||||
final_solution_data = None
|
||||
|
||||
while objVal == 0:
|
||||
|
||||
c += 1
|
||||
m.optimize()
|
||||
|
||||
PAIRS = []
|
||||
for key in x.keys():
|
||||
if x[key].x > 0.1:
|
||||
PAIRS.append(key)
|
||||
x[key].ub = 0
|
||||
|
||||
print("\n" + "="*80)
|
||||
print(f"SOLUTION #{c}")
|
||||
print("="*80)
|
||||
|
||||
print("\n--- GIFT PAIRS (2025) ---")
|
||||
for p in PAIRS:
|
||||
print(f" {p[0]} -> {p[1]}")
|
||||
|
||||
with open(f"sol_2024_{c}.json", "w") as f:
|
||||
f.write(json.dumps(PAIRS))
|
||||
|
||||
print("\n--- HISTORY PER PERSON ---")
|
||||
for n in NAMES:
|
||||
print(n)
|
||||
print("\t2018", [t[1] for t in PAIRS_2018 if t[0] == n])
|
||||
print("\t2019", [t[1] for t in PAIRS_2019 if t[0] == n])
|
||||
print("\t2021", [t[1] for t in PAIRS_2021 if t[0] == n])
|
||||
print("\t2022", [t[1] for t in PAIRS_2022 if t[0] == n])
|
||||
print("\t2023", [t[1] for t in PAIRS_2023 if t[0] == n])
|
||||
print("\t2024", [t[1] for t in PAIRS_2024 if t[0] == n])
|
||||
print("\t2025", [t[1] for t in PAIRS if t[0] == n])
|
||||
|
||||
# Check for violations
|
||||
print("\n" + "="*80)
|
||||
print("VIOLATION CHECKS")
|
||||
print("="*80)
|
||||
|
||||
violations_found = False
|
||||
|
||||
# Check same-sex pairs
|
||||
# same_sex_violations = []
|
||||
# for giver, receiver in PAIRS:
|
||||
# if (giver in MEN and receiver in MEN) or (giver in WOMEN and receiver in WOMEN):
|
||||
# same_sex_violations.append((giver, receiver))
|
||||
|
||||
# if same_sex_violations:
|
||||
# violations_found = True
|
||||
# print("\n❌ SAME-SEX PAIR VIOLATIONS:")
|
||||
# for giver, receiver in same_sex_violations:
|
||||
# print(f" {giver} -> {receiver}")
|
||||
# else:
|
||||
# print("\n✓ No same-sex pair violations")
|
||||
|
||||
# Check sex pairs (forbidden pairs)
|
||||
sex_pair_violations = []
|
||||
for giver, receiver in PAIRS:
|
||||
if (giver, receiver) in SEX_PAIRS or (receiver, giver) in SEX_PAIRS:
|
||||
sex_pair_violations.append((giver, receiver))
|
||||
|
||||
if sex_pair_violations:
|
||||
violations_found = True
|
||||
print("\n❌ SEX PAIR VIOLATIONS (forbidden pairs):")
|
||||
for giver, receiver in sex_pair_violations:
|
||||
print(f" {giver} -> {receiver}")
|
||||
else:
|
||||
print("\n✓ No sex pair violations")
|
||||
|
||||
# Check previous year violations
|
||||
prev_year_violations = []
|
||||
|
||||
for giver, receiver in PAIRS:
|
||||
# Check 2018
|
||||
if (giver, receiver) in PAIRS_2018:
|
||||
prev_year_violations.append(("2018", giver, receiver, "same direction"))
|
||||
if (receiver, giver) in PAIRS_2018:
|
||||
prev_year_violations.append(("2018", giver, receiver, "reverse"))
|
||||
|
||||
# Check 2019
|
||||
if (giver, receiver) in PAIRS_2019:
|
||||
prev_year_violations.append(("2019", giver, receiver, "same direction"))
|
||||
if (receiver, giver) in PAIRS_2019:
|
||||
prev_year_violations.append(("2019", giver, receiver, "reverse"))
|
||||
|
||||
# Check 2021
|
||||
if (giver, receiver) in PAIRS_2021:
|
||||
prev_year_violations.append(("2021", giver, receiver, "same direction"))
|
||||
if (receiver, giver) in PAIRS_2021:
|
||||
prev_year_violations.append(("2021", giver, receiver, "reverse"))
|
||||
|
||||
# Check 2022
|
||||
if (giver, receiver) in PAIRS_2022:
|
||||
prev_year_violations.append(("2022", giver, receiver, "same direction"))
|
||||
if (receiver, giver) in PAIRS_2022:
|
||||
prev_year_violations.append(("2022", giver, receiver, "reverse"))
|
||||
|
||||
# Check 2023
|
||||
if (giver, receiver) in PAIRS_2023:
|
||||
prev_year_violations.append(("2023", giver, receiver, "same direction"))
|
||||
if (receiver, giver) in PAIRS_2023:
|
||||
prev_year_violations.append(("2023", giver, receiver, "reverse"))
|
||||
|
||||
# Check 2024
|
||||
if (giver, receiver) in PAIRS_2024:
|
||||
prev_year_violations.append(("2024", giver, receiver, "same direction"))
|
||||
if (receiver, giver) in PAIRS_2024:
|
||||
prev_year_violations.append(("2024", giver, receiver, "reverse"))
|
||||
|
||||
if prev_year_violations:
|
||||
violations_found = True
|
||||
print("\n❌ PREVIOUS YEAR PAIR VIOLATIONS:")
|
||||
for year, giver, receiver, direction in prev_year_violations:
|
||||
print(f" {year}: {giver} -> {receiver} ({direction})")
|
||||
else:
|
||||
print("\n✓ No previous year pair violations")
|
||||
|
||||
# Check mutual gifting (should not happen)
|
||||
mutual_violations = []
|
||||
pair_set = set(PAIRS)
|
||||
for giver, receiver in PAIRS:
|
||||
if (receiver, giver) in pair_set:
|
||||
mutual_violations.append((giver, receiver, receiver, giver))
|
||||
|
||||
if mutual_violations:
|
||||
violations_found = True
|
||||
print("\n❌ MUTUAL GIFTING VIOLATIONS:")
|
||||
for g1, r1, g2, r2 in mutual_violations:
|
||||
print(f" {g1} <-> {r1} (mutual)")
|
||||
else:
|
||||
print("\n✓ No mutual gifting violations")
|
||||
|
||||
# Check if everyone gives and receives exactly once
|
||||
givers = [p[0] for p in PAIRS]
|
||||
receivers = [p[1] for p in PAIRS]
|
||||
missing_givers = set(NAMES) - set(givers)
|
||||
missing_receivers = set(NAMES) - set(receivers)
|
||||
duplicate_givers = [name for name in givers if givers.count(name) > 1]
|
||||
duplicate_receivers = [name for name in receivers if receivers.count(name) > 1]
|
||||
|
||||
assignment_issues = {}
|
||||
if missing_givers or missing_receivers or duplicate_givers or duplicate_receivers:
|
||||
violations_found = True
|
||||
print("\n❌ ASSIGNMENT VIOLATIONS:")
|
||||
if missing_givers:
|
||||
print(f" Missing givers: {missing_givers}")
|
||||
assignment_issues['missing_givers'] = missing_givers
|
||||
if missing_receivers:
|
||||
print(f" Missing receivers: {missing_receivers}")
|
||||
assignment_issues['missing_receivers'] = missing_receivers
|
||||
if duplicate_givers:
|
||||
print(f" Duplicate givers: {set(duplicate_givers)}")
|
||||
assignment_issues['duplicate_givers'] = duplicate_givers
|
||||
if duplicate_receivers:
|
||||
print(f" Duplicate receivers: {set(duplicate_receivers)}")
|
||||
assignment_issues['duplicate_receivers'] = duplicate_receivers
|
||||
else:
|
||||
print("\n✓ All participants give and receive exactly once")
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*80)
|
||||
if violations_found:
|
||||
print("⚠️ VIOLATIONS DETECTED")
|
||||
else:
|
||||
print("✓ NO VIOLATIONS - SOLUTION IS VALID")
|
||||
print("="*80)
|
||||
|
||||
# Objective value details
|
||||
print(f"\nObjective value: {m.objVal}")
|
||||
# print(f" Sex penalty: {sex_penalty.x} -> {100*sex_penalty.x}")
|
||||
print(f" Pair penalty: {pair_penalty.x} -> {100*pair_penalty.x}")
|
||||
print(f" 2024 penalty: {old_penalty_2024.x} -> {5000*old_penalty_2024.x}")
|
||||
print(f" 2024 reverse penalty: {old_penalty_2024_reverse.x} -> {100*old_penalty_2024_reverse.x}")
|
||||
print(f" 2023 penalty: {old_penalty_2023.x} -> {1000*old_penalty_2023.x}")
|
||||
print(f" 2022 penalty: {old_penalty_2022.x} -> {500*old_penalty_2022.x}")
|
||||
print(f" 2021 penalty: {old_penalty_2021.x} -> {100*old_penalty_2021.x}")
|
||||
print(f" 2019 penalty: {old_penalty_2019.x} -> {50*old_penalty_2019.x}")
|
||||
print(f" 2018 penalty: {old_penalty_2018.x} -> {10*old_penalty_2018.x}")
|
||||
print("="*80 + "\n")
|
||||
|
||||
# Store data for HTML report
|
||||
violations_data = {
|
||||
'violations_found': violations_found,
|
||||
'sex_pair_violations': sex_pair_violations,
|
||||
'prev_year_violations': prev_year_violations,
|
||||
'mutual_violations': mutual_violations,
|
||||
'assignment_issues': assignment_issues
|
||||
}
|
||||
|
||||
obj_data = [
|
||||
("Gesamtwert", m.objVal, m.objVal),
|
||||
("Pair Penalty", pair_penalty.x, 100 * pair_penalty.x),
|
||||
("2024 Penalty", old_penalty_2024.x, 5000 * old_penalty_2024.x),
|
||||
("2024 Reverse", old_penalty_2024_reverse.x, 100 * old_penalty_2024_reverse.x),
|
||||
("2023 Penalty", old_penalty_2023.x, 1000 * old_penalty_2023.x),
|
||||
("2022 Penalty", old_penalty_2022.x, 500 * old_penalty_2022.x),
|
||||
("2021 Penalty", old_penalty_2021.x, 100 * old_penalty_2021.x),
|
||||
("2019 Penalty", old_penalty_2019.x, 50 * old_penalty_2019.x),
|
||||
("2018 Penalty", old_penalty_2018.x, 10 * old_penalty_2018.x),
|
||||
]
|
||||
|
||||
final_solution_data = {
|
||||
'PAIRS': PAIRS,
|
||||
'c': c,
|
||||
'violations_data': violations_data,
|
||||
'obj_data': obj_data
|
||||
}
|
||||
|
||||
objVal = m.objVal
|
||||
|
||||
# Generate HTML report after optimization loop
|
||||
if final_solution_data:
|
||||
html_report = generate_html_report(
|
||||
final_solution_data['PAIRS'],
|
||||
final_solution_data['c'],
|
||||
final_solution_data['violations_data'],
|
||||
final_solution_data['obj_data']
|
||||
)
|
||||
|
||||
html_filename = f"silvester2025_report_{final_solution_data['c']}.html"
|
||||
with open(html_filename, "w", encoding="utf-8") as f:
|
||||
f.write(html_report)
|
||||
|
||||
print(f"\n{'='*80}")
|
||||
print(f"HTML Report erstellt: {html_filename}")
|
||||
print(f"{'='*80}\n")
|
||||
|
||||
# %%
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import networkx as nx
|
||||
|
||||
G = nx.DiGraph()
|
||||
G.clear()
|
||||
G.add_nodes_from(NAMES)
|
||||
G.add_edges_from(PAIRS)
|
||||
|
||||
pos = nx.kamada_kawai_layout(G)
|
||||
nx.draw_networkx_nodes(G, pos, node_size=500)
|
||||
nx.draw_networkx_labels(G, pos)
|
||||
nx.draw_networkx_edges(G, pos, edgelist=PAIRS, edge_color="r", arrows=True)
|
||||
# nx.draw_networkx_edges(G, pos, edgelist=black_edges, arrows=False)
|
||||
plt.savefig(f'silvester2025_{c}.png')
|
||||
# plt.savefig(f'silvester2022_{c}.pdf')
|
||||
plt.show()
|
||||
|
||||
|
||||
# %%
|
||||
BIN
silvester2019.pdf
Normal file
BIN
silvester2019.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
silvester2021.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
silvester2022_1.pdf
Normal file
BIN
silvester2022_1.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
silvester2022_2.pdf
Normal file
BIN
silvester2022_2.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
silvester2022_3.pdf
Normal file
BIN
silvester2022_3.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
silvester2023_1.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
silvester2023_2.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
silvester2024_1.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
silvester2025_1.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
489
silvester2025_report_1.html
Normal file
@ -0,0 +1,489 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Silvester Wichteln 2025 - Lösung #1</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 1.2em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.content {
|
||||
padding: 40px;
|
||||
}
|
||||
section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
h2 {
|
||||
color: #667eea;
|
||||
border-bottom: 3px solid #667eea;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
.pairs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.pair-card {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.pair-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
.pair-arrow {
|
||||
font-size: 1.5em;
|
||||
color: #667eea;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.history-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.history-table th, .history-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.history-table th {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
.history-table tr:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.violation-section {
|
||||
background: #fff5f5;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 5px solid #fc8181;
|
||||
}
|
||||
.violation-section.no-violations {
|
||||
background: #f0fff4;
|
||||
border-left-color: #68d391;
|
||||
}
|
||||
.violation-item {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.violation-badge {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.badge-error {
|
||||
background: #fc8181;
|
||||
color: white;
|
||||
}
|
||||
.badge-success {
|
||||
background: #68d391;
|
||||
color: white;
|
||||
}
|
||||
.objective-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.objective-card {
|
||||
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.objective-label {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.objective-value {
|
||||
font-size: 1.5em;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
.summary-box {
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.summary-valid {
|
||||
background: #68d391;
|
||||
color: white;
|
||||
}
|
||||
.summary-invalid {
|
||||
background: #fc8181;
|
||||
color: white;
|
||||
}
|
||||
.year-badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.8em;
|
||||
margin-right: 5px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
@media print {
|
||||
body {
|
||||
background: white;
|
||||
}
|
||||
.container {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>🎁 Silvester Wichteln 2025</h1>
|
||||
<div class="subtitle">Lösung #1 - Generiert am 31.10.2025 um 11:11:33</div>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<section>
|
||||
<h2>Geschenkpaare 2025</h2>
|
||||
<div class="pairs-grid">
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Herbert</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Christoph</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Christoph</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Christian</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Martin</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Marlon</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Christian</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Martin</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Marlon</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Iris</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Gaby</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Johanna</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Teresa</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Birgit</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Iris</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Anne-Christin</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Anne-Christin</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Teresa</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Jenni</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Herbert</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Birgit</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Gaby</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Johanna</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Jenni</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Historie pro Person</h2>
|
||||
<table class="history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Person</th>
|
||||
<th>2018</th>
|
||||
<th>2019</th>
|
||||
<th>2021</th>
|
||||
<th>2022</th>
|
||||
<th>2023</th>
|
||||
<th>2024</th>
|
||||
<th>2025</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Herbert</strong></td>
|
||||
<td>Peter</td>
|
||||
<td>Gaby</td>
|
||||
<td>Martin</td>
|
||||
<td>Anne-Christin</td>
|
||||
<td>Jenni</td>
|
||||
<td>Teresa</td>
|
||||
<td><strong style="color: #667eea;">Christoph</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Christoph</strong></td>
|
||||
<td>Martin</td>
|
||||
<td>Birgit</td>
|
||||
<td>Gaby</td>
|
||||
<td>Teresa</td>
|
||||
<td>Herbert</td>
|
||||
<td>Birgit</td>
|
||||
<td><strong style="color: #667eea;">Christian</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Martin</strong></td>
|
||||
<td>Birgit</td>
|
||||
<td>Iris</td>
|
||||
<td>Christoph</td>
|
||||
<td>Jenni</td>
|
||||
<td>Johanna</td>
|
||||
<td>Anne-Christin</td>
|
||||
<td><strong style="color: #667eea;">Marlon</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Christian</strong></td>
|
||||
<td>Anne-Christin</td>
|
||||
<td>Renate</td>
|
||||
<td>Birgit</td>
|
||||
<td>Herbert</td>
|
||||
<td>Christoph</td>
|
||||
<td>Johanna</td>
|
||||
<td><strong style="color: #667eea;">Martin</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Marlon</strong></td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>Gaby</td>
|
||||
<td><strong style="color: #667eea;">Iris</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Gaby</strong></td>
|
||||
<td>Christoph</td>
|
||||
<td>Christian</td>
|
||||
<td>Anne-Christin</td>
|
||||
<td>Martin</td>
|
||||
<td>Birgit</td>
|
||||
<td>Christian</td>
|
||||
<td><strong style="color: #667eea;">Johanna</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Teresa</strong></td>
|
||||
<td>Herbert</td>
|
||||
<td>Jenni</td>
|
||||
<td>Christian</td>
|
||||
<td>Gaby</td>
|
||||
<td>Anne-Christin</td>
|
||||
<td>Iris</td>
|
||||
<td><strong style="color: #667eea;">Birgit</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Iris</strong></td>
|
||||
<td>Gaby</td>
|
||||
<td>Teresa</td>
|
||||
<td>Herbert</td>
|
||||
<td>Christoph</td>
|
||||
<td>Christian</td>
|
||||
<td>Martin</td>
|
||||
<td><strong style="color: #667eea;">Anne-Christin</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Anne-Christin</strong></td>
|
||||
<td>Jenni</td>
|
||||
<td>Martin</td>
|
||||
<td>Peter</td>
|
||||
<td>Birgit</td>
|
||||
<td>Iris</td>
|
||||
<td>Jenni</td>
|
||||
<td><strong style="color: #667eea;">Teresa</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Jenni</strong></td>
|
||||
<td>-</td>
|
||||
<td>Christoph</td>
|
||||
<td>Iris</td>
|
||||
<td>Peter</td>
|
||||
<td>Gaby</td>
|
||||
<td>Christoph</td>
|
||||
<td><strong style="color: #667eea;">Herbert</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Birgit</strong></td>
|
||||
<td>Teresa</td>
|
||||
<td>Peter</td>
|
||||
<td>Jenni</td>
|
||||
<td>Iris</td>
|
||||
<td>Martin</td>
|
||||
<td>Marlon</td>
|
||||
<td><strong style="color: #667eea;">Gaby</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Johanna</strong></td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>Teresa</td>
|
||||
<td>Herbert</td>
|
||||
<td><strong style="color: #667eea;">Jenni</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Verletzungsprüfung</h2>
|
||||
<div class="violation-section ">
|
||||
<div class="violation-item">
|
||||
<span class="violation-badge badge-success">✓ OK</span>
|
||||
<strong>Verbotene Paare:</strong> Keine Verletzungen
|
||||
</div>
|
||||
<div class="violation-item">
|
||||
<span class="violation-badge badge-error">❌ VERLETZUNG</span>
|
||||
<strong>Vorjahrespaare:</strong>
|
||||
<ul style="margin-top: 10px; margin-left: 20px;">
|
||||
<li><span class='year-badge'>2023</span> Herbert → Christoph (reverse)</li>
|
||||
<li><span class='year-badge'>2023</span> Christoph → Christian (reverse)</li>
|
||||
<li><span class='year-badge'>2018</span> Teresa → Birgit (reverse)</li>
|
||||
<li><span class='year-badge'>2023</span> Iris → Anne-Christin (reverse)</li>
|
||||
<li><span class='year-badge'>2023</span> Anne-Christin → Teresa (reverse)</li>
|
||||
<li><span class='year-badge'>2023</span> Jenni → Herbert (reverse)</li>
|
||||
<li><span class='year-badge'>2023</span> Birgit → Gaby (reverse)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="violation-item">
|
||||
<span class="violation-badge badge-success">✓ OK</span>
|
||||
<strong>Gegenseitiges Beschenken:</strong> Keine Verletzungen
|
||||
</div>
|
||||
<div class="violation-item">
|
||||
<span class="violation-badge badge-success">✓ OK</span>
|
||||
<strong>Zuordnungen:</strong> Alle Teilnehmer geben und erhalten genau einmal
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-box summary-invalid">
|
||||
⚠️ VERLETZUNGEN ERKANNT
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Zielfunktionswerte</h2>
|
||||
<div class="objective-grid">
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">Gesamtwert</div>
|
||||
<div class="objective-value">0.0329</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.03</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">Pair Penalty</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2024 Penalty</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2024 Reverse</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2023 Penalty</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2022 Penalty</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2021 Penalty</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2019 Penalty</div>
|
||||
<div class="objective-value">-0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ -0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2018 Penalty</div>
|
||||
<div class="objective-value">-0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ -0.00</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center; margin-top: 20px; padding: 20px; background: #f5f7fa; border-radius: 10px;">
|
||||
<strong style="font-size: 1.3em;">Gesamter Zielfunktionswert: 0.0329</strong>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
490
silvester2025_report_2.html
Normal file
@ -0,0 +1,490 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Silvester Wichteln 2025 - Lösung #1</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 1.2em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.content {
|
||||
padding: 40px;
|
||||
}
|
||||
section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
h2 {
|
||||
color: #667eea;
|
||||
border-bottom: 3px solid #667eea;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
.pairs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.pair-card {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.pair-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
.pair-arrow {
|
||||
font-size: 1.5em;
|
||||
color: #667eea;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.history-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.history-table th, .history-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.history-table th {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
.history-table tr:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.violation-section {
|
||||
background: #fff5f5;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 5px solid #fc8181;
|
||||
}
|
||||
.violation-section.no-violations {
|
||||
background: #f0fff4;
|
||||
border-left-color: #68d391;
|
||||
}
|
||||
.violation-item {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.violation-badge {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.badge-error {
|
||||
background: #fc8181;
|
||||
color: white;
|
||||
}
|
||||
.badge-success {
|
||||
background: #68d391;
|
||||
color: white;
|
||||
}
|
||||
.objective-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.objective-card {
|
||||
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.objective-label {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.objective-value {
|
||||
font-size: 1.5em;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
.summary-box {
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.summary-valid {
|
||||
background: #68d391;
|
||||
color: white;
|
||||
}
|
||||
.summary-invalid {
|
||||
background: #fc8181;
|
||||
color: white;
|
||||
}
|
||||
.year-badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.8em;
|
||||
margin-right: 5px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
@media print {
|
||||
body {
|
||||
background: white;
|
||||
}
|
||||
.container {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>🎁 Silvester Wichteln 2025</h1>
|
||||
<div class="subtitle">Lösung #1 - Generiert am 31.10.2025 um 10:50:18</div>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<section>
|
||||
<h2>Geschenkpaare 2025</h2>
|
||||
<div class="pairs-grid">
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Herbert</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Christoph</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Christoph</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Iris</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Martin</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Gaby</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Christian</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Martin</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Marlon</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Christian</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Gaby</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Jenni</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Teresa</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Johanna</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Iris</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Marlon</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Anne-Christin</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Herbert</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Jenni</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Teresa</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Birgit</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Anne-Christin</div>
|
||||
</div>
|
||||
<div class="pair-card">
|
||||
<div style="font-weight: bold; color: #667eea;">Johanna</div>
|
||||
<div class="pair-arrow">↓</div>
|
||||
<div style="font-weight: bold; color: #764ba2;">Birgit</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Historie pro Person</h2>
|
||||
<table class="history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Person</th>
|
||||
<th>2018</th>
|
||||
<th>2019</th>
|
||||
<th>2021</th>
|
||||
<th>2022</th>
|
||||
<th>2023</th>
|
||||
<th>2024</th>
|
||||
<th>2025</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Herbert</strong></td>
|
||||
<td>Peter</td>
|
||||
<td>Gaby</td>
|
||||
<td>Martin</td>
|
||||
<td>Anne-Christin</td>
|
||||
<td>Jenni</td>
|
||||
<td>Teresa</td>
|
||||
<td><strong style="color: #667eea;">Christoph</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Christoph</strong></td>
|
||||
<td>Martin</td>
|
||||
<td>Birgit</td>
|
||||
<td>Gaby</td>
|
||||
<td>Teresa</td>
|
||||
<td>Herbert</td>
|
||||
<td>Birgit</td>
|
||||
<td><strong style="color: #667eea;">Iris</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Martin</strong></td>
|
||||
<td>Birgit</td>
|
||||
<td>Iris</td>
|
||||
<td>Christoph</td>
|
||||
<td>Jenni</td>
|
||||
<td>Johanna</td>
|
||||
<td>Anne-Christin</td>
|
||||
<td><strong style="color: #667eea;">Gaby</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Christian</strong></td>
|
||||
<td>Anne-Christin</td>
|
||||
<td>Renate</td>
|
||||
<td>Birgit</td>
|
||||
<td>Herbert</td>
|
||||
<td>Christoph</td>
|
||||
<td>Johanna</td>
|
||||
<td><strong style="color: #667eea;">Martin</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Marlon</strong></td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>Gaby</td>
|
||||
<td><strong style="color: #667eea;">Christian</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Gaby</strong></td>
|
||||
<td>Christoph</td>
|
||||
<td>Christian</td>
|
||||
<td>Anne-Christin</td>
|
||||
<td>Martin</td>
|
||||
<td>Birgit</td>
|
||||
<td>Christian</td>
|
||||
<td><strong style="color: #667eea;">Jenni</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Teresa</strong></td>
|
||||
<td>Herbert</td>
|
||||
<td>Jenni</td>
|
||||
<td>Christian</td>
|
||||
<td>Gaby</td>
|
||||
<td>Anne-Christin</td>
|
||||
<td>Iris</td>
|
||||
<td><strong style="color: #667eea;">Johanna</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Iris</strong></td>
|
||||
<td>Gaby</td>
|
||||
<td>Teresa</td>
|
||||
<td>Herbert</td>
|
||||
<td>Christoph</td>
|
||||
<td>Christian</td>
|
||||
<td>Martin</td>
|
||||
<td><strong style="color: #667eea;">Marlon</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Anne-Christin</strong></td>
|
||||
<td>Jenni</td>
|
||||
<td>Martin</td>
|
||||
<td>Peter</td>
|
||||
<td>Birgit</td>
|
||||
<td>Iris</td>
|
||||
<td>Jenni</td>
|
||||
<td><strong style="color: #667eea;">Herbert</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Jenni</strong></td>
|
||||
<td>-</td>
|
||||
<td>Christoph</td>
|
||||
<td>Iris</td>
|
||||
<td>Peter</td>
|
||||
<td>Gaby</td>
|
||||
<td>Christoph</td>
|
||||
<td><strong style="color: #667eea;">Teresa</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Birgit</strong></td>
|
||||
<td>Teresa</td>
|
||||
<td>Peter</td>
|
||||
<td>Jenni</td>
|
||||
<td>Iris</td>
|
||||
<td>Martin</td>
|
||||
<td>Marlon</td>
|
||||
<td><strong style="color: #667eea;">Anne-Christin</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Johanna</strong></td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>Teresa</td>
|
||||
<td>Herbert</td>
|
||||
<td><strong style="color: #667eea;">Birgit</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Verletzungsprüfung</h2>
|
||||
<div class="violation-section ">
|
||||
<div class="violation-item">
|
||||
<span class="violation-badge badge-success">✓ OK</span>
|
||||
<strong>Verbotene Paare:</strong> Keine Verletzungen
|
||||
</div>
|
||||
<div class="violation-item">
|
||||
<span class="violation-badge badge-error">❌ VERLETZUNG</span>
|
||||
<strong>Vorjahrespaare:</strong>
|
||||
<ul style="margin-top: 10px; margin-left: 20px;">
|
||||
<li><span class='year-badge'>2023</span> Herbert → Christoph (reverse)</li>
|
||||
<li><span class='year-badge'>2022</span> Christoph → Iris (reverse)</li>
|
||||
<li><span class='year-badge'>2022</span> Martin → Gaby (reverse)</li>
|
||||
<li><span class='year-badge'>2023</span> Gaby → Jenni (reverse)</li>
|
||||
<li><span class='year-badge'>2023</span> Teresa → Johanna (reverse)</li>
|
||||
<li><span class='year-badge'>2022</span> Anne-Christin → Herbert (reverse)</li>
|
||||
<li><span class='year-badge'>2019</span> Jenni → Teresa (reverse)</li>
|
||||
<li><span class='year-badge'>2022</span> Birgit → Anne-Christin (reverse)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="violation-item">
|
||||
<span class="violation-badge badge-success">✓ OK</span>
|
||||
<strong>Gegenseitiges Beschenken:</strong> Keine Verletzungen
|
||||
</div>
|
||||
<div class="violation-item">
|
||||
<span class="violation-badge badge-success">✓ OK</span>
|
||||
<strong>Zuordnungen:</strong> Alle Teilnehmer geben und erhalten genau einmal
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-box summary-invalid">
|
||||
⚠️ VERLETZUNGEN ERKANNT
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Zielfunktionswerte</h2>
|
||||
<div class="objective-grid">
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">Gesamtwert</div>
|
||||
<div class="objective-value">0.0383</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.04</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">Pair Penalty</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2024 Penalty</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2024 Reverse</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2023 Penalty</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2022 Penalty</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2021 Penalty</div>
|
||||
<div class="objective-value">0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ 0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2019 Penalty</div>
|
||||
<div class="objective-value">-0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ -0.00</div>
|
||||
</div>
|
||||
<div class="objective-card">
|
||||
<div class="objective-label">2018 Penalty</div>
|
||||
<div class="objective-value">-0.0000</div>
|
||||
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">→ -0.00</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center; margin-top: 20px; padding: 20px; background: #f5f7fa; border-radius: 10px;">
|
||||
<strong style="font-size: 1.3em;">Gesamter Zielfunktionswert: 0.0383</strong>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
silvester_1.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
silvester_2.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
1
sol_1.json
Normal file
@ -0,0 +1 @@
|
||||
[["Herbert", "Teresa"], ["Christoph", "Birgit"], ["Martin", "Anne-Christin"], ["Christian", "Johanna"], ["Marlon", "Gaby"], ["Gaby", "Christian"], ["Teresa", "Iris"], ["Iris", "Martin"], ["Anne-Christin", "Jenni"], ["Jenni", "Christoph"], ["Birgit", "Marlon"], ["Johanna", "Herbert"]]
|
||||
1
sol_2.json
Normal file
@ -0,0 +1 @@
|
||||
[["Herbert", "Anne-Christin"], ["Christoph", "Teresa"], ["Martin", "Jenni"], ["Christian", "Herbert"], ["Peter", "Christian"], ["Gaby", "Martin"], ["Teresa", "Gaby"], ["Iris", "Christoph"], ["Anne-Christin", "Birgit"], ["Jenni", "Peter"], ["Birgit", "Iris"]]
|
||||
1
sol_2024_1.json
Normal file
@ -0,0 +1 @@
|
||||
[["Herbert", "Christoph"], ["Christoph", "Christian"], ["Martin", "Marlon"], ["Christian", "Martin"], ["Marlon", "Iris"], ["Gaby", "Johanna"], ["Teresa", "Birgit"], ["Iris", "Anne-Christin"], ["Anne-Christin", "Teresa"], ["Jenni", "Herbert"], ["Birgit", "Gaby"], ["Johanna", "Jenni"]]
|
||||
1
sol_3.json
Normal file
@ -0,0 +1 @@
|
||||
[["Herbert", "Christian"], ["Christoph", "Iris"], ["Martin", "Peter"], ["Christian", "Martin"], ["Peter", "Jenni"], ["Gaby", "Birgit"], ["Teresa", "Christoph"], ["Iris", "Anne-Christin"], ["Anne-Christin", "Teresa"], ["Jenni", "Gaby"], ["Birgit", "Herbert"]]
|
||||