mgokg commited on
Commit
1883e9c
·
verified ·
1 Parent(s): 03d3365

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -154
app.py CHANGED
@@ -1,191 +1,153 @@
1
  import gradio as gr
2
  import requests
3
- from datetime import datetime
4
  import xml.etree.ElementTree as ET
 
5
 
6
  def get_station_info(pattern, client_id, api_key):
7
- """
8
- Sucht über die Timetables API nach einem Bahnhof und gibt EVA-Nummer
9
- und den korrekten Namen zurück.
10
- """
11
  url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/station/{pattern}"
12
-
13
- headers = {
14
- 'DB-Client-Id': client_id,
15
- 'DB-Api-Key': api_key,
16
- 'accept': 'application/xml'
17
- }
18
-
19
  try:
20
  response = requests.get(url, headers=headers, timeout=10)
21
- response.raise_for_status()
22
-
23
  root = ET.fromstring(response.content)
24
- stations = []
25
- for station in root.findall('station'):
26
- stations.append({
27
- 'name': station.get('name'),
28
- 'eva': station.get('eva')
29
- })
30
-
31
- # Falls Stationen gefunden wurden, nehmen wir die erste (beste Übereinstimmung)
32
- return stations[0] if stations else None
33
 
34
- except Exception as e:
35
- print(f"Fehler bei Stationssuche: {e}")
36
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- def search_connections(departure, destination, client_id, api_key):
39
- """Sucht Verbindungen zwischen zwei Bahnhöfen"""
40
-
41
- if not departure or not destination:
42
- return "❌ Bitte Abfahrts- und Zielort eingeben"
43
-
44
- if not client_id or not api_key:
45
- return "❌ Bitte Client ID und API Key eingeben"
46
-
47
- # 1. IBNR (EVA) für Abfahrtsort ermitteln
48
  dep_info = get_station_info(departure, client_id, api_key)
49
- if not dep_info:
50
- return f"❌ Abfahrtsort '{departure}' wurde nicht gefunden."
51
-
52
- # 2. IBNR (EVA) für Zielort ermitteln (für genaueres Matching im Pfad)
53
  dest_info = get_station_info(destination, client_id, api_key)
54
- if not dest_info:
55
- return f"❌ Zielort '{destination}' wurde nicht gefunden."
56
-
57
- departure_eva = dep_info['eva']
58
- departure_name = dep_info['name']
59
- destination_name = dest_info['name']
60
 
61
- # Aktuelles Datum und Stunde
 
 
62
  now = datetime.now()
63
- datum = now.strftime("%y%m%d") # YYMMDD
64
- stunde = now.strftime("%H") # HH
65
-
66
- # API-Aufruf für den Fahrplan
67
- url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/plan/{departure_eva}/{datum}/{stunde}"
68
-
69
- headers = {
70
- "DB-Client-Id": client_id,
71
- "DB-Api-Key": api_key,
72
- "accept": "application/xml"
73
- }
74
 
75
- try:
76
- response = requests.get(url, headers=headers, timeout=10)
 
77
 
78
- if response.status_code != 200:
79
- return f"❌ API Fehler beim Abrufen des Fahrplans: {response.status_code}"
 
80
 
81
- root = ET.fromstring(response.content)
82
- connections = []
83
 
84
- for train in root.findall('.//s'):
85
- dp = train.find('dp')
86
- if dp is None:
87
- continue
88
-
89
- # Route des Zugs (ppth = planned path)
90
- path = dp.get('ppth', '')
91
 
92
- # Prüfen ob Zielort im Pfad enthalten ist
93
- # Wir prüfen sowohl den eingegebenen Namen als auch den gefundenen offiziellen Namen
94
- if destination_name.lower() in path.lower() or destination.lower() in path.lower():
95
- # Zugdetails extrahieren
96
- tl = train.find('tl') # Train Label
97
- train_class = tl.get('c', '') # z.B. ICE
98
- train_number = tl.get('n', '') # z.B. 123
99
- full_train_name = f"{train_class} {train_number}"
100
 
101
- planned_time = dp.get('pt', '')
102
- platform = dp.get('pp', 'n/a')
103
-
104
- if len(planned_time) == 10: # Format YYMMDDHHMM
105
- planned_time = f"{planned_time[8:10]}:{planned_time[10:12]}"
106
- elif len(planned_time) == 4:
107
- planned_time = f"{planned_time[:2]}:{planned_time[2:]}"
108
-
109
- route = ' → '.join(path.split('|'))
110
-
111
- connections.append({
112
- 'train': full_train_name,
113
- 'time': planned_time,
114
- 'platform': platform,
115
- 'route': route
116
- })
117
-
118
- if not connections:
119
- return f"ℹ️ Keine direkten Verbindungen von {departure_name} nach {destination_name} gefunden.\n(Suche im Zeitraum {now.strftime('%H:00')} - {int(now.strftime('%H'))+1}:00 Uhr)"
120
-
121
- result = f"🚆 Verbindungen von: {departure_name}\n"
122
- result += f"🏁 Ziel: {destination_name}\n"
123
- result += f"📅 Datum: {now.strftime('%d.%m.%Y')}\n\n"
124
-
125
- for i, conn in enumerate(connections[:15], 1):
126
- result += f"─────────────────────────────────\n"
127
- result += f"{i}. {conn['train']}\n"
128
- result += f" ⏰ Abfahrt: {conn['time']} Uhr\n"
129
- result += f" 🚉 Gleis: {conn['platform']}\n"
130
- result += f" 🗺️ Route: {conn['route'][:100]}...\n\n"
131
-
132
- return result
133
-
134
- except Exception as e:
135
- return f"❌ Fehler: {str(e)}"
136
 
