Tabela krzyżowa rekordów i autorów na stronie „Wyszukiwanie zapytaniem" (fazy 2-4) - #686
Open
mpasternak wants to merge 26 commits into
Open
Tabela krzyżowa rekordów i autorów na stronie „Wyszukiwanie zapytaniem" (fazy 2-4)#686mpasternak wants to merge 26 commits into
mpasternak wants to merge 26 commits into
Conversation
Eksporty (CSV/XLSX/HTML/DOCX/BibTeX) i tabela krzyżowa na stronie /zapytanie/ w parytecie z multiseekiem, plus nowy rejestr wymiarów tabeli krzyżowej dla modelu Autor (bazy: kadrowa / prace / udziały). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Część IV: usunięta sprzeczność (jeden annotate vs osobne zapytania); metryki dorobku liczone dwoma zapytaniami, bo dwa JOIN-y do relacji do-wielu w jednym annotate zawyżają sumy - postać dla model=autor to 'rekordy' + 'pivot' (nie 'lista') - doprecyzowany routing eksportu przy postac=pivot i sanityzacja ?tytul= Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cztery fazy: wspólny silnik wyjścia + eksporty rekordów, pivot rekordów, pakiet bpp/pivot/ + rejestr autorski (baza kadrowa), metryki bibliometryczne + eksport autorów. Self-review planu wykrył trzy blokery reużycia report-body-pivot.html (formularz GET gubiący model/query, relatywne linki eksportu, selektory nieświadome baz agregacji) — wszystkie rozpisane jako konkretne edycje. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pre-flight scan planu: testy dotykające Rekordu wymagają fixture denorms i denorms.flush() (Rekord to zdenormalizowany cache), fixture 'tytul' nie istnieje (jest 'tytuly'), a dwie asercje były zbyt słabe (dowolne '2' w wierszu CSV; brak '5000' w komunikacie) — zamienione na parsowanie CSV i sprawdzenie treści komunikatu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 2 wykrył, że plan brał drugi argument ReportType('list', 'lista') —
etykietę UI — za wartość report_type. postac=tabela nigdy nie trafiłaby
w TABLE_REPORT_TYPES, więc tabela renderowałaby się jako lista, bez błędu.
Poprawione w planie (zadania 2-6, 12) i w specyfikacji.
Druga poprawka: nh3 w sanitize_export_html zdejmuje atrybuty class, więc
asercje testów eksportu HTML/DOCX nie mogą opierać się na klasach CSS —
tylko render na stronie je zachowuje.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review Task 3: postać 'tabela krzyżowa' była wybieralna, a renderowała placeholder listy (dla autorów: puste wiersze, bo Autor nie ma js_safe_pk ani opisu bibliograficznego). Zadanie 3 usuwa ją z list postaci, zadania 6 i 9 dopisują ją z powrotem wraz z implementacją. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…a bibtex, ikona fi-page-edit - POSTACIE_REKORD/POSTACIE_AUTOR: wyrzucone "pivot" (bez dedykowanego partiala renderowal sie jako zepsuta lista rekordow/autorow). - postac="bibtex" renderuje teraz wlasny partial multiseeka (report-body-bibtex.html) zamiast placeholder-owej listy opisow. - ikona edycji ✎ -> fi-page-edit (Foundation Icons, zgodnie z konwencja repo dla frontendu publicznego) w obu partialach (list/table). - test_postac_tabela_ma_sumy: asercja na realnej wartosci sumy (dwa rekordy, 40+10=50,00), nie tylko na obecnosci kontenera. - nowe testy regresyjne: pivot niewybieralny/degradujacy, bibtex uzywa wlasnego partiala, pager zachowuje postac miedzy stronami, stopka "Suma:" tylko na ostatniej stronie tabeli.
…ekiem
Przycisk "Skopiuj wszystko do schowka" w report-body-bibtex.html ma
handler JS tylko inline w multiseekowym common-results.html — na
/zapytanie/ (hide_chrome=True) byl martwy (klik bez efektu). Ten sam
wzorzec co widget usuwania (❌): {% if not hide_chrome %} wokol
.bibtex-actions. Multiseek nie przekazuje hide_chrome, wiec jego
przycisk dziala jak dotad; /zapytanie/ dostaje sam <pre> z BibTeX-em
(w pelni zaznaczalny/kopiowalny recznie).
Zadanie 5 celowo zwróciło pustą krotkę formatów dla model=autor, bo backend 400-ował każdy format i pasek pokazywałby martwe linki. Po wdrożeniu eksportu autorów gałąź ma wrócić do (csv, xlsx). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eksporty, komunikat nieznanego modelu Test uprawnien testuje teraz sztywno 403 (raise_exception=True gwarantuje to niezaleznie od stanu logowania — potwierdzone w kodzie mixinow Django). Usuniete szesc martwych re-eksportow z mymultiseek.py (niepotwierdzone grepem uzycie poza tym plikiem). Rozdzielony komunikat 400 dla eksportu autorow i dla realnie nieznanego modelu. Dwa testy degradacji postaci dla autora dostaly pozytywna asercje na "rekord-id-cell" zamiast samego "not in". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Postać "tabela krzyżowa" (postac=pivot) wraca do selektora dla modelu rekord — zadanie 3 ją usunęło, bo bez implementacji renderowała placeholder listy. Silnik pivota (parse_pivot_params/zbuduj_pivot/DIMENSIONS/METRICS) jest brany z multiseeka bez zmian; widok dokłada tylko kontekst i trzy klucze, których partial potrzebuje pod tym URL-em: form action, ukryte pola model/query/postac (strona zapytania trzyma filtr w URL-u, nie w sesji jak multiseek) i prefiks linków eksportu. Eksport macierzy do CSV/XLSX omija capy rekordowe 25000/5000 — rozmiar macierzy nie zależy od liczby rekordów źródłowych. Bramka PIVOT_MAX_CELLS pokazuje komunikat na ekranie zamiast 500. Przy okazji naprawione cztery fałszywie zielone asercje testowe: sprawdzały obecność gołej nazwy klasy "rekord-id-cell", która występuje na KAŻDEJ stronie, bo zapytanie.html definiuje dla niej regułę CSS w bloku <style>. Test "pivot dla rekordu degraduje" przechodził więc nadal, mimo że pivot dostał już własny render. Asercje celują teraz w '<td class="rekord-id-cell">', a strażnik wybieralności pivota został odwrócony na model autor, który własnego rejestru wymiarów jeszcze nie ma. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Silnik tabeli krzyżowej przestał być multiseek-specyficzny w momencie, gdy dostała ją też strona „Wyszukiwanie zapytaniem", a zadania 8-10 dokładają drugi rejestr wymiarów (dla modelu Autor, z trzema bazami agregacji). Kod rozdzielony na generyczny silnik (bpp/pivot/core.py) i rejestr wymiarów Rekordu (bpp/pivot/rekord.py); multiseek_registry/pivot.py zostaje cienkim re-eksportem, żeby nie ruszać call-site'ów w szablonach i widokach. Jedyna zmiana sygnatury: zbuduj_pivot(..., *, model=None) — strategia dedupu potrzebuje modelu bazowego, dotąd zaszytego jako Rekord. Domyślny None zachowuje wszystkie istniejące wywołania; zadanie 8 poda tu Autora. Progi PIVOT_MAX_CELLS/PIVOT_MAX_PAIRS świadomie NIE są re-eksportowane przez shim: zbuduj_pivot czyta je jako globalne swojego modułu, więc kopia nazwy pod starą ścieżką byłaby martwa — monkeypatch na niej przestawiłby kopię, nie bramkę. Trzy testy patchujące progi celują teraz w bpp.pivot.core, gdzie te stałe mieszkają. Brak nazwy w shimie jest tu cechą, nie luką: monkeypatch domyślnie podnosi AttributeError na nieistniejącym atrybucie, więc pomyłka wyjdzie głośno w linii patcha, zamiast dać fałszywie zielony test. Neutralność: 25 testów pivota multiseeka przechodzi bez zmian w asercjach (zmienione tylko trzy cele monkeypatcha — patch site idzie za przeprowadzką stałej dokładnie tak jak import site). Cała suita pivot/zapytanie/multiseek: 458 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drugi rejestr silnika pivota (bpp/pivot/autor.py) obok rejestru rekordowego z zadania 7 — model Autor, na razie tylko baza K (kadrowa, metryka „liczba autorów"). Bazy P (liczba prac) i U (Σ slotów, Σ pkdaut) to zadanie 10; seam dla nich jest gotowy (PivotDimension.expr jako dict per baza, PivotMetric.baza), więc dołożenie ich nie wymaga przeprojektowania. core.py: PivotDimension zyskuje annotation (adnotacja ORM dokładana przed grupowaniem — potrzebna dla wymiarów bool typu „ma ORCID", gdzie grupowanie po surowym polu dałoby grupę na wartość) oraz expr_dla()/alias() do odczytu ścieżki per baza; PivotMetric zyskuje baza i distinct_field. Rejestr rekordowy nie zauważa zmiany — przekazuje expr jako zwykły str; 25 testów bez zmian. Poprawki względem brief-u: fixture „tytul" w tym repo nie istnieje (jest tylko „tytuly") — Tytul tworzony inline; test odrzucania wymiaru niedostępnego w bazie był wsobny przy jednej bazie K (klucz „rok" nie istnieje w DIMENSIONS autorskich, więc fallback zadziałałby i bez logiki expr_dla) — przemianowany na to, co faktycznie sprawdza; asercja liczby autorów zastąpiona pełnym mapowaniem etykieta→suma ze stałą BRAK zamiast wklejonego literału. Nośność dedupu zmierzona, nie założona: po usunięciu dedupu w zbuduj_pivot_autora wszystkie 7 testów nadal przechodzi, bo Count(pk, distinct=True) sam zwija wiersze zwielokrotnione JOIN-em. Dedup zostaje (poprawny, tani, jednakowy dla baz), ale test przestał twierdzić, że go dowodzi, a wymóg testu padającego bez dedupu — możliwego dopiero dla metryk Σ, gdzie Sum nie rozpozna duplikatu — jest dopisany do zadania 10 w planie. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rejestr pivota autorskiego (bpp.pivot.autor, baza kadrowa K) zostaje wpięty w /zapytanie/ i eksport: postac=pivot jest teraz wybieralna dla obu modeli, ZapytanieView._pivot_context i ZapytanieExportView rozgałęziają się po modelu przez wspólny helper bpp.pivot.wybierz_rejestr_pivota, eksport macierzy autorskiej (CSV/XLSX) naprawdę działa i ma link w pasku, a sekcja pomocy dostaje cztery klikalne presety (struktura kadrowa, ORCID, PBN UID, płeć wg tytułów). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…Σ pkdaut) Bazy P (prace, przez `autorzy` → Rekord) i U (udziały, przez `cache_punktacja_autora_query` → Rekord) w rejestrze pivota autorskiego, razem z wymiarami publikacyjnymi o `expr` per baza: rok, dyscyplina, jednostka przy pracy, typ odpowiedzialności, charakter formalny. KRYTYCZNE: każdy wymiar publikacyjny idzie DOKŁADNIE tą samą relacją do-wielu, po której agreguje metryka jego bazy (P → `autorzy__…`, U → `cache_punktacja_autora_query__…`). Zmieszanie dwóch relacji daje iloczyn kartezjański i `Sum` zwraca wielokrotność prawdy — zmierzone: autor z dwiema pracami i dwoma wierszami udziału daje Σ slotów 3.0 zamiast 1.5. Pilnuje tego test niezawyżania na dokładnej liczbie. Dedup przez świeży queryset w `zbuduj_pivot_autora`, dotąd redundantny w bazie K (`Count(pk, distinct=True)` sam zwija duplikaty), staje się nośny dla metryk Σ — dowodzi tego nowy test, który po zakomentowaniu tamtej linii pada (0.5 → 1.5). Do UI dochodzą cztery presety oparte na nowych bazach (produktywność jednostek, ranking slotowy autorów, udziały dyscyplinowe, wkład punktowy jednostek) plus test, że każdy preset przeżywa `parse_pivot_params_autor` bez cichej podmiany wymiaru. Zdjęty `xfail(strict=True)` z `test_metryka_slotowa_pokazuje_rok`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ZapytanieExportView eksportuje teraz listę autorów (nie tylko macierz pivota) do CSV/XLSX: dane kadrowe (jednostka, tytuł, ORCID, PBN UID i inne) plus liczba prac, Σ slotów i Σ pkdaut. _metryki_dorobku() liczy te trzy metryki DWOMA osobnymi zapytaniami (Count przez relację "autorzy", Sum przez "cache_punktacja_autora_query") — jeden wspólny annotate() na dwóch różnych relacjach do-wielu zawyżałby sumy (dowód: test_eksport_autorow_nie_zawyza_metryk). Te same ścieżki ORM, co metryki bpp.pivot.autor.METRICS, żeby lista i tabela krzyżowa autorów nigdy nie pokazały dwóch różnych liczb dla tej samej populacji. eksport_formaty() (zapytanie.py) pokazuje teraz pasek CSV/XLSX dla modelu "autor" niezależnie od postaci wyniku — wcześniej ograniczone do postac="pivot", bo lista 400-owała. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dopisuje test przegladarkowy dowodzacy, ze wybor postaci wyniku (#id_postac) faktycznie jedzie przez formularz GET /zapytanie/ - domyslna tabela redakcyjna ustepuje partialowi multiseeka po przelaczeniu na "lista", a URL zawiera postac=list. Dodaje sekcje w dokumentacji redaktora o postaciach wyniku, eksporcie i tabeli krzyzowej strony /zapytanie/. Newsfragmenty na ta funkcjonalnosc juz istnieja z wczesniejszych zadan tego planu (zapytanie-eksporty, zapytanie-tabela-krzyzowa[-autorzy], zapytanie-pivot-autor-metryki-bibliometryczne, zapytanie-eksport-autorow) - nie dodano duplikatu. Pelna suita bez Playwrighta: 8748 passed, 4 skipped, 1 xfailed. Suita Playwright: 119 passed. pre-commit: czysto. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
K1 (krytyczne) — górny pasek eksportu przy postac=pivot ściągał INNĄ macierz
niż widoczna: budował URL z samych model/query/postac, więc gubił
pivot_row/pivot_col/pivot_val. Zmierzone: link z górnego paska zwracał
"Rok,RAZEM / 2026,1,1 / RAZEM,1" (macierz domyślna), a link spod macierzy
"Charakter ogólny (rodzaj),…" — dwa przyciski CSV kilka centymetrów od
siebie, dwa różne pliki. Górny pasek nie renderuje się teraz przy pivocie:
pasek w report-body-pivot.html składa URL z request.GET.urlencode, więc jest
z konstrukcji zgodny z ekranem (także po dołożeniu kolejnego parametru
pivota) i stoi przy selektorach, które macierz konfigurują. Wariant
"dołóż trzy parametry do href-a" odrzucony — zostawiałby dwie listy
parametrów do ręcznego synchronizowania, czyli dokładnie mechanizm, który
K1 wyprodukował.
W1 — pivot autorski nigdy nie pokazywał adnotacji o dublowaniu (twarde
has_autorzy_dim=False), choć baza P dubluje tak samo jak pivot rekordowy.
Zmierzone: jedna praca, dwóch autorów z różnych jednostek, row=jednostka →
GRAND 2 przy jednej unikatowej pracy i BRAK ostrzeżenia (a to jest preset
"Produktywność jednostek"). Flaga liczona teraz uczciwie: nowe pole
PivotDimension.atrybut_rekordu (ten sam kształt co `autorzy` w rejestrze
rekordowym), ustawione dla `rok` i `charakter_formalny`, plus _dubluje():
baza P + jakikolwiek wymiar, który nie jest atrybutem pracy. Po poprawce
jednostka → 2 + True, rok → 1 + False. Tekst notki przeredagowany na
neutralny modelowo.
W2 — distinct=True w METRICS["liczba_prac"] nie było pokryte: wszystkie
testy grupowały po `autor`, gdzie DISTINCT jest no-opem (zmierzone: bez=1,
z=1; przy row=jednostka bez=2, z=1). Dopisany test dwóch współautorów jednej
jednostki (grand_total == 1) plus testy dublowania/niedublowania z W1.
Mutacja "usuń distinct=True" wywala teraz 2 testy.
W3 — testy eksportu macierzy asertowały samo b"RAZEM", które jest w KAŻDEJ
macierzy (także w błędnej domyślnej) — dlatego K1 przeszedł trzy rundy
review. Cztery testy sprawdzają teraz etykiety zamówionych wymiarów i
dokładne wartości komórek (XLSX przez openpyxl, nie przez Content-Type),
doszedł też test biorący WSZYSTKIE linki eksportu ze strony pivota i
wymagający od każdego macierzy zgodnej z ekranem. Mutacja "eksport ignoruje
parametry GET" wywala 4 testy, mutacja "przywróć stary górny pasek" — 2.
W4 — presety były martwe na świeżej stronie: renderowały się z query= i po
kliknięciu dawały 200 bez wyników, bez macierzy i bez komunikatu. Puste
zapytanie nie jest "wszyscy autorzy" — apply_search rzuca DjangoQLParserError
("Unexpected end of input"), więc widok w ogóle nie wchodzi w render_results.
Bez wysłanego zapytania blok pokazuje więc nazwy presetów jako tekst plus
zdanie, co zrobić, żeby ożyły (discoverability bez klikalnej obietnicy);
z zapytaniem — linki jak dotąd, z tekstem mówiącym wprost, że jadą
z WYSŁANYM zapytaniem, nie z niewysłaną treścią pola.
Drobne: prawdziwy docstring PivotDimension.expr_dla (D2); martwa bramka
{% if eksport_formaty %} i nieaktualny komentarz zastąpione realną bramką
postac != "pivot" (D3); jeden słownik TAK/NIE/— brak — dla wartości
logicznych (core.etykieta_bool) używany i przez pivot, i przez eksport
autorów, gdzie orcid_w_pbn jechał dotąd jako "True"/"False" (D5).
Świadomie odłożone (bez zmian): grupy o sumie 0 znikające z macierzy,
paginacja/URL-e admina liczone na darmo przy postac=pivot, rsplit("csv/").
Weryfikacja: pytest -k "pivot or zapytanie or multiseek" → 496 passed
(było 490), pre-commit czysty, zero zmian w migracjach i baseline-sql.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…krzyzowa # Conflicts: # src/django_bpp/templates/multiseek/report-body-pivot.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fazy 2-4 planu „wachlarz wyjść dla /zapytanie/". Spiętrzone na #685 (faza 1) —
base tej gałęzi to
feat/zapytanie-wyjscia, żeby diff pokazywał wyłącznie nowąpracę. Po zmergowaniu #685 do
devten PR należy przetargetować nadev.Co dochodzi
Tabela krzyżowa rekordów (
postac=pivot) — ten sam silnik co w wyszukiwaniuformularzowym, z eksportem macierzy do CSV/XLSX. Eksport macierzy nie podlega
capom rekordowym (25000/5000), bo rozmiar macierzy nie zależy od liczby rekordów
źródłowych; bramka
PIVOT_MAX_CELLSpokazuje komunikat zamiast 500.Tabela krzyżowa autorów — to jest odpowiedź na pytanie „nie wiem, jakie
mógłbym mieć konfiguracje tabeli krzyżowej dla autorów". Zaprojektowane są trzy
bazy agregacji, bo to metryka decyduje o ścieżce JOIN-u:
bpp_autorautorzy(widokbpp_autorzy_mat)cache_punktacja_autora_queryWymiary kadrowe (jednostka, tytuł, stopień, funkcja, płeć, rok urodzenia,
kompletność ORCID / PBN UID / e-maila / ID kadrowego) są dostępne we wszystkich
bazach. Wymiary publikacyjne (rok, dyscyplina, jednostka pracy, typ
odpowiedzialności, charakter formalny) mają osobną ścieżkę ORM per baza i
znikają z selektora tam, gdzie nie mają sensu. Do tego 8 gotowych presetów
(Struktura kadrowa, Audyt kompletności ORCID, Gotowość do PBN, Produktywność
jednostek…).
Eksport kartoteki autorów do CSV/XLSX — kolumny kadrowe plus trzy metryki
dorobku (liczba prac, Σ slotów, Σ pkdaut).
Refaktor: silnik pivota wyprowadzony z
bpp/multiseek_registry/pivot.pydopakietu
bpp/pivot/(core= silnik generyczny,rekordiautor= rejestrywymiarów). Stary moduł zostaje cienkim re-eksportem — multiseek nie zauważa
zmiany.
Czego świadomie NIE ma
Σ IF / Σ PK / Σ cytowań w pivocie autorskim. To wartości rekordowe: sumowane
per autor zwielokrotniają się między współautorami. Kto ich potrzebuje, używa
pivota rekordowego z wymiarem „jednostka" — tam strategia par liczy je poprawnie.
Dlaczego liczbom można wierzyć
Django zamienia queryset spinający dwie relacje „do wielu" w iloczyn kartezjański,
co zawyża
Sum(choć nieCount(distinct=True)). Trzy zabezpieczenia, każdeudowodnione mutacyjnie, nie założone:
autorzy__, każdy bazy U odcache_punktacja_autora_query__— przestawienie jednego na cudzą relację dajew teście
3.0 ≠ 1.5;annotate()dajeΣ 3.0000zamiast1.5000;Σ 1.5 ≠ 0.5.Eksport i tabela krzyżowa używają identycznych ścieżek ORM, więc nie mogą
pokazać dwóch różnych liczb dla tej samej populacji.
Testy
-k "pivot or zapytanie or multiseek": 496 passedmake tests-without-playwright: 8748 passed, 4 skippedpre-commit: czystosrc/*/migrations/ibaseline-sql/25 testów pivota multiseeka przechodzi z niezmienionymi asercjami. Zmieniły się
w nich trzy cele
monkeypatch— stałaPIVOT_MAX_CELLSprzeprowadziła się dobpp.pivot.core, a shim celowo jej nie re-eksportuje: kopia nazwy pod starąścieżką byłaby martwa (patch przestawiłby kopię, nie bramkę), a
monkeypatchdomyślnie podnosi
AttributeErrorna nieistniejącym atrybucie, więc pomyłkawychodzi głośno zamiast dać fałszywie zielony test.
Końcowe review
Przegląd całości (osobny agent) wyłapał 1 Critical i 4 Important — wszystkie
naprawione w
2a59fea2d. Krytyczny: górny pasek eksportu przypostac=pivotgubiłparametry
pivot_row/col/vali pobierał domyślną macierz zamiast tej naekranie, a strona pokazywała dwa przyciski CSV dające różne pliki. Przepuściły to
testy asertujące wyłącznie obecność napisu „RAZEM" — obecnego w każdej macierzy.
Testy eksportu sprawdzają teraz etykiety żądanych wymiarów i konkretne komórki, a
osobny test przechodzi po wszystkich linkach eksportu na stronie i wymaga
macierzy zgodnej z ekranem.
🤖 Generated with Claude Code