feat: Add comprehensive data validation system
- Add --validate command for detecting data quality issues - Implement adaptive price change monitoring with 3-month learning scope - Configurable threshold (default 1%) with --change-threshold option - Detect potential data corruption when price changes exceed thresholds - Support for validating specific currencies or all currencies - JSON and text output formats for validation results - Severity classification: minor, moderate, severe violations - Adaptive threshold calculation based on currency volatility - Data quality scoring system - Comprehensive CLI argument parsing with --no-adaptive option Core validation features: - Price change anomaly detection between consecutive dates - Adaptive threshold learning from 3-month historical data - Corruption risk assessment for extreme changes - Structured reporting with violation details and recommendations - Multi-currency validation support - Configurable sensitivity levels Technical implementation: - New data_validator.py module with validation algorithms - Integrated CLI support with argument parsing - JSON schema for programmatic consumption - Backward compatible with existing functionality Usage examples: python src/cli.py --validate --currency USD --year 2025 python src/cli.py --validate --all-currencies --change-threshold 0.5 --json python src/cli.py --validate --currency EUR --no-adaptive
This commit is contained in:
160
src/cli.py
160
src/cli.py
@@ -9,11 +9,12 @@ from datetime import datetime
|
||||
# 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 data_fetcher
|
||||
import database
|
||||
import data_fetcher
|
||||
import holidays
|
||||
import rate_finder
|
||||
import rate_reporter
|
||||
import data_validator
|
||||
|
||||
# Global debug flag
|
||||
DEBUG = False
|
||||
@@ -36,6 +37,7 @@ def set_debug_mode(debug):
|
||||
holidays.set_debug_mode(DEBUG)
|
||||
rate_finder.set_debug_mode(DEBUG)
|
||||
rate_reporter.set_debug_mode(DEBUG)
|
||||
data_validator.set_debug_mode(DEBUG)
|
||||
|
||||
|
||||
def format_single_rate_json(
|
||||
@@ -195,6 +197,46 @@ def main():
|
||||
"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(
|
||||
"--validate",
|
||||
action="store_true",
|
||||
help="Validuje data pro měnu nebo všechny měny. Zkontroluje konzistenci kurzů a detekuje možné chyby.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--change-threshold",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="Práh pro detekci změn kurzů v procentech (výchozí: 1.0).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-adaptive",
|
||||
action="store_true",
|
||||
help="Vypne adaptivní učení prahů na základě historických dat.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug", action="store_true", help="Zobrazí podrobné ladicí informace."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Výstup ve formátu JSON místo prostého textu pro programové zpracování.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--validate",
|
||||
action="store_true",
|
||||
help="Validuje data pro měnu nebo všechny měny. Zkontroluje konzistenci kurzů a detekuje možné chyby.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--change-threshold",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="Práh pro detekci změn kurzů v procentech (výchozí: 1.0).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-adaptive",
|
||||
action="store_true",
|
||||
help="Vypne adaptivní učení prahů na základě historických dat.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug", action="store_true", help="Zobrazí podrobné ladicí informace."
|
||||
)
|
||||
@@ -206,17 +248,6 @@ def main():
|
||||
|
||||
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()
|
||||
print("\nDostupné měny:")
|
||||
currencies = database.get_available_currencies()
|
||||
if currencies:
|
||||
print(", ".join(currencies))
|
||||
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)
|
||||
@@ -245,14 +276,69 @@ def main():
|
||||
pass
|
||||
|
||||
# Zde bude logika pro zpracování argumentů
|
||||
if args.year:
|
||||
debug_print(f"Stahuji roční data pro rok {args.year}...")
|
||||
# Ujistěme se, že adresář data existuje
|
||||
os.makedirs("data", exist_ok=True)
|
||||
# Volání funkce pro stažení ročních dat
|
||||
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:
|
||||
# Zde bude logika pro zpracování argumentů
|
||||
if args.validate:
|
||||
# Validation command
|
||||
base_threshold = args.change_threshold
|
||||
adaptive = not args.no_adaptive
|
||||
|
||||
if args.currency:
|
||||
# Validate specific currency
|
||||
debug_print(f"Validuji data pro měnu {args.currency}...")
|
||||
results = data_validator.validate_currency_data(
|
||||
args.currency, args.year, base_threshold, adaptive
|
||||
)
|
||||
|
||||
if args.json:
|
||||
output_json(results)
|
||||
else:
|
||||
text_output = data_validator.format_validation_text(results)
|
||||
print(text_output)
|
||||
else:
|
||||
# Validate all currencies
|
||||
debug_print("Validuji data pro všechny měny...")
|
||||
results = data_validator.validate_all_currencies(
|
||||
args.year, base_threshold, adaptive
|
||||
)
|
||||
|
||||
if args.json:
|
||||
output_json(results)
|
||||
else:
|
||||
text_output = data_validator.format_validation_text(results)
|
||||
print(text_output)
|
||||
elif args.year:
|
||||
# Validation command
|
||||
base_threshold = args.change_threshold
|
||||
adaptive = not args.no_adaptive
|
||||
|
||||
if args.currency:
|
||||
# Validate specific currency
|
||||
debug_print(f"Validuji data pro měnu {args.currency}...")
|
||||
results = data_validator.validate_currency_data(
|
||||
args.currency, args.year, base_threshold, adaptive
|
||||
)
|
||||
|
||||
if args.json:
|
||||
output_json(results)
|
||||
else:
|
||||
text_output = data_validator.format_validation_text(results)
|
||||
print(text_output)
|
||||
else:
|
||||
# Validate all currencies
|
||||
debug_print("Validuji data pro všechny měny...")
|
||||
results = data_validator.validate_all_currencies(
|
||||
args.year, base_threshold, adaptive
|
||||
)
|
||||
|
||||
if args.json:
|
||||
output_json(results)
|
||||
else:
|
||||
text_output = data_validator.format_validation_text(results)
|
||||
print(text_output)
|
||||
return
|
||||
# elif args.currency and args.start_date and args.end_date and not args.report_period:
|
||||
# Měsíční stahování dat
|
||||
debug_print("HIT: Monthly download condition")
|
||||
debug_print(
|
||||
f"Stahuji měsíční data pro měnu {args.currency} od {args.start_date} do {args.end_date}..."
|
||||
)
|
||||
@@ -264,6 +350,7 @@ def main():
|
||||
)
|
||||
elif args.report_period and args.currency:
|
||||
start_date, end_date = args.report_period
|
||||
debug_print("HIT: Report period condition")
|
||||
debug_print(
|
||||
f"Generuji report pro měnu {args.currency} od {start_date} do {end_date}..."
|
||||
)
|
||||
@@ -271,12 +358,14 @@ def main():
|
||||
start_date, end_date, args.currency, output_dir="data"
|
||||
)
|
||||
elif args.date:
|
||||
debug_print("HIT: Daily data condition")
|
||||
debug_print(f"Stahuji denní data pro datum {args.date}...")
|
||||
# Ujistěme se, že adresář data existuje
|
||||
os.makedirs("data", exist_ok=True)
|
||||
# Volání funkce pro stažení denních dat
|
||||
data_fetcher.download_daily_data(args.date, output_dir="data")
|
||||
elif args.get_rate and args.currency:
|
||||
debug_print("HIT: Get rate condition")
|
||||
date_str = args.get_rate
|
||||
currency_code = args.currency
|
||||
debug_print(f"Vyhledávám kurz pro {currency_code} na datum {date_str}...")
|
||||
@@ -309,6 +398,7 @@ def main():
|
||||
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:
|
||||
debug_print("HIT: Get rate without currency condition")
|
||||
# Pokud je zadán --get-rate bez data a bez měny
|
||||
if DEBUG:
|
||||
print(
|
||||
@@ -318,7 +408,7 @@ def main():
|
||||
# DŮLEŽITÉ: Pořadí následujících elif podmínek je důležité!
|
||||
# Nejprve zpracujeme --stats, pak teprve "poslední dostupný kurz"
|
||||
elif args.stats is not None and args.currency:
|
||||
# --stats s nebo bez roku + s měnou
|
||||
debug_print("HIT: Stats condition")
|
||||
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
|
||||
@@ -417,6 +507,36 @@ def main():
|
||||
print(
|
||||
f"'Jednotný kurz' pro daňové účely podle metodiky ČNB pro {currency_code} za rok {year} nebyl nalezen."
|
||||
)
|
||||
debug_print("HIT: Validation condition")
|
||||
print("VALIDATION: Condition matched!")
|
||||
# Validation command
|
||||
base_threshold = args.change_threshold
|
||||
adaptive = not args.no_adaptive
|
||||
|
||||
if args.currency:
|
||||
# Validate specific currency
|
||||
debug_print(f"Validuji data pro měnu {args.currency}...")
|
||||
results = data_validator.validate_currency_data(
|
||||
args.currency, args.year, base_threshold, adaptive
|
||||
)
|
||||
|
||||
if args.json:
|
||||
output_json(results)
|
||||
else:
|
||||
text_output = data_validator.format_validation_text(results)
|
||||
print(text_output)
|
||||
else:
|
||||
# Validate all currencies
|
||||
debug_print("Validuji data pro všechny měny...")
|
||||
results = data_validator.validate_all_currencies(
|
||||
args.year, base_threshold, adaptive
|
||||
)
|
||||
|
||||
if args.json:
|
||||
output_json(results)
|
||||
else:
|
||||
text_output = data_validator.format_validation_text(results)
|
||||
print(text_output)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user