From f8d938e3532019006a09cd08478c8b2743269708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Sp=C3=A1=C4=8Dil?= Date: Thu, 1 Oct 2026 15:33:41 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20hesl=C3=A1=C5=99e=20osob=20se=20po=20akt?= =?UTF-8?q?ualizaci=20ti=C5=A1e=20vypr=C3=A1zdn=C3=AD=20(#68)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Digiarchiv v4.1.0 (Solr 10, json.nl=arrarr) vrací položky facet jako dvojice ["hodnota", počet] místo objektů {"name": ...}. fetch_set četl r["name"], spadl na TypeError a hesláře vedoucích a nálezců se uložily prázdné. - _facet_name() přijímá oba formáty facet (starý i nový). - Selhání setu vrací prázdný seznam i při přerušeném stránkování, ať se neuloží jen část hesláře. - download_heslare() ponechá u selhaného nebo prázdného setu předchozí hodnoty z heslar.csv a vrátí seznam selhaných setů. - Dialog při částečném selhání zobrazí varování místo „Hotovo“. - Verze 2.1.3 + changelog. Ověřeno proti produkčnímu API: vedoucí 2497, nálezci 426, ostatní hesláře beze změny; simulované selhání ponechá předchozí hodnoty. Refs #67, #66 Připraveno s pomocí AI (Claude). --- amcr_viewer/amcr_codelists.py | 71 ++++++++++++++++++++++++++++++++--- amcr_viewer/amcr_dialog.py | 30 ++++++++++++--- amcr_viewer/metadata.txt | 5 ++- 3 files changed, 95 insertions(+), 11 deletions(-) diff --git a/amcr_viewer/amcr_codelists.py b/amcr_viewer/amcr_codelists.py index 8646b38..4001e5e 100644 --- a/amcr_viewer/amcr_codelists.py +++ b/amcr_viewer/amcr_codelists.py @@ -104,6 +104,21 @@ def load_all_data(): return categorized_data +def _facet_name(item): + """ + Returns the value of one facet item from the Digiarchive API. + + Digiarchive v4.1.0 (Solr 10, json.nl=arrarr) returns facet items as + ["value", count] pairs; older versions returned {"name": "value", ...} + objects. Both shapes are accepted so the plugin works against either. + """ + if isinstance(item, dict): + return item.get("name") + if isinstance(item, (list, tuple)) and item: + return item[0] + return None + + def fetch_set(base_url, internal_name, api_set, task=None): dataset = [] params_amcr = { @@ -206,7 +221,9 @@ def fetch_set(base_url, internal_name, api_set, task=None): for r in records: - nazev = r["name"] + nazev = _facet_name(r) + if not nazev: + continue dataset.append({ 'Název': nazev, @@ -217,17 +234,48 @@ def fetch_set(base_url, internal_name, api_set, task=None): break except Exception as e: + # A partial set (e.g. pagination interrupted halfway) would + # silently drop codes – report the whole set as failed instead + # and let the caller keep the previous values QgsMessageLog.logMessage( f"Chyba u setu {api_set}: {e}", "AMČR", Qgis.MessageLevel.Warning) - break + return [] return dataset -def download_heslare(task=None): - """Fetches the codelists from the AMČR API and saves it to a CSV file.""" +def _read_existing_rows(): + """ + Returns the rows of the current heslar.csv grouped by category, so a set + that fails to download can keep its previous values. + """ + rows = {} + if not os.path.exists(OUTPUT_FILE): + return rows + try: + with open(OUTPUT_FILE, encoding='utf-8-sig', newline='') as f: + for row in csv.DictReader(f, delimiter=';'): + cat = (row.get('Kategorie') or '').strip() + if cat: + rows.setdefault(cat, []).append(row) + except Exception as e: + QgsMessageLog.logMessage( + f"Nelze načíst stávající hesláře: {e}", + "AMČR", Qgis.MessageLevel.Warning) + return rows + + +def download_heslare(task=None, failed=None): + """ + Fetches the codelists from the AMČR API and saves it to a CSV file. + + A set that fails or comes back empty keeps its rows from the current + heslar.csv instead of being wiped; its name is appended to ``failed`` + (if given) so the caller can warn the user. + """ ensure_codelists_dir() + existing = _read_existing_rows() all_data = [] total_sets = len(slovnicek) # index, (interni, api_nazev) @@ -251,6 +299,18 @@ def download_heslare(task=None): if data is None: return False # Cancelled mid-download + if not data: + # Never replace a working codelist with nothing – an API change + # would otherwise silently empty the filter in the dialog + old = existing.get(interni, []) + QgsMessageLog.logMessage( + f"Heslář '{interni}' se nepodařilo stáhnout, " + f"ponechávám předchozí hodnoty ({len(old)} položek).", + "AMČR", Qgis.MessageLevel.Warning) + if failed is not None: + failed.append(interni) + data = old + all_data.extend(data) # Report progress (0-100) @@ -261,7 +321,8 @@ def download_heslare(task=None): # Save to CSV with open(OUTPUT_FILE, 'w', newline='', encoding='utf-8-sig') as f: fieldnames = ['Název', 'Kód', 'Kategorie'] - writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';') + writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';', + extrasaction='ignore') writer.writeheader() writer.writerows(all_data) diff --git a/amcr_viewer/amcr_dialog.py b/amcr_viewer/amcr_dialog.py index 1fac5f7..26848d7 100644 --- a/amcr_viewer/amcr_dialog.py +++ b/amcr_viewer/amcr_dialog.py @@ -75,12 +75,15 @@ class UpdateCodelistsTask(QgsTask): super().__init__(description, QgsTask.Flag.CanCancel) self.success = False self.exception = None + # Codelists that failed to download and kept their previous values + self.failed_sets = [] def run(self): """Runs in a background thread.""" try: # Call the download function with the task reference - self.success = download_heslare(task=self) + self.success = download_heslare( + task=self, failed=self.failed_sets) return self.success except Exception as e: self.exception = e @@ -91,10 +94,17 @@ class UpdateCodelistsTask(QgsTask): if result: # Safely update the global variables in the main thread refresh_globals() - QgsMessageLog.logMessage( - "Hesláře AMČR byly úspěšně aktualizovány.", - "AMČR", Qgis.MessageLevel.Info - ) + if self.failed_sets: + QgsMessageLog.logMessage( + "Hesláře AMČR aktualizovány částečně, beze změny " + f"zůstaly: {', '.join(self.failed_sets)}", + "AMČR", Qgis.MessageLevel.Warning + ) + else: + QgsMessageLog.logMessage( + "Hesláře AMČR byly úspěšně aktualizovány.", + "AMČR", Qgis.MessageLevel.Info + ) else: if self.isCanceled(): QgsMessageLog.logMessage( @@ -629,6 +639,16 @@ class AmcrFilterDialog(QDialog): def on_completed(): _cleanup() + if task.failed_sets: + QMessageBox.warning( + parent_win, + "Hesláře aktualizovány částečně", + "Některé hesláře se nepodařilo stáhnout, " + "ponechány byly jejich předchozí hodnoty:\n" + + "\n".join(f"• {name}" for name in task.failed_sets) + + "\n\nPodrobnosti jsou v panelu Zprávy, záložka AMČR." + ) + return QMessageBox.information( parent_win, "Hotovo", diff --git a/amcr_viewer/metadata.txt b/amcr_viewer/metadata.txt index 3dc8fd2..f5c05d5 100644 --- a/amcr_viewer/metadata.txt +++ b/amcr_viewer/metadata.txt @@ -8,7 +8,7 @@ name=AMČR Viewer qgisMinimumVersion=3.44.0 qgisMaximumVersion=4.99.0 description=Viewing and downloading the AMČR data. -version=2.1.2 +version=2.1.3 author=David Spáčil email=spacil@arub.cz @@ -24,6 +24,9 @@ hasProcessingProvider=no # Uncomment the following line and add your changelog: changelog= Plný seznam změn v češtině je dostupný zde: https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer/releases/tag/v2.1.1 + v2.1.3 (2026-10-01) + * Fixed empty person codelists (excavation leaders, finders) after updating codelists against Digiarchive v4.1.0 + * A codelist that fails to download keeps its previous values and the user is warned v2.1.2 (2026-09-01) * Qt6 compatibility * Code clean-up