mgokg commited on
Commit
23f05a3
·
verified ·
1 Parent(s): 3f6498b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -79
app.py CHANGED
@@ -3,124 +3,140 @@ import requests
3
  from datetime import datetime, timedelta
4
  import xml.etree.ElementTree as ET
5
  import json
6
- import os
7
-
8
- api_key=os.environ.get("api_key")
9
- client_id=os.environ.get("client_id")
10
 
11
  def get_station_info(pattern, client_id, api_key):
 
12
  url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/station/{pattern}"
13
  headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
14
  try:
15
  response = requests.get(url, headers=headers, timeout=10)
 
16
  root = ET.fromstring(response.content)
17
  station = root.find('station')
18
- return {'name': station.get('name'), 'eva': station.get('eva')} if station is not None else None
19
- except: return None
 
 
 
20
 
21
- def get_arrival_time(trip_id, dest_name, client_id, api_key):
22
- """Ruft die Ankunftszeit für einen spezifischen Zug am Zielbahnhof ab."""
23
- url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/journey/{trip_id}"
24
  headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
25
  try:
26
  response = requests.get(url, headers=headers, timeout=10)
27
- if response.status_code != 200: return None
28
- root = ET.fromstring(response.content)
29
- # Suche im Fahrtverlauf nach dem Zielbahnhof
30
- for stop in root.findall('.//s'):
31
- if dest_name.lower() in stop.get('static_name', '').lower():
32
- ar = stop.find('ar')
33
- if ar is not None: return ar.get('pt') # Geplante Ankunftszeit
34
- except: pass
35
  return None
36
 
37
- def calculate_duration(dep_str, arr_str):
38
- """Berechnet Dauer in Stunden und Minuten."""
39
- fmt = "%y%m%d%H%M"
40
- dep_dt = datetime.strptime(dep_str, fmt)
41
- arr_dt = datetime.strptime(arr_str, fmt)
42
- diff = arr_dt - dep_dt
43
- hours, remainder = divmod(diff.seconds, 3600)
44
- minutes = remainder // 60
45
- return f"{hours}h {minutes}min"
46
 
47
- def get_next_3_connections(departure, destination, client_id, api_key):
48
  dep_info = get_station_info(departure, client_id, api_key)
49
  dest_info = get_station_info(destination, client_id, api_key)
50
- if not dep_info or not dest_info: return None, "Bahnhof nicht gefunden."
 
 
51
 
52
  now = datetime.now()
53
- conns = []
54
 
55
- # Suche über die nächsten Stunden
56
- for hour_offset in range(5): # Größeres Fenster um sicher 3 zu finden
57
- t = now + timedelta(hours=hour_offset)
58
- url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/plan/{dep_info['eva']}/{t.strftime('%y%m%d')}/{t.strftime('%H')}"
59
- headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
60
 
61
- res = requests.get(url, headers=headers, timeout=10)
62
- if res.status_code != 200: continue
63
- root = ET.fromstring(res.content)
64
-
65
- for s in root.findall('.//s'):
66
- dp = s.find('dp')
67
  if dp is None: continue
68
 
69
  path = dp.get('ppth', '')
70
  if dest_info['name'].lower() in path.lower() or destination.lower() in path.lower():
71
- dep_time_raw = dp.get('pt')
72
- if not dep_time_raw or datetime.strptime(dep_time_raw, "%y%m%d%H%M") < now: continue
73
 
74
- # Jetzt Ankunftszeit via Journey-API ermitteln
75
- trip_id = s.get('id')
76
- arr_time_raw = get_arrival_time(trip_id, dest_info['name'], client_id, api_key)
77
 
78
- if arr_time_raw:
79
- conns.append({
80
- "zug": f"{s.find('tl').get('c')}{s.find('tl').get('n')}",
81
- "abfahrt": datetime.strptime(dep_time_raw, "%y%m%d%H%M").strftime("%H:%M"),
82
- "ankunft": datetime.strptime(arr_time_raw, "%y%m%d%H%M").strftime("%H:%M"),
83
- "dauer": calculate_duration(dep_time_raw, arr_time_raw),
 
84
  "gleis": dp.get('pp', '-'),
85
- "_dt": datetime.strptime(dep_time_raw, "%y%m%d%H%M")
 
86
  })
