Sprint 15: Zeitzone-Fix, Gewichts-Sync, Öffnungszeiten, KI-Bericht, POI-Moderation — SW by-v432, APP_VER 411
- client_time: Browser-Lokalzeit bei allen Creates mitschicken (Tagebuch, Notizen, Forum, Verlorener Hund, Routen) — kein UTC-Versatz mehr bei Einträgen - Gewicht-Sync: health typ=gewicht schreibt dogs.gewicht_kg, einmalige Migration - Praxen: opening_hours + lat/lon/osm_id in tieraerzte-Tabelle, OSM-Nearby-Lookup, Öffnungszeiten in Karte und Detailansicht - KI-Gesundheitsbericht: alle 2 Wochen automatisch, ki_health_reports-Tabelle, Frontend-Banner mit Archiv (letzten 5 Berichte) - POI-Korrekturen: User schlägt Öffnungszeiten-Änderung vor, Moderatoren-Tab genehmigt/lehnt ab, user_edited-Flag schützt vor Overpass-Überschreibung - timeutils.py: safe_client_time() zentral für alle Routen
This commit is contained in:
parent
679dbdd862
commit
06bd8525ed
21 changed files with 724 additions and 75 deletions
|
|
@ -1,6 +1,7 @@
|
|||
"""BAN YARO — Tierärzte Routes (user-level, nie löschen)"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
import math
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from database import db
|
||||
|
|
@ -11,34 +12,60 @@ router = APIRouter()
|
|||
|
||||
class TierarztCreate(BaseModel):
|
||||
name: str
|
||||
strasse: Optional[str] = None
|
||||
plz: Optional[str] = None
|
||||
ort: Optional[str] = None
|
||||
telefon: Optional[str] = None
|
||||
notfall_telefon: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
website: Optional[str] = None
|
||||
notizen: Optional[str] = None
|
||||
ist_notfallpraxis: bool = False
|
||||
strasse: Optional[str] = None
|
||||
plz: Optional[str] = None
|
||||
ort: Optional[str] = None
|
||||
telefon: Optional[str] = None
|
||||
notfall_telefon: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
website: Optional[str] = None
|
||||
notizen: Optional[str] = None
|
||||
ist_notfallpraxis: bool = False
|
||||
opening_hours: Optional[str] = None
|
||||
lat: Optional[float] = None
|
||||
lon: Optional[float] = None
|
||||
osm_id: Optional[str] = None
|
||||
|
||||
|
||||
class TierarztUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
strasse: Optional[str] = None
|
||||
plz: Optional[str] = None
|
||||
ort: Optional[str] = None
|
||||
telefon: Optional[str] = None
|
||||
notfall_telefon: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
website: Optional[str] = None
|
||||
notizen: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
strasse: Optional[str] = None
|
||||
plz: Optional[str] = None
|
||||
ort: Optional[str] = None
|
||||
telefon: Optional[str] = None
|
||||
notfall_telefon: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
website: Optional[str] = None
|
||||
notizen: Optional[str] = None
|
||||
ist_notfallpraxis: Optional[bool] = None
|
||||
aktiv: Optional[bool] = None # False = inaktiv (Umzug etc.)
|
||||
aktiv: Optional[bool] = None
|
||||
opening_hours: Optional[str] = None
|
||||
lat: Optional[float] = None
|
||||
lon: Optional[float] = None
|
||||
osm_id: Optional[str] = None
|
||||
|
||||
|
||||
def _fmt_opening_hours(raw: str | None) -> str | None:
|
||||
"""Wandelt OSM-opening_hours-String in lesbares Deutsch um.
|
||||
|
||||
Beispiel: "Mo-Fr 08:00-18:00; Sa 09:00-13:00"
|
||||
→ "Mo–Fr 08:00–18:00 · Sa 09:00–13:00"
|
||||
"""
|
||||
if not raw:
|
||||
return None
|
||||
if raw.strip().lower() == "24/7":
|
||||
return "24/7 geöffnet"
|
||||
result = raw.replace(" - ", "–").replace("-", "–", 1)
|
||||
result = "; ".join(
|
||||
part.strip().replace(";", "").replace(",", " · ")
|
||||
for part in raw.split(";")
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_tieraerzte(user=Depends(get_current_user)):
|
||||
"""Alle Tierärzte des Users — aktive zuerst, dann inaktive (für Historienansicht)."""
|
||||
"""Alle Tierärzte des Users — aktive zuerst, dann inaktive."""
|
||||
with db() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM tieraerzte WHERE user_id=? ORDER BY aktiv DESC, name",
|
||||
|
|
@ -47,17 +74,64 @@ async def list_tieraerzte(user=Depends(get_current_user)):
|
|||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("/osm-nearby")
|
||||
async def osm_nearby(
|
||||
lat: float = Query(...),
|
||||
lon: float = Query(...),
|
||||
radius_km: float = Query(5.0),
|
||||
user=Depends(get_current_user)
|
||||
):
|
||||
"""Findet Tierarzt-POIs aus dem OSM-Cache in der Nähe (max. 10 Treffer)."""
|
||||
with db() as conn:
|
||||
rows = conn.execute(
|
||||
"""SELECT osm_id, name, lat, lon, opening_hours, phone, website
|
||||
FROM osm_pois
|
||||
WHERE type = 'tierarzt'
|
||||
AND lat BETWEEN ? AND ?
|
||||
AND lon BETWEEN ? AND ?""",
|
||||
(lat - radius_km / 111.0, lat + radius_km / 111.0,
|
||||
lon - radius_km / 111.0, lon + radius_km / 111.0)
|
||||
).fetchall()
|
||||
|
||||
def _dist(r):
|
||||
dlat = (r["lat"] - lat) * math.pi / 180
|
||||
dlon = (r["lon"] - lon) * math.pi / 180
|
||||
a = math.sin(dlat/2)**2 + math.cos(lat*math.pi/180) * math.cos(r["lat"]*math.pi/180) * math.sin(dlon/2)**2
|
||||
return 6371 * 2 * math.asin(math.sqrt(a))
|
||||
|
||||
results = []
|
||||
for r in rows:
|
||||
d = _dist(r)
|
||||
if d <= radius_km:
|
||||
results.append({
|
||||
"osm_id": r["osm_id"],
|
||||
"name": r["name"],
|
||||
"lat": r["lat"],
|
||||
"lon": r["lon"],
|
||||
"opening_hours": r["opening_hours"],
|
||||
"opening_hours_fmt": _fmt_opening_hours(r["opening_hours"]),
|
||||
"phone": r["phone"],
|
||||
"website": r["website"],
|
||||
"distanz_km": round(d, 2),
|
||||
})
|
||||
|
||||
results.sort(key=lambda x: x["distanz_km"])
|
||||
return results[:10]
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
async def create_tierarzt(data: TierarztCreate, user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
conn.execute(
|
||||
"""INSERT INTO tieraerzte
|
||||
(user_id, name, strasse, plz, ort, telefon, notfall_telefon,
|
||||
email, website, notizen, ist_notfallpraxis)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
email, website, notizen, ist_notfallpraxis,
|
||||
opening_hours, lat, lon, osm_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||
(user["id"], data.name, data.strasse, data.plz, data.ort,
|
||||
data.telefon, data.notfall_telefon, data.email, data.website,
|
||||
data.notizen, int(data.ist_notfallpraxis))
|
||||
data.notizen, int(data.ist_notfallpraxis),
|
||||
data.opening_hours, data.lat, data.lon, data.osm_id)
|
||||
)
|
||||
row = conn.execute(
|
||||
"SELECT * FROM tieraerzte WHERE user_id=? ORDER BY id DESC LIMIT 1",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue