Fotos: Gesichtssuche FH-weit (Studierende + Lektoren) als Job mit Live-Progress, Person-Info-Seiten, Scope-Auswahl

This commit is contained in:
Nicolas Gruber
2026-10-19 14:43:09 +02:00
parent 567bcb40ee
commit 6763faf9c1
7 changed files with 405 additions and 72 deletions
+14 -2
View File
@@ -49,7 +49,7 @@
<div class="avatar-initials">{{ (p.nachname[:1] ~ p.vorname[:1]) | upper }}</div>
{% endif %}
</div>
<div class="photo-name">{{ p.nachname }}{% if p.vorname %}, {{ p.vorname }}{% endif %}</div>
<div class="photo-name"><a href="/person/{{ p.matrikel }}">{{ p.nachname }}{% if p.vorname %}, {{ p.vorname }}{% endif %}</a></div>
{% if p.matrikel == own_uid %}<div class="photo-own">Das bist du</div>{% endif %}
</div>
{% else %}
@@ -60,11 +60,23 @@
<div class="card">
<h2>Gesichtssuche</h2>
<p class="hint">Foto von dir hochladen - die App findet die passenden Profile in diesem Verband.</p>
<p class="hint">Foto hochladen - die App vergleicht es mit den Profilfotos im gewählten Suchbereich.
Beim ersten Mal für große Bereiche (z.B. FH-weit) kann die Suche etwas dauern;
die Ergebnisse erscheinen live, während sie berechnet werden.</p>
<form class="form-row form-stack" method="post" action="/fotos/suche" enctype="multipart/form-data">
<input type="hidden" name="kz" value="{{ kz }}">
<input type="hidden" name="sem" value="{{ sem }}">
<input type="hidden" name="ver" value="{{ ver }}">
<div class="field">
<label>Suchbereich</label>
<select name="scope">
<option value="verband">Nur Verband {{ ver }} (Sem. {{ sem }})</option>
<option value="stg">Gesamter Studiengang (alle Semester + Verbände)</option>
<option value="stud">Alle Studierenden (FH-weit)</option>
<option value="lektor">Alle Lektoren</option>
<option value="alle">Alle Personen (Studierende + Lektoren)</option>
</select>
</div>
<div class="field">
<label>Foto</label>
<input type="file" name="file" accept="image/*" required>
@@ -2,7 +2,7 @@
{% block title %}Gesichtssuche{% endblock %}
{% block content %}
<h1 class="page-title">Gesichtssuche
{% if stg %}<span class="badge">{{ stg.code }} - {{ stg.name }}</span>{% endif %}
<span class="badge badge-muted">{{ scope_label }}</span>
</h1>
<div class="card">
@@ -42,33 +42,23 @@
{% else %}
<div class="card">
<h2>
Ergebnisse
<span class="count-pill">{{ results | selectattr('sim', 'ge', 0.35) | list | length }} Treffer</span>
{% if took is defined %}<span class="badge badge-muted">{{ took }} s</span>{% endif %}
<span id="search-state">Suche läuft…</span>
<span class="count-pill" id="search-count">0 Treffer</span>
<span class="badge badge-muted" id="search-progress">0/{{ job.total }}</span>
<span class="badge badge-muted" id="search-time" style="display:none"></span>
</h2>
{% if results | selectattr('sim', 'ge', 0.35) | list | length %}
<div class="match-grid">
{% for r in results[:30] %}
<div class="match-tile {{ 'match-strong' if r.sim >= 0.5 else ('match-mid' if r.sim >= 0.35 else '') }}">
<img src="{{ r.img }}" alt="{{ r.nachname }}">
<div class="match-name">
<a href="/plan?uid={{ r.matrikel }}">{{ r.nachname }}{% if r.vorname %}, {{ r.vorname }}{% endif %}</a>
</div>
<div class="match-sim">
<div class="sim-bar"><div style="width: {{ [r.sim * 100, 0] | max | round(0) }}%"></div></div>
<span class="sim-num">{{ [r.sim * 100, 0] | max | round(0) }} %</span>
</div>
</div>
{% endfor %}
</div>
<p class="hint" style="margin-top:12px">
<div class="search-progressbar" id="search-progressbar"><div id="search-bar"></div></div>
<div class="match-grid" id="match-grid"></div>
<p class="hint" id="search-hint" style="display:none">
<strong>Hinweis:</strong> Hohe Werte (grün, &ge; 50 %) deuten auf dieselbe Person hin.
Werte um 35–50 % (gelb) sind mögliche Treffer - bitte prüfen. Unter 35 % wird nicht als Treffer gezählt.
Werte um 35–50 % (gelb) sind mögliche Treffer - bitte prüfen.
</p>
{% else %}
<p class="empty">Kein Treffer mit &ge; 35 % Ähnlichkeit gefunden.
Prüfe, ob das richtige Semester/Verband gewählt ist und das Foto ein klar sichtbares Gesicht zeigt.</p>
{% endif %}
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<script>
window.__SEARCH_JOB__ = {{ job | tojson if job else 'null' }};
</script>
{% endblock %}
+49
View File
@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% block title %}{% if prof %}{{ prof.nachname }}, {{ prof.vorname }}{% else %}Person{% endif %}{% endblock %}
{% block content %}
{% if not prof %}
<h1 class="page-title">Person nicht gefunden</h1>
<div class="card">
<p class="empty">Zu <span class="mono">{{ uid }}</span> existiert kein CIS-Profil
(oder das Profil ist nicht mehr aktiv).</p>
<a class="btn" href="/fotos">Zurück zur Fotowand</a>
</div>
{% else %}
<div class="person-card">
<div class="person-photo">
{% if prof.person_id %}
<img src="/fotos/img/{{ prof.person_id }}" alt="{{ prof.nachname }}"
onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'avatar-initials',textContent:'{{ (prof.nachname[:1] ~ prof.vorname[:1]) | upper }}'}))">
{% else %}
<div class="avatar-initials">{{ (prof.nachname[:1] ~ prof.vorname[:1]) | upper }}</div>
{% endif %}
</div>
<div class="person-info">
<h1 class="person-name">
{{ prof.titel }} {{ prof.vorname }} {{ prof.nachname }}{{ ' ' if prof.postnomen }}{{ prof.postnomen }}
</h1>
<div class="person-badges">
<span class="badge">{{ prof.role or 'Person' }}</span>
{% if dec %}<span class="badge badge-muted">Start {{ dec.start }}</span>
{% if dec.stg_code %}<span class="badge badge-muted">{{ dec.stg_code }}</span>{% endif %}{% endif %}
{% if uid == own_uid %}<span class="badge badge-green">Das bist du</span>{% endif %}
</div>
{% if prof.nachname or prof.username or prof.semester or prof.stg or prof.verband or prof.kurzzeichen or prof.mail %}
<table class="person-kv">
{% if prof.username %}<tr><td>Username</td><td class="mono">{{ prof.username }}</td></tr>{% endif %}
{% if prof.stg %}<tr><td>Studiengang</td><td>{{ prof.stg }}</td></tr>{% endif %}
{% if prof.semester %}<tr><td>Studien-Semester</td><td>{{ prof.semester }}</td></tr>{% endif %}
{% if prof.verband %}<tr><td>Verband</td><td>{{ prof.verband }}{{ verband_label(prof.verband) }}</td></tr>{% endif %}
{% if prof.kurzzeichen %}<tr><td>Kurzzeichen</td><td>{{ prof.kurzzeichen }}</td></tr>{% endif %}
{% if prof.mail %}<tr><td>E-Mail</td><td><a href="mailto:{{ prof.mail }}">{{ prof.mail }}</a></td></tr>{% endif %}
{% if prof.mail_alias %}<tr><td>E-Mail (Alias)</td><td><a href="mailto:{{ prof.mail_alias }}">{{ prof.mail_alias }}</a></td></tr>{% endif %}
</table>
{% endif %}
<div class="person-actions">
{% if uid.isdigit() %}<a class="btn btn-primary" href="/plan?uid={{ uid }}">LV-Plan anzeigen</a>{% endif %}
<a class="btn" href="/fotos">Zurück zur Fotowand</a>
</div>
</div>
</div>
{% endif %}
{% endblock %}