feat: Add JSON output support and auto-download functionality
- Add --json CLI flag for structured JSON output across all commands - Implement JSON formatting functions for different data types: * Single rate lookups with fallback information * Unified tax rates (single year and multi-year) * Last available rates * Error responses with codes and details - Add auto-download functionality for missing monthly data in tax calculations - Modify calculate_tax_yearly_average to automatically fetch missing months - Add rate limiting (1s delay) between API calls to be respectful - Update CLI argument parsing and output logic for JSON/text modes - Maintain full backward compatibility - existing commands work unchanged - Enhance documentation with JSON usage examples and schema - Update help text to include new --json option Features: - JSON output for programmatic consumption - Automatic data fetching for incomplete years - Structured error handling - Comprehensive documentation updates Breaking changes: None (fully backward compatible)
This commit is contained in:
91
README.md
91
README.md
@@ -16,7 +16,9 @@ Tento projekt je určen pro stahování a správu kurzů cizích měn vůči če
|
||||
- **Kontrola konzistence ročních dat**: Při startu programu automaticky kontroluje, zda roční data pro aktuální rok obsahují záznamy za poslední 3 pracovní dny. Pokud ne, data jsou automaticky aktualizována.
|
||||
- **Automatické stahování ročních dat**: Pokud jsou požadována data pro rok, který v databázi není, program automaticky stáhne roční data pro daný rok, aktualizuje databázi a teprve poté vrátí výsledek.
|
||||
- **Generování reportů**: Lze vygenerovat report kurzů pro zadaný rok, měsíc nebo časové období včetně dopočítaných kurzů pro dny, kdy ve vstupních datech neexistovali.
|
||||
- **Správné dopočítání kurzů**: Program správně aplikuje pravidla ČNB pro dopočítání kurzů pro víkendy a svátky jak při vyhledávání (`--get-rate`), tak při generování reportů.
|
||||
- **Správné dopočítání kurzů**: Program správně aplikuje pravidla ČNB pro dopočítání kurzů pro víkendy a svátky jak při vyhledávání (`--get-rate`), tak při generování reportů.
|
||||
- **Výpočet Jednotného kurzu**: Lze vypočítat 'Jednotný kurz' pro daňové účely podle metodiky ČNB jako aritmetický průměr kurzů k posledním dnům každého měsíce v roce.
|
||||
- **JSON výstup**: Všechny příkazy podporují JSON formát pro programové zpracování pomocí přepínače `--json`.
|
||||
|
||||
## Požadavky
|
||||
|
||||
@@ -60,8 +62,10 @@ Při každém spuštění programu:
|
||||
- `--date DATUM`: Stáhne denní data pro zadané datum. Formát: `DD.MM.YYYY`.
|
||||
- `--get-rate DATUM`: Vyhledá kurz pro zadané datum. Formát: `DD.MM.YYYY`. Vyžaduje `-c` nebo `--currency`.
|
||||
- `--auto-download`: Povolí automatické stahování denních dat pro dnešní datum, pokud je po 14:30 a kurz není k dispozici.
|
||||
- `--report-year ROK [--report-month MESIC]`: Vygeneruje report kurzů pro zadaný rok (a případně měsíc). Vyžaduje `-c` nebo `--currency`.
|
||||
- `--report-period ZACATEK KONEC`: Vygeneruje report kurzů pro zadané časové období. Vyžaduje `-c` nebo `--currency`.
|
||||
- `--report-year ROK [--report-month MESIC]`: Vygeneruje report kurzů pro zadaný rok (a případně měsíc). Vyžaduje `-c` nebo `--currency`.
|
||||
- `--report-period ZACATEK KONEC`: Vygeneruje report kurzů pro zadané časové období. Vyžaduje `-c` nebo `--currency`.
|
||||
- `--stats [ROK]`: Vypočítá 'Jednotný kurz' pro daňové účely podle metodiky ČNB. Pokud je zadán rok, vytvoří kurz pro konkrétní rok. Pokud není rok zadán, vytvoří kurzy pro všechny roky s dostupnými daty. Vyžaduje `-c` nebo `--currency`.
|
||||
- `--json`: Výstup ve formátu JSON místo prostého textu pro programové zpracování.
|
||||
|
||||
### Příklady
|
||||
|
||||
@@ -111,6 +115,87 @@ Při každém spuštění programu:
|
||||
python src/cli.py --report-period 01.07.2020 31.07.2020 -c USD
|
||||
```
|
||||
|
||||
10. **Výpočet Jednotného kurzu pro daňové účely pro USD za rok 2025**:
|
||||
```bash
|
||||
python src/cli.py --stats 2025 -c USD
|
||||
```
|
||||
|
||||
11. **Výpočet Jednotného kurzu pro daňové účely pro všechny roky s daty**:
|
||||
```bash
|
||||
python src/cli.py --stats -c USD
|
||||
```
|
||||
|
||||
12. **Získání posledního dostupného kurzu USD**:
|
||||
```bash
|
||||
python src/cli.py -c USD
|
||||
```
|
||||
|
||||
13. **JSON výstup pro vyhledání kurzu**:
|
||||
```bash
|
||||
python src/cli.py --get-rate 01.01.2025 -c USD --json
|
||||
```
|
||||
|
||||
14. **JSON výstup pro výpočet Jednotného kurzu**:
|
||||
```bash
|
||||
python src/cli.py --stats 2025 -c USD --json
|
||||
```
|
||||
|
||||
## JSON formát
|
||||
|
||||
Při použití přepínače `--json` program vrací strukturovaná data ve formátu JSON:
|
||||
|
||||
### Jednotlivý kurz
|
||||
```json
|
||||
{
|
||||
"currency": "USD",
|
||||
"rate": 24.214,
|
||||
"date": "01.01.2025",
|
||||
"timestamp": "2025-01-12T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Jednotný kurz pro jeden rok
|
||||
```json
|
||||
{
|
||||
"currency": "USD",
|
||||
"year": 2025,
|
||||
"unified_rate": 21.84,
|
||||
"calculation_date": "2025-01-12T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Jednotný kurz pro více let
|
||||
```json
|
||||
{
|
||||
"currency": "USD",
|
||||
"results": [
|
||||
{"year": 2023, "unified_rate": 23.45},
|
||||
{"year": 2024, "unified_rate": 23.28},
|
||||
{"year": 2025, "unified_rate": 21.84}
|
||||
],
|
||||
"calculation_date": "2025-01-12T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Poslední dostupný kurz
|
||||
```json
|
||||
{
|
||||
"currency": "USD",
|
||||
"rate": 20.632,
|
||||
"date": "31.12.2025",
|
||||
"timestamp": "2025-01-12T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Chyba
|
||||
```json
|
||||
{
|
||||
"error": "Kurz nebyl nalezen",
|
||||
"code": "RATE_NOT_FOUND",
|
||||
"timestamp": "2025-01-12T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Chování při různých časech a datumech
|
||||
|
||||
- **Budoucí datum**: Program vrátí chybu, protože kurzy pro budoucí data ještě nebyly vydány.
|
||||
|
||||
369
src/cli.py
369
src/cli.py
@@ -3,6 +3,7 @@
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# Přidání adresáře src do sys.path, aby bylo možné importovat moduly
|
||||
@@ -17,16 +18,18 @@ import rate_reporter
|
||||
# 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
|
||||
|
||||
|
||||
# Nastavíme debug mód pro všechny moduly
|
||||
database.set_debug_mode(DEBUG)
|
||||
data_fetcher.set_debug_mode(DEBUG)
|
||||
@@ -34,18 +37,91 @@ def set_debug_mode(debug):
|
||||
rate_finder.set_debug_mode(DEBUG)
|
||||
rate_reporter.set_debug_mode(DEBUG)
|
||||
|
||||
|
||||
def format_single_rate_json(
|
||||
currency, rate, requested_date, actual_date=None, fallback=False
|
||||
):
|
||||
"""Format single rate lookup as JSON."""
|
||||
data = {
|
||||
"currency": currency,
|
||||
"rate": float(rate) if rate is not None else None,
|
||||
"date": requested_date,
|
||||
"timestamp": datetime.now().isoformat() + "Z",
|
||||
}
|
||||
if fallback and actual_date:
|
||||
data["actual_date"] = actual_date
|
||||
data["fallback"] = True
|
||||
return data
|
||||
|
||||
|
||||
def format_tax_rate_json(currency, year, rate, monthly_rates=None):
|
||||
"""Format unified tax rate as JSON."""
|
||||
data = {
|
||||
"currency": currency,
|
||||
"year": year,
|
||||
"unified_rate": float(rate) if rate is not None else None,
|
||||
"calculation_date": datetime.now().isoformat() + "Z",
|
||||
}
|
||||
if monthly_rates:
|
||||
data["monthly_rates"] = monthly_rates
|
||||
return data
|
||||
|
||||
|
||||
def format_multi_year_json(currency, year_results):
|
||||
"""Format multi-year stats as JSON."""
|
||||
data = {
|
||||
"currency": currency,
|
||||
"results": [
|
||||
{"year": year, "unified_rate": float(rate) if rate is not None else None}
|
||||
for year, rate in year_results
|
||||
],
|
||||
"calculation_date": datetime.now().isoformat() + "Z",
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
def format_last_rate_json(currency, rate, date):
|
||||
"""Format last available rate as JSON."""
|
||||
data = {
|
||||
"currency": currency,
|
||||
"rate": float(rate) if rate is not None else None,
|
||||
"date": date,
|
||||
"timestamp": datetime.now().isoformat() + "Z",
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
def format_error_json(error_msg, error_code=None, details=None):
|
||||
"""Format error response as JSON."""
|
||||
data = {"error": error_msg, "timestamp": datetime.now().isoformat() + "Z"}
|
||||
if error_code:
|
||||
data["code"] = error_code
|
||||
if details:
|
||||
data["details"] = details
|
||||
return data
|
||||
|
||||
|
||||
def output_json(data):
|
||||
"""Output data as formatted JSON."""
|
||||
print(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
def check_and_update_yearly_data():
|
||||
"""
|
||||
Zkontroluje konzistenci ročních dat pro aktuální rok a případně je aktualizuje.
|
||||
"""
|
||||
current_year = datetime.now().year
|
||||
debug_print(f"Kontroluji konzistenci ročních dat pro rok {current_year}...")
|
||||
|
||||
|
||||
# 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"
|
||||
)
|
||||
|
||||
if not is_consistent:
|
||||
debug_print(f"Roční data pro rok {current_year} nejsou konzistentní. Stahuji aktualizovaná data...")
|
||||
debug_print(
|
||||
f"Roční data pro rok {current_year} nejsou konzistentní. Stahuji aktualizovaná data..."
|
||||
)
|
||||
# Ujistěme se, že adresář data existuje
|
||||
os.makedirs("data", exist_ok=True)
|
||||
# Stáhneme roční data znovu
|
||||
@@ -53,81 +129,83 @@ def check_and_update_yearly_data():
|
||||
else:
|
||||
debug_print(f"Roční data pro rok {current_year} jsou aktuální.")
|
||||
|
||||
|
||||
def main():
|
||||
global DEBUG
|
||||
|
||||
|
||||
# Inicializace databáze
|
||||
database.init_db()
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description="Stahování a správa kurzů měn z ČNB.")
|
||||
parser.add_argument(
|
||||
"--year",
|
||||
type=int,
|
||||
help="Rok, pro který se mají stáhnout data (např. 2020)."
|
||||
"--year", type=int, help="Rok, pro který se mají stáhnout data (např. 2020)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--currency",
|
||||
"-c",
|
||||
"--currency",
|
||||
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 nebo generování reportu.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--start-date",
|
||||
type=str,
|
||||
help="Počáteční datum pro měsíční stahování nebo generování reportu ve formátu DD.MM.YYYY."
|
||||
help="Počáteční datum pro měsíční stahování nebo generování reportu ve formátu DD.MM.YYYY.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--end-date",
|
||||
type=str,
|
||||
help="Koncové datum pro měsíční stahování nebo generování reportu ve formátu DD.MM.YYYY."
|
||||
help="Koncové datum pro měsíční stahování nebo generování reportu ve formátu DD.MM.YYYY.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--date",
|
||||
type=str,
|
||||
help="Datum pro stažení denních kurzů ve formátu DD.MM.YYYY."
|
||||
help="Datum pro stažení denních kurzů ve formátu DD.MM.YYYY.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--get-rate", "-d",
|
||||
"--get-rate",
|
||||
"-d",
|
||||
type=str,
|
||||
help="Vyhledá kurz pro zadané datum. Formát: DD.MM.YYYY"
|
||||
help="Vyhledá kurz pro zadané datum. Formát: DD.MM.YYYY",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--auto-download",
|
||||
action="store_true",
|
||||
help="Automaticky stáhne denní data, pokud je po 14:30 a kurz pro dnešní datum není k dispozici."
|
||||
help="Automaticky stáhne denní data, pokud je po 14:30 a kurz pro dnešní datum není k dispozici.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report-year",
|
||||
type=int,
|
||||
help="Rok, pro který se má vygenerovat report kurzů."
|
||||
"--report-year", type=int, help="Rok, pro který se má vygenerovat report kurzů."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report-month",
|
||||
type=int,
|
||||
help="Měsíc, pro který se má vygenerovat report kurzů (1-12). Vyžaduje --report-year."
|
||||
help="Měsíc, pro který se má vygenerovat report kurzů (1-12). Vyžaduje --report-year.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report-period",
|
||||
nargs=2,
|
||||
metavar=('START_DATE', 'END_DATE'),
|
||||
help="Období, pro které se má vygenerovat report kurzů. Formát: DD.MM.YYYY DD.MM.YYYY"
|
||||
metavar=("START_DATE", "END_DATE"),
|
||||
help="Období, pro které se má vygenerovat report kurzů. Formát: DD.MM.YYYY DD.MM.YYYY",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stats",
|
||||
nargs='?',
|
||||
nargs="?",
|
||||
const=True,
|
||||
type=int,
|
||||
help="Vygeneruje 'Jednotný kurz' pro daňové účely podle metodiky ČNB. "
|
||||
"Pokud je zadán rok, vytvoří kurz pro konkrétní rok. "
|
||||
"Pokud není rok zadán, vytvoří kurzy pro všechny roky s dostupnými daty."
|
||||
"Pokud je zadán rok, vytvoří kurz pro konkrétní rok. "
|
||||
"Pokud není rok zadán, vytvoří kurzy pro všechny roky s dostupnými daty.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
"--debug", action="store_true", help="Zobrazí podrobné ladicí informace."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Zobrazí podrobné ladicí informace."
|
||||
help="Výstup ve formátu JSON místo prostého textu pro programové zpracování.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
# Pokud nebyly zadány žádné argumenty, vytiskneme nápovědu a seznam dostupných měn
|
||||
if len(sys.argv) == 1:
|
||||
parser.print_help()
|
||||
@@ -138,25 +216,34 @@ def main():
|
||||
else:
|
||||
print("Žádné měny nejsou v databázi k dispozici.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
# Nastavíme debug mód
|
||||
DEBUG = args.debug
|
||||
set_debug_mode(DEBUG)
|
||||
|
||||
|
||||
# Převedeme kód měny na velká písmena, pokud byl zadán
|
||||
if args.currency:
|
||||
args.currency = args.currency.upper()
|
||||
|
||||
|
||||
# Kontrola a případná aktualizace ročních dat pro aktuální rok (pouze v debug módu)
|
||||
if DEBUG:
|
||||
check_and_update_yearly_data()
|
||||
else:
|
||||
# V normálním módu zkontrolujeme pouze při stahování dat
|
||||
if args.year or args.start_date or args.end_date or args.date or args.get_rate or args.report_year or args.report_period or args.stats:
|
||||
if (
|
||||
args.year
|
||||
or args.start_date
|
||||
or args.end_date
|
||||
or args.date
|
||||
or args.get_rate
|
||||
or args.report_year
|
||||
or args.report_period
|
||||
or args.stats
|
||||
):
|
||||
current_year = datetime.now().year
|
||||
# Pro jednoduchost v normálním módu nebudeme kontrolovat konzistenci automaticky
|
||||
pass
|
||||
|
||||
|
||||
# Zde bude logika pro zpracování argumentů
|
||||
if args.year:
|
||||
debug_print(f"Stahuji roční data pro rok {args.year}...")
|
||||
@@ -166,11 +253,23 @@ def main():
|
||||
data_fetcher.download_yearly_data(args.year, output_dir="data")
|
||||
elif args.currency and args.start_date and args.end_date and not args.report_period:
|
||||
# Měsíční stahování dat
|
||||
debug_print(f"Stahuji měsíční data pro měnu {args.currency} od {args.start_date} do {args.end_date}...")
|
||||
debug_print(
|
||||
f"Stahuji měsíční data pro měnu {args.currency} od {args.start_date} do {args.end_date}..."
|
||||
)
|
||||
# Ujistěme se, že adresář data existuje
|
||||
os.makedirs("data", exist_ok=True)
|
||||
# Volání funkce pro stažení měsíčních dat
|
||||
data_fetcher.download_monthly_data(args.currency, args.start_date, args.end_date, output_dir="data")
|
||||
data_fetcher.download_monthly_data(
|
||||
args.currency, args.start_date, args.end_date, output_dir="data"
|
||||
)
|
||||
elif args.report_period and args.currency:
|
||||
start_date, end_date = args.report_period
|
||||
debug_print(
|
||||
f"Generuji report pro měnu {args.currency} od {start_date} do {end_date}..."
|
||||
)
|
||||
rate_reporter.generate_period_report(
|
||||
start_date, end_date, args.currency, output_dir="data"
|
||||
)
|
||||
elif args.date:
|
||||
debug_print(f"Stahuji denní data pro datum {args.date}...")
|
||||
# Ujistěme se, že adresář data existuje
|
||||
@@ -183,64 +282,38 @@ def main():
|
||||
debug_print(f"Vyhledávám kurz pro {currency_code} na datum {date_str}...")
|
||||
rate = rate_finder.get_rate_for_date(date_str, currency_code)
|
||||
if rate:
|
||||
# Pro --get-rate v normálním režimu zobrazíme pouze kurz
|
||||
if not DEBUG:
|
||||
print(rate)
|
||||
if args.json:
|
||||
json_data = format_single_rate_json(currency_code, rate, date_str)
|
||||
output_json(json_data)
|
||||
else:
|
||||
print(f"Kurz {currency_code} na datum {date_str} (nebo nejbližší pracovní den): {rate}")
|
||||
# Pro --get-rate v normálním režimu zobrazíme pouze kurz
|
||||
if not DEBUG:
|
||||
print(rate)
|
||||
else:
|
||||
print(
|
||||
f"Kurz {currency_code} na datum {date_str} (nebo nejbližší pracovní den): {rate}"
|
||||
)
|
||||
else:
|
||||
# Pokud nebyl kurz nalezen a je aktivní přepínač --auto-download, zkusíme stáhnout denní data
|
||||
if args.auto_download:
|
||||
try:
|
||||
requested_date = datetime.strptime(date_str, "%d.%m.%Y")
|
||||
today = datetime.now()
|
||||
|
||||
# Zkontrolujeme, zda je požadované datum dnešní
|
||||
if requested_date.date() == today.date():
|
||||
# Zkontrolujeme, zda je čas po 14:30
|
||||
if today.time() >= datetime.strptime("14:30", "%H:%M").time():
|
||||
debug_print("Automaticky stahuji denní data...")
|
||||
# Ujistěme se, že adresář data existuje
|
||||
os.makedirs("data", exist_ok=True)
|
||||
# Stáhneme denní data pro dnešní datum
|
||||
today_str = today.strftime("%d.%m.%Y")
|
||||
data_fetcher.download_daily_data(today_str, output_dir="data")
|
||||
# Zkusíme znovu vyhledat kurz
|
||||
rate = rate_finder.get_rate_for_date(date_str, currency_code)
|
||||
if rate:
|
||||
if not DEBUG:
|
||||
print(rate)
|
||||
else:
|
||||
print(f"Kurz {currency_code} na datum {date_str} (nebo nejbližší pracovní den): {rate}")
|
||||
else:
|
||||
if not DEBUG:
|
||||
print("Kurz nenalezen")
|
||||
else:
|
||||
print(f"Kurz {currency_code} na datum {date_str} (ani v předchozích dnech) nebyl nalezen ani po stažení denních dat.")
|
||||
else:
|
||||
if not DEBUG:
|
||||
print("Kurz nenalezen")
|
||||
else:
|
||||
print(f"Chyba: Automatické stahování nelze provést, protože čas ještě není po 14:30. Aktuální čas je {today.strftime('%H:%M')}.")
|
||||
else:
|
||||
if not DEBUG:
|
||||
print("Kurz nenalezen")
|
||||
else:
|
||||
print("Automatické stahování denních dat je možné pouze pro dnešní datum.")
|
||||
except ValueError:
|
||||
if not DEBUG:
|
||||
print("Kurz nenalezen")
|
||||
else:
|
||||
print(f"Neplatný formát data: {date_str}")
|
||||
# Rate not found
|
||||
if args.json:
|
||||
error_data = format_error_json(
|
||||
f"Kurz {currency_code} na datum {date_str} nebyl nalezen",
|
||||
"RATE_NOT_FOUND",
|
||||
)
|
||||
output_json(error_data)
|
||||
else:
|
||||
if not DEBUG:
|
||||
print("Kurz nenalezen")
|
||||
else:
|
||||
print(f"Kurz {currency_code} na datum {date_str} (ani v předchozích dnech) nebyl nalezen.")
|
||||
print(
|
||||
f"Kurz {currency_code} na datum {date_str} (ani v předchozích dnech) nebyl nalezen."
|
||||
)
|
||||
elif args.get_rate is not None and not args.currency:
|
||||
# Pokud je zadán --get-rate bez data a bez měny
|
||||
if DEBUG:
|
||||
print("Chyba: Pro použití --get-rate musí být zadána měna pomocí -c/--currency.")
|
||||
print(
|
||||
"Chyba: Pro použití --get-rate musí být zadána měna pomocí -c/--currency."
|
||||
)
|
||||
sys.exit(1)
|
||||
# DŮLEŽITÉ: Pořadí následujících elif podmínek je důležité!
|
||||
# Nejprve zpracujeme --stats, pak teprve "poslední dostupný kurz"
|
||||
@@ -249,8 +322,10 @@ def main():
|
||||
currency_code = args.currency
|
||||
if args.stats is True:
|
||||
# Pokud je --stats zadán bez roku, vytvoříme kurzy pro všechny roky s dostupnými daty
|
||||
debug_print(f"Generuji 'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} pro všechny roky...")
|
||||
|
||||
debug_print(
|
||||
f"Generuji 'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} pro všechny roky..."
|
||||
)
|
||||
|
||||
# Získáme seznam všech roků s daty
|
||||
years = database.get_years_with_data()
|
||||
if not years:
|
||||
@@ -259,77 +334,117 @@ def main():
|
||||
else:
|
||||
print("Databáze neobsahuje žádná data.")
|
||||
return
|
||||
|
||||
|
||||
# Pro každý rok vypočítáme 'Jednotný kurz'
|
||||
year_results = []
|
||||
for year in years:
|
||||
# Zkontrolujeme, zda databáze obsahuje data pro daný rok
|
||||
if not rate_finder.check_year_data_in_db(year):
|
||||
debug_print(f"Databáze neobsahuje data pro rok {year}. Stahuji roční data...")
|
||||
debug_print(
|
||||
f"Databáze neobsahuje data pro rok {year}. Stahuji roční data..."
|
||||
)
|
||||
# Ujistěme se, že adresář data existuje
|
||||
os.makedirs("data", exist_ok=True)
|
||||
# Stáhneme roční data s vynuceným stažením
|
||||
data_fetcher.download_yearly_data(year, output_dir="data", force=True)
|
||||
|
||||
data_fetcher.download_yearly_data(
|
||||
year, output_dir="data", force=True
|
||||
)
|
||||
|
||||
# Vypočítáme 'Jednotný kurz' podle metodiky ČNB
|
||||
tax_rate = rate_reporter.calculate_tax_yearly_average(year, currency_code, output_dir="data")
|
||||
if tax_rate:
|
||||
# Pro --stats v normálním režimu zobrazíme pouze 'Jednotný kurz' zaokrouhlený na 2 desetinná místa
|
||||
if not DEBUG:
|
||||
print(f"{year}: {tax_rate:.2f}")
|
||||
tax_rate = rate_reporter.calculate_tax_yearly_average(
|
||||
year, currency_code, output_dir="data"
|
||||
)
|
||||
year_results.append((year, tax_rate))
|
||||
|
||||
if not args.json:
|
||||
if tax_rate:
|
||||
# Pro --stats v normálním režimu zobrazíme pouze 'Jednotný kurz' zaokrouhlený na 2 desetinná místa
|
||||
if not DEBUG:
|
||||
print(f"{year}: {tax_rate:.2f}")
|
||||
else:
|
||||
print(
|
||||
f"'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year}: {tax_rate:.2f}"
|
||||
)
|
||||
else:
|
||||
print(f"'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year}: {tax_rate:.2f}")
|
||||
else:
|
||||
if not DEBUG:
|
||||
print(f"{year}: 'Jednotný kurz' nenalezen")
|
||||
else:
|
||||
print(f"'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year} nebyl nalezen.")
|
||||
if not DEBUG:
|
||||
print(f"{year}: 'Jednotný kurz' nenalezen")
|
||||
else:
|
||||
print(
|
||||
f"'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year} nebyl nalezen."
|
||||
)
|
||||
|
||||
# Output JSON for multi-year results
|
||||
if args.json:
|
||||
json_data = format_multi_year_json(currency_code, year_results)
|
||||
output_json(json_data)
|
||||
else:
|
||||
# Pokud je --stats zadán s konkrétním rokem
|
||||
year = args.stats
|
||||
debug_print(f"Generuji 'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year}...")
|
||||
|
||||
debug_print(
|
||||
f"Generuji 'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year}..."
|
||||
)
|
||||
|
||||
# Zkontrolujeme, zda databáze obsahuje data pro daný rok
|
||||
if not rate_finder.check_year_data_in_db(year):
|
||||
debug_print(f"Databáze neobsahuje data pro rok {year}. Stahuji roční data...")
|
||||
debug_print(
|
||||
f"Databáze neobsahuje data pro rok {year}. Stahuji roční data..."
|
||||
)
|
||||
# Ujistěme se, že adresář data existuje
|
||||
os.makedirs("data", exist_ok=True)
|
||||
# Stáhneme roční data s vynuceným stažením
|
||||
data_fetcher.download_yearly_data(year, output_dir="data", force=True)
|
||||
|
||||
|
||||
# Vypočítáme 'Jednotný kurz' podle metodiky ČNB
|
||||
tax_rate = rate_reporter.calculate_tax_yearly_average(year, currency_code, output_dir="data")
|
||||
if tax_rate:
|
||||
# Pro --stats v normálním režimu zobrazíme pouze 'Jednotný kurz' zaokrouhlený na 2 desetinná místa
|
||||
if not DEBUG:
|
||||
print(f"{tax_rate:.2f}")
|
||||
else:
|
||||
print(f"'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year}: {tax_rate:.2f}")
|
||||
tax_rate = rate_reporter.calculate_tax_yearly_average(
|
||||
year, currency_code, output_dir="data"
|
||||
)
|
||||
if args.json:
|
||||
json_data = format_tax_rate_json(currency_code, year, tax_rate)
|
||||
output_json(json_data)
|
||||
else:
|
||||
if not DEBUG:
|
||||
print("'Jednotný kurz' nenalezen")
|
||||
if tax_rate:
|
||||
# Pro --stats v normálním režimu zobrazíme pouze 'Jednotný kurz' zaokrouhlený na 2 desetinná místa
|
||||
if not DEBUG:
|
||||
print(f"{tax_rate:.2f}")
|
||||
else:
|
||||
print(
|
||||
f"'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year}: {tax_rate:.2f}"
|
||||
)
|
||||
else:
|
||||
print(f"'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year} nebyl nalezen.")
|
||||
if not DEBUG:
|
||||
print("'Jednotný kurz' nenalezen")
|
||||
else:
|
||||
print(
|
||||
f"'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year} nebyl nalezen."
|
||||
)
|
||||
elif args.currency and not args.get_rate:
|
||||
# Pokud je zadána měna, ale není zadán --get-rate, vytiskneme poslední dostupný kurz
|
||||
# Toto musí být až po --stats, jinak by se --stats nikdy nevykonalo
|
||||
currency_code = args.currency
|
||||
debug_print(f"Vyhledávám poslední dostupný kurz pro {currency_code}...")
|
||||
rate, date = database.get_last_rate_for_currency(currency_code)
|
||||
if rate and date:
|
||||
# Pro normální režim zobrazíme kurz ve formátu "11.11 # dated: dd.mm.yyyy"
|
||||
if not DEBUG:
|
||||
print(f"{rate} # dated: {date}")
|
||||
else:
|
||||
print(f"Poslední dostupný kurz {currency_code}: {rate} # dated: {date}")
|
||||
if args.json:
|
||||
json_data = format_last_rate_json(currency_code, rate, date)
|
||||
output_json(json_data)
|
||||
else:
|
||||
if not DEBUG:
|
||||
print("Kurz nenalezen")
|
||||
if rate and date:
|
||||
# Pro normální režim zobrazíme kurz ve formátu "11.11 # dated: dd.mm.yyyy"
|
||||
if not DEBUG:
|
||||
print(f"{rate} # dated: {date}")
|
||||
else:
|
||||
print(
|
||||
f"Poslední dostupný kurz {currency_code}: {rate} # dated: {date}"
|
||||
)
|
||||
else:
|
||||
print(f"Poslední dostupný kurz pro {currency_code} nebyl nalezen.")
|
||||
if not DEBUG:
|
||||
print("Kurz nenalezen")
|
||||
else:
|
||||
print(f"Poslední dostupný kurz pro {currency_code} nebyl nalezen.")
|
||||
else:
|
||||
if DEBUG:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import calendar
|
||||
|
||||
@@ -14,16 +15,53 @@ import rate_finder
|
||||
# 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_czech_day_name(date_str):
|
||||
"""
|
||||
Vrátí český název dne v týdnu pro zadané datum.
|
||||
|
||||
:param date_str: Datum ve formátu DD.MM.YYYY
|
||||
:return: Český název dne v týdnu
|
||||
"""
|
||||
try:
|
||||
date_obj = datetime.strptime(date_str, "%d.%m.%Y")
|
||||
# Czech day names
|
||||
czech_days = [
|
||||
"pondělí",
|
||||
"úterý",
|
||||
"středa",
|
||||
"čtvrtek",
|
||||
"pátek",
|
||||
"sobota",
|
||||
"neděle",
|
||||
]
|
||||
return czech_days[date_obj.weekday()]
|
||||
except ValueError:
|
||||
return "neznámý den"
|
||||
|
||||
|
||||
def is_holiday(date_str):
|
||||
"""
|
||||
Zkontroluje, zda je zadané datum státní svátek.
|
||||
|
||||
:param date_str: Datum ve formátu DD.MM.YYYY
|
||||
:return: True pokud je svátek, jinak False
|
||||
"""
|
||||
return holidays.is_holiday(date_str)
|
||||
|
||||
|
||||
def get_rate_for_date_with_fallback(date_str, currency_code):
|
||||
"""
|
||||
Vyhledá kurz pro zadané datum a měnu. Pokud kurz pro dané datum neexistuje,
|
||||
@@ -38,21 +76,21 @@ def get_rate_for_date_with_fallback(date_str, currency_code):
|
||||
requested_date = datetime.strptime(date_str, "%d.%m.%Y")
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
# 1. Zkusíme najít kurz pro přesné datum
|
||||
rate = database.get_rate(date_str, currency_code)
|
||||
if rate is not None:
|
||||
return rate
|
||||
|
||||
|
||||
# 2. Pokud kurz neexistuje, aplikujeme pravidla podle ČNB:
|
||||
# "Kurzy devizového trhu jsou vyhlašovány pro běžně obchodované měny,
|
||||
# a to každý pracovní den po 14.30 s platností pro aktuální pracovní den
|
||||
# "Kurzy devizového trhu jsou vyhlašovány pro běžně obchodované měny,
|
||||
# a to každý pracovní den po 14.30 s platností pro aktuální pracovní den
|
||||
# a pro případnou následující sobotu, neděli či státní svátek"
|
||||
#
|
||||
#
|
||||
# To znamená:
|
||||
# - Pro víkendy a svátky hledáme kurz zpět v čase podle pravidel ČNB
|
||||
# - Pro víkendy a svátky hledáme kurz zpět v čase
|
||||
# - Pro běžné dny, které nemají kurz, hledáme kurz z posledního pracovního dne před nimi
|
||||
|
||||
|
||||
# Zkontrolujeme, zda je datum víkend nebo svátek
|
||||
if holidays.is_weekend(date_str) or holidays.is_holiday(date_str):
|
||||
# Pro víkendy a svátky hledáme kurz zpět v čase
|
||||
@@ -78,15 +116,208 @@ def get_rate_for_date_with_fallback(date_str, currency_code):
|
||||
for _ in range(10):
|
||||
current_date -= timedelta(days=1)
|
||||
check_date_str = current_date.strftime("%d.%m.%Y")
|
||||
|
||||
|
||||
# Zkontrolujeme, zda je to pracovní den
|
||||
if not holidays.is_weekend(check_date_str) and not holidays.is_holiday(check_date_str):
|
||||
if not holidays.is_weekend(check_date_str) and not holidays.is_holiday(
|
||||
check_date_str
|
||||
):
|
||||
rate = database.get_rate(check_date_str, currency_code)
|
||||
if rate is not None:
|
||||
return rate
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_missing_months_for_tax_calculation(year, currency_code):
|
||||
"""
|
||||
Vrátí seznam měsíců, pro které chybí kurzy k posledním dnům pro výpočet 'Jednotného kurzu'.
|
||||
Zahrnuje pouze měsíce, jejichž poslední den je v minulosti (lze stáhnout).
|
||||
|
||||
:param year: Rok k ověření
|
||||
:param currency_code: Kód měny
|
||||
:return: Seznam měsíců (1-12) s chybějícími kurzy
|
||||
"""
|
||||
import calendar
|
||||
|
||||
missing_months = []
|
||||
|
||||
for month in range(1, 13):
|
||||
# Získáme poslední den měsíce
|
||||
last_day = calendar.monthrange(year, month)[1]
|
||||
date_str = f"{last_day:02d}.{month:02d}.{year}"
|
||||
|
||||
# Pokud je datum v budoucnosti, přeskočíme (nelze stáhnout)
|
||||
date_obj = datetime.strptime(date_str, "%d.%m.%Y")
|
||||
current_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
if date_obj > current_date:
|
||||
continue
|
||||
|
||||
# Zkusíme najít kurz pro dané datum
|
||||
rate = database.get_rate(date_str, currency_code)
|
||||
if rate is None:
|
||||
# Zkusíme dopočítat
|
||||
calculated_rate = get_rate_for_date_with_fallback(date_str, currency_code)
|
||||
if calculated_rate is None:
|
||||
missing_months.append(month)
|
||||
|
||||
return missing_months
|
||||
|
||||
|
||||
def _is_year_complete_for_tax_calculation(year):
|
||||
"""
|
||||
Zkontroluje, zda je rok kompletní pro výpočet 'Jednotného kurzu' podle metodiky ČNB.
|
||||
Rok je kompletní, pokud máme kurzy pro poslední den každého měsíce.
|
||||
|
||||
:param year: Rok k ověření
|
||||
:return: True pokud je rok kompletní, jinak False
|
||||
"""
|
||||
import calendar
|
||||
|
||||
current_year = datetime.now().year
|
||||
current_month = datetime.now().month
|
||||
current_day = datetime.now().day
|
||||
|
||||
# Pokud je rok v budoucnosti, není kompletní
|
||||
if year > current_year:
|
||||
return False
|
||||
|
||||
# Pro všechny roky (včetně minulých) zkontrolujeme, zda máme data pro všechny měsíce
|
||||
for month in range(1, 13):
|
||||
# Získáme poslední den měsíce
|
||||
last_day = calendar.monthrange(year, month)[1]
|
||||
date_str = f"{last_day:02d}.{month:02d}.{year}"
|
||||
|
||||
# Pokud je datum v budoucnosti, nemůže být kompletní
|
||||
date_obj = datetime.strptime(date_str, "%d.%m.%Y")
|
||||
current_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
if date_obj > current_date:
|
||||
return False
|
||||
|
||||
# Zkusíme najít kurz pro dané datum
|
||||
rate = database.get_rate(date_str, "USD") # Testujeme na USD jako ukázku
|
||||
if rate is None:
|
||||
# Zkusíme dopočítat
|
||||
calculated_rate = get_rate_for_date_with_fallback(date_str, "USD")
|
||||
if calculated_rate is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Pro aktuální rok zkontrolujeme, zda máme data pro všechny měsíce
|
||||
for month in range(1, 13):
|
||||
# Získáme poslední den měsíce
|
||||
last_day = calendar.monthrange(year, month)[1]
|
||||
date_str = f"{last_day:02d}.{month:02d}.{year}"
|
||||
|
||||
# Pokud je měsíc v budoucnosti, nemůže být kompletní
|
||||
if year == current_year and month > current_month:
|
||||
return False
|
||||
|
||||
# Zkusíme najít kurz pro dané datum
|
||||
rate = database.get_rate(date_str, "USD") # Testujeme na USD jako ukázku
|
||||
if rate is None:
|
||||
# Zkusíme dopočítat
|
||||
calculated_rate = get_rate_for_date_with_fallback(date_str, "USD")
|
||||
if calculated_rate is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def calculate_tax_yearly_average(year, currency_code, output_dir="data"):
|
||||
"""
|
||||
Vypočítá 'Jednotný kurz' pro daňové účely podle metodiky ČNB.
|
||||
Jedná se o aritmetický průměr kurzů k posledním dnům každého kalendářního měsíce v roce.
|
||||
|
||||
:param year: Rok
|
||||
:param currency_code: Kód měny (např. USD)
|
||||
:param output_dir: Adresář s daty
|
||||
:return: 'Jednotný kurz' jako desetinné číslo, nebo None pokud není k dispozici
|
||||
"""
|
||||
debug_print(
|
||||
f"Vypočítávám 'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year}..."
|
||||
)
|
||||
|
||||
# Zkusíme stáhnout chybějící měsíční data
|
||||
missing_months = get_missing_months_for_tax_calculation(year, currency_code)
|
||||
if missing_months:
|
||||
debug_print(
|
||||
f"Nalezeny chybějící měsíce pro rok {year}: {', '.join(f'{m:02d}' for m in missing_months)}. Stahuji měsíční data..."
|
||||
)
|
||||
for month in missing_months:
|
||||
start_date = f"01.{month:02d}.{year}"
|
||||
last_day = calendar.monthrange(year, month)[1]
|
||||
end_date = f"{last_day:02d}.{month:02d}.{year}"
|
||||
debug_print(
|
||||
f"Stahuji měsíční data pro {currency_code} za {month:02d}/{year}..."
|
||||
)
|
||||
data_fetcher.download_monthly_data(
|
||||
currency_code, start_date, end_date, output_dir="data"
|
||||
)
|
||||
# Přidáme zpoždění, abychom nezatěžovali API
|
||||
time.sleep(1)
|
||||
|
||||
# Zkontrolujeme, zda je rok kompletní po stažení dat
|
||||
if not _is_year_complete_for_tax_calculation(year):
|
||||
debug_print(
|
||||
f"Rok {year} není kompletní pro výpočet 'Jednotného kurzu'. Všechny měsíce musí mít dostupné kurzy k posledním dnům."
|
||||
)
|
||||
return None
|
||||
|
||||
# Zkontrolujeme, zda databáze obsahuje data pro daný rok
|
||||
if not rate_finder.check_year_data_in_db(year):
|
||||
debug_print(f"Databáze neobsahuje data pro rok {year}. Stahuji roční data...")
|
||||
# Stáhneme roční data s vynuceným stažením
|
||||
os.makedirs("data", exist_ok=True)
|
||||
data_fetcher.download_yearly_data(year, output_dir="data", force=True)
|
||||
|
||||
# Získáme seznam posledních dní všech měsíců v roce
|
||||
monthly_rates = []
|
||||
monthly_dates = []
|
||||
|
||||
debug_print(f"Získávám kurzy k posledním dnům všech měsíců v roce {year}...")
|
||||
|
||||
for month in range(1, 13):
|
||||
# Získáme poslední den měsíce
|
||||
last_day = calendar.monthrange(year, month)[1]
|
||||
date_str = f"{last_day:02d}.{month:02d}.{year}"
|
||||
|
||||
# Získáme kurz pro dané datum
|
||||
rate = database.get_rate(date_str, currency_code)
|
||||
if rate is not None:
|
||||
monthly_rates.append(rate)
|
||||
monthly_dates.append(date_str)
|
||||
debug_print(f"Měsíc {month:02d}: {date_str} = {rate}")
|
||||
else:
|
||||
# Kurz nebyl nalezen, zkusíme dopočítat
|
||||
calculated_rate = get_rate_for_date_with_fallback(date_str, currency_code)
|
||||
if calculated_rate is not None:
|
||||
monthly_rates.append(calculated_rate)
|
||||
monthly_dates.append(date_str)
|
||||
debug_print(
|
||||
f"Měsíc {month:02d}: {date_str} = {calculated_rate} (dopočítaný kurz)"
|
||||
)
|
||||
else:
|
||||
debug_print(f"Měsíc {month:02d}: {date_str} = kurz nenalezen")
|
||||
|
||||
debug_print(f"Počet měsíců s kurzy: {len(monthly_rates)}/12")
|
||||
|
||||
# Musíme mít kurzy pro všech 12 měsíců
|
||||
if len(monthly_rates) != 12:
|
||||
debug_print(
|
||||
f"Varování: Nenalezeny kurzy pro všech 12 měsíců ({len(monthly_rates)}/12)"
|
||||
)
|
||||
return None
|
||||
|
||||
# Výpočet aritmetického průměru
|
||||
average = sum(monthly_rates) / len(monthly_rates)
|
||||
debug_print(f"Součet kurzů: {sum(monthly_rates):.6f}")
|
||||
debug_print(f"Počet měsíců: {len(monthly_rates)}")
|
||||
debug_print(f"'Jednotný kurz' pro daňové účely: {average:.6f}")
|
||||
|
||||
return average
|
||||
|
||||
|
||||
def generate_yearly_report(year, currency_code, output_dir="data"):
|
||||
"""
|
||||
Vygeneruje report kurzů pro zadaný rok a měnu.
|
||||
@@ -97,59 +328,61 @@ def generate_yearly_report(year, currency_code, output_dir="data"):
|
||||
:return: Cesta k vytvořenému CSV souboru.
|
||||
"""
|
||||
debug_print(f"Generuji report kurzů pro {currency_code} za rok {year}...")
|
||||
|
||||
|
||||
# Zkontrolujeme, zda databáze obsahuje data pro daný rok
|
||||
# Pro generování reportu vždy stáhneme aktuální roční data, aby byl report kompletní
|
||||
debug_print(f"Stahuji roční data pro rok {year}...")
|
||||
# Stáhneme roční data s vynuceným stažením
|
||||
os.makedirs("data", exist_ok=True)
|
||||
data_fetcher.download_yearly_data(year, output_dir="data", force=True)
|
||||
|
||||
|
||||
# Vytvoříme seznam všech dní v roce
|
||||
start_date = datetime(year, 1, 1)
|
||||
end_date = datetime(year, 12, 31)
|
||||
|
||||
|
||||
# Ale nebudeme generovat report pro budoucí datumy
|
||||
today = datetime.now()
|
||||
# Pokud je požadovaný rok v budoucnosti, nepokračujeme
|
||||
if year > today.year:
|
||||
debug_print(f"Chyba: Nelze generovat report pro budoucí rok {year}.")
|
||||
return None
|
||||
|
||||
|
||||
# Pokud je požadovaný rok aktuální rok, omezíme konec na dnešní datum
|
||||
if year == today.year:
|
||||
end_date = today
|
||||
|
||||
|
||||
# Otevřeme CSV soubor pro zápis
|
||||
filename = f"report_{currency_code}_{year}.csv"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
||||
|
||||
try:
|
||||
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
||||
with open(filepath, "w", newline="", encoding="utf-8") as csvfile:
|
||||
# Nové pořadí sloupců: Datum, Kurz, Den v týdnu, Svátek, Poznámka
|
||||
csvfile.write("Datum,Kurz,Den v týdnu,Svátek,Poznámka\n")
|
||||
|
||||
|
||||
current_date = start_date
|
||||
while current_date <= end_date:
|
||||
date_str = current_date.strftime("%d.%m.%Y")
|
||||
|
||||
|
||||
# Získáme den v týdnu
|
||||
day_name = get_czech_day_name(date_str)
|
||||
|
||||
|
||||
# Zkontrolujeme, zda je svátek
|
||||
is_holiday_flag = is_holiday(date_str)
|
||||
holiday_text = "ano" if is_holiday_flag else "ne"
|
||||
|
||||
|
||||
# Získáme kurz pro dané datum
|
||||
rate = database.get_rate(date_str, currency_code)
|
||||
note = ""
|
||||
|
||||
|
||||
if rate is not None:
|
||||
# Kurz byl nalezen v databázi
|
||||
pass
|
||||
else:
|
||||
# Kurz nebyl nalezen, zkusíme dopočítat
|
||||
calculated_rate = get_rate_for_date_with_fallback(date_str, currency_code)
|
||||
calculated_rate = get_rate_for_date_with_fallback(
|
||||
date_str, currency_code
|
||||
)
|
||||
if calculated_rate is not None:
|
||||
rate = calculated_rate
|
||||
note = "Dopočítaný kurz"
|
||||
@@ -158,19 +391,22 @@ def generate_yearly_report(year, currency_code, output_dir="data"):
|
||||
note += " (svátek)"
|
||||
else:
|
||||
note = "Kurz není k dispozici"
|
||||
|
||||
|
||||
# Zapišeme řádek do CSV
|
||||
csvfile.write(f"{date_str},{rate if rate is not None else ''},{day_name},{holiday_text},{note}\n")
|
||||
|
||||
csvfile.write(
|
||||
f"{date_str},{rate if rate is not None else ''},{day_name},{holiday_text},{note}\n"
|
||||
)
|
||||
|
||||
# Přejdeme na další den
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
|
||||
debug_print(f"Report byl úspěšně uložen do: {filepath}")
|
||||
return filepath
|
||||
except IOError as e:
|
||||
debug_print(f"Chyba při zápisu do souboru: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def generate_monthly_report(year, month, currency_code, output_dir="data"):
|
||||
"""
|
||||
Vygeneruje report kurzů pro zadaný měsíc, rok a měnu.
|
||||
@@ -182,61 +418,63 @@ def generate_monthly_report(year, month, currency_code, output_dir="data"):
|
||||
:return: Chta k vytvořenému CSV souboru.
|
||||
"""
|
||||
debug_print(f"Generuji report kurzů pro {currency_code} za {month}/{year}...")
|
||||
|
||||
|
||||
# Zkontrolujeme, zda databáze obsahuje data pro daný rok
|
||||
# Pro generování reportu vždy stáhneme aktuální roční data, aby byl report kompletní
|
||||
debug_print(f"Stahuji roční data pro rok {year}...")
|
||||
# Stáhneme roční data s vynuceným stažením
|
||||
os.makedirs("data", exist_ok=True)
|
||||
data_fetcher.download_yearly_data(year, output_dir="data", force=True)
|
||||
|
||||
|
||||
# Vytvoříme seznam všech dní v měsíci
|
||||
start_date = datetime(year, month, 1)
|
||||
# Poslední den měsíce
|
||||
last_day = calendar.monthrange(year, month)[1]
|
||||
end_date = datetime(year, month, last_day)
|
||||
|
||||
|
||||
# Ale nebudeme generovat report pro budoucí datumy
|
||||
today = datetime.now()
|
||||
# Pokud je požadovaný měsíc v budoucnosti, nepokračujeme
|
||||
if year > today.year or (year == today.year and month > today.month):
|
||||
debug_print(f"Chyba: Nelze generovat report pro budoucí měsíc {month}/{year}.")
|
||||
return None
|
||||
|
||||
|
||||
# Pokud je požadovaný měsíc aktuální měsíc, omezíme konec na dnešní datum
|
||||
if year == today.year and month == today.month:
|
||||
end_date = today
|
||||
|
||||
|
||||
# Otevřeme CSV soubor pro zápis
|
||||
filename = f"report_{currency_code}_{year}_{month:02d}.csv"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
||||
|
||||
try:
|
||||
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
||||
with open(filepath, "w", newline="", encoding="utf-8") as csvfile:
|
||||
# Nové pořadí sloupců: Datum, Kurz, Den v týdnu, Svátek, Poznámka
|
||||
csvfile.write("Datum,Kurz,Den v týdnu,Svátek,Poznámka\n")
|
||||
|
||||
|
||||
current_date = start_date
|
||||
while current_date <= end_date:
|
||||
date_str = current_date.strftime("%d.%m.%Y")
|
||||
|
||||
|
||||
# Získáme den v týdnu
|
||||
day_name = get_czech_day_name(date_str)
|
||||
|
||||
|
||||
# Zkontrolujeme, zda je svátek
|
||||
is_holiday_flag = is_holiday(date_str)
|
||||
holiday_text = "ano" if is_holiday_flag else "ne"
|
||||
|
||||
|
||||
# Získáme kurz pro dané datum
|
||||
rate = database.get_rate(date_str, currency_code)
|
||||
note = ""
|
||||
|
||||
|
||||
if rate is not None:
|
||||
# Kurz byl nalezen v databázi
|
||||
pass
|
||||
else:
|
||||
# Kurz nebyl nalezen, zkusíme dopočítat
|
||||
calculated_rate = get_rate_for_date_with_fallback(date_str, currency_code)
|
||||
calculated_rate = get_rate_for_date_with_fallback(
|
||||
date_str, currency_code
|
||||
)
|
||||
if calculated_rate is not None:
|
||||
rate = calculated_rate
|
||||
note = "Dopočítaný kurz"
|
||||
@@ -245,20 +483,25 @@ def generate_monthly_report(year, month, currency_code, output_dir="data"):
|
||||
note += " (svátek)"
|
||||
else:
|
||||
note = "Kurz není k dispozici"
|
||||
|
||||
|
||||
# Zapišeme řádek do CSV
|
||||
csvfile.write(f"{date_str},{rate if rate is not None else ''},{day_name},{holiday_text},{note}\n")
|
||||
|
||||
csvfile.write(
|
||||
f"{date_str},{rate if rate is not None else ''},{day_name},{holiday_text},{note}\n"
|
||||
)
|
||||
|
||||
# Přejdeme na další den
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
|
||||
debug_print(f"Report byl úspěšně uložen do: {filepath}")
|
||||
return filepath
|
||||
except IOError as e:
|
||||
debug_print(f"Chyba při zápisu do souboru: {e}")
|
||||
return None
|
||||
|
||||
def generate_period_report(start_date_str, end_date_str, currency_code, output_dir="data"):
|
||||
|
||||
def generate_period_report(
|
||||
start_date_str, end_date_str, currency_code, output_dir="data"
|
||||
):
|
||||
"""
|
||||
Vygeneruje report kurzů pro zadané období a měnu.
|
||||
|
||||
@@ -268,68 +511,76 @@ def generate_period_report(start_date_str, end_date_str, currency_code, output_d
|
||||
:param output_dir: Adresář, kam se má CSV soubor s reportem uložit.
|
||||
:return: Chta k vytvořenému CSV souboru.
|
||||
"""
|
||||
debug_print(f"Generuji report kurzů pro {currency_code} za období {start_date_str} - {end_date_str}...")
|
||||
|
||||
debug_print(
|
||||
f"Generuji report kurzů pro {currency_code} za období {start_date_str} - {end_date_str}..."
|
||||
)
|
||||
|
||||
try:
|
||||
start_date = datetime.strptime(start_date_str, "%d.%m.%Y")
|
||||
end_date = datetime.strptime(end_date_str, "%d.%m.%Y")
|
||||
except ValueError as e:
|
||||
debug_print(f"Neplatný formát data: {e}")
|
||||
return None
|
||||
|
||||
|
||||
# Ale nebudeme generovat report pro budoucí datumy
|
||||
today = datetime.now()
|
||||
# Pokud je požadované období v budoucnosti, nepokračujeme
|
||||
if start_date.date() > today.date():
|
||||
debug_print(f"Chyba: Nelze generovat report pro období v budoucnosti (od {start_date_str}).")
|
||||
debug_print(
|
||||
f"Chyba: Nelze generovat report pro období v budoucnosti (od {start_date_str})."
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
# Pokud je konec období v budoucnosti, omezíme ho na dnešní datum
|
||||
if end_date.date() > today.date():
|
||||
debug_print(f"Upozornění: Konec období byl omezen na dnešní datum ({today.strftime('%d.%m.%Y')}), protože zbytek je v budoucnosti.")
|
||||
debug_print(
|
||||
f"Upozornění: Konec období byl omezen na dnešní datum ({today.strftime('%d.%m.%Y')}), protože zbytek je v budoucnosti."
|
||||
)
|
||||
end_date = today
|
||||
|
||||
|
||||
# Zkontrolujeme, zda databáze obsahuje data pro roky v rozmezí
|
||||
# Pro generování reportu vždy stáhneme aktuální roční data, aby byl report kompletní
|
||||
start_year = start_date.year
|
||||
end_year = end_date.year
|
||||
|
||||
|
||||
for year in range(start_year, end_year + 1):
|
||||
debug_print(f"Stahuji roční data pro rok {year}...")
|
||||
# Stáhneme roční data s vynuceným stažením
|
||||
os.makedirs("data", exist_ok=True)
|
||||
data_fetcher.download_yearly_data(year, output_dir="data", force=True)
|
||||
|
||||
data_fetcher.download_yearly_data(year, output_dir="data")
|
||||
|
||||
# Otevřeme CSV soubor pro zápis
|
||||
filename = f"report_{currency_code}_{start_date_str.replace('.', '_')}_to_{end_date_str.replace('.', '_')}.csv"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
||||
|
||||
try:
|
||||
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
||||
with open(filepath, "w", newline="", encoding="utf-8") as csvfile:
|
||||
# Nové pořadí sloupců: Datum, Kurz, Den v týdnu, Svátek, Poznámka
|
||||
csvfile.write("Datum,Kurz,Den v týdnu,Svátek,Poznámka\n")
|
||||
|
||||
|
||||
current_date = start_date
|
||||
while current_date <= end_date:
|
||||
date_str = current_date.strftime("%d.%m.%Y")
|
||||
|
||||
|
||||
# Získáme den v týdnu
|
||||
day_name = get_czech_day_name(date_str)
|
||||
|
||||
|
||||
# Zkontrolujeme, zda je svátek
|
||||
is_holiday_flag = is_holiday(date_str)
|
||||
holiday_text = "ano" if is_holiday_flag else "ne"
|
||||
|
||||
|
||||
# Získáme kurz pro dané datum
|
||||
rate = database.get_rate(date_str, currency_code)
|
||||
note = ""
|
||||
|
||||
|
||||
if rate is not None:
|
||||
# Kurz byl nalezen v databázi
|
||||
pass
|
||||
else:
|
||||
# Kurz nebyl nalezen, zkusíme dopočítat
|
||||
calculated_rate = get_rate_for_date_with_fallback(date_str, currency_code)
|
||||
calculated_rate = get_rate_for_date_with_fallback(
|
||||
date_str, currency_code
|
||||
)
|
||||
if calculated_rate is not None:
|
||||
rate = calculated_rate
|
||||
note = "Dopočítaný kurz"
|
||||
@@ -338,49 +589,28 @@ def generate_period_report(start_date_str, end_date_str, currency_code, output_d
|
||||
note += " (svátek)"
|
||||
else:
|
||||
note = "Kurz není k dispozici"
|
||||
|
||||
|
||||
# Zapišeme řádek do CSV
|
||||
csvfile.write(f"{date_str},{rate if rate is not None else ''},{day_name},{holiday_text},{note}\n")
|
||||
|
||||
csvfile.write(
|
||||
f"{date_str},{rate if rate is not None else ''},{day_name},{holiday_text},{note}\n"
|
||||
)
|
||||
|
||||
# Přejdeme na další den
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
|
||||
debug_print(f"Report byl úspěšně uložen do: {filepath}")
|
||||
return filepath
|
||||
except IOError as e:
|
||||
debug_print(f"Chyba při zápisu do souboru: {e}")
|
||||
return None
|
||||
|
||||
def get_czech_day_name(date_str):
|
||||
"""
|
||||
Vrátí český název dne v týdnu pro zadané datum.
|
||||
|
||||
:param date_str: Datum ve formátu DD.MM.YYYY
|
||||
:return: Český název dne v týdnu
|
||||
"""
|
||||
try:
|
||||
date_obj = datetime.strptime(date_str, "%d.%m.%Y")
|
||||
# Czech day names
|
||||
czech_days = ["pondělí", "úterý", "středa", "čtvrtek", "pátek", "sobota", "neděle"]
|
||||
return czech_days[date_obj.weekday()]
|
||||
except ValueError:
|
||||
return "neznámý den"
|
||||
|
||||
def is_holiday(date_str):
|
||||
"""
|
||||
Zkontroluje, zda je zadané datum státní svátek.
|
||||
|
||||
:param date_str: Datum ve formátu DD.MM.YYYY
|
||||
:return: True pokud je svátek, jinak False
|
||||
"""
|
||||
return holidays.is_holiday(date_str)
|
||||
|
||||
# Příklad použití
|
||||
if __name__ == "__main__":
|
||||
# Inicializace databáze (pro případ spuštění samostatně)
|
||||
database.init_db()
|
||||
|
||||
|
||||
# Test
|
||||
# generate_yearly_report(2020, "USD")
|
||||
# generate_monthly_report(2020, 7, "USD")
|
||||
# generate_period_report("01.07.2020", "31.07.2020", "USD")
|
||||
# generate_period_report("01.07.2020", "31.07.2020", "USD")
|
||||
|
||||
Reference in New Issue
Block a user