diff --git a/Manual/.~lock.manual_kap10-12.odt# b/Manual/.~lock.manual_kap10-12.odt# new file mode 100644 index 0000000..519a26c --- /dev/null +++ b/Manual/.~lock.manual_kap10-12.odt# @@ -0,0 +1 @@ +,carsten,carsten-mint,04.04.2026 02:27,file:///home/carsten/.config/libreoffice/4; \ No newline at end of file diff --git a/Manual/.~lock.manual_kap13-16.odt# b/Manual/.~lock.manual_kap13-16.odt# new file mode 100644 index 0000000..6dc4e3b --- /dev/null +++ b/Manual/.~lock.manual_kap13-16.odt# @@ -0,0 +1 @@ +,carsten,carsten-mint,04.04.2026 02:40,file:///home/carsten/.config/libreoffice/4; \ No newline at end of file diff --git a/Manual/.~lock.manual_kap13-16_v2.odt# b/Manual/.~lock.manual_kap13-16_v2.odt# new file mode 100644 index 0000000..d68957e --- /dev/null +++ b/Manual/.~lock.manual_kap13-16_v2.odt# @@ -0,0 +1 @@ +,carsten,carsten-mint,04.04.2026 02:53,file:///home/carsten/.config/libreoffice/4; \ No newline at end of file diff --git a/Manual/manual_kap1-3.odt b/Manual/manual_kap1-3.odt new file mode 100644 index 0000000..1f5721e Binary files /dev/null and b/Manual/manual_kap1-3.odt differ diff --git a/Manual/manual_kap10-12.odt b/Manual/manual_kap10-12.odt new file mode 100644 index 0000000..eee382e Binary files /dev/null and b/Manual/manual_kap10-12.odt differ diff --git a/Manual/manual_kap13-16.odt b/Manual/manual_kap13-16.odt new file mode 100644 index 0000000..acae0ab Binary files /dev/null and b/Manual/manual_kap13-16.odt differ diff --git a/Manual/manual_kap13-16_v2.odt b/Manual/manual_kap13-16_v2.odt new file mode 100644 index 0000000..6f505c1 Binary files /dev/null and b/Manual/manual_kap13-16_v2.odt differ diff --git a/Manual/manual_kap4-6_v2.odt b/Manual/manual_kap4-6_v2.odt new file mode 100644 index 0000000..d59a0ac Binary files /dev/null and b/Manual/manual_kap4-6_v2.odt differ diff --git a/Manual/manual_kap7-9_v2.odt b/Manual/manual_kap7-9_v2.odt new file mode 100644 index 0000000..3f4d873 Binary files /dev/null and b/Manual/manual_kap7-9_v2.odt differ diff --git a/udpak_semistruktur.py b/udpak_semistruktur.py index 3fddebf..afa06ca 100644 --- a/udpak_semistruktur.py +++ b/udpak_semistruktur.py @@ -1,3 +1,4 @@ +import os import sys import argparse @@ -30,9 +31,40 @@ def _byg_argument_parser() -> argparse.ArgumentParser: ddl.add_cli_args(parser) return parser +def _skriv_fejl_fil( + tmp_data: dict, + fejl_ext: str, + base_navn: str, + kolonner: list, + separator: str, + encoding: str, +) -> str | None: + """Skriver fejlede rækker til en fejl-fil hvis der er nogen.""" + if not fejl_ext or not tmp_data.get("fejlede_rækker"): + return + + fejl_sti = f"{base_navn}{fejl_ext}" + + def skriv_fejl(): + with open(fejl_sti, "a", encoding=encoding) as f: + for række in tmp_data["fejlede_rækker"]: + linje = separator.join( + str(række.get(k["navn"], "")) for k in kolonner + ) + f.write(linje + "\n") + + skriv_fil_med_retry(skriv_fejl, fejl_sti) + logger.warning( + f"Fejl-fil: {fejl_sti} skrevet ({len(tmp_data['fejlede_rækker'])} fejlede rækker)" + ) + + return fejl_sti + def _kør_udtræk(config: dict, global_config: dict) -> None: """Kører den normale udtræks- og transformationspipeline.""" - print(f"DEBUG: Antal output_filer = {len(config.get('output_filer', []))}") + + fejl_filer_skrevet = [] + input_fil = global_config.get("input_fil") input_fil_liste = global_config.get("input_fil_liste") @@ -50,10 +82,17 @@ def _kør_udtræk(config: dict, global_config: dict) -> None: ) for record in læs_filer(global_config, input_fil, input_fil_liste): + + samlet_antal_rækker = 0 + for cfg in config.get("output_filer", []): # 1) Udtræk tmp_data = generer_datafil(record, cfg, global_config) + # Hent separator og encoding én gang for begge output-typer + separator = global_config["separator"] + encoding = global_config["encoding"] + # 2) Transform-pipeline tmp_data = join(tmp_data, cfg) tmp_data = flatten(tmp_data, cfg) @@ -68,6 +107,8 @@ def _kør_udtræk(config: dict, global_config: dict) -> None: tmp_data = beregn_hash(tmp_data, cfg, global_config) tmp_data = filename(tmp_data, cfg, global_config) + samlet_antal_rækker += len(tmp_data["rækker"]) + # 3) Load – afhænger af cfg["type"] if cfg.get("type") == "fil": fil_navn = generer_filnavn(cfg["fil_navn"], global_config) @@ -76,9 +117,6 @@ def _kør_udtræk(config: dict, global_config: dict) -> None: overskrifter = cfg.get("overskrifter", True) generer_filer_med_overskrifter(overskrifter, output_sti, cfg["kolonner"], global_config) - separator = global_config["separator"] - encoding = global_config["encoding"] - # Bemærk: skriv() lukker over loop-variabler – kaldes straks af skriv_fil_med_retry def skriv(): with open(output_sti, "a", encoding=encoding) as f: @@ -90,18 +128,44 @@ def _kør_udtræk(config: dict, global_config: dict) -> None: skriv_fil_med_retry(skriv, output_sti) logger.info(f"Fil: {output_sti} skrevet ({len(tmp_data['rækker'])} rækker)") + skrevet = _skriv_fejl_fil(tmp_data, global_config.get("fejl_fil_ext"), + output_sti, cfg["kolonner"], separator, encoding) + if skrevet: + fejl_filer_skrevet.append(skrevet) elif cfg.get("type") == "tabel": kolonner = [k["navn"] for k in cfg["kolonner"]] indsatte, fejlede = insert_rows_ase(conn, cfg["tabel_navn"], kolonner, tmp_data["rækker"]) logger.info(f"DB: {indsatte} rækker indsat i {cfg['tabel_navn']}") + if fejlede: logger.warning(f"DB: {len(fejlede)} rækker fejlede i {cfg['tabel_navn']}") + + fejl_base = os.path.join(global_config["output_path"], + cfg["tabel_navn"].replace(".", "_")) + skrevet = _skriv_fejl_fil(tmp_data, global_config.get("fejl_fil_ext"), + fejl_base, cfg["kolonner"], separator, encoding) + if skrevet: + fejl_filer_skrevet.append(skrevet) + + # Tjek om alle output-filer gav 0 rækker + if samlet_antal_rækker == 0: + logger.warning("0 rækker genereret i alle output-filer for dette input.") + if global_config.get("stop_ved_0_output"): + logger.error("Stopper kørsel pga. stop_ved_0_output = true.") + sys.exit(1) if conn is not None: conn.close() logger.debug("DB-forbindelse lukket.") + if fejl_filer_skrevet and global_config.get("stop_ved_fejl", False): + logger.error( + f"stop_ved_fejl: {len(fejl_filer_skrevet)} fejl-fil(er) blev oprettet:\n" + + "\n".join(f" - {f}" for f in fejl_filer_skrevet) + ) + sys.exit(2) + def main(): """Hovedfunktion der eksekveres ved kørsel af scriptet.""" @@ -124,10 +188,7 @@ def main(): ) # Eksekver DDL-flowet - print(f"MAIN DEBUG: output_filer count = {len(config.get('output_filer', []))}", flush=True) - for i, cfg in enumerate(config.get("output_filer", [])): - print(f"MAIN DEBUG [{i}]: rod={cfg.get('rod')}, fil={cfg.get('fil_navn')}, type={cfg.get('type')}", flush=True) - + if ddl.is_enabled(args): ddl.run_ddl_mode(args, config, global_config) else: diff --git a/udpak_semistruktur/config.py b/udpak_semistruktur/config.py index 42fc916..67f87de 100644 --- a/udpak_semistruktur/config.py +++ b/udpak_semistruktur/config.py @@ -344,7 +344,6 @@ def valider_yaml(yaml_file_path: str) -> dict: global_config.setdefault("encoding", "utf-8") global_config.setdefault("separator", "\t") global_config.setdefault("logfil", "log_{yyyy}{mm}{dd}.txt") - global_config.setdefault("debug", False) global_config.setdefault("dan_ok", True) global_config.setdefault("global_rens", False) global_config.setdefault("global_fjern_linjeskift", False) @@ -353,6 +352,7 @@ def valider_yaml(yaml_file_path: str) -> dict: global_config.setdefault("fejl_fil_ext", None) global_config.setdefault("stop_ved_0_output", False) global_config.setdefault("db_char_set", "latin-1") + global_config.setdefault("stop_ved_fejl", False) # 14) Logfilnavn formateres med dato og placeres i output_path global_config["logfil"] = os.path.join( @@ -368,10 +368,6 @@ def valider_yaml(yaml_file_path: str) -> dict: # 15) Timestamp global_config["var_timestamp"] = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f") - # 16) Debug - if global_config.get("debug") is True: - logger.debug("YAML-konfigurationen er gyldig.") - # 17) Udvid kolonne-skabeloner (din eksisterende funktion) config = udvid_kolonne_skabeloner(config) diff --git a/udpak_semistruktur/extract/extractor.py b/udpak_semistruktur/extract/extractor.py index a33a058..f015c7d 100644 --- a/udpak_semistruktur/extract/extractor.py +++ b/udpak_semistruktur/extract/extractor.py @@ -96,7 +96,7 @@ def hent_kolonne_værdi_med_fallback(kol: dict, el: Any, json_data: Any, sti_ind missing = raw_value is None else: returner_liste = bool(kol.get("join", False) or kol.get("flatten", False)) - print(f"DEBUG kolonne={kolnavn}, join={kol.get('join')}, flatten={kol.get('flatten')}, returner_liste={returner_liste}") + primær_spec = {"felt": kol["felt"]} if kol.get("rod"): primær_spec["rod"] = kol["rod"]