diff --git a/app.py b/app.py index 4ebec37..29e9d76 100644 --- a/app.py +++ b/app.py @@ -297,30 +297,30 @@ def datenschutz() -> str: # ── Blog-Artikel-Datenbank ─────────────────────────────── _ARTIKEL = [ - ("/blog/telematik-vorschriften", "TI-Anschlusspflicht 2025", "artikel-01-ti-anschlusspflicht.html"), - ("/blog/telematik-vorschriften", "Elektronische Abrechnung ab 12/2026", "artikel-02-elektronische-abrechnung.html"), - ("/blog/telematik-vorschriften", "SMC-B-Karte — 33% Abdeckung", "artikel-03-smc-b-karte.html"), - ("/blog/dsgvo-it-sicherheit", "46,9 Mio. EUR DSGVO-Bussgelder", "artikel-04-dsgvo-bussgelder.html"), - ("/blog/dsgvo-it-sicherheit", "BSI: 16 Schwachstellen Pflegesoftware", "artikel-05-bsi-schwachstellen.html"), - ("/blog/dsgvo-it-sicherheit", "138 Sicherheitsvorfaelle Gesundheitswesen", "artikel-06-sicherheitsvorfaelle.html"), - ("/blog/telematik-vorschriften", "NIS-2 Registrierungsfrist", "artikel-07-nis2.html"), - ("/blog/fachkraeftemangel", "200.000 unbesetzte Pflegestellen", "artikel-08-fachkraeftemangel.html"), - ("/blog/fachkraeftemangel", "62% Altenpflege — Personalplanung", "artikel-09-personalplanung.html"), - ("/blog/fachkraeftemangel", "KI-Sprachassistent spart 27% Zeit", "artikel-10-ki-sprachassistent.html"), - ("/blog/foerderung-finanzierung", "Bis 12.000 EUR Digitalisierungszuschuss", "artikel-11-digitalisierungszuschuss.html"), - ("/blog/foerderung-finanzierung", "DiPA — 70 EUR monatlich ab 2026", "artikel-12-dipa.html"), - ("/blog/foerderung-finanzierung", "Pflegeversicherung 1 Mrd. Defizit", "artikel-13-pflegeversicherung.html"), - ("/blog/qualitaet-pruefung", "QPR ambulant ab Juli 2026", "artikel-14-qpr-ambulant.html"), - ("/blog/qualitaet-pruefung", "PeBeM verpflichtend ab 2026", "artikel-15-pebem.html"), - ("/blog/qualitaet-pruefung", "MDK-Pruefung digital bestehen", "artikel-16-mdk-pruefung.html"), - ("/blog/telematik-vorschriften", "ePA fuer alle — Opt-out", "artikel-17-epa.html"), - ("/blog/dsgvo-it-sicherheit", "Cloud-Hosting Deutschland", "artikel-18-cloud-hosting.html"), - ("/blog/praxis-tipps", "Tablets fuer die Pflege — Top 5", "artikel-19-tablets.html"), - ("/blog/praxis-tipps", "WLAN im Pflegeheim", "artikel-20-wlan.html"), - ("/blog/praxis-tipps", "Offline-Dokumentation", "artikel-21-offline.html"), - ("/blog/dsgvo-it-sicherheit", "E-Mail-Verschluesselung", "artikel-22-email-verschluesselung.html"), - ("/blog/praxis-tipps", "Medikationsplan digital", "artikel-23-medikationsplan.html"), - ("/blog/praxis-tipps", "IT-Notfallplan 10-Punkte", "artikel-24-it-notfallplan.html"), + ("/blog/telematik-vorschriften", "TI-Anschlusspflicht 2025", "artikel-01-ti-anschlusspflicht.html", "ti-anschlusspflicht-2025"), + ("/blog/telematik-vorschriften", "Elektronische Abrechnung ab 12/2026", "artikel-02-elektronische-abrechnung.html", "elektronische-abrechnung-2026"), + ("/blog/telematik-vorschriften", "SMC-B-Karte — 33% Abdeckung", "artikel-03-smc-b-karte.html", "smc-b-karte-pflege"), + ("/blog/dsgvo-it-sicherheit", "46,9 Mio. EUR DSGVO-Bussgelder", "artikel-04-dsgvo-bussgelder.html", "dsgvo-bussgelder-gesundheitswesen"), + ("/blog/dsgvo-it-sicherheit", "BSI: 16 Schwachstellen Pflegesoftware", "artikel-05-bsi-schwachstellen.html", "bsi-schwachstellen-pflegesoftware"), + ("/blog/dsgvo-it-sicherheit", "138 Sicherheitsvorfaelle Gesundheitswesen", "artikel-06-sicherheitsvorfaelle.html", "sicherheitsvorfaelle-gesundheitswesen"), + ("/blog/telematik-vorschriften", "NIS-2 Registrierungsfrist", "artikel-07-nis2.html", "nis2-registrierungsfrist"), + ("/blog/fachkraeftemangel", "200.000 unbesetzte Pflegestellen", "artikel-08-fachkraeftemangel.html", "fachkraeftemangel-pflege"), + ("/blog/fachkraeftemangel", "62% Altenpflege — Personalplanung", "artikel-09-personalplanung.html", "personalplanung-altenpflege"), + ("/blog/fachkraeftemangel", "KI-Sprachassistent spart 27% Zeit", "artikel-10-ki-sprachassistent.html", "ki-sprachassistent-pflege"), + ("/blog/foerderung-finanzierung", "Bis 12.000 EUR Digitalisierungszuschuss", "artikel-11-digitalisierungszuschuss.html", "digitalisierungszuschuss-pflege"), + ("/blog/foerderung-finanzierung", "DiPA — 70 EUR monatlich ab 2026", "artikel-12-dipa.html", "dipa-digitale-pflegeanwendungen"), + ("/blog/foerderung-finanzierung", "Pflegeversicherung 1 Mrd. Defizit", "artikel-13-pflegeversicherung.html", "pflegeversicherung-defizit"), + ("/blog/qualitaet-pruefung", "QPR ambulant ab Juli 2026", "artikel-14-qpr-ambulant.html", "qpr-ambulant-2026"), + ("/blog/qualitaet-pruefung", "PeBeM verpflichtend ab 2026", "artikel-15-pebem.html", "pebem-2026"), + ("/blog/qualitaet-pruefung", "MDK-Pruefung digital bestehen", "artikel-16-mdk-pruefung.html", "mdk-pruefung-digital"), + ("/blog/telematik-vorschriften", "ePA fuer alle — Opt-out", "artikel-17-epa.html", "epa-opt-out"), + ("/blog/dsgvo-it-sicherheit", "Cloud-Hosting Deutschland", "artikel-18-cloud-hosting.html", "cloud-hosting-deutschland"), + ("/blog/praxis-tipps", "Tablets fuer die Pflege — Top 5", "artikel-19-tablets.html", "tablets-pflege-top5"), + ("/blog/praxis-tipps", "WLAN im Pflegeheim", "artikel-20-wlan.html", "wlan-pflegeheim"), + ("/blog/praxis-tipps", "Offline-Dokumentation", "artikel-21-offline.html", "offline-dokumentation-pflege"), + ("/blog/dsgvo-it-sicherheit", "E-Mail-Verschluesselung", "artikel-22-email-verschluesselung.html", "email-verschluesselung-pflege"), + ("/blog/praxis-tipps", "Medikationsplan digital", "artikel-23-medikationsplan.html", "medikationsplan-digital"), + ("/blog/praxis-tipps", "IT-Notfallplan 10-Punkte", "artikel-24-it-notfallplan.html", "it-notfallplan-pflege"), ] # ── Leistungen-Daten ───────────────────────────────────── @@ -392,15 +392,15 @@ _IT_SERVICE = { # ── Helper: Seitenleiste ────────────────────────────────── -def _sidebar(routes: list[tuple[str, str, str]]) -> str: +def _sidebar(routes: list[tuple[str, str, str, str]]) -> str: """Erstelle Seitenleiste mit Artikel-Links. Args: - routes: Liste von (Route, Titel, Datei)-Tupeln. + routes: Liste von (Sektion, Titel, Datei, Slug)-Tupeln. """ - links = [] - for route, titel, _datei in routes: - links.append('' + titel + "") + links: list[str] = [] + for _sektion, titel, _datei, slug in routes: + links.append('' + titel + "") return ( "
" + "".join(links) @@ -414,18 +414,19 @@ def _cards(titel: str, beschreibung: str) -> str: return '

' + titel + "

" + beschreibung + "

" -# ── Blog-Route (FIX: Nutzt _ARTIKEL-Mapping) ───────────── +# ── Blog-Sektionen (Übersichtsseiten) ───────────────────── @app.route("/blog/") -def blog(slug: str) -> str: - """Blog-Artikel — nutzt _ARTIKEL-Mapping fuer korrekte Dateinamen.""" +def blog_section(slug: str) -> str: + """Blog-Sektion — zeigt ersten Artikel + Sidebar mit allen Artikeln der Sektion.""" route_key = "/blog/" + slug - # Finde ALLE Artikel dieser Sektion - sektions_artikel = [(r, t, d) for r, t, d in _ARTIKEL if r == route_key] + # Finde ALLE Artikel dieser Sektion (4-Tupel) + sektions_artikel: list[tuple[str, str, str, str]] = [ + (r, t, d, s) for r, t, d, s in _ARTIKEL if r == route_key + ] if sektions_artikel: - # Ersten Artikel als Hauptinhalt laden - _, haupt_titel, datei = sektions_artikel[0] + _, haupt_titel, datei, _ = sektions_artikel[0] pfad = ARTIKEL_DIR / datei if pfad.exists(): inhalt = pfad.read_text(encoding="utf-8") @@ -434,11 +435,10 @@ def blog(slug: str) -> str: else: inhalt = "

Artikeltext folgt in Kuerze.

" else: - inhalt = "

Artikel nicht gefunden.

" - sektions_artikel = _ARTIKEL + inhalt = "

Keine Artikel in dieser Sektion gefunden.

" haupt_titel = slug.replace("-", " ").title() + sektions_artikel = _ARTIKEL - # Seitenleiste: alle Artikel dieser Sektion sidebar = _sidebar(sektions_artikel) content = ( @@ -453,6 +453,49 @@ def blog(slug: str) -> str: return _base_template("Blog: " + haupt_titel, content, active="blog") +# ── Individuelle Artikel-Seiten (SEO-optimiert) ────────── +_ARTIKEL_BY_SLUG: dict[str, tuple[str, str, str, str]] = { + slug: (sektion, titel, datei, slug) + for sektion, titel, datei, slug in _ARTIKEL +} + + +@app.route("/artikel/") +def artikel_detail(slug: str) -> str: + """Einzelner Artikel — eigene SEO-URL mit vollstaendigem Inhalt.""" + eintrag = _ARTIKEL_BY_SLUG.get(slug) + + if eintrag: + sektion, titel, datei, _ = eintrag + pfad = ARTIKEL_DIR / datei + if pfad.exists(): + inhalt = pfad.read_text(encoding="utf-8") + match = re.search(r"]*>(.*?)", inhalt, re.DOTALL | re.IGNORECASE) + inhalt = match.group(1) if match else inhalt + else: + inhalt = "

Artikeltext folgt in Kuerze.

" + + # Sidebar: andere Artikel derselben Sektion + sektions_artikel: list[tuple[str, str, str, str]] = [ + (r, t, d, s) for r, t, d, s in _ARTIKEL if r == sektion and s != slug + ] + sidebar = _sidebar(sektions_artikel) + + content = ( + "

" + + titel + + "

" + + inhalt + + "
" + + sidebar + + "
" + ) + return _base_template(titel, content, active="blog") + else: + content = "

Artikel nicht gefunden

Dieser Artikel existiert leider nicht.

" + return _base_template("Artikel nicht gefunden", content) + + # ── Weitere Routen ─────────────────────────────────────── @app.route("/ueber-uns") def ueber_uns() -> str: