"""STRJ: synthetic teaching dataset only. Standard library, no network.""" import argparse import csv from decimal import Decimal, InvalidOperation from pathlib import Path COLUMNS = ["id", "oblast", "stav", "odhad_h"] STATES = ("otevreno", "ceka_podklad", "hotovo") AREAS = {"kotelna", "MaR", "TZB", "facility"} def load_report(source): rows = [] ids = set() with Path(source).open(encoding="utf-8-sig", newline="") as stream: reader = csv.DictReader(stream) if reader.fieldnames != COLUMNS: raise ValueError("Sloupce musí být přesně: " + ",".join(COLUMNS)) for line, row in enumerate(reader, start=2): if None in row or any(value is None or not value.strip() for value in row.values()): raise ValueError(f"Řádek {line}: chybějící nebo nadbytečná pole") if row["id"] in ids: raise ValueError(f"Řádek {line}: duplicitní ID") if row["oblast"] not in AREAS or row["stav"] not in STATES: raise ValueError(f"Řádek {line}: neznámá oblast nebo stav") try: hours = Decimal(row["odhad_h"]) except InvalidOperation as error: raise ValueError(f"Řádek {line}: neplatné hodiny") from error if not hours.is_finite() or hours < 0: raise ValueError(f"Řádek {line}: hodiny musí být konečné a nezáporné") ids.add(row["id"]) rows.append((row["stav"], hours)) if not rows: raise ValueError("Prázdný datový vzorek") groups = {state: {"count": 0, "hours": Decimal("0")} for state in STATES} for state, hours in rows: groups[state]["count"] += 1 groups[state]["hours"] += hours total = sum((hours for _, hours in rows), Decimal("0")) assert sum(group["count"] for group in groups.values()) == len(rows) assert sum((group["hours"] for group in groups.values()), Decimal("0")) == total return {"count": len(rows), "hours": total, "groups": groups} def render(report): lines = ["# Modelový přehled úkolů", "", "Syntetická výuková data STRJ. Nejde o klienta ani skutečně odpracovaný čas.", "", "| Stav | Počet | Odhad hodin |", "|---|---:|---:|"] for state, group in report["groups"].items(): lines.append(f'| {state} | {group["count"]} | {group["hours"]} |') lines.append(f'| CELKEM | {report["count"]} | {report["hours"]} |') lines.extend(["", "Součty řádků a hodin po skupinách odpovídají vstupu.", "Odhady nejsou naměřená úspora, plán kapacity ani technický výpočet."]) return "\n".join(lines) + "\n" def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("input", type=Path) parser.add_argument("output", type=Path) args = parser.parse_args() if args.input.resolve() == args.output.resolve(): parser.error("Výstup nesmí přepsat zdroj.") report = load_report(args.input) # x means create only: neither a source nor an existing report is overwritten. with args.output.open("x", encoding="utf-8", newline="\n") as stream: stream.write(render(report)) if __name__ == "__main__": main()