87
- if len(conns) >= 3: break
88
 
89
- conns.sort(key=lambda x: x['_dt'])
90
- return conns[:3], None
91
 
92
- def format_output(dep, dest, client_id, api_key):
93
- data, err = get_next_3_connections(dep, dest, client_id, api_key)
94
- if err: return err, json.dumps({"error": err})
 
95
 
96
- # JSON Output
97
- clean_json = [{k: v for k, v in c.items() if k != "_dt"} for c in data]
98
 
99
- # Markdown Table Output (Smartphone optimiert)
100
- md = f"### 🚆 {dep.upper()} {dest.upper()}\n\n"
101
- md += "| Zug | Ab | An | Dauer | Gl. |\n"
102
- md += "|:---|:---|:---|:---|:---|\n"
103
- for c in clean_json:
104
- md += f"| **{c['zug']}** | {c['abfahrt']} | {c['ankunft']} | {c['dauer']} | {c['gleis']} |\n"
105
 
106
- md += f"\n\n*Stand: {datetime.now().strftime('%H:%M')}*"
107
-
108
- return md, clean_json
109
 
110
- # --- Interface ---
111
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
112
- gr.Markdown("# 🚄 DB Timetable Pro")
 
 
 
 
 
 
 
 
 
 
 
 
113
 
 
 
 
 
 
 
 
 
114
  with gr.Row():
115
- start = gr.Textbox(label="Start", placeholder="z.B. Köln Hbf")
116
- ziel = gr.Textbox(label="Ziel", placeholder="z.B. Berlin Hbf")
117
 
118
- btn = gr.Button("Verbindungen finden", variant="primary")
119
 
120
- display = gr.Markdown()
121
- js_out = gr.JSON(label="JSON Daten")
122
-
123
- btn.click(format_output, [start, ziel,], [display, js_out])
 
 
 
 
 
 
 
124
 
125
  if __name__ == "__main__":
126
  demo.launch()
 
3
  from datetime import datetime, timedelta
4
  import xml.etree.ElementTree as ET
5
  import json
 
 
 
 
6
 
7
  def get_station_info(pattern, client_id, api_key):
8
+ """Ermittelt EVA-Nummer und offiziellen Namen."""
9
  url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/station/{pattern}"
10
  headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
11
  try:
12
  response = requests.get(url, headers=headers, timeout=10)
13
+ response.raise_for_status()
14
  root = ET.fromstring(response.content)
15
  station = root.find('station')
16
+ if station is not None:
17
+ return {'name': station.get('name'), 'eva': station.get('eva')}
18
+ except Exception:
19
+ return None
20
+ return None
21
 
22
+ def fetch_timetable_hour(eva, date_str, hour_str, client_id, api_key):
23
+ """Ruft den Fahrplan für eine bestimmte Stunde ab."""
24
+ url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/plan/{eva}/{date_str}/{hour_str}"
25
  headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
26
  try:
27
  response = requests.get(url, headers=headers, timeout=10)
28
+ if response.status_code == 200:
29
+ return ET.fromstring(response.content)
30
+ except Exception:
31
+ pass
 
 
 
 
32
  return None
33
 
34
+ def get_connections(departure, destination, client_id, api_key):
35
+ """Sucht die nächsten 3 Verbindungen ab jetzt."""
36
+ if not all([departure, destination, client_id, api_key]):
37
+ return None, "❌ Bitte alle Felder ausfüllen."
 
 
 
 
 
38
 
 
39
  dep_info = get_station_info(departure, client_id, api_key)
40
  dest_info = get_station_info(destination, client_id, api_key)
41
+
42
+ if not dep_info or not dest_info:
43
+ return None, "❌ Bahnhof nicht gefunden. Bitte Namen prüfen."
44
 
45
  now = datetime.now()
46
+ found_connections = []
47
 
48
+ # Suche über 3 Stunden-Slots hinweg (aktuelle + nächste zwei)
49
+ for hour_offset in range(3):
50
+ search_time = now + timedelta(hours=hour_offset)
51
+ datum = search_time.strftime("%y%m%d")
52
+ stunde = search_time.strftime("%H")
53
 
