feat: Add --stats option for yearly, quarterly and monthly average prices
- Added --stats YEAR option to generate statistical reports - Calculates yearly, quarterly and monthly average prices for a given year and currency - Includes working day counts for accurate averaging - Supports both normal (minimal) and debug output modes - Automatically downloads required yearly data if not present - Shows detailed processing steps in debug mode - Works with any currency code supported by CNB - Provides formatted output with Czech month names - Example usage: --stats 2024 -c USD
This commit is contained in:
18
src/cli.py
18
src/cli.py
@@ -12,6 +12,7 @@ import data_fetcher
|
|||||||
import database
|
import database
|
||||||
import rate_finder
|
import rate_finder
|
||||||
import rate_reporter
|
import rate_reporter
|
||||||
|
import rate_stats
|
||||||
import holidays
|
import holidays
|
||||||
|
|
||||||
# Global debug flag
|
# Global debug flag
|
||||||
@@ -35,6 +36,7 @@ def check_and_update_yearly_data():
|
|||||||
holidays.set_debug_mode(DEBUG)
|
holidays.set_debug_mode(DEBUG)
|
||||||
rate_finder.set_debug_mode(DEBUG)
|
rate_finder.set_debug_mode(DEBUG)
|
||||||
rate_reporter.set_debug_mode(DEBUG)
|
rate_reporter.set_debug_mode(DEBUG)
|
||||||
|
rate_stats.set_debug_mode(DEBUG)
|
||||||
|
|
||||||
# Zkontrolujeme konzistenci dat
|
# Zkontrolujeme konzistenci dat
|
||||||
is_consistent = data_fetcher.check_yearly_data_consistency(current_year, output_dir="data")
|
is_consistent = data_fetcher.check_yearly_data_consistency(current_year, output_dir="data")
|
||||||
@@ -63,7 +65,7 @@ def main():
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-c", "--currency",
|
"-c", "--currency",
|
||||||
type=str,
|
type=str,
|
||||||
help="Kód měny (např. USD) pro měsíční stahování, vyhledání kurzu nebo generování reportu."
|
help="Kód měny (např. USD) pro měsíční stahování, vyhledání kurzu, generování reportu nebo statistik."
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--start-date",
|
"--start-date",
|
||||||
@@ -106,6 +108,11 @@ def main():
|
|||||||
metavar=('START_DATE', 'END_DATE'),
|
metavar=('START_DATE', 'END_DATE'),
|
||||||
help="Období, pro které se má vygenerovat report kurzů. Formát: DD.MM.YYYY DD.MM.YYYY"
|
help="Období, pro které se má vygenerovat report kurzů. Formát: DD.MM.YYYY DD.MM.YYYY"
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--stats",
|
||||||
|
type=int,
|
||||||
|
help="Vygeneruje statistiky pro zadaný rok. Formát: ROK"
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--debug",
|
"--debug",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -123,13 +130,14 @@ def main():
|
|||||||
holidays.set_debug_mode(DEBUG)
|
holidays.set_debug_mode(DEBUG)
|
||||||
rate_finder.set_debug_mode(DEBUG)
|
rate_finder.set_debug_mode(DEBUG)
|
||||||
rate_reporter.set_debug_mode(DEBUG)
|
rate_reporter.set_debug_mode(DEBUG)
|
||||||
|
rate_stats.set_debug_mode(DEBUG)
|
||||||
|
|
||||||
# Kontrola a případná aktualizace ročních dat pro aktuální rok (pouze v debug módu)
|
# Kontrola a případná aktualizace ročních dat pro aktuální rok (pouze v debug módu)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
check_and_update_yearly_data()
|
check_and_update_yearly_data()
|
||||||
else:
|
else:
|
||||||
# V normálním módu zkontrolujeme pouze při stahování dat
|
# V normálním módu zkontrolujeme pouze při stahování dat
|
||||||
if args.year or args.start_date or args.date or args.get_rate or args.report_year or args.report_period:
|
if args.year or args.start_date or args.date or args.get_rate or args.report_year or args.report_period or args.stats:
|
||||||
current_year = datetime.now().year
|
current_year = datetime.now().year
|
||||||
# Pro jednoduchost v normálním módu nebudeme kontrolovat konzistenci automaticky
|
# Pro jednoduchost v normálním módu nebudeme kontrolovat konzistenci automaticky
|
||||||
pass
|
pass
|
||||||
@@ -228,6 +236,12 @@ def main():
|
|||||||
start_date, end_date = args.report_period
|
start_date, end_date = args.report_period
|
||||||
debug_print(f"Generuji report pro {args.currency} za období {start_date} - {end_date}...")
|
debug_print(f"Generuji report pro {args.currency} za období {start_date} - {end_date}...")
|
||||||
rate_reporter.generate_period_report(start_date, end_date, args.currency, output_dir="data")
|
rate_reporter.generate_period_report(start_date, end_date, args.currency, output_dir="data")
|
||||||
|
elif args.stats and args.currency:
|
||||||
|
# Generování statistik
|
||||||
|
year = args.stats
|
||||||
|
debug_print(f"Generuji statistiky pro {args.currency} za rok {year}...")
|
||||||
|
stats = rate_stats.generate_yearly_stats(year, args.currency, output_dir="data")
|
||||||
|
rate_stats.print_stats(stats)
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|||||||
211
src/rate_stats.py
Normal file
211
src/rate_stats.py
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import calendar
|
||||||
|
|
||||||
|
# Přidání adresáře src do sys.path, aby bylo možné importovat moduly
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
import database
|
||||||
|
import data_fetcher
|
||||||
|
import holidays
|
||||||
|
|
||||||
|
# Global debug flag
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
def debug_print(*args, **kwargs):
|
||||||
|
"""Print debug messages only if debug mode is enabled."""
|
||||||
|
if DEBUG:
|
||||||
|
print(*args, **kwargs)
|
||||||
|
|
||||||
|
def set_debug_mode(debug):
|
||||||
|
"""Set the debug mode for this module."""
|
||||||
|
global DEBUG
|
||||||
|
DEBUG = debug
|
||||||
|
|
||||||
|
def get_working_days_in_month(year, month):
|
||||||
|
"""
|
||||||
|
Vrátí seznam pracovních dní v daném měsíci.
|
||||||
|
|
||||||
|
:param year: Rok
|
||||||
|
:param month: Měsíc (1-12)
|
||||||
|
:return: Seznam pracovních dnů ve formátu DD.MM.YYYY
|
||||||
|
"""
|
||||||
|
working_days = []
|
||||||
|
|
||||||
|
# Poslední den měsíce
|
||||||
|
last_day = calendar.monthrange(year, month)[1]
|
||||||
|
|
||||||
|
for day in range(1, last_day + 1):
|
||||||
|
date_str = f"{day:02d}.{month:02d}.{year}"
|
||||||
|
# Zkontrolujeme, zda je to pracovní den (není víkend ani svátek)
|
||||||
|
if not holidays.is_weekend(date_str) and not holidays.is_holiday(date_str):
|
||||||
|
working_days.append(date_str)
|
||||||
|
|
||||||
|
return working_days
|
||||||
|
|
||||||
|
def get_working_days_in_quarter(year, quarter):
|
||||||
|
"""
|
||||||
|
Vrátí seznam pracovních dní ve čtvrtletí.
|
||||||
|
|
||||||
|
:param year: Rok
|
||||||
|
:param quarter: Čtvrtletí (1-4)
|
||||||
|
:return: Seznam pracovních dnů ve formátu DD.MM.YYYY
|
||||||
|
"""
|
||||||
|
working_days = []
|
||||||
|
|
||||||
|
# Určíme měsíce v čtvrtletí
|
||||||
|
start_month = (quarter - 1) * 3 + 1
|
||||||
|
end_month = start_month + 2
|
||||||
|
|
||||||
|
for month in range(start_month, end_month + 1):
|
||||||
|
working_days.extend(get_working_days_in_month(year, month))
|
||||||
|
|
||||||
|
return working_days
|
||||||
|
|
||||||
|
def get_working_days_in_year(year):
|
||||||
|
"""
|
||||||
|
Vrátí seznam pracovních dní v roce.
|
||||||
|
|
||||||
|
:param year: Rok
|
||||||
|
:return: Seznam pracovních dnů ve formátu DD.MM.YYYY
|
||||||
|
"""
|
||||||
|
working_days = []
|
||||||
|
|
||||||
|
for month in range(1, 13):
|
||||||
|
working_days.extend(get_working_days_in_month(year, month))
|
||||||
|
|
||||||
|
return working_days
|
||||||
|
|
||||||
|
def calculate_average_rate_for_dates(dates, currency_code):
|
||||||
|
"""
|
||||||
|
Vypočítá průměrný kurz pro zadané datumy.
|
||||||
|
|
||||||
|
:param dates: Seznam datumů ve formátu DD.MM.YYYY
|
||||||
|
:param currency_code: Kód měny (např. USD)
|
||||||
|
:return: Průměrný kurz nebo None pokud nejsou k dispozici žádné kurzy
|
||||||
|
"""
|
||||||
|
if not dates:
|
||||||
|
return None
|
||||||
|
|
||||||
|
rates = []
|
||||||
|
for date in dates:
|
||||||
|
rate = database.get_rate(date, currency_code)
|
||||||
|
if rate is not None:
|
||||||
|
rates.append(rate)
|
||||||
|
|
||||||
|
if not rates:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return sum(rates) / len(rates)
|
||||||
|
|
||||||
|
def generate_yearly_stats(year, currency_code, output_dir="data"):
|
||||||
|
"""
|
||||||
|
Vygeneruje statistiky pro zadaný rok a měnu.
|
||||||
|
|
||||||
|
:param year: Rok
|
||||||
|
:param currency_code: Kód měny (např. USD)
|
||||||
|
:param output_dir: Adresář pro ukládání dat
|
||||||
|
:return: Slovník se statistikami
|
||||||
|
"""
|
||||||
|
debug_print(f"Generuji statistiky pro {currency_code} za rok {year}...")
|
||||||
|
|
||||||
|
# Zkontrolujeme, zda databáze obsahuje data pro daný rok
|
||||||
|
debug_print(f"Stahuji roční data pro rok {year}...")
|
||||||
|
os.makedirs("data", exist_ok=True)
|
||||||
|
data_fetcher.download_yearly_data(year, output_dir="data", force=False)
|
||||||
|
|
||||||
|
# Získáme pracovní dny v roce
|
||||||
|
working_days = get_working_days_in_year(year)
|
||||||
|
debug_print(f"Počet pracovních dní v roce {year}: {len(working_days)}")
|
||||||
|
|
||||||
|
# Vypočítáme průměrný kurz pro celý rok
|
||||||
|
yearly_avg = calculate_average_rate_for_dates(working_days, currency_code)
|
||||||
|
|
||||||
|
# Vypočítáme průměrné kurzy pro jednotlivé měsíce
|
||||||
|
monthly_stats = {}
|
||||||
|
for month in range(1, 13):
|
||||||
|
month_working_days = get_working_days_in_month(year, month)
|
||||||
|
if month_working_days:
|
||||||
|
month_avg = calculate_average_rate_for_dates(month_working_days, currency_code)
|
||||||
|
monthly_stats[month] = {
|
||||||
|
'average': month_avg,
|
||||||
|
'working_days_count': len(month_working_days)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Vypočítáme průměrné kurzy pro čtvrtletí
|
||||||
|
quarterly_stats = {}
|
||||||
|
for quarter in range(1, 5):
|
||||||
|
quarter_working_days = get_working_days_in_quarter(year, quarter)
|
||||||
|
if quarter_working_days:
|
||||||
|
quarter_avg = calculate_average_rate_for_dates(quarter_working_days, currency_code)
|
||||||
|
quarterly_stats[quarter] = {
|
||||||
|
'average': quarter_avg,
|
||||||
|
'working_days_count': len(quarter_working_days)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'year': year,
|
||||||
|
'currency': currency_code,
|
||||||
|
'yearly_average': yearly_avg,
|
||||||
|
'monthly_stats': monthly_stats,
|
||||||
|
'quarterly_stats': quarterly_stats,
|
||||||
|
'total_working_days': len(working_days)
|
||||||
|
}
|
||||||
|
|
||||||
|
def print_stats(stats):
|
||||||
|
"""
|
||||||
|
Vytiskne statistiky ve formátované podobě.
|
||||||
|
|
||||||
|
:param stats: Slovník se statistikami
|
||||||
|
"""
|
||||||
|
year = stats['year']
|
||||||
|
currency = stats['currency']
|
||||||
|
|
||||||
|
print(f"\nStatistiky pro {currency} za rok {year}:")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
if stats['yearly_average']:
|
||||||
|
print(f"Roční průměr: {stats['yearly_average']:.3f}")
|
||||||
|
else:
|
||||||
|
print("Roční průměr: N/A")
|
||||||
|
|
||||||
|
print(f"Celkový počet pracovních dní: {stats['total_working_days']}")
|
||||||
|
|
||||||
|
print("\nPrůměry podle čtvrtletí:")
|
||||||
|
print("-" * 30)
|
||||||
|
for quarter in range(1, 5):
|
||||||
|
if quarter in stats['quarterly_stats']:
|
||||||
|
q_stats = stats['quarterly_stats'][quarter]
|
||||||
|
if q_stats['average']:
|
||||||
|
print(f" Q{quarter}: {q_stats['average']:.3f} ({q_stats['working_days_count']} pracovních dní)")
|
||||||
|
else:
|
||||||
|
print(f" Q{quarter}: N/A ({q_stats['working_days_count']} pracovních dní)")
|
||||||
|
else:
|
||||||
|
print(f" Q{quarter}: N/A")
|
||||||
|
|
||||||
|
print("\nPrůměry podle měsíců:")
|
||||||
|
print("-" * 30)
|
||||||
|
czech_months = [
|
||||||
|
"", "Leden", "Únor", "Březen", "Duben", "Květen", "Červen",
|
||||||
|
"Červenec", "Srpen", "Září", "Říjen", "Listopad", "Prosinec"
|
||||||
|
]
|
||||||
|
|
||||||
|
for month in range(1, 13):
|
||||||
|
if month in stats['monthly_stats']:
|
||||||
|
m_stats = stats['monthly_stats'][month]
|
||||||
|
if m_stats['average']:
|
||||||
|
print(f" {czech_months[month]}: {m_stats['average']:.3f} ({m_stats['working_days_count']} pracovních dní)")
|
||||||
|
else:
|
||||||
|
print(f" {czech_months[month]}: N/A ({m_stats['working_days_count']} pracovních dní)")
|
||||||
|
else:
|
||||||
|
print(f" {czech_months[month]}: N/A")
|
||||||
|
|
||||||
|
# Příklad použití
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Inicializace databáze (pro případ spuštění samostatně)
|
||||||
|
database.init_db()
|
||||||
|
|
||||||
|
# Test
|
||||||
|
# stats = generate_yearly_stats(2020, "USD")
|
||||||
|
# print_stats(stats)
|
||||||
Reference in New Issue
Block a user