diff --git a/web/app.py b/web/app.py
index 97f0160..64f4f88 100644
--- a/web/app.py
+++ b/web/app.py
@@ -403,9 +403,16 @@ def _wohnungen_context(user) -> dict:
}, filters):
continue
last = db.last_application_for_flat(uid, f["id"])
- flats_view.append({"row": f, "last": last})
+ enrichment_data = None
+ if f["enrichment_json"]:
+ try:
+ enrichment_data = json.loads(f["enrichment_json"])
+ except Exception:
+ enrichment_data = None
+ flats_view.append({"row": f, "last": last, "enrichment": enrichment_data})
rejected_view = db.rejected_flats(uid)
+ enrichment_counts = db.enrichment_counts()
allowed, reason = _manual_apply_allowed()
alert_label, alert_chip = _alert_status(notif_row)
@@ -441,6 +448,7 @@ def _wohnungen_context(user) -> dict:
return {
"flats": flats_view,
"rejected_flats": rejected_view,
+ "enrichment_counts": enrichment_counts,
"map_points": map_points,
"has_filters": _has_filters(filters_row),
"alert_label": alert_label,
diff --git a/web/db.py b/web/db.py
index 065f8f6..7e4471a 100644
--- a/web/db.py
+++ b/web/db.py
@@ -479,6 +479,23 @@ def flats_needing_enrichment(limit: int = 100) -> list[sqlite3.Row]:
).fetchall())
+def enrichment_counts() -> dict:
+ row = _conn.execute(
+ """SELECT
+ COUNT(*) AS total,
+ SUM(CASE WHEN enrichment_status = 'ok' THEN 1 ELSE 0 END) AS ok,
+ SUM(CASE WHEN enrichment_status = 'pending' THEN 1 ELSE 0 END) AS pending,
+ SUM(CASE WHEN enrichment_status = 'failed' THEN 1 ELSE 0 END) AS failed
+ FROM flats"""
+ ).fetchone()
+ return {
+ "total": int(row["total"] or 0),
+ "ok": int(row["ok"] or 0),
+ "pending": int(row["pending"] or 0),
+ "failed": int(row["failed"] or 0),
+ }
+
+
# ---------------------------------------------------------------------------
# Applications
# ---------------------------------------------------------------------------
diff --git a/web/templates/_wohnungen_body.html b/web/templates/_wohnungen_body.html
index 6c26aaa..46c2f15 100644
--- a/web/templates/_wohnungen_body.html
+++ b/web/templates/_wohnungen_body.html
@@ -81,7 +81,19 @@
{{ flats|length }} gefunden
{% if next_scrape_utc %}
-
· nächste Aktualisierung …
+
·
+
nächste Aktualisierung …
+ {% endif %}
+ {% if is_admin and (enrichment_counts.pending or enrichment_counts.failed) %}
+
·
+
{% endif %}
- {% if f.rooms %}{{ "%.1f"|format(f.rooms) }} Z{% endif %}
- {% if f.size %} · {{ "%.0f"|format(f.size) }} m²{% endif %}
- {% if f.total_rent %} · {{ "%.0f"|format(f.total_rent) }} €{% endif %}
- {% if f.wbs %} · WBS: {{ f.wbs }}{% endif %}
- · …
+ {% if f.enrichment_status == 'pending' %}
+ Infos werden abgerufen…
+ · …
+ {% elif f.enrichment_status == 'failed' %}
+ Fehler beim Abrufen der Infos
+ · …
+ {% else %}
+ {% set e = item.enrichment or {} %}
+ {% set parts = [] %}
+ {% if e.rooms %}{% set _ = parts.append('%g Z'|format(e.rooms)) %}{% endif %}
+ {% if e.size_sqm %}{% set _ = parts.append('%.0f m²'|format(e.size_sqm)) %}{% endif %}
+ {% set rent = e.rent_total or e.rent_cold %}
+ {% if rent %}{% set _ = parts.append('%.0f €'|format(rent)) %}{% endif %}
+ {% if e.wbs_required is true %}
+ {% set _ = parts.append('WBS: ' ~ (e.wbs_type or 'erforderlich')) %}
+ {% elif e.wbs_required is false %}
+ {% set _ = parts.append('ohne WBS') %}
+ {% endif %}
+ {{ parts|join(' · ') }}{% if parts %} · {% endif %}…
+ {% endif %}
@@ -176,18 +203,6 @@
-{% if is_admin %}
-
-{% endif %}
{% if rejected_flats %}
diff --git a/web/templates/base.html b/web/templates/base.html
index 963821d..8f9bda7 100644
--- a/web/templates/base.html
+++ b/web/templates/base.html
@@ -48,6 +48,12 @@
transition: border-color .15s, box-shadow .15s; }
.input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(47,138,224,.18); }
.chip { padding: .2rem .7rem; border-radius: 999px; font-size: .75rem; font-weight: 500; display: inline-block; }
+ /* Countdown — tabular digits + fixed width so the text doesn't wobble
+ while the seconds tick down (3 → 10 → 59 → 1 min). */
+ .countdown { font-variant-numeric: tabular-nums; display: inline-block;
+ min-width: 4.2em; text-align: left; }
+ /* Neutral separator in flex rows so the '·' has equal gap on both sides. */
+ .sep { color: var(--muted); user-select: none; }
.chip-ok { background: #e4f6ec; color: #1f8a4a; border: 1px solid #b7e4c7; }
.chip-warn { background: #fff4dd; color: #a36a1f; border: 1px solid #f5d48b; }
.chip-bad { background: #fde6e9; color: #b8404e; border: 1px solid #f5b5bf; }