diff --git a/src/strava_offline/cli.py b/src/strava_offline/cli.py index caf922e..d494085 100644 --- a/src/strava_offline/cli.py +++ b/src/strava_offline/cli.py @@ -1,5 +1,5 @@ import datetime -from typing import TextIO +from typing import Optional, TextIO import click @@ -44,6 +44,11 @@ def cli_gpx(config: config.GpxConfig) -> None: option_output = click.option('-o', '--output', type=click.File('w'), default='-', help="Output file") option_year = click.argument('year', type=int, default=datetime.datetime.now().year) +option_year_start = click.argument('start_year', type=int, default=datetime.datetime.fromtimestamp(0).year) +option_year_end = click.argument('end_year', type=int, default=datetime.datetime.now().year) +option_time_resolution = click.option('-r', '--resolution', type=click.Choice(['day', 'month', 'year']), default='day') +option_output_format = click.option('-f', '--format', type=click.Choice(['plain', 'csv']), default='plain') +option_bike = click.option('-b', '--bike', type=str, required=False) @cli.command(name='report-yearly') @@ -65,6 +70,27 @@ def cli_report_yearly_bikes(config: config.DatabaseConfig, output: TextIO, year: with sync.database(config) as db: print(reports.yearly_bikes(db, year), file=output) +@cli.command(name='report-bikes-cummulative-distance') +@config.DatabaseConfig.options() +@option_output +@option_time_resolution +@option_output_format +@option_bike +@option_year_start +@option_year_end +def cli_report_bikes_cummulative_distance( + config: config.DatabaseConfig, + output: TextIO, + resolution: str, + format: str, + bike: Optional[str], + start_year: int, end_year: int, +) -> None: + "Show monthly report by bike" + + with sync.database(config) as db: + print(reports.bikes_cummulative_distance(db, resolution, format, bike, start_year, end_year), file=output) + @cli.command(name='report-bikes') @config.DatabaseConfig.options() diff --git a/src/strava_offline/reports.py b/src/strava_offline/reports.py index 72f91c2..5982a68 100644 --- a/src/strava_offline/reports.py +++ b/src/strava_offline/reports.py @@ -1,11 +1,31 @@ +from io import StringIO import sqlite3 +import csv +from typing import Optional from tabulate import tabulate -def tabulate_execute(db: sqlite3.Connection, sql: str, *params) -> str: +def tabulate_execute(db: sqlite3.Connection, sql: str, *params, format='plain') -> str: + # db.set_trace_callback(print) + # sqlite3.enable_callback_tracebacks(True) table = (dict(row) for row in db.execute(sql, params)) - return tabulate(table, headers='keys') + if format == 'csv': + table = list(table) + if len(table) == 0: + return "" + + keys = list(table[0].keys()) + stream = StringIO() + writer = csv.DictWriter(stream, keys) + writer.writeheader() + for row in table: + writer.writerow(row) + + return stream.getvalue() + + else: + return tabulate(table, headers='keys', tablefmt=format) def yearly(db: sqlite3.Connection, year: int) -> str: @@ -34,6 +54,54 @@ def yearly_bikes(db: sqlite3.Connection, year: int) -> str: """, f"{year}-%") +def bikes_cummulative_distance(db: sqlite3.Connection, resolution: str, output_format: str, bike: Optional[str], start_year: int, end_year) -> str: + sql_template = """ + WITH + daily AS ( + SELECT + b.name AS "Bike" + ,CASE + WHEN ? == "day" THEN strftime("%Y-%m-%d", a.start_date) + WHEN ? == "month" THEN strftime("%Y-%m-01", a.start_date) + WHEN ? == "year" THEN strftime("%Y-01-01", a.start_date) + END AS "Date" + ,CAST(SUM(a.distance) / 1000.0 AS DOUBLE) AS "Distance (km)" + ,CAST(SUM(a.moving_time) / 3600.0 AS DOUBLE) AS "Moving time (hour)" + ,CAST(SUM(a.total_elevation_gain) AS DOUBLE) AS "Total Elevation (m)" + FROM activity a INNER JOIN bike b ON a.gear_id = b.id + GROUP BY 1, 2 + ) + + ,daily_with_cumsum AS ( + SELECT + * + ,SUM(`Distance (km)`) OVER (PARTITION BY Bike ORDER BY Date ASC) AS "Cummulative (km)" + ,SUM(`Moving time (hour)`) OVER (PARTITION BY Bike ORDER BY Date ASC) AS "Cummulative (hour)" + ,SUM(`Total Elevation (m)`) OVER (PARTITION BY Bike ORDER BY Date ASC) AS "Cummulative (elevation)" + FROM daily + ) + + SELECT + Bike + ,Date + ,printf("%.02f", `Distance (km)`) AS `Distance (km)` + ,printf("%.02f", `Cummulative (km)`) AS `Cummulative (km)` + ,printf("%.02f", `Total Elevation (m)`) AS `Total Elevation (m)` + ,printf("%.02f", `Cummulative (elevation)`) AS `Cummulative (elevation)` + ,printf("%.02f", `Moving Time (hour)`) AS `Moving Time (hour)` + ,printf("%.02f", `Cummulative (hour)`) AS `Cummulative (hour)` + FROM daily_with_cumsum + WHERE + CAST(strftime("%Y", Date) AS INT) BETWEEN ? AND ? + AND + Bike LIKE ? + ORDER BY Bike ASC, Date ASC + """ + + bike = bike or "" + return tabulate_execute(db, sql_template , resolution, resolution, resolution, start_year, end_year, f'{bike}%', format=output_format) + + def bikes(db: sqlite3.Connection) -> str: return tabulate_execute(db, """ SELECT