Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
env.bak/
venv.bak/
pythonenv*

# Flask
instance/
.webassets-cache
.env.local
.env.*.local

# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
desktop.ini

# Логи и базы данных
*.log
*.sql
*.sqlite
*.db

# Кэш станций (создается автоматически при запуске)
backend/stations_cache.json
backend/__pycache__/

# Виртуальное окружение
backend/venv/
backend/env/
backend/venv*/

# Файлы с секретами
backend/.env
.secrets

# Временные файлы
*.tmp
*.temp
*.bak
*.backup

# Результаты тестов
.coverage
htmlcov/
.pytest_cache/
.tox/

# Файлы зависимостей (они уже есть в requirements.txt)
# Не удаляем сам requirements.txt

# Node modules (если появятся)
node_modules/
npm-debug.log

# Системные файлы Windows
Thumbs.db
ehthumbs.db
Desktop.ini

# Системные файлы macOS
.AppleDouble
.LSOverride
._*

# Файлы отладки
*.pid
*.seed
*.pid.lock

# Secrets
backend/.env
.env
*.env

# Python
__pycache__/
*.py[cod]
venv/
env/

# Cache
electric-train-tracker/backend/stations_cache.json

# IDE
.vscode/
.idea/

# Yandex schedule cache
yaschedule.core.cache
*.cache
backend/yaschedule.core.cache

# Temporary files
*.tmp
*.temp
*.cache
5 changes: 5 additions & 0 deletions electric-train-tracker/backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
flask==2.3.3
flask-cors==4.0.0
requests==2.31.0
yaschedule==0.0.4.2
python-dotenv==1.0.0
143 changes: 143 additions & 0 deletions electric-train-tracker/backend/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import os
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
from yaschedule.core import YaSchedule
from station_cache import load_all_stations, search_stations
from datetime import datetime
import requests
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__, static_folder='../frontend/static', static_url_path='/static')
CORS(app)

API_KEY = os.getenv('YANDEX_API_KEY')
FLASK_DEBUG = os.getenv('FLASK_DEBUG', 'True') == 'True'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

== 'True'

явно лишнее

PORT = int(os.getenv('PORT', 5000))

if not API_KEY:
print("❌ ОШИБКА: YANDEX_API_KEY не найден в .env файле!")
print("Создайте файл .env в папке backend и добавьте:")
print("YANDEX_API_KEY=ваш_ключ_здесь")
exit(1)

print(f"✅ API ключ загружен из .env")

train_api = YaSchedule(API_KEY)

print("📡 Загрузка базы станций...")
station_db = load_all_stations(API_KEY)
print("✅ База готова!")

@app.route('/')
def serve_index():
return send_from_directory('../frontend/pages', 'index.html')

@app.route('/api/search/stations', methods=['GET'])
def search_stations_by_name():
search_term = request.args.get('q', '')
if len(search_term) < 2:
return jsonify([])

results = search_stations(station_db, search_term)
return jsonify(results)

@app.route('/api/geo/nearby', methods=['GET'])
def get_nearby_railway_stations():
lat = request.args.get('lat')
lng = request.args.get('lng')
distance = request.args.get('distance', 50)

if not lat or not lng:
return jsonify([])

try:
url = "https://api.rasp.yandex.net/v3.0/nearest_stations/"
params = {
'apikey': API_KEY,
'lat': lat,
'lng': lng,
'distance': distance,
'transport_types': 'train,suburban',
'station_types': 'train_station',
'lang': 'ru_RU',
'limit': 20
}

response = requests.get(url, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
stations = []
for station in data.get('stations', []):
if station.get('transport_type') in ['train', 'suburban']:
stations.append({
'title': station.get('title'),
'code': station.get('code'),
'distance': station.get('distance'),
'latitude': station.get('lat'),
'longitude': station.get('lng'),
'city': station.get('city', '')
})
return jsonify(stations)
else:
return jsonify([])
except Exception as e:
print(f"Error: {e}")
return jsonify([])

@app.route('/api/recommended/stations', methods=['GET'])
def get_recommended_stations():
popular_stations = [
{'name': 'Москва (Киевский вокзал)', 'code': 's9603402'},
{'name': 'Санкт-Петербург (Витебский)', 'code': 's9603551'},
{'name': 'Москва (Казанский вокзал)', 'code': 's9603404'},
{'name': 'Москва (Ярославский вокзал)', 'code': 's9603408'},
{'name': 'Москва (Павелецкий вокзал)', 'code': 's9603405'},
]
return jsonify(popular_stations)
Comment on lines +91 to +98
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В целом нормально, так как написание алгоритма отбора популярных остановок явно сильно выходит за рамки лабы


@app.route('/api/timetable/station', methods=['GET'])
def get_station_timetable():
station_id = request.args.get('station')
travel_date = request.args.get('date', datetime.now().strftime('%Y-%m-%d'))

if not station_id:
return jsonify({'error': 'Station required'}), 400

try:
schedule = train_api.get_station_schedule(
station=station_id,
transport_types='suburban',
date=travel_date
)
return jsonify(schedule)
except Exception as e:
return jsonify({'error': str(e)}), 500

@app.route('/api/timetable/route', methods=['GET'])
def get_route_timetable():
from_station = request.args.get('from')
to_station = request.args.get('to')
travel_date = request.args.get('date', datetime.now().strftime('%Y-%m-%d'))

if not from_station or not to_station:
return jsonify({'error': 'Both stations required'}), 400

try:
route_data = train_api.get_schedule(
from_station=from_station,
to_station=to_station,
transport_types='suburban',
date=travel_date
)
return jsonify(route_data)
except Exception as e:
return jsonify({'error': str(e)}), 500

@app.route('/api/health', methods=['GET'])
def health_check():
return jsonify({'status': 'ok', 'api_configured': bool(API_KEY)})

if __name__ == '__main__':
app.run(debug=FLASK_DEBUG, port=PORT)
52 changes: 52 additions & 0 deletions electric-train-tracker/backend/station_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import json
import os
from yaschedule.core import YaSchedule

def init_api(api_key):
return YaSchedule(api_key)

def load_all_stations(api_key):
cache_file = 'stations_cache.json'

if os.path.exists(cache_file):
print("Загрузка из кэша...")
with open(cache_file, 'r', encoding='utf-8') as f:
return json.load(f)

print("Скачивание с API (первый запуск, подождите)...")
api = init_api(api_key)
stations_data = api.get_all_stations()

with open(cache_file, 'w', encoding='utf-8') as f:
json.dump(stations_data, f, ensure_ascii=False, indent=2)

print("Кэш сохранен!")
return stations_data

def search_stations(stations_data, query):
results = []
query_lower = query.lower()

for country in stations_data.get('countries', []):
for region in country.get('regions', []):
for settlement in region.get('settlements', []):
for station in settlement.get('stations', []):
title = station.get('title', '')
station_type = station.get('station_type', '')

if station_type == 'train_station':
if query_lower in title.lower():
results.append({
'title': title,
'code': station.get('codes', {}).get('yandex_code'),
'city': settlement.get('title', '')
})

unique = []
seen = set()
for r in results:
if r['title'] not in seen:
seen.add(r['title'])
unique.append(r)

return unique[:20]
Loading