Spaces:
Sleeping
Sleeping
| import os, sys, json | |
| from datetime import datetime, date | |
| from pathlib import Path | |
| import gradio as gr | |
| # Make src importable | |
| sys.path.append("src") | |
| from src.latest_froschgruppe.tools.organizer_tool import OrganizerTool | |
| from src.latest_froschgruppe.tools.payroll_tool import PayrollTool | |
| from src.latest_froschgruppe.tools.roadkill_gbif_tool import RoadkillGBIFTool | |
| from src.latest_froschgruppe.tools.calendar_tool import CalendarTool # ICS generator | |
| from src.latest_froschgruppe.tools.gbif_occurrence_tool import GBIFOccurrenceTool | |
| # Optional: if you have folium map tool | |
| # from latest_froschgruppe.tools.map_tool import build_map | |
| DATA_DIR = Path(os.getenv("DATA_DIR", "data")) | |
| DATA_DIR.mkdir(parents=True, exist_ok=True) | |
| EVENTS_FILE = DATA_DIR / "events.json" | |
| organizer = OrganizerTool() | |
| payroll = PayrollTool() | |
| gbif = GBIFOccurrenceTool() | |
| roadkill = RoadkillGBIFTool() | |
| cal = CalendarTool() | |
| def _load_events(): | |
| if not EVENTS_FILE.exists(): | |
| return [] | |
| return json.loads(EVENTS_FILE.read_text(encoding="utf-8")) | |
| def _save_events(events): | |
| EVENTS_FILE.write_text(json.dumps(events, ensure_ascii=False, indent=2), encoding="utf-8") | |
| def add_event(title: str, start_date: str, start_time: str, end_time: str, location: str): | |
| events = _load_events() | |
| # store ISO-ish strings | |
| start = f"{start_date}T{start_time}" | |
| end = f"{start_date}T{end_time}" | |
| ev = { | |
| "id": f"E{len(events)+1:04d}", | |
| "title": title.strip(), | |
| "start": start, | |
| "end": end, | |
| "location": location.strip(), | |
| "created_at": datetime.utcnow().isoformat() + "Z", | |
| } | |
| events.append(ev) | |
| _save_events(events) | |
| return list_events() | |
| def delete_event(event_id: str): | |
| events = _load_events() | |
| events = [e for e in events if e["id"] != event_id.strip()] | |
| _save_events(events) | |
| return list_events() | |
| def list_events(): | |
| events = _load_events() | |
| events_sorted = sorted(events, key=lambda e: e.get("start", "")) | |
| # dataframe-like list | |
| rows = [] | |
| for e in events_sorted[:200]: | |
| rows.append([e["id"], e["start"], e.get("end",""), e["title"], e.get("location","")]) | |
| return rows | |
| def dashboard_snapshot(): | |
| # Todos | |
| todos_md = organizer._run("list_todos", "{}") | |
| vols_md = organizer._run("list_volunteers", "{}") | |
| # Payroll report | |
| payroll_md = payroll._run("report", "{}") | |
| # Events | |
| events_rows = list_events() | |
| events_md = "## Nächste Events\n" | |
| if not events_rows: | |
| events_md += "Keine Events gespeichert." | |
| else: | |
| # show only next 10 | |
| events_md += "\n".join([f"- {r[1]} | {r[3]} @ {r[4]} (ID {r[0]})" for r in events_rows[:10]]) | |
| # Next migration window (simple heuristic) | |
| today = date.today() | |
| year = today.year | |
| # Typical window for DE: Feb 15 – Apr 30 (configurable) | |
| # If already past, show next year. | |
| window_start = date(year, 2, 15) | |
| window_end = date(year, 4, 30) | |
| if today > window_end: | |
| window_start = date(year + 1, 2, 15) | |
| window_end = date(year + 1, 4, 30) | |
| mig_md = ( | |
| "## Nächste Wanderphase (operatives Fenster)\n" | |
| f"- Fenster: **{window_start.isoformat()} bis {window_end.isoformat()}**\n" | |
| "- Trigger (praktisch): milde, feuchte Abende/Nächte + Regen → Einsatzwahrscheinlichkeit hoch.\n" | |
| "- Tipp: täglich 17:00 Wettercheck + bei Trigger Nachtteam aktivieren." | |
| ) | |
| return mig_md, events_md, todos_md, vols_md, payroll_md | |
| def add_todo(title: str, priority: str): | |
| payload = json.dumps({"title": title, "priority": priority}, ensure_ascii=False) | |
| msg = organizer._run("add_todo", payload) | |
| return msg, organizer._run("list_todos", "{}") | |
| def add_volunteer(name: str, phone: str, availability: str): | |
| payload = json.dumps({"name": name, "phone": phone, "availability": availability}, ensure_ascii=False) | |
| msg = organizer._run("add_volunteer", payload) | |
| return msg, organizer._run("list_volunteers", "{}") | |
| def payroll_plan(hourly_rate: float, monthly_cap: float): | |
| payload = json.dumps({"hourly_rate": hourly_rate, "monthly_cap": monthly_cap}, ensure_ascii=False) | |
| return payroll._run("plan", payload) | |
| def payroll_add_worker(name: str, hourly_rate: float, monthly_cap: float): | |
| payload = json.dumps({"name": name, "hourly_rate": hourly_rate, "monthly_cap": monthly_cap}, ensure_ascii=False) | |
| msg = payroll._run("add_worker", payload) | |
| rep = payroll._run("report", "{}") | |
| return msg, rep | |
| def payroll_log_hours(name: str, date_str: str, hours: float, note: str): | |
| payload = json.dumps({"name": name, "date": date_str, "hours": hours, "note": note}, ensure_ascii=False) | |
| msg = payroll._run("log_hours", payload) | |
| rep = payroll._run("report", "{}") | |
| return msg, rep | |
| def fetch_hotspots(species: str, country: str, year_from: int, year_to: int): | |
| # Tools return markdown tables + google maps links | |
| gbif_md = gbif._run(scientific_name=species, country=country, year_from=year_from, year_to=year_to, limit=300) | |
| road_md = roadkill._run(country=country, scientific_name=species, year_from=year_from, year_to=year_to, limit=300) | |
| return gbif_md, road_md | |
| def generate_migration_ics(region: str, year: int, start_month: int, start_day: int, end_month: int, end_day: int): | |
| # CalendarTool returns a path in text; we want a file output | |
| txt = cal._run(region=region, year=year, start_month=start_month, start_day=start_day, end_month=end_month, end_day=end_day) | |
| # Extract file path from tool output | |
| # expected: "✅ ICS erstellt: <path>" | |
| path = None | |
| for token in txt.split(): | |
| if token.endswith(".ics"): | |
| path = token | |
| break | |
| return txt, path | |
| with gr.Blocks(title="Teich – Management Partner (Froschrettung)") as demo: | |
| gr.Markdown("# 🐸 Teich – Management Partner (Froschrettung)\n**Dashboard • Kalender • Einsätze • Daten • Team • Budget**") | |
| with gr.Tab("Dashboard"): | |
| btn_refresh = gr.Button("🔄 Dashboard aktualisieren") | |
| mig_md = gr.Markdown() | |
| events_md = gr.Markdown() | |
| todos_md = gr.Markdown() | |
| vols_md = gr.Markdown() | |
| payroll_md = gr.Markdown() | |
| btn_refresh.click(fn=dashboard_snapshot, inputs=[], outputs=[mig_md, events_md, todos_md, vols_md, payroll_md]) | |
| demo.load(fn=dashboard_snapshot, inputs=[], outputs=[mig_md, events_md, todos_md, vols_md, payroll_md]) | |
| with gr.Tab("Kalender & Schichten"): | |
| gr.Markdown("## Events\nEvents werden lokal in `data/events.json` gespeichert.") | |
| events_table = gr.Dataframe(headers=["ID", "Start", "Ende", "Titel", "Ort"], interactive=False) | |
| with gr.Row(): | |
| ev_title = gr.Textbox(label="Titel", placeholder="z.B. Zaunkontrolle / Nachtpatrouille") | |
| ev_location = gr.Textbox(label="Ort", placeholder="z.B. Straße X / Teich Y") | |
| with gr.Row(): | |
| ev_date = gr.Textbox(label="Datum (YYYY-MM-DD)", value=str(date.today())) | |
| ev_start = gr.Textbox(label="Start (HH:MM)", value="20:00") | |
| ev_end = gr.Textbox(label="Ende (HH:MM)", value="23:00") | |
| with gr.Row(): | |
| btn_add_ev = gr.Button("➕ Event anlegen") | |
| del_id = gr.Textbox(label="Event ID löschen", placeholder="E0001") | |
| btn_del_ev = gr.Button("🗑️ Löschen") | |
| btn_reload_ev = gr.Button("📋 Events neu laden") | |
| btn_reload_ev.click(fn=list_events, inputs=[], outputs=[events_table]) | |
| demo.load(fn=list_events, inputs=[], outputs=[events_table]) | |
| btn_add_ev.click(fn=add_event, inputs=[ev_title, ev_date, ev_start, ev_end, ev_location], outputs=[events_table]) | |
| btn_del_ev.click(fn=delete_event, inputs=[del_id], outputs=[events_table]) | |
| gr.Markdown("## Wanderfenster (ICS)\nErzeugt einen Standard-ICS (Wettercheck, Zaunkontrolle, Patrouillenblock).") | |
| with gr.Row(): | |
| region = gr.Textbox(label="Region", value="Deutschland") | |
| year = gr.Number(label="Jahr", value=date.today().year, precision=0) | |
| with gr.Row(): | |
| sm = gr.Number(label="Start Monat", value=2, precision=0) | |
| sd = gr.Number(label="Start Tag", value=15, precision=0) | |
| em = gr.Number(label="Ende Monat", value=4, precision=0) | |
| ed = gr.Number(label="Ende Tag", value=30, precision=0) | |
| btn_ics = gr.Button("📅 ICS generieren") | |
| ics_status = gr.Markdown() | |
| ics_file = gr.File(label="ICS Download") | |
| btn_ics.click(fn=generate_migration_ics, inputs=[region, year, sm, sd, em, ed], outputs=[ics_status, ics_file]) | |
| with gr.Tab("Einsatz planen"): | |
| gr.Markdown("## Einsatzplanung\nHier kannst du wie bisher einen Einsatz planen (später: Crew-Kickoff anbinden).") | |
| user_prompt = gr.Textbox(label="Auftrag / Situation", lines=5, placeholder="z.B. Plane nächste Woche Froschrettung in Region X ...") | |
| country = gr.Textbox(label="Land (GBIF Country Code)", value="DE") | |
| region2 = gr.Textbox(label="Region (Freitext)", value="Deutschland") | |
| species = gr.Dropdown(["Rana temporaria", "Bufo bufo", "Lissotriton vulgaris", "Triturus cristatus"], value="Rana temporaria", label="Zielart") | |
| year_from = gr.Number(label="Jahr von", value=2020, precision=0) | |
| year_to = gr.Number(label="Jahr bis", value=date.today().year, precision=0) | |
| # Optional: Hier später Crew kickoff (run_manager) wieder einhängen. | |
| gr.Markdown("➡️ (Nächster Schritt) Crew-Kickoff als Button: erzeugt Plan + Tasks + Kalender + Budget automatisch.") | |
| with gr.Tab("Daten & Hotspots (GBIF/Roadkill)"): | |
| gr.Markdown("## Amphibien-Sichtungen & Roadkill-Hotspots\nTop-Cluster inkl. Google-Maps Links.") | |
| with gr.Row(): | |
| d_species = gr.Dropdown(["Rana temporaria", "Bufo bufo", "Lissotriton vulgaris", "Triturus cristatus"], value="Rana temporaria", label="Art") | |
| d_country = gr.Textbox(label="Land", value="DE") | |
| d_y1 = gr.Number(label="Jahr von", value=2020, precision=0) | |
| d_y2 = gr.Number(label="Jahr bis", value=date.today().year, precision=0) | |
| btn_data = gr.Button("📡 Daten laden (Hotspots)") | |
| out_gbif = gr.Markdown() | |
| out_road = gr.Markdown() | |
| btn_data.click(fn=fetch_hotspots, inputs=[d_species, d_country, d_y1, d_y2], outputs=[out_gbif, out_road]) | |
| with gr.Tab("Team & Budget (450€)"): | |
| gr.Markdown("## To-Dos / Volunteers / Payroll\nAlles wird lokal gespeichert (DATA_DIR).") | |
| with gr.Accordion("To-Dos", open=True): | |
| todo_title = gr.Textbox(label="Neue Aufgabe", placeholder="z.B. Materialliste erstellen") | |
| todo_prio = gr.Dropdown(["L", "M", "H"], value="M", label="Priorität") | |
| btn_todo = gr.Button("➕ To-Do anlegen") | |
| todo_msg = gr.Markdown() | |
| todo_list = gr.Markdown() | |
| btn_todo.click(fn=add_todo, inputs=[todo_title, todo_prio], outputs=[todo_msg, todo_list]) | |
| demo.load(fn=lambda: organizer._run("list_todos", "{}"), inputs=[], outputs=[todo_list]) | |
| with gr.Accordion("Volunteers", open=False): | |
| v_name = gr.Textbox(label="Name") | |
| v_phone = gr.Textbox(label="Telefon") | |
| v_avail = gr.Textbox(label="Verfügbarkeit", placeholder="z.B. Mo/Di ab 18 Uhr") | |
| btn_vol = gr.Button("➕ Volunteer hinzufügen") | |
| vol_msg = gr.Markdown() | |
| vol_list = gr.Markdown() | |
| btn_vol.click(fn=add_volunteer, inputs=[v_name, v_phone, v_avail], outputs=[vol_msg, vol_list]) | |
| demo.load(fn=lambda: organizer._run("list_volunteers", "{}"), inputs=[], outputs=[vol_list]) | |
| with gr.Accordion("450€ Plan & Payroll", open=True): | |
| p_rate = gr.Number(label="Stundenlohn €", value=12.0, precision=2) | |
| p_cap = gr.Number(label="Monatscap €", value=450.0, precision=2) | |
| btn_plan = gr.Button("🧮 450€ Plan berechnen") | |
| plan_md = gr.Markdown() | |
| btn_plan.click(fn=payroll_plan, inputs=[p_rate, p_cap], outputs=[plan_md]) | |
| gr.Markdown("### Worker anlegen") | |
| w_name = gr.Textbox(label="Name") | |
| btn_add_w = gr.Button("➕ Worker hinzufügen") | |
| w_msg = gr.Markdown() | |
| rep_md = gr.Markdown() | |
| btn_add_w.click(fn=payroll_add_worker, inputs=[w_name, p_rate, p_cap], outputs=[w_msg, rep_md]) | |
| demo.load(fn=lambda: payroll._run("report", "{}"), inputs=[], outputs=[rep_md]) | |
| gr.Markdown("### Stunden loggen") | |
| l_name = gr.Textbox(label="Name (wie Worker)") | |
| l_date = gr.Textbox(label="Datum", value=str(date.today())) | |
| l_hours = gr.Number(label="Stunden", value=3.0, precision=2) | |
| l_note = gr.Textbox(label="Notiz", placeholder="z.B. Nachtpatrouille") | |
| btn_log = gr.Button("⏱️ log_hours") | |
| log_msg = gr.Markdown() | |
| btn_log.click(fn=payroll_log_hours, inputs=[l_name, l_date, l_hours, l_note], outputs=[log_msg, rep_md]) | |
| gr.Markdown("---\n**Hinweis:** In Hugging Face empfehle ich Persistent Storage aktivieren und `DATA_DIR=/data` setzen, dann bleiben Tasks/Events auch nach Restart erhalten.") | |
| demo.queue() | |
| demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", 7860))) | |