mgokg commited on
Commit
bcf09fd
·
verified ·
1 Parent(s): 21dc540

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +152 -129
app.py CHANGED
@@ -1,164 +1,187 @@
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
- """Sucht Bahnhof und gibt Name sowie EVA-Nummer zurück."""
 
 
 
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
- root = ET.fromstring(response.content)
14
- station = root.find('station')
15
- if station is not None:
16
- return {'name': station.get('name'), 'eva': station.get('eva')}
17
- except:
18
- return None
19
- return None
20
 
21
- def get_arrival_info(trip_id, dest_eva, client_id, api_key):
22
- """Ruft die Ankunftszeit für eine spezifische trip_id am Ziel-Bahnhof 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:
28
- root = ET.fromstring(response.content)
29
- for stop in root.findall('.//s'):
30
- if stop.get('eva') == dest_eva:
31
- ar = stop.find('ar')
32
- if ar is not None:
33
- return ar.get('pt') # Format: YYMMDDHHMM
34
- except:
35
- pass
36
- return None
 
 
37
 
38
- def calculate_duration(dep_raw, arr_raw):
39
- """Berechnet die Reisedauer aus zwei Zeitstempeln (YYMMDDHHMM)."""
40
- try:
41
- fmt = "%y%m%d%H%M"
42
- dep_dt = datetime.strptime(dep_raw, fmt)
43
- arr_dt = datetime.strptime(arr_raw, fmt)
44
- diff = arr_dt - dep_dt
45
- hours, remainder = divmod(int(diff.total_seconds()), 3600)
46
- minutes = remainder // 60
47
- return f"{hours}h {minutes}min"
48
- except:
49
- return "n/a"
50
 
51
  def search_connections(departure, destination, client_id, api_key):
52
- if not all([departure, destination, client_id, api_key]):
53
- return "❌ Bitte alle Felder ausfüllen.", []
54
-
 
 
 
 
 
 
55
  dep_info = get_station_info(departure, client_id, api_key)
 
 
 
 
56
  dest_info = get_station_info(destination, client_id, api_key)
 
 
57
 
58
- if not dep_info or not dest_info:
59
- return "❌ Bahnhof nicht gefunden. Bitte Schreibweise prüfen.", []
60
-
 
 
61
  now = datetime.now()
62
- found_conns = []
63
-
64
- # Suche über die nächsten 12 Stunden hinweg
65
- for hour_offset in range(12):
66
- if len(found_conns) >= 3:
67
- break
 
 
 
 
 
 
 
 
68
 
69
- search_time = now + timedelta(hours=hour_offset)
70
- datum = search_time.strftime("%y%m%d")
71
- stunde = search_time.strftime("%H")
72
 
73
- url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/plan/{dep_info['eva']}/{datum}/{stunde}"
74
- headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
75
 
76
- try:
77
- response = requests.get(url, headers=headers, timeout=10)
78
- if response.status_code != 200:
79
  continue
80
 
81
- root = ET.fromstring(response.content)
82
- for s in root.findall('.//s'):
83
- dp = s.find('dp')
84
- if dp is None:
85
- continue
 
 
 
 
 
 
86
 
87
- path = dp.get('ppth', '')
88
- # Prüfen ob Ziel im Fahrtverlauf
89
- if dest_info['name'].lower() in path.lower() or destination.lower() in path.lower():
90
- dep_raw = dp.get('pt', '')
91
- dep_dt = datetime.strptime(dep_raw, "%y%m%d%H%M")
92
-
93
- if dep_dt >= (now - timedelta(minutes=2)):
94
- trip_id = s.get('id')
95
- arr_raw = get_arrival_info(trip_id, dest_info['eva'], client_id, api_key)
96
-
97
- if arr_raw:
98
- tl = s.find('tl')
99
- arr_dt = datetime.strptime(arr_raw, "%y%m%d%H%M")
100
-
101
- found_conns.append({
102
- "zug": f"{tl.get('c','')}{tl.get('n','')}",
103
- "abfahrt": dep_dt.strftime("%H:%M"),
104
- "ankunft": arr_dt.strftime("%H:%M"),
105
- "dauer": calculate_duration(dep_raw, arr_raw),
106
- "gleis": dp.get('pp', '-'),
107
- "start": dep_info['name'],
108
- "ziel": dest_info['name'],
109
- "_dt": dep_dt
110
- })
111
- if len(found_conns) >= 3:
112
- break
113
- except:
114
- continue
115
-
116
- found_conns.sort(key=lambda x: x['_dt'])
117
- return None, found_conns[:3]
 
 
 
 
118
 
119
- def main(departure, destination, client_id, api_key):
120
- err, conns = search_connections(departure, destination, client_id, api_key)
 
 
 
 
 
 
121
 
122
- if err:
123
- return err, json.dumps({"error": err})
124
- if not conns:
125
- return "Keine Direktverbindungen gefunden.", "[]"
126
-
127
- # JSON Output (ohne internes Sortierfeld)
128
- clean_json = [{k: v for k, v in c.items() if k != "_dt"} for c in conns]
129
 
130
- # Markdown Tabelle (Flat & Professional)
131
- md = f"### 🚆 {conns[0]['start']} → {conns[0]['ziel']}\n\n"
132
- md += "| Zug | Abfahrt | Ankunft | Dauer | Gleis |\n"
133
- md += "|:---|:---|:---|:---|:---|\n"
134
- for c in clean_json:
135
- md += f"| **{c['zug']}** | {c['abfahrt']} | {c['ankunft']} | {c['dauer']} | {c['gleis']} |\n"
 
 
 
 
 
136
 
137
- md += f"\n\n*Abfragezeitpunkt: {datetime.now().strftime('%H:%M')}*"
138
 
139
- return md, clean_json
140
-
141
- # --- Gradio UI ---
142
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
143
- gr.Markdown("# 🚄 DB Timetable Service")
144
- with gr.Row():
145
- cid = gr.Textbox(label="Client ID", type="password")
146
- akey = gr.Textbox(label="API Key", type="password")
147
- with gr.Row():
148
- start = gr.Textbox(label="Start", placeholder="z.B. Berlin Hbf")
149
- ziel = gr.Textbox(label="Ziel", placeholder="z.B. Hamburg Hbf")
150
 
151
- btn = gr.Button("Nächste 3 Verbindungen suchen", variant="primary")
 
 
 
 
 
 
 
 
152
 
153
- output_md = gr.Markdown()
154
- output_json = gr.JSON(label="JSON Result")
155
-
156
- btn.click(main, [start, ziel, cid, akey], [output_md, output_json])
 
157
 
158
  if __name__ == "__main__":
159
  demo.launch()
160
-
161
-
162
  """
163
  import gradio as gr
164
  import requests
 
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
  import gradio as gr
187
  import requests