137
- # Gradio Interface
138
- with gr.Blocks(title="DB Timetable API - Live Suche", theme=gr.themes.Soft()) as demo:
139
- gr.Markdown(
140
- """
141
- # 🚆 DB Live-Fahrplanauskunft
142
- Ermittelt automatisch die IBNR (EVA-Nummer) und sucht nach Verbindungen.
143
- """
144
- )
145
 
146
- with gr.Row():
147
- with gr.Column():
148
- client_id_input = gr.Textbox(label="DB Client ID", type="password")
149
- api_key_input = gr.Textbox(label="DB API Key", type="password")
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  with gr.Row():
152
- with gr.Column():
153
- departure_input = gr.Textbox(
154
- label="Abfahrtsort",
155
- placeholder="z.B. Berlin Hbf oder Frankfurt"
156
- )
157
- with gr.Column():
158
- destination_input = gr.Textbox(
159
- label="Zielort",
160
- placeholder="z.B. München Hbf"
161
- )
162
 
163
- search_button = gr.Button("🔍 Verbindungen suchen", variant="primary")
 
 
164
 
165
- output = gr.Textbox(label="Ergebnisse", lines=20)
166
 
167
- gr.Markdown(
168
- """
169
- ### ℹ️ Funktionsweise
170
- 1. Die App sendet Ihren Suchbegriff an den `/station` Endpoint.
171
- 2. Die erste gefundene **EVA-Nummer (IBNR)** wird für die Abfrage genutzt.
172
- 3. Der Fahrplan für die aktuelle Stunde wird abgerufen.
173
- 4. Es wird gefiltert, ob der Zielbahnhof im geplanten Fahrtverlauf vorkommt.
174
- """
175
- )
176
 
177
- search_button.click(
178
- fn=search_connections,
179
- inputs=[departure_input, destination_input, client_id_input, api_key_input],
180
- outputs=output
181
- )
182
 
183
  if __name__ == "__main__":
184
  demo.launch()
185
 
186
 
