From f2d8433c56a524ec9af259f8c2e32f1d908c6ece Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Valenta?= <git@imaniti.org>
Date: Fri, 21 Apr 2023 13:20:34 +0200
Subject: [PATCH] old registry import normalization

---
 Makefile                                      |   4 +-
 .../commands/import_old_contracts.py          | 216 ++++++++++++++++--
 contracts/views.py                            |   1 -
 3 files changed, 201 insertions(+), 20 deletions(-)

diff --git a/Makefile b/Makefile
index f0b56fe..7c3c25a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,9 @@
 #!/usr/bin/make -f
 
-PYTHON   = python3.10
+PYTHON   = python3.11
 VENV     = .venv
 PORT     = 8013
-SETTINGS = registry.settings.production
+SETTINGS = registry.settings.dev
 
 .PHONY: help venv install install-hooks hooks build run shell migrations migrate
 
diff --git a/contracts/management/commands/import_old_contracts.py b/contracts/management/commands/import_old_contracts.py
index 3c406a1..53c9294 100644
--- a/contracts/management/commands/import_old_contracts.py
+++ b/contracts/management/commands/import_old_contracts.py
@@ -1,5 +1,6 @@
 import io
 import os
+import re
 import string
 from datetime import date, datetime
 
@@ -48,9 +49,173 @@ class Command(BaseCommand):
         parser.add_argument(
             "--purge",
             action="store_true",
-            help="Purge all previous contracts before the import.",
+            help="Purge all previous contracts, types and filing areas before the import.",
         )
 
