Files
BrickTracker/bricktracker/views/statistics.py
T

194 lines
6.1 KiB
Python

"""
Statistics views for BrickTracker
Provides statistics and analytics pages
"""
import logging
from flask import Blueprint, render_template, request, url_for, redirect, current_app
from flask_login import login_required
from werkzeug.wrappers.response import Response
from .exceptions import exception_handler
from ..statistics import BrickStatistics
logger = logging.getLogger(__name__)
statistics_page = Blueprint('statistics', __name__, url_prefix='/statistics')
@statistics_page.route('/', methods=['GET'])
@login_required
@exception_handler(__file__)
def overview() -> str:
"""Statistics overview page with metrics"""
stats = BrickStatistics()
# Get all statistics data
overview_stats = stats.get_overview()
theme_stats = stats.get_theme_statistics()
storage_stats = stats.get_storage_statistics()
purchase_location_stats = stats.get_purchase_location_statistics()
financial_summary = stats.get_financial_summary()
collection_summary = stats.get_collection_summary()
sets_by_year_stats = stats.get_sets_by_year_statistics()
purchases_by_year_stats = stats.get_purchases_by_year_statistics()
year_summary = stats.get_year_summary()
# Prepare chart data for visualization (only if charts are enabled)
chart_data = {}
if current_app.config['STATISTICS_SHOW_CHARTS']:
chart_data = prepare_chart_data(sets_by_year_stats, purchases_by_year_stats)
# Get filter parameters for clickable statistics
filter_type = request.args.get('filter_type')
filter_value = request.args.get('filter_value')
# If a filter is applied, redirect to sets page with appropriate filters
if filter_type and filter_value:
return redirect_to_filtered_sets(filter_type, filter_value)
return render_template(
'statistics.html',
overview=overview_stats,
theme_statistics=theme_stats,
storage_statistics=storage_stats,
purchase_location_statistics=purchase_location_stats,
financial_summary=financial_summary,
collection_summary=collection_summary,
sets_by_year_statistics=sets_by_year_stats,
purchases_by_year_statistics=purchases_by_year_stats,
year_summary=year_summary,
chart_data=chart_data,
title="Statistics Overview"
)
def redirect_to_filtered_sets(filter_type: str, filter_value: str) -> Response:
"""Redirect to sets page with appropriate filters based on statistics click"""
# Map filter types to sets page parameters
filter_mapping = {
'theme': {'theme': filter_value},
'storage': {'storage': filter_value},
'purchase_location': {'purchase_location': filter_value},
'has_price': {'has_price': '1'} if filter_value == '1' else {},
'missing_parts': {'status': 'has-missing'},
'damaged_parts': {'status': 'has-damaged'},
'has_storage': {'status': 'has-storage'},
'no_storage': {'status': '-has-storage'},
}
# Get the appropriate filter parameters
filter_params = filter_mapping.get(filter_type, {})
if filter_params:
return redirect(url_for('set.list', **filter_params))
else:
# Default fallback to sets page
return redirect(url_for('set.list'))
@statistics_page.route('/themes', methods=['GET'])
@login_required
@exception_handler(__file__)
def themes() -> str:
"""Detailed theme statistics page"""
stats = BrickStatistics()
theme_stats = stats.get_theme_statistics()
return render_template(
'statistics_themes.html',
theme_statistics=theme_stats,
title="Theme Statistics"
)
@statistics_page.route('/storage', methods=['GET'])
@login_required
@exception_handler(__file__)
def storage() -> str:
"""Detailed storage statistics page"""
stats = BrickStatistics()
storage_stats = stats.get_storage_statistics()
return render_template(
'statistics_storage.html',
storage_statistics=storage_stats,
title="Storage Statistics"
)
@statistics_page.route('/purchase-locations', methods=['GET'])
@login_required
@exception_handler(__file__)
def purchase_locations() -> str:
"""Detailed purchase location statistics page"""
stats = BrickStatistics()
purchase_stats = stats.get_purchase_location_statistics()
return render_template(
'statistics_purchase_locations.html',
purchase_location_statistics=purchase_stats,
title="Purchase Location Statistics"
)
def prepare_chart_data(sets_by_year_stats, purchases_by_year_stats):
"""Prepare data for Chart.js visualization"""
import json
# Get all years from both datasets
all_years = set()
# Add years from sets by year
if sets_by_year_stats:
for year_stat in sets_by_year_stats:
if 'year' in year_stat:
all_years.add(year_stat['year'])
# Add years from purchases by year
if purchases_by_year_stats:
for year_stat in purchases_by_year_stats:
if 'purchase_year' in year_stat:
all_years.add(int(year_stat['purchase_year']))
# Create sorted list of years
years = sorted(list(all_years))
# Initialize data arrays
sets_data = []
parts_data = []
minifigs_data = []
# Create lookup dictionaries for quick access
sets_by_year_lookup = {}
if sets_by_year_stats:
for year_stat in sets_by_year_stats:
if 'year' in year_stat:
sets_by_year_lookup[year_stat['year']] = year_stat
# Fill data arrays
for year in years:
# Get sets and parts data from sets_by_year
year_data = sets_by_year_lookup.get(year)
if year_data:
sets_data.append(year_data.get('total_sets') or 0)
parts_data.append(year_data.get('total_parts') or 0)
# Use actual minifigure count from the database
minifigs_data.append(year_data.get('total_minifigures') or 0)
else:
sets_data.append(0)
parts_data.append(0)
minifigs_data.append(0)
return {
'years': json.dumps(years),
'sets_data': json.dumps(sets_data),
'parts_data': json.dumps(parts_data),
'minifigs_data': json.dumps(minifigs_data)
}