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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -104
app.py CHANGED
@@ -4,153 +4,122 @@ 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 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()
 
4
  import xml.etree.ElementTree as ET
5
  import json
6
 
7
+ api_key=os.environ.get("api_key")
8
+ client_id=os.environ.get("client_id")
9
+
10
  def get_station_info(pattern, client_id, api_key):
 
11
  url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/station/{pattern}"
12
  headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
13
  try:
14
  response = requests.get(url, headers=headers, timeout=10)
15
  root = ET.fromstring(response.content)
16
  station = root.find('station')
17
+ return {'name': station.get('name'), 'eva': station.get('eva')} if station is not None else None
18
+ except: return None
 
 
 
19
 
20
+ def get_arrival_time(trip_id, dest_name, client_id, api_key):
21
+ """Ruft die Ankunftszeit für einen spezifischen Zug am Zielbahnhof ab."""
22
  url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/journey/{trip_id}"
23
  headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
24
  try:
25
  response = requests.get(url, headers=headers, timeout=10)
26
  if response.status_code != 200: return None
27
  root = ET.fromstring(response.content)
28
+ # Suche im Fahrtverlauf nach dem Zielbahnhof
 
29
  for stop in root.findall('.//s'):
30
+ if dest_name.lower() in stop.get('static_name', '').lower():
31
  ar = stop.find('ar')
32
+ if ar is not None: return ar.get('pt') # Geplante Ankunftszeit
 
33
  except: pass
34
  return None
35
 
36
  def calculate_duration(dep_str, arr_str):
37
+ """Berechnet Dauer in Stunden und Minuten."""
38
  fmt = "%y%m%d%H%M"
39
+ dep_dt = datetime.strptime(dep_str, fmt)
40
+ arr_dt = datetime.strptime(arr_str, fmt)
41
+ diff = arr_dt - dep_dt
42
+ hours, remainder = divmod(diff.seconds, 3600)
43
+ minutes = remainder // 60
44
+ return f"{hours}h {minutes}min"
45
 
46
+ def get_next_3_connections(departure, destination, client_id, api_key):
47
+ dep_info = get_station_info(departure, client_id, api_key)
48
+ dest_info = get_station_info(destination, client_id, api_key)
49
+ if not dep_info or not dest_info: return None, "Bahnhof nicht gefunden."
 
 
 
50
 
51
  now = datetime.now()
52
+ conns = []
53
 
54
+ # Suche über die nächsten Stunden
55
+ for hour_offset in range(5): # Größeres Fenster um sicher 3 zu finden
56
+ t = now + timedelta(hours=hour_offset)
57
+ url = f"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/plan/{dep_info['eva']}/{t.strftime('%y%m%d')}/{t.strftime('%H')}"
 
 
 
 
 
58
  headers = {'DB-Client-Id': client_id, 'DB-Api-Key': api_key, 'accept': 'application/xml'}
59
 
60
+ res = requests.get(url, headers=headers, timeout=10)
61
+ if res.status_code != 200: continue
62
+ root = ET.fromstring(res.content)
63
+
64
+ for s in root.findall('.//s'):
65
+ dp = s.find('dp')
66
+ if dp is None: continue
67
 
68
+ path = dp.get('ppth', '')
69
+ if dest_info['name'].lower() in path.lower() or destination.lower() in path.lower():
70
+ dep_time_raw = dp.get('pt')
71
+ if not dep_time_raw or datetime.strptime(dep_time_raw, "%y%m%d%H%M") < now: continue
72
 
73
+ # Jetzt Ankunftszeit via Journey-API ermitteln
74
+ trip_id = s.get('id')
75
+ arr_time_raw = get_arrival_time(trip_id, dest_info['name'], client_id, api_key)
76
 
77
+ if arr_time_raw:
78
+ conns.append({
79
+ "zug": f"{s.find('tl').get('c')}{s.find('tl').get('n')}",
80
+ "abfahrt": datetime.strptime(dep_time_raw, "%y%m%d%H%M").strftime("%H:%M"),
81
+ "ankunft": datetime.strptime(arr_time_raw, "%y%m%d%H%M").strftime("%H:%M"),
82
+ "dauer": calculate_duration(dep_time_raw, arr_time_raw),
83
+ "gleis": dp.get('pp', '-'),
84
+ "_dt": datetime.strptime(dep_time_raw, "%y%m%d%H%M")
85
+ })
86
+ if len(conns) >= 3: break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
+ conns.sort(key=lambda x: x['_dt'])
89
+ return conns[:3], None
90
 
91
+ def format_output(dep, dest, client_id, api_key):
92
+ data, err = get_next_3_connections(dep, dest, client_id, api_key)
93
+ if err: return err, json.dumps({"error": err})
 
 
94
 
95
+ # JSON Output
96
+ clean_json = [{k: v for k, v in c.items() if k != "_dt"} for c in data]
 
 
 
97
 
98
+ # Markdown Table Output (Smartphone optimiert)
99
+ md = f"### 🚆 {dep.upper()} → {dest.upper()}\n\n"
100
+ md += "| Zug | Ab | An | Dauer | Gl. |\n"
101
  md += "|:---|:---|:---|:---|:---|\n"
102
+ for c in clean_json:
103
  md += f"| **{c['zug']}** | {c['abfahrt']} | {c['ankunft']} | {c['dauer']} | {c['gleis']} |\n"
104
 
105
+ md += f"\n\n*Stand: {datetime.now().strftime('%H:%M')}*"
106
 
107
+ return md, clean_json
108
+
109
+ # --- Interface ---
110
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
111
+ gr.Markdown("# 🚄 DB Timetable Pro")
112
 
 
 
 
 
 
 
 
 
113
  with gr.Row():
114
+ start = gr.Textbox(label="Start", placeholder="z.B. Köln Hbf")
115
+ ziel = gr.Textbox(label="Ziel", placeholder="z.B. Berlin Hbf")
116
 
117
+ btn = gr.Button("Verbindungen finden", variant="primary")
118
 
119
+ display = gr.Markdown()
120
+ js_out = gr.JSON(label="JSON Daten")
 
 
 
121
 
122
+ btn.click(format_output, [start, ziel,], [display, js_out])
123
 
124
  if __name__ == "__main__":
125
  demo.launch()