File size: 13,310 Bytes
f00396e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
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)))