From f8104b627b5116dbe43512d8b0691594d0b01710 Mon Sep 17 00:00:00 2001 From: NABU Jena Date: Tue, 8 Jul 2025 07:43:16 +0200 Subject: [PATCH] update statistics --- .../templates/statistic/overview_clean.html | 388 ++++++++ .../templates/statistic/overview_full.html | 855 ++++++++++++++++++ app/statistik/README.md | 97 ++ app/statistik/admin.py | 5 + app/statistik/apps.py | 7 + .../templates/statistik/overview.html | 311 +++++++ app/statistik/urls.py | 8 + app/statistik/views.py | 85 ++ 8 files changed, 1756 insertions(+) create mode 100644 app/statistic/templates/statistic/overview_clean.html create mode 100644 app/statistic/templates/statistic/overview_full.html create mode 100644 app/statistik/README.md create mode 100644 app/statistik/admin.py create mode 100644 app/statistik/apps.py create mode 100644 app/statistik/templates/statistik/overview.html create mode 100644 app/statistik/urls.py create mode 100644 app/statistik/views.py diff --git a/app/statistic/templates/statistic/overview_clean.html b/app/statistic/templates/statistic/overview_clean.html new file mode 100644 index 0000000..c1d02a5 --- /dev/null +++ b/app/statistic/templates/statistic/overview_clean.html @@ -0,0 +1,388 @@ +{% extends "base.html" %} +{% load static %} + +{% block head_title %}Statistik - Fallen Birdy{% endblock %} + +{% block header %} + +{% endblock %} + +{% block content %} +
+
+
+
+

+ Statistik Übersicht +

+
+
+ + +
+

+ Übersicht {{ current_year }} +

+
+ +
+
+
+
+
Patienten gesamt
+
{{ patients_this_year }}
+
+
+
+ + {% for group in year_groups %} +
+
+
+
{{ group.name }}
+
{{ group.count }}
+
+
+
+ {% endfor %} +
+ + + + +
+
+
+
+
+
{{ current_year }}
+
+
+
+
+ +
+ + Wird geladen... +
+
+
+ + {% if circumstances_this_year %} +
+ {% for item in circumstances_this_year %} +
+
+
{{ item.name }}
+
{{ item.count }} ({{ item.percentage }}%)
+
+ {% endfor %} +
+ {% endif %} +
+
+
+ +
+
+
+
Alle Jahre
+
+
+
+
+ +
+ + Wird geladen... +
+
+
+ + {% if circumstances_all_time %} +
+ {% for item in circumstances_all_time %} +
+
+
{{ item.name }}
+
{{ item.count }} ({{ item.percentage }}%)
+
+ {% endfor %} +
+ {% endif %} +
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/app/statistic/templates/statistic/overview_full.html b/app/statistic/templates/statistic/overview_full.html new file mode 100644 index 0000000..bd0942c --- /dev/null +++ b/app/statistic/templates/statistic/overview_full.html @@ -0,0 +1,855 @@ +{% extends "base.html" %} +{% load static %} + +{% block head_title %}Statistik - Fallen Birdy{% endblock %} + +{% block header %} + +{% endblock %} + +{% block content %} + +{% if not user.is_authenticated %} + +{% else %} +
+
+
+
+

+ Statistik Übersicht +

+
+
+ + +
+

+ Übersicht {{ current_year }} +

+
+ +
+ {% if config.show_year_total_patients %} +
+
+
+
Aufgenommene Patienten
+
{{ patients_this_year }}
+

dieses Jahr ({{ current_year }})

+
+
+
+ {% endif %} + + + {% for group in year_summary %} +
+
+
+
{{ group.name }}
+
{{ group.count }}
+

dieses Jahr ({{ current_year }})

+
+
+
+ {% endfor %} +
+ + +
+

+ Gesamtübersicht (alle Jahre) +

+
+ +
+ {% if config.show_total_patients %} +
+
+
+
Patienten insgesamt
+
{{ total_patients }}
+

seit Beginn der Aufzeichnungen

+
+
+
+ {% endif %} + + + {% for group in total_summary %} +
+
+
+
{{ group.name }}
+
{{ group.count }}
+

+ {% if total_patients > 0 %} + ({{ group.count }}/{{ total_patients }} = + {% widthratio group.count total_patients 100 %}%) + {% endif %} +

