Implement API endpoints
This commit is contained in:
parent
20bdce68a3
commit
9847ba105c
21
projects/project-3/backend/api.py
Normal file
21
projects/project-3/backend/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, APIRouter
|
||||
|
||||
from routers import accidents, bikepoints, dashboard
|
||||
|
||||
app = FastAPI(
|
||||
title="London Bikestations Dashboard API",
|
||||
docs_url="/api/docs",
|
||||
redoc_url="/api/redoc"
|
||||
)
|
||||
|
||||
v1_router = APIRouter()
|
||||
v1_router.include_router(accidents.router)
|
||||
v1_router.include_router(bikepoints.router)
|
||||
v1_router.include_router(dashboard.router)
|
||||
|
||||
app.include_router(v1_router, prefix="/api/latest")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("api:app", host="0.0.0.0", port=8080, reload=True)
|
101
projects/project-3/backend/api_database.py
Normal file
101
projects/project-3/backend/api_database.py
Normal file
@ -0,0 +1,101 @@
|
||||
import sqlite3
|
||||
|
||||
UPSTREAM_BASE_URL = "https://api.tfl.gov.uk"
|
||||
DB_NAME = "bike-data.db"
|
||||
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect(DB_NAME, timeout=300)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
|
||||
# ACCIDENTS
|
||||
def get_all_accidents():
|
||||
query = """SELECT id, lat, lon, location, date, severity FROM accidents"""
|
||||
return get_db_connection().execute(query).fetchall()
|
||||
|
||||
|
||||
def get_accidents(year: str):
|
||||
query = """
|
||||
SELECT id, lat, lon, location, date, severity
|
||||
FROM accidents WHERE STRFTIME('%Y', date) = ?"""
|
||||
return get_db_connection().execute(query, (year,)).fetchall()
|
||||
|
||||
|
||||
# DASHBOARD
|
||||
def get_dashboard(station_id):
|
||||
query = """
|
||||
SELECT
|
||||
b.id_num as id,
|
||||
b.common_name AS commonName,
|
||||
b.lat,
|
||||
b.lon,
|
||||
max(date(u.start_date, 'unixepoch')) AS maxEndDate,
|
||||
min(date(u.start_date, 'unixepoch')) AS maxStartDate
|
||||
FROM usage_stats u
|
||||
JOIN bike_points b ON u.start_station_id = b.id_num
|
||||
WHERE u.start_station_id = ?"""
|
||||
return get_db_connection().execute(query, (station_id,)).fetchall()
|
||||
|
||||
|
||||
def get_dashboard_to(station_id, start_date, end_date):
|
||||
query = """
|
||||
SELECT
|
||||
u.start_station_name AS startStationName,
|
||||
u.end_station_name AS endStationName,
|
||||
count(*) AS number,
|
||||
round(avg(u.duration)) AS avgDuration
|
||||
FROM usage_stats u
|
||||
WHERE u.start_station_id = ? AND date(u.start_date, 'unixepoch') BETWEEN ? AND ?
|
||||
GROUP BY u.end_station_name
|
||||
ORDER BY number DESC
|
||||
LIMIT 3"""
|
||||
return get_db_connection().execute(query, (station_id, start_date, end_date)).fetchall()
|
||||
|
||||
|
||||
def get_dashboard_from(station_id, start_date, end_date):
|
||||
query = """
|
||||
SELECT
|
||||
u.start_station_name AS startStationName,
|
||||
u.end_station_name AS endStationName,
|
||||
count(*) AS number,
|
||||
round(avg(u.duration)) AS avgDuration
|
||||
FROM usage_stats u
|
||||
WHERE u.end_station_id = ? AND date(u.start_date, 'unixepoch') BETWEEN ? AND ?
|
||||
GROUP BY u.start_station_name
|
||||
ORDER BY number DESC
|
||||
LIMIT 3"""
|
||||
return get_db_connection().execute(query, (station_id, start_date, end_date)).fetchall()
|
||||
|
||||
|
||||
def get_dashboard_duration(station_id, start_date, end_date):
|
||||
query = """
|
||||
SELECT
|
||||
count(*) AS number,
|
||||
CASE WHEN duration <= 300 THEN '0-5'
|
||||
WHEN duration <= 900 THEN '5-15'
|
||||
WHEN duration <= 1800 THEN '15-30'
|
||||
WHEN duration <= 2700 THEN '30-45'
|
||||
ELSE '45+'
|
||||
END AS minutesGroup
|
||||
FROM usage_stats
|
||||
WHERE
|
||||
start_station_id = ? AND
|
||||
date(start_date, 'unixepoch') BETWEEN ? AND ?
|
||||
GROUP BY minutesGroup"""
|
||||
return get_db_connection().execute(query, (station_id, start_date, end_date)).fetchall()
|
||||
|
||||
|
||||
def get_dashboard_time(station_id, start_date, end_date):
|
||||
query = """
|
||||
SELECT
|
||||
substr(strftime('%H:%M', start_date, 'unixepoch'), 1, 4) || '0' as timeFrame,
|
||||
count(*) AS number,
|
||||
round(avg(duration)) AS avgDuration
|
||||
FROM usage_stats
|
||||
WHERE
|
||||
start_station_id = ?
|
||||
AND date(start_date, 'unixepoch') BETWEEN ? AND ?
|
||||
GROUP BY substr(strftime('%H:%M', start_date, 'unixepoch'), 1, 4)"""
|
||||
return get_db_connection().execute(query, (station_id, start_date, end_date)).fetchall()
|
0
projects/project-3/backend/routers/__init__.py
Normal file
0
projects/project-3/backend/routers/__init__.py
Normal file
36
projects/project-3/backend/routers/accidents.py
Normal file
36
projects/project-3/backend/routers/accidents.py
Normal file
@ -0,0 +1,36 @@
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter
|
||||
from pydantic.main import BaseModel
|
||||
|
||||
import api_database
|
||||
|
||||
router = APIRouter(prefix="/accidents", tags=["accidents", "local"])
|
||||
LOG = logging.getLogger()
|
||||
|
||||
|
||||
class Accident(BaseModel):
|
||||
lat: float
|
||||
lon: float
|
||||
severity: str
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
name="Get all accidents",
|
||||
description="Get all bike accidents in London.",
|
||||
response_model=List[Accident]
|
||||
)
|
||||
def get_accidents():
|
||||
return api_database.get_all_accidents()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{year}",
|
||||
name="Get accidents by year",
|
||||
description="Get bike accidents in London for a specific year.",
|
||||
response_model=List[Accident]
|
||||
)
|
||||
def get_accidents(year: str):
|
||||
return api_database.get_accidents(year)
|
61
projects/project-3/backend/routers/bikepoints.py
Normal file
61
projects/project-3/backend/routers/bikepoints.py
Normal file
@ -0,0 +1,61 @@
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
import requests
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
|
||||
from api_database import UPSTREAM_BASE_URL
|
||||
|
||||
router = APIRouter(prefix="/bikepoints", tags=["bikepoints"])
|
||||
|
||||
|
||||
class BikepointStatus(BaseModel):
|
||||
NbBikes: int
|
||||
NbEmptyDocks: int
|
||||
NbDocks: int
|
||||
|
||||
|
||||
class Bikepoint(BaseModel):
|
||||
id: str
|
||||
commonName: str
|
||||
lat: float
|
||||
lon: float
|
||||
status: BikepointStatus
|
||||
|
||||
|
||||
def bikepoint_mapper(bikepoint):
|
||||
mapped_point = {
|
||||
"id": bikepoint['id'].removeprefix("BikePoints_"),
|
||||
"url": bikepoint['url'],
|
||||
"commonName": bikepoint['commonName'],
|
||||
"lat": bikepoint['lat'],
|
||||
"lon": bikepoint['lon']
|
||||
}
|
||||
props = list(filter(
|
||||
lambda p: p['key'] in ["NbBikes", "NbEmptyDocks", "NbDocks"],
|
||||
bikepoint['additionalProperties']
|
||||
))
|
||||
mapped_point['status'] = {prop['key']: int(prop['value']) for prop in props}
|
||||
return mapped_point
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
tags=["upstream"],
|
||||
response_model=List[Bikepoint]
|
||||
)
|
||||
def get_all():
|
||||
bikepoints = json.loads(requests.get(UPSTREAM_BASE_URL + "/BikePoint").text)
|
||||
mapped_points = list(map(bikepoint_mapper, bikepoints))
|
||||
return mapped_points
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{id}",
|
||||
tags=["upstream"],
|
||||
response_model=Bikepoint
|
||||
)
|
||||
def get_single(id: int):
|
||||
bikepoint = json.loads(requests.get(UPSTREAM_BASE_URL + f"/BikePoint/BikePoints_{id}").text)
|
||||
return bikepoint_mapper(bikepoint)
|
71
projects/project-3/backend/routers/dashboard.py
Normal file
71
projects/project-3/backend/routers/dashboard.py
Normal file
@ -0,0 +1,71 @@
|
||||
import datetime
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
import api_database
|
||||
|
||||
router = APIRouter(prefix="/dashboard/{station_id}", tags=["dashboard", "local"])
|
||||
|
||||
|
||||
def validate_daterange(start_date: datetime.date, end_date: datetime.date):
|
||||
days_requested = (end_date - start_date).days
|
||||
if days_requested < 0:
|
||||
raise HTTPException(status_code=400, detail="Requested date-range is negative")
|
||||
|
||||
|
||||
class StationDashboard(BaseModel):
|
||||
id: Optional[int]
|
||||
commonName: Optional[str]
|
||||
lat: Optional[float]
|
||||
lon: Optional[float]
|
||||
maxEndDate: Optional[datetime.date]
|
||||
maxStartDate: Optional[datetime.date]
|
||||
|
||||
|
||||
@router.get("/", response_model=StationDashboard)
|
||||
def get_general_dashboard(station_id: int):
|
||||
return api_database.get_dashboard(station_id)[0]
|
||||
|
||||
|
||||
class StationDashboardTopStationsEntry(BaseModel):
|
||||
startStationName: str
|
||||
endStationName: str
|
||||
number: int
|
||||
avgDuration: int
|
||||
|
||||
|
||||
@router.get("/to", response_model=List[StationDashboardTopStationsEntry])
|
||||
def get_to_dashboard_for_station(station_id: int, start_date: datetime.date, end_date: datetime.date):
|
||||
validate_daterange(start_date, end_date)
|
||||
return api_database.get_dashboard_to(station_id, start_date, end_date)
|
||||
|
||||
|
||||
@router.get("/from", response_model=List[StationDashboardTopStationsEntry])
|
||||
def get_from_dashboard_for_station(station_id: int, start_date: datetime.date, end_date: datetime.date):
|
||||
validate_daterange(start_date, end_date)
|
||||
return api_database.get_dashboard_from(station_id, start_date, end_date)
|
||||
|
||||
|
||||
class StationDashboardDurationGroup(BaseModel):
|
||||
number: int
|
||||
minutesGroup: str
|
||||
|
||||
|
||||
@router.get("/duration", response_model=List[StationDashboardDurationGroup])
|
||||
def get_duration_dashboard_for_station(station_id: int, start_date: datetime.date, end_date: datetime.date):
|
||||
validate_daterange(start_date, end_date)
|
||||
return api_database.get_dashboard_duration(station_id, start_date, end_date)
|
||||
|
||||
|
||||
class StationDashboardTimeGroup(BaseModel):
|
||||
timeFrame: str
|
||||
number: int
|
||||
avgDuration: int
|
||||
|
||||
|
||||
@router.get("/time", response_model=List[StationDashboardTimeGroup])
|
||||
def get_time_dashboard_for_station(station_id: int, start_date: datetime.date, end_date: datetime.date):
|
||||
validate_daterange(start_date, end_date)
|
||||
return api_database.get_dashboard_time(station_id, start_date, end_date)
|
Loading…
Reference in New Issue
Block a user