mgokg commited on
Commit
15b7b17
·
verified ·
1 Parent(s): d796615

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -85
app.py CHANGED
@@ -14,146 +14,143 @@ def get_station_info(pattern, client_id, api_key):
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()
 
14
  station = root.find('station')
15
  if station is not None:
16
  return {'name': station.get('name'), 'eva': station.get('eva')}
17
+ except Exception as e:
18
+ print(f"Error fetching station: {e}")
19
  return None
20
 
21
+ def get_journey_details(trip_id, dest_eva, client_id, api_key):
22
+ """Holt Ankunftszeit am Ziel-Bahnhof via Trip-ID."""
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
+
30
+ # Suche nach dem Stop, der die Ziel-EVA hat
31
+ for stop in root.findall('.//s'):
32
+ if stop.get('eva') == dest_eva:
33
+ ar = stop.find('ar')
34
+ if ar is not None:
35
+ return ar.get('pt') # Geplante Ankunftszeit
36
+ except: pass
37
  return None
38
 
39
+ def calculate_duration(dep_str, arr_str):
40
+ fmt = "%y%m%d%H%M"
41
  try:
42
+ diff = datetime.strptime(arr_str, fmt) - datetime.strptime(dep_str, fmt)
43
+ hours, remainder = divmod(diff.total_seconds(), 3600)
44
+ return f"{int(hours)}h {int(remainder // 60)}min"
45
+ except: return "n/a"
 
 
 
 
 
 
 
 
 
46
 
47
+ def get_next_3_connections(departure_query, destination_query, client_id, api_key):
48
+ # 1. Bahnhofs-IDs holen
49
+ dep_info = get_station_info(departure_query, client_id, api_key)
50
+ dest_info = get_station_info(destination_query, client_id, api_key)
51
+
52
+ if not dep_info: return None, f"Startbahnhof '{departure_query}' nicht gefunden."
53
+ if not dest_info: return None, f"Zielbahnhof '{destination_query}' nicht gefunden."
54
 
55
  now = datetime.now()
56
  found_conns = []
57
+
58
+ # Wir suchen in den nächsten 10 Stunden (um sicher 3 zu finden, auch in der Nacht)
59
+ for hour_offset in range(10):
60
+ if len(found_conns) >= 3: break
 
61
 
62
  search_time = now + timedelta(hours=hour_offset)
63
+ date_str = search_time.strftime("%y%m%d")
64
+ hour_str = search_time.strftime("%H")
65
 
66
+ url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/plan/{dep_info['eva']}/{date_str}/{hour_str}"
67
  headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
68
 
69
  try:
70
+ res = requests.get(url, headers=headers, timeout=10)
71
+ if res.status_code != 200: continue
72
+ root = ET.fromstring(res.content)
73
 
 
74
  for s in root.findall('.//s'):
75
  dp = s.find('dp')
76
+ if dp is None: continue
 
77
 
78
+ # Check ob Ziel in der Route (ppth) vorkommt
79
  path = dp.get('ppth', '')
80
+ dest_name = dest_info['name']
81
+
82
+ # Filter: Ist das Ziel im Fahrtverlauf?
83
+ if dest_name.lower() in path.lower() or destination_query.lower() in path.lower():
84
+ dep_time_raw = dp.get('pt')
85
+ dep_dt = datetime.strptime(dep_time_raw, "%y%m%d%H%M")
86
 
87
+ # Nur zukünftige Züge (mit 5 Min Puffer)
88
+ if dep_dt >= (now - timedelta(minutes=5)):
89
  trip_id = s.get('id')
90
+ arr_time_raw = get_journey_details(trip_id, dest_info['eva'], client_id, api_key)
91
 
92
+ if arr_time_raw:
93
  tl = s.find('tl')
 
 
94
  found_conns.append({
95
  "zug": f"{tl.get('c','')}{tl.get('n','')}",
96
  "abfahrt": dep_dt.strftime("%H:%M"),
97
+ "ankunft": datetime.strptime(arr_time_raw, "%y%m%d%H%M").strftime("%H:%M"),
98
+ "dauer": calculate_duration(dep_time_raw, arr_time_raw),
99
  "gleis": dp.get('pp', '-'),
100
+ "startort": dep_info['name'],
101
+ "zielort": dest_info['name'],
102
  "_dt": dep_dt
103
  })
104
+ if len(found_conns) >= 3: break
105
+ except: continue
 
 
106
 
107
  found_conns.sort(key=lambda x: x['_dt'])
108
+ return found_conns[:3], None
109
 
110
+ def process(dep, dest, cid, akey):
111
+ data, error = get_next_3_connections(dep, dest, cid, akey)
112
 
113
+ if error:
114
+ return f"### ❌ Fehler\n{error}", json.dumps({"error": error}, indent=2)
 
 
115
 
116
+ if not data:
117
+ return "### ℹ️ Keine direkten Verbindungen gefunden.", "[]"
118
+
119
  # JSON Output (ohne internes Sortierfeld)
120
+ json_clean = [{k: v for k, v in c.items() if k != "_dt"} for c in data]
121
 
122
+ # Markdown Table
123
+ md = f"### 🚆 {data[0]['startort']} → {data[0]['zielort']}\n\n"
124
  md += "| Zug | Abfahrt | Ankunft | Dauer | Gleis |\n"
125
  md += "|:---|:---|:---|:---|:---|\n"
126
+ for c in json_clean:
127
  md += f"| **{c['zug']}** | {c['abfahrt']} | {c['ankunft']} | {c['dauer']} | {c['gleis']} |\n"
128
 
129
  md += f"\n\n*Abfragezeitpunkt: {datetime.now().strftime('%H:%M')}*"
130
 
131
+ return md, json_clean
132
 
133
  # --- Gradio UI ---
134
+ with gr.Blocks(theme=gr.themes.Soft(), title="DB Timetable Pro") as demo:
135
+ gr.Markdown("# 🚄 DB Timetable Professional")
136
+
137
  with gr.Row():
138
+ cid = gr.Textbox(label="Client ID", type="password", value="")
139
+ akey = gr.Textbox(label="API Key", type="password", value="")
140
+
141
  with gr.Row():
142
+ start = gr.Textbox(label="Von", placeholder="z.B. Berlin Hbf")
143
+ ziel = gr.Textbox(label="Nach", placeholder="z.B. Hamburg Hbf")
144
 
145
  btn = gr.Button("Nächste 3 Verbindungen suchen", variant="primary")
146
 
147
+ with gr.Tabs():
148
+ with gr.TabItem("📱 Verbindung"):
149
+ md_out = gr.Markdown("Ergebnisse erscheinen hier...")
150
+ with gr.TabItem("💻 JSON"):
151
+ json_out = gr.JSON()
152
 
153
+ btn.click(process, [start, ziel, cid, akey], [md_out, json_out])
154
 
155
  if __name__ == "__main__":
156
  demo.launch()