+    def normalize_type(self, type_name: str) -> str:
+        type_name = string.capwords(type_name)
+
+        patterns = (
+            (r" O ", " o "),
+            (r" S ", " s "),
+            (r" K ", " k "),
+            (r" V ", " v "),
+            (r"Nda", "NDA"),
+            (r"Dds", "DDS"),
+            (r"^Dohoda o Podpoře.*$", "Dohoda o Podpoře"),
+            (
+                r"^(Dohoda o Ochraně Důvěrných Informací \(nda\)|Dohoda o Ochraně Důvě"
+                r"rných Dat|Dohoda o Ochraně Důvěrných Informací|Smlouva o Zachování D"
+                r"ůvěrnosti Informací|Smlouva o Zachování Důvěrnosti Informací \(nda\)"
+                ")$",
+                "NDA",
+            ),
+            (r"^(Darovací.*|Ds)$", "Darovací Smlouva"),
+            (r".*Dodatek.*", "Dodatek"),
+            (
+                r"^(Dpp|Dohoda o Provední Práce|Dohoda o Proveení Práce)$",
+                "Dohoda o Provedení Práce",
+            ),
+            (r"^Dpč$", "Dohoda o Pracovní Činnosti"),
+            (r"^NS$", "Nájemní Smlouva"),
+            (r"^Koaliční Smlouva.*$", "Koaliční Smlouva"),
+            (r"^Pronajem$", "Pronájem"),
+            (
+                r"^(Rácová Smlouva|Rámcová Kupní Smlouva|Rámcová Smlouva|Rámcová Smlouva- Do"
+                r"datek|Ramcova Smlouva o Najmu Kopií Filmů A Rámcová Smlouva Podlicenční|Rs"
+                r")$",
+                "Rámcová Smlouva",
+            ),
+            (
+                r"^(Oof|Oosf|Osf|Potvrzení o Funkci|Potvrzeni o Stranicke Funkci|Potvrzení o"
+                r"Stranické Funkci)",
+                "Osvědčení o Stranické Funkci",
+            ),
+            (r"^Smlouva o Bezúročné Zápůjčce.*$", "Bezúročná Zápůjčka Peněz"),
+            (
+                r"^(Smlouva o Dilo|Smlouva o Dílo A Veřejná Licenční Smlouva|Smlouva o Dílo - "
+                r"Dodatek|Sod)$",
+                "Smlouva o Dílo",
+            ),
+            (r"^Smlouva o Nájmu.*$", "Smlouva o Nájmu"),
+            (
+                r"^Smlouva o Poskytovani Pravnich Sluzeb$",
+                "Smlouva o Poskytování Právních Služeb",
+            ),
+            ("\.$", ""),
+            (r"^Ks$", "Koaliční Smlouva"),
+            (r"^Pm$", "Plná moc"),
+            (r"Pronajmu", "Pronájmu"),
+            (r"^(Zl .*|(Zadavací|Zdávací) list)$", "Zadávací list"),
+            (r"Hpp", "Hlavní Pracovní Poměr"),
+            (r"^Sops - Smlouva o Poskytnutí Služeb", "Smlouva o Poskytnutí Služeb"),
+            (r"^Suv$", "Smlouva o Uměleckém Vystoupení"),
+            (
+                r"^(Příkaz|Příkazní|Prikazni Smlouva|Příkazní Smlouva|Příkazní Smlouva, Bude"
+                r"Fakturováno|Příkazní Smlouva (dodatek)|Příkazní Smlouva - Dodatek|Příkazní"
+                r"Smlouva Dodatek|Příkazní Smlouva- Dodatek|Příkazní Smlouva - Dodatek Č\. 1"
+                r"|Příkazní Smlouva - Dodatek Č\.1|Příkazní Smlouva Dodatek Č\. 1|Příkazní S"
+                r"mlouva Dodatek Č\.1|Příkazní Smlouva s Dodavatelem|Příkazní Smlouva- Ukonč"
+                r"ení|Příkazní Smlouvy|Příkazní Smlova|Příkazní Smluva|Příkazní Smuva|Příkaz"
+                r"ní Spisovna|Příkzaní Smlouva|Ps)$",
+                "Příkazní smlouva",
+            ),
+        )
+
+        for pattern in patterns:
+            type_name = re.sub(pattern[0], pattern[1], type_name)
+
+        return type_name
+
+    def normalize_filing_area(self, area_name: str) -> str:
+        area_name = string.capwords(area_name)
+
+        patterns = (
+            (
+                r"^(Cenrální Spisovna|Censtrální Spisovna|Centrála|Centrálách Archiv Str"
+                r"any|Centrála Strany|Centrální Achiv Strany|Centrální Archiv|Centralni "
+                r"Archiv Strany|Centrální Archiv Strany|Centrální Skupina|Centrální Spid"
+                r"ovna|Centrální Spisivna|Centrální Spisovna|Centrální Spísovna|Centráln"
+                r"í Spisovna Strany|Centrální Sposovna|Centrální Sposovná|Centrální Úlož"
+                r"na|Centrální Úschovna|Česká Pirítská Strana, Na Moráni 360\/3, Praha 2"
+                r"|Cnterální Spisovna|Archiv|Archív|Archiv Centrály|Archiv České Pirátsk"
+                r"é Strany|Archiv Pice|Archív Piráti|Archiv Pirátské Strany|Archiv Praha"
+                r"|Archiv, Sídlo Strany|Archiv, Sídlo Strany,|Archív, Sídlo Strany|Archi"
+                r"v, Sídlo Strany, Řehořova 19, Praha 3|Archiv, Sídlo Strany, Řehořova 1"
+                r"9, Praha 3, Zastupitelský Klub Pirátů, Mariánské Nám. 2, Praha 1 \/pří"
+                r"lohy|Archiv, Sídlo Strany, Řehořova 943\/19, Praha 3|Archív, Sídlo Str"
+                r"any, Řehořova 943\/19, Praha 3|Archiv, Sídlo Strany, Řehova 19, Praha "
+                r"3|Archiv Služeb|Archiv Smlouvy|Archiv Starny|Archiv Strany|Archív Stra"
+                r"ny|Archiv Strany, Řehořova 943\/19, Praha|Archiv Strany, Sídlo Strany|"
+                r"Archiv Strany \(zatím Koks, Do Konce 6\/2020 Doručím Do Archivu\)|Arch"
+                r"ív V Sídle Strany|Archvi Strany|Archyv Strany|Arhiv Strany|Ar Hiv Stra"
+                r"ny|Bude Zasláno Do Prahy|Mariánské Náměstí 2, Praha 1|Na Morání 360\/3"
+                r"|Na Moráni 360\/3, Praha|Na Moráni 360\/3. Praha|Na Moráni 360\/3; Pra"
+                r"ha|Na Morání 360\/3, Praha 120 00|Na Moráni 360\/3; Praha 12800|Na Mor"
+                r"áni 360\/3, Praha 2|Na Moráni Praha|Na Moráni, Praha|Pirátská Centrum "
+                r"Na Moráni 360\/3, Praha|Pirátská Centrum Praha, Na Moráni|Pirátské Cen"
+                r"trum, Na Moráni 360\/3, Praha|Pirátské Centrum, Na Moráni 360, Praha|P"
+                r"irátské Centrum Praha, Na Moráni|Pirátské Centrum, Řehořova 19|Pirátsk"
+                r"é Centrum, Řehořova 19 <!-- Donesl Štěpán Štrébl -->|Pirátské Centrum,"
+                r" Řehořova 19, Praha 3|Praha, Kancelář Strany|Řehořova 943\/19 130 00 P"
+                r"raha 3|Řehořova 943\/19, 130 00 Praha 3|Řehořova 943\/19, 130 00, Prah"
+                r"a 3|Řehořova 943\/19, Praha 3|Sídlo Strany|Sídlo Strany, Řehořova 19, "
+                r"Praha 3|Smlouva Uložena Na Místě: Centrální Spisovna|Smlouva Uložena V"
+                r" Archivu Strany|Stranický Archív|V Archivu|V Archivukrajského Koordiná"
+                r"tora, Později V Archivu Smluv Strany|Zatím Archiv Krajského Koordináto"
+                r"ra, Bude Odeslána Do Registru|Zastupitelský Klub Pirátů, Sídlo Strany|"
+                r"Kancelář Srtrany|Koks Od 2019 V Archivu|Originál Uložen V Centrálním A"
+                r"rchivu Strany|Originál Včetně Osobních Údajů Uložen V Centrálním Archi"
+                r"vu Strany|Originál Včetně Podpisů A Osobních Údajů Je Uložen V Centrál"
+                r"ním Archivu Strany|Originál Vč. Osobních Údajů A Podpisů Uložen V Cent"
+                r"rálním Archivu Strany|Originál Vč. Osobních Údajů Uložen V Centrálním "
+                r"Archivu Strany|Pirátské Centrum Praha|Pice Praha|Podepsaný Orgininál V"
+                r"č. Osobních Údajů Uložen V Centrálním Archivu Strany)$",
+                "Centrální spisovna",
+            ),
+            (
+                r"^(Archiv Pirátské Centrum V Plzni|Archiv Plzen Piratske Centrum|Archiv"
+                r" Plzeňské Pirátské Centrum|Archiv V Piratskem Centru Plzen|Archiv V Pl"
+                r"zenskem Piratskem Centru|Petr Vileta, Plzeň, Archiv, Sídlo Strany|Petr"
+                r" Vileta, Plzeň, Vedoucí Fo|Petr Vileta, Vedoucí Fo, Plzeň|Pice Plzeň|P"
+                r"iratske Centrum Plzen|Pirátské Centrum Plzeň|Pirátské Centrum V Plzni|"
+                r"Plzeň|Plzenske Piratske Centrum|Plzeňské Pirátské Centrum|V Archivu Pl"
+                r"zeňského Kraje|Vedoucí Fo, Plzeň)$",
+                "Plzeňská spisovna",
+            ),
+            (
+                r"^(Archiv Klubu|Archiv Pk|Archiv Pk Piráti|Archiv Poslaneckého Klubu|Ar"
+                r"chiv Poslaneckého Klubu Piráti|Archiv, Poslanecký Klub Pirátů, Sněmovn"
+                r"í 4, 118 00, Praha 1|Kancelář Pirátského Klubu Č. 40, Mhmp, Mariánské "
+                r"Náměstí 2, Praha 1|Posklanecký Klub|Poslanecký Kllub|Poslanecký Klub|P"
+                r"oslanecký Klub Piráti|Poslanecký Klub Pirátů|Poslanecký Kub|Poslanekcý"
+                r" Klub|Poslaneský Klub Piráti)$",
+                "Spisovna poslaneckého klubu",
+            ),
+            (
+                r"^(Pirátská Centrum Hradec Králové|Pirátské Centrum Hradec Králové|Koks"
+                r" - Khk Kraj - Krapice|Koks - Pirátské Centrum Krapice Hradec Králové|K"
+                r"rapice|Krapice Hk|Krapice Hradec Králové|Krapice - Masarykovo Nám. 368"
+                r"/1, Hradec Králové|Smlouva Uložena Na Místě: Koks - Khk Kraj - Krapice"
+                r")$",
+                "Spisovna Hradec Králové",
+            ),
+            (
+                r"^(Archiv Psb|Archiv Psb Brno|Psb Brno|Psb Archiv)$",
+                "Brněnská spisovna",
+            ),
+            (
+                r"^(Pice Olomouc|Archiv Olk \(dočasně\)|Pice Olomouc)$",
+                "Olomoucká spisovna",
+            ),
+            (r"^Archiv Ks Vysočina$", "Vysočincká spisovna"),
+        )
+
+        for pattern in patterns:
+            area_name = re.sub(pattern[0], pattern[1], area_name)
+
+        return area_name
+
     def parse_index(
         self,
         contract_root: str,
@@ -61,13 +226,12 @@ class Command(BaseCommand):
         if len(split_contents) < 2:
             raise ValueError(f"{contract_root} index does not have valid metadata.")
 
-        # Use Jan 1, 1970 as the default, if any defined year is 0.
-        # Use Jan 1, 2100 as the default for infinitely valid contracts.
+        # Use Jan 1, 1970 as the default for invalid years.
         split_contents[1] = (
             split_contents[1]
             .replace("0000-00-00", "1970-01-01")
-            .replace("2222-22-22", "2100-01-01")
-            .replace("2222-00-00", "2100-01-01")
+            .replace("2222-22-22", "1970-01-01")
+            .replace("2222-00-00", "1970-01-01")
             .replace("-32", "-31")
             .replace("\t", " ")
         )
@@ -106,7 +270,7 @@ class Command(BaseCommand):
             match key:
                 case "datum účinnosti":
                     if isinstance(value, date):
-                        if date.year not in (1970, 2100):  # Ignore placeholder years
+                        if value.year not in (1970, 2100):  # Ignore placeholder years
                             contract.valid_start_date = value
                     elif value is not None:
                         observed_issues_count += 1
@@ -120,7 +284,7 @@ class Command(BaseCommand):
                             )
                 case "datum ukončení":
                     if isinstance(value, date):