+
+
+
+ {% endfor %} +
+ + + + +
+
+
+ {% if bird_stats %} + +
+ {% for group in statistic_individuals %} +
+
+ {{ group.name }} ({{ group.get_status_names }}) +
+ {% endfor %} +
+ +
+ {% for bird in bird_stats %} +
+
+ {{ bird.name }} + {% if bird.species and bird.species != 'Unbekannt' %} +
{{ bird.species }} + {% endif %} +
+
+ + {% for group_data in bird.groups %} + {% if group_data.count > 0 %} +
+ {% if group_data.bar_width|floatformat:0|add:0 > 15 %} +
{{ group_data.count }}
+ {% endif %} +
+ {% endif %} + {% endfor %} +
+
+ {{ bird.total }} +
+ {% for group_data in bird.groups %} + {% if group_data.count > 0 %} + {{ group_data.count }}{% if not forloop.last %} / {% endif %} + {% endif %} + {% endfor %} +
+
+ {% endfor %} +
+ {% else %} +
+ +
Keine Daten verfügbar
+

Es wurden noch keine Patienten erfasst.

+
+ {% endif %} +
+
+
+ + + + +
+
+
+
+ +
+

+ {{ current_year }} + ({{ circumstances_this_year_total }} Patienten) +

+ {% if circumstances_this_year %} +
+
+ + Diagramm wird geladen... +
+
+ +
+ {% for item in circumstances_this_year %} +
+
+
{{ item.name }}
+
{{ item.count }} ({{ item.percentage }}%)
+
+ {% endfor %} +
+ {% else %} +
+ +
Keine Fundumstände erfasst
+

Für dieses Jahr wurden noch keine Fundumstände dokumentiert.

+
+ {% endif %} +
+ + +
+

+ Alle Jahre + ({{ circumstances_all_time_total }} Patienten) +

+ {% if circumstances_all_time %} +
+
+ + Diagramm wird geladen... +
+
+
+ {% for item in circumstances_all_time %} +
+
+
{{ item.name }}
+
{{ item.count }} ({{ item.percentage }}%)
+
+ {% endfor %} +
+ {% else %} +
+ +
Keine Fundumstände erfasst
+

Es wurden noch keine Fundumstände dokumentiert.