54
+ root = fetch_timetable_hour(dep_info['eva'], datum, stunde, client_id, api_key)
55
+ if root is None: continue
56
+
57
+ for train in root.findall('.//s'):
58
+ dp = train.find('dp')
 
59
  if dp is None: continue
60
 
61
  path = dp.get('ppth', '')
62
  if dest_info['name'].lower() in path.lower() or destination.lower() in path.lower():
63
+ pt = dp.get('pt', '')
64
+ if not pt: continue
65
 
66
+ dep_dt = datetime.strptime(pt, "%y%m%d%H%M")
 
 
67
 
68
+ if dep_dt >= now:
69
+ tl = train.find('tl')
70
+ found_connections.append({
71
+ "startort": dep_info['name'],
72
+ "zielort": dest_info['name'],
73
+ "abfahrtszeit": dep_dt.strftime("%H:%M"),
74
+ "datum": dep_dt.strftime("%d.%m.%Y"),
75
  "gleis": dp.get('pp', '-'),
76
+ "zug": f"{tl.get('c', '')} {tl.get('n', '')}",
77
+ "_dt": dep_dt
78
  })
 
79
 
80
+ found_connections.sort(key=lambda x: x['_dt'])
81
+ return found_connections[:3], None
82
 
83
+ def format_markdown(connections):
84
+ """Erstellt eine Smartphone-optimierte Markdown-Ansicht."""
85
+ if not connections:
86
+ return "### ℹ️ Keine Verbindungen gefunden\nIn den nächsten 3 Stunden wurden keine Direktverbindungen gefunden."
87
 
88
+ md = f"## 🚆 {connections[0]['startort']} → {connections[0]['zielort']}\n"
89
+ md += f"*{datetime.now().strftime('%H:%M')} Uhr Stand*\n\n"
90
 
91
+ for i, c in enumerate(connections, 1):
92
+ md += f"### {i}. {c['abfahrtszeit']} Uhr\n"
93
+ md += f"**{c['zug']}** \n"
94
+ md += f"Gleis: **{c['gleis']}** | Datum: {c['datum']}\n"
95
+ md += "---\n" # Trennlinie für bessere Lesbarkeit auf dem Handy
 
96
 
97
+ return md
 
 
98
 
99
+ def main_interface(dep, dest, cid, akey):
100
+ conns, error = get_connections(dep, dest, cid, akey)
101
+
102
+ if error:
103
+ return error, json.dumps({"error": error}, indent=2)
104
+
105
+ # JSON-Bereinigung (internes Sortierfeld entfernen)
106
+ clean_json = []
107
+ for c in conns:
108
+ clean_json.append({k: v for k, v in c.items() if k != "_dt"})
109
+
110
+ md_output = format_markdown(clean_json)
111
+ json_output = json.dumps(clean_json, indent=4, ensure_ascii=False)
112
+
113
+ return md_output, json_output
114
 
115
+ # --- Gradio UI ---
116
+ with gr.Blocks(title="DB Mobile Fahrplan", theme=gr.themes.Soft()) as demo:
117
+ gr.Markdown("# 📱 DB Quick-Check")
118
+
119
+ with gr.Accordion("⚙️ API-Konfiguration (Hier klicken)", open=False):
120
+ cid_input = gr.Textbox(label="DB Client ID", type="password")
121
+ akey_input = gr.Textbox(label="DB API Key", type="password")
122
+
123
  with gr.Row():
124
+ dep_input = gr.Textbox(label="Von", placeholder="z.B. Berlin", scale=2)
125
+ dest_input = gr.Textbox(label="Nach", placeholder="z.B. Hamburg", scale=2)
126
 
127
+ btn = gr.Button("🔍 Verbindungen suchen", variant="primary")
128
 
129
+ with gr.Tabs():
130
+ with gr.TabItem("📱 Ansicht"):
131
+ md_display = gr.Markdown("Geben Sie Start und Ziel ein und drücken Sie Suchen.")
132
+ with gr.TabItem("💻 JSON"):
133
+ json_display = gr.Code(label="API Response", language="json")
134
+
135
+ btn.click(
136
+ fn=main_interface,
137
+ inputs=[dep_input, dest_input, cid_input, akey_input],
138
+ outputs=[md_display, json_display]
139
+ )
140
 
141
  if __name__ == "__main__":
142
  demo.launch()