-                        if date.year not in (1970, 2100):  # Ignore placeholder years
+                        if value.year not in (1970, 2100):  # Ignore placeholder years
                             contract.valid_end_date = value
                     elif value is not None and (
                         isinstance(value, str) and value.lower() != "na dobu neurčitou"
@@ -148,7 +312,7 @@ class Command(BaseCommand):
                         break
                 case "použité smluvní typy":
                     if isinstance(value, str):
-                        value = string.capwords(value)
+                        value = self.normalize_type(value)
 
                         try:
                             type_instance = ContractType.objects.get(name=value)
@@ -185,7 +349,7 @@ class Command(BaseCommand):
 
                             continue
 
-                        type_name = string.capwords(type_name.strip())
+                        type_name = self.normalize_type(type_name)
 
                         try:
                             type_instance = ContractType.objects.get(name=type_name)
@@ -248,8 +412,13 @@ class Command(BaseCommand):
                         if value != 0:
                             contract.cost_amount = int(value)
                             contract.cost_unit = contract.CostUnits.TOTAL
-                    elif isinstance(value, str) and value.endswith("Kč/h"):
-                        split_value = value.split("Kč/h")
+                    elif isinstance(value, str):
+                        value = value.lower().replace(",-", "")
+
+                        if not value.endswith("kč/h"):
+                            continue
+
+                        split_value = value.split("kč/h")
 
                         if (
                             len(split_value) != 2
@@ -281,13 +450,17 @@ class Command(BaseCommand):
                             )
                 case "místo uložení":
                     if isinstance(value, str):
-                        value = string.capwords(value)  # Some normalization
+                        value = self.normalize_filing_area(value)
 
                     try:
+                        contract.paper_form_state = contract.PaperFormStates.STORED
                         filing_area = ContractFilingArea.objects.get(name=value)
                     except ContractFilingArea.DoesNotExist:
-                        if isinstance(value, str):
-                            filing_area = ContractFilingArea(name=value)
+                        if isinstance(value, str) and not value.startswith("Neznám"):
+                            filing_area = ContractFilingArea(
+                                name=value,
+                                person_responsible="Doplň osobu!",
+                            )
                         else:
                             observed_issues_count += 1
                             contract.notes += f"Špatně zadaná spisovna: {value}\n"
@@ -302,6 +475,15 @@ class Command(BaseCommand):
                             continue
 
         if not is_already_imported:
+            if contract.name in (None, "") or (
+                isinstance(contract.name, str)
+                and re.sub(r"/\s\s+/", "", contract.name) == ""
+            ):
+                contract.name = slug
+
+            if contract.valid_start_date is None:
+                contract.valid_start_date = date(year=1970, month=1, day=1)
+
             if observed_issues_count != 0:
                 self.partial_import_count += 1
                 self.issue_count += observed_issues_count
@@ -351,7 +533,7 @@ class Command(BaseCommand):
 
                         self.fatal_error_count += 1
 
-                        continue
+                        return
 
                     self.assign_contract_metadata(
                         contract,
@@ -362,8 +544,6 @@ class Command(BaseCommand):
                     # TODO
                     continue
 
-        contract.save()
-
     def import_all_contracts(self, git_dir) -> None:
         year_root = os.path.join(
             git_dir,
@@ -474,6 +654,8 @@ class Command(BaseCommand):
 
         if options["purge"]:
             Contract.objects.filter().delete()
+            ContractType.objects.filter().delete()
+            ContractFilingArea.objects.filter().delete()
 
             if self.verbosity >= 1:
                 self.stdout.write(self.style.SUCCESS("Deleted all previous records."))
diff --git a/contracts/views.py b/contracts/views.py
index 43f09b4..693fca2 100644
--- a/contracts/views.py
+++ b/contracts/views.py
@@ -1,5 +1,4 @@
 import os
-
 from xml.etree import ElementTree
 
 import requests
-- 
GitLab