+
+ {% endif %} +
+
+
+
+
+
+
+{% endif %} + + + +{% endif %} +{% endblock %} diff --git a/app/statistik/README.md b/app/statistik/README.md new file mode 100644 index 0000000..aeb1147 --- /dev/null +++ b/app/statistik/README.md @@ -0,0 +1,97 @@ +# Statistik App + +Die Statistik-App bietet umfassende Übersichten über die Patientendaten in der FBF (Fallen Birdy) Anwendung. + +## 📊 Funktionen + +### 1. Übersicht aktuelles Jahr +- **Aufgenommene Patienten**: Anzahl der neu aufgenommenen Patienten im aktuellen Jahr +- **In Behandlung/Auswilderung**: Aktuell aktive Fälle (Status: "In Behandlung" oder "In Auswilderung") +- **Gerettete Tiere**: Erfolgreich behandelte Patienten (Status: "Ausgewildert" oder "Übermittelt") + +### 2. Gesamtübersicht (alle Jahre) +- **Patienten insgesamt**: Gesamtanzahl aller jemals erfassten Patienten +- **Erfolgreiche Rettungen**: Gesamtanzahl geretteter Tiere mit Erfolgsquote in Prozent + +### 3. Statistik pro Vogelart (aufklappbar) +- **Interaktives Balkendiagramm** mit zweifarbigen Balken: + - 🟢 **Grün**: Gerettete Vögel (ausgewildert + übermittelt) + - 🔴 **Rot**: Verstorbene Vögel +- **Detaillierte Zahlen** an jedem Balken +- **Sortierung** nach Gesamtanzahl der Patienten (absteigend) +- **Zusatzinformationen**: Lateinischer Artname (falls verfügbar) + +## 🎨 Design-Features + +- **Responsive Design**: Optimiert für Desktop, Tablet und Mobile +- **Animierte Karten**: Hover-Effekte und sanfte Übergänge +- **Farbkodierung**: Intuitive Farben für verschiedene Statuskategorien +- **Aufklappbare Bereiche**: Übersichtliche Darstellung großer Datenmengen +- **Bootstrap 5**: Moderne, konsistente Benutzeroberfläche + +## 🔧 Technische Details + +### Datenmodell +Die Statistiken basieren auf folgenden Modellen: +- `FallenBird`: Patientendaten mit Status und Funddatum +- `Bird`: Vogelarten/Bezeichnungen +- `BirdStatus`: Status-Definitionen (In Behandlung, Ausgewildert, etc.) + +### Status-Kategorien +1. **In Behandlung** (ID: 1) - Aktive Patienten +2. **In Auswilderung** (ID: 2) - Vorbereitung zur Entlassung +3. **Ausgewildert** (ID: 3) - Erfolgreich freigelassen +4. **Übermittelt** (ID: 4) - An andere Einrichtungen weitergegeben +5. **Verstorben** (ID: 5) - Nicht gerettete Patienten + +### View-Logik +```python +# Beispiel für Jahresstatistik +patients_this_year = FallenBird.objects.filter( + date_found__year=current_year +).count() + +# Beispiel für Erfolgsrate +rescued_count = FallenBird.objects.filter( + status__id__in=[3, 4] # Ausgewildert, Übermittelt +).count() +``` + +## 📍 Navigation + +Die Statistik-App ist in der Hauptnavigation zwischen **"Volieren"** und **"Kosten"** positioniert. + +**URL**: `/statistik/` + +## 🔍 Datenanalyse + +### Aktueller Datenstand (Beispiel) +- **Gesamte Patienten**: 1.267 +- **Vogelarten**: 112 verschiedene Arten +- **Dieses Jahr (2025)**: 393 neue Patienten +- **Erfolgsquote**: ~62% (780 von 1.267 gerettet) + +### Status-Verteilung +- In Behandlung: 143 Patienten +- Ausgewildert: 683 Patienten +- Übermittelt: 97 Patienten +- Verstorben: 344 Patienten + +## 🎯 Zukünftige Erweiterungen + +Mögliche weitere Features: +- **Zeitreihen-Diagramme**: Entwicklung über mehrere Jahre +- **Monatsstatistiken**: Saisonale Verteilungen +- **Fundort-Analyse**: Geografische Statistiken +- **Kosten-Integration**: Behandlungskosten pro Art +- **Export-Funktionen**: PDF/Excel-Reports +- **Interaktive Charts**: D3.js oder Chart.js Integration + +## 📱 Responsive Verhalten + +- **Desktop**: Drei-spaltige Kartenlayouts +- **Tablet**: Zwei-spaltige Anordnung +- **Mobile**: Ein-spaltige Darstellung +- **Balkendiagramm**: Automatische Anpassung der Beschriftungen + +Die Statistik-App bietet eine umfassende, benutzerfreundliche Übersicht über alle wichtigen Kennzahlen der Wildvogel-Rettungsstation. diff --git a/app/statistik/admin.py b/app/statistik/admin.py new file mode 100644 index 0000000..e56a33e --- /dev/null +++ b/app/statistik/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin + +# Da die Statistik-App keine eigenen Modelle hat, +# ist keine Admin-Registrierung erforderlich. +# Die Statistik ist über die normale Web-Oberfläche unter /statistik/ zugänglich. diff --git a/app/statistik/apps.py b/app/statistik/apps.py new file mode 100644 index 0000000..2a124c4 --- /dev/null +++ b/app/statistik/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class StatistikConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'statistik' + verbose_name = 'Statistik' diff --git a/app/statistik/templates/statistik/overview.html b/app/statistik/templates/statistik/overview.html new file mode 100644 index 0000000..59035dd --- /dev/null +++ b/app/statistik/templates/statistik/overview.html @@ -0,0 +1,311 @@ +{% extends "base.html" %} +{% load static %} + +{% block head_title %}Statistik - Fallen Birdy{% endblock %} + +{% block header %} + +{% endblock %} + +{% block content %} +
+
+
+

+ Statistik Übersicht +

+
+
+ + +
+

+ Übersicht {{ current_year }} +

+
+ +
+
+
+
+
Aufgenommene Patienten
+
{{ patients_this_year }}
+

dieses Jahr ({{ current_year }})

+
+
+
+
+
+
+
In Behandlung / Auswilderung
+
{{ in_treatment_or_release }}
+

aktuell aktive Fälle

+
+
+
+
+
+
+
Gerettete Tiere
+
{{ rescued_this_year }}
+

ausgewildert & übermittelt

+
+
+
+
+ + +
+

+ Gesamtübersicht (alle Jahre) +

+
+ +
+
+
+
+
Patienten insgesamt
+
{{ total_patients }}
+

seit Beginn der Aufzeichnungen

+
+
+
+
+
+
+
Erfolgreiche Rettungen
+
{{ total_rescued }}
+

+ {% if total_patients > 0 %} + ({{ total_rescued|floatformat:0 }}/{{ total_patients }} = + {% widthratio total_rescued total_patients 100 %}%) + {% endif %} +

+
+
+
+
+ + + + +
+
+
+ {% if bird_stats %} +
+
+
+ Gerettet (ausgewildert + übermittelt) +
+
+
+ Verstorben +
+
+ +
+ {% for bird in bird_stats %} +
+
+ {{ bird.name }} + {% if bird.species and bird.species != 'Unbekannt' %} +
{{ bird.species }} + {% endif %} +
+
+ {% if bird.rescued > 0 %} +
+ {% if bird.rescued_percentage > 15 %} +
{{ bird.rescued }}
+ {% endif %} +
+ {% endif %} + {% if bird.deceased > 0 %} +
+ {% if bird.deceased_percentage > 15 %} +
{{ bird.deceased }}
+ {% endif %} +
+ {% endif %} +
+
+ {{ bird.total }} +
+ {{ bird.rescued }} / + {{ bird.deceased }} +
+
+ {% endfor %} +
+ {% else %} +
+ +
Keine Daten verfügbar
+

Es wurden noch keine Patienten erfasst.

+
+ {% endif %} +
+
+
+
+ + +{% endblock %} diff --git a/app/statistik/urls.py b/app/statistik/urls.py new file mode 100644 index 0000000..4377312 --- /dev/null +++ b/app/statistik/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from . import views + +app_name = 'statistik' + +urlpatterns = [ + path('', views.StatistikView.as_view(), name='overview'), +] diff --git a/app/statistik/views.py b/app/statistik/views.py new file mode 100644 index 0000000..2102085 --- /dev/null +++ b/app/statistik/views.py @@ -0,0 +1,85 @@ +from django.views.generic import TemplateView +from django.db.models import Count, Q +from django.utils import timezone +from datetime import datetime +from bird.models import FallenBird, Bird, BirdStatus + + +class StatistikView(TemplateView): + template_name = 'statistik/overview.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + # Aktuelles Jahr + current_year = timezone.now().year + + # 1. Übersicht über das aktuelle Jahr + context['current_year'] = current_year + + # Patienten dieses Jahr aufgenommen + patients_this_year = FallenBird.objects.filter( + date_found__year=current_year + ).count() + context['patients_this_year'] = patients_this_year + + # Aktuell in Behandlung oder Auswilderung + in_treatment_or_release = FallenBird.objects.filter( + date_found__year=current_year, + status__id__in=[1, 2] # In Behandlung, In Auswilderung + ).count() + context['in_treatment_or_release'] = in_treatment_or_release + + # Ausgewildert + Übermittelt dieses Jahr + rescued_this_year = FallenBird.objects.filter( + date_found__year=current_year, + status__id__in=[3, 4] # Ausgewildert, Übermittelt + ).count() + context['rescued_this_year'] = rescued_this_year + + # 2. Übersicht über alle Jahre + total_patients = FallenBird.objects.count() + context['total_patients'] = total_patients + + total_rescued = FallenBird.objects.filter( + status__id__in=[3, 4] # Ausgewildert, Übermittelt + ).count() + context['total_rescued'] = total_rescued + + # 3. Statistik pro Vogelart + bird_stats = [] + for bird in Bird.objects.all(): + fallen_birds = FallenBird.objects.filter(bird=bird) + + total_count = fallen_birds.count() + rescued_count = fallen_birds.filter(status__id__in=[3, 4]).count() + deceased_count = fallen_birds.filter(status__id=5).count() + + if total_count > 0: # Nur Vögel anzeigen, die auch Patienten haben + bird_stats.append({ + 'name': bird.name, + 'species': bird.species or 'Unbekannt', + 'total': total_count, + 'rescued': rescued_count, + 'deceased': deceased_count, + 'rescued_percentage': round((rescued_count / total_count) * 100, 1) if total_count > 0 else 0, + 'deceased_percentage': round((deceased_count / total_count) * 100, 1) if total_count > 0 else 0 + }) + + # Sortiere nach Gesamtanzahl (absteigend) + bird_stats.sort(key=lambda x: x['total'], reverse=True) + context['bird_stats'] = bird_stats + + # Status-Namen für das Template + try: + context['status_names'] = { + 1: BirdStatus.objects.get(id=1).description, + 2: BirdStatus.objects.get(id=2).description, + 3: BirdStatus.objects.get(id=3).description, + 4: BirdStatus.objects.get(id=4).description, + 5: BirdStatus.objects.get(id=5).description, + } + except BirdStatus.DoesNotExist: + context['status_names'] = {} + + return context