187
-
188
-
189
  """
190
  import gradio as gr
191
  import requests
 
1
  import gradio as gr
2
  import requests
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
  url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/station/{pattern}"
9
+ headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
 
 
 
 
 
 
10
  try:
11
  response = requests.get(url, headers=headers, timeout=10)
 
 
12
  root = ET.fromstring(response.content)
13
+ station = root.find('station')
14
+ if station is not None:
15
+ return {'name': station.get('name'), 'eva': station.get('eva')}
16
+ except: return None
17
+ return None
 
 
 
 
18
 
19
+ def get_arrival_info(trip_id, dest_eva, client_id, api_key):
20
+ """Holt die Ankunftszeit am Zielort für eine spezifische Fahrt."""
21
+ url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/journey/{trip_id}"
22
+ headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
23
+ try:
24
+ response = requests.get(url, headers=headers, timeout=10)
25
+ if response.status_code == 200:
26
+ root = ET.fromstring(response.content)
27
+ for stop in root.findall('.//s'):
28
+ if stop.get('eva') == dest_eva:
29
+ ar = stop.find('ar')
30
+ if ar is not None:
31
+ return ar.get('pt') # Format: YYMMDDHHMM
32
+ except: pass
33
+ return None
34
+
35
+ def calculate_duration(dep_raw, arr_raw):
36
+ """Berechnet die Reisedauer."""
37
+ try:
38
+ fmt = "%y%m%d%H%M"
39
+ diff = datetime.strptime(arr_raw, fmt) - datetime.strptime(dep_raw, fmt)
40
+ hours, remainder = divmod(diff.total_seconds(), 3600)
41
+ minutes = remainder // 60
42
+ return f"{int(hours)}h {int(minutes)}min"
43
+ except: return "n/a"
44
+
45
+ def get_connections(departure, destination, client_id, api_key):
46
+ if not all([departure, destination, client_id, api_key]):
47
+ return None, "❌ Bitte alle Felder ausfüllen."
48
 
 
 
 
 
 
 
 
 
 
 
49
  dep_info = get_station_info(departure, client_id, api_key)
 
 
 
 
50
  dest_info = get_station_info(destination, client_id, api_key)
 
 
 
 
 
 
51
 
52
+ if not dep_info or not dest_info:
53
+ return None, "❌ Bahnhof nicht gefunden."
54
+
55
  now = datetime.now()
56
+ found_connections = []
 
 
 
 
 
 
 
 
 
 
57
 
58
+ # Suche über 12 Stunden hinweg, um sicher 3 Verbindungen zu finden
59
+ for hour_offset in range(12):
60
+ if len(found_connections) >= 3: break
61
 
62
+ search_time = now + timedelta(hours=hour_offset)
63
+ datum = search_time.strftime("%y%m%d")
64
+ stunde = search_time.strftime("%H")
65
 
66
+ url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/plan/{dep_info['eva']}/{datum}/{stunde}"
67
+ headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
68
 
69
+ try:
70
+ response = requests.get(url, headers=headers, timeout=10)
71
+ if response.status_code != 200: continue
72
+ root = ET.fromstring(response.content)
 
 
 
73
 
74
+ for train in root.findall('.//s'):
75
+ dp = train.find('dp')
76
+ if dp is None: continue
 
 
 
 
 
77
 
78
+ path = dp.get('ppth', '')
79
+ # Suche im Pfad nach dem Namen oder der EVA des Ziels
80
+ if dest_info['name'].lower() in path.lower() or destination.lower() in path.lower():
81
+ dep_raw = dp.get('pt', '')
82
+ dep_dt = datetime.strptime(dep_raw, "%y%m%d%H%M")
83
+
84
+ if dep_dt >= now:
85
+ # Journey Details für Ankunft und Dauer
86
+ trip_id = train.get('id')
87
+ arr_raw = get_arrival_info(trip_id, dest_info['eva'], client_id, api_key)
88
+
89
+ arrival_time = datetime.strptime(arr_raw, "%y%m%d%H%M").strftime("%H:%M") if arr_raw else "n/a"
90
+ duration = calculate_duration(dep_raw, arr_raw) if arr_raw else "n/a"
91
+
92
+ tl = train.find('tl')
93
+ found_connections.append({
94
+ "zug": f"{tl.get('c', '')}{tl.get('n', '')}",
95
+ "abfahrt": dep_dt.strftime("%H:%M"),
96
+ "ankunft": arrival_time,
97
+ "dauer": duration,
98
+ "gleis": dp.get('pp', '-'),
99
+ "start": dep_info['name'],
100
+ "ziel": dest_info['name'],
101
+ "_dt": dep_dt
102
+ })
103
+ if len(found_connections) >= 3: break
104
+ except: continue
 
 
 
 
 
 
 
 
105
 
106
+ found_connections.sort(key=lambda x: x['_dt'])
107
+ return found_connections[:3], None
108
+
109
+ def main_interface(dep, dest, cid, akey):
110
+ conns, error = get_connections(dep, dest, cid, akey)
111
+ if error: return error, json.dumps({"error": error})
112
+ if not conns: return "Keine Verbindungen gefunden.", "[]"
 
113
 
114
+ # JSON-Output (Clean)
115
+ json_output = [{k: v for k, v in c.items() if k != "_dt"} for c in conns]
116
+
117
+ # Professional Markdown Table
118
+ md = f"### 🚆 {conns[0]['start']} → {conns[0]['ziel']}\n\n"
119
+ md += "| Zug | Abfahrt | Ankunft | Dauer | Gleis |\n"
120
+ md += "|:---|:---|:---|:---|:---|\n"
121
+ for c in json_output:
122
+ md += f"| **{c['zug']}** | {c['abfahrt']} | {c['ankunft']} | {c['dauer']} | {c['gleis']} |\n"
123
+
124
+ md += f"\n\n*Abfrage: {datetime.now().strftime('%H:%M')}*"
125
+
126
+ return md, json.dumps(json_output, indent=4, ensure_ascii=False)
127
+
128
+ # --- Gradio UI ---
129
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
130
+ gr.Markdown("# 🚄 DB Verbindungs-Check")
131
 
132
  with gr.Row():
133
+ cid_input = gr.Textbox(label="Client ID", type="password")
134
+ akey_input = gr.Textbox(label="API Key", type="password")
 
 
 
 
 
 
 
 
135
 
136
+ with gr.Row():
137
+ dep_input = gr.Textbox(label="Start", placeholder="z.B. Berlin Hbf")
138
+ dest_input = gr.Textbox(label="Ziel", placeholder="z.B. Hamburg Hbf")
139
 
140
+ btn = gr.Button("Suchen", variant="primary")
141
 
142
+ md_display = gr.Markdown()
143
+ json_display = gr.Code(label="JSON", language="json")
 
 
 
 
 
 
 
144
 
145
+ btn.click(main_interface, [dep_input, dest_input, cid_input, akey_input], [md_display, json_display])
 
 
 
 
146
 
147
  if __name__ == "__main__":
148
  demo.launch()
149
 
150
 
 
 
151
  """
152
  import gradio as gr
153
  import requests