Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions src/ewaluacja_common/const.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,46 @@
# Okres ewaluacyjny — JEDNO miejsce, z którego bierze się zakres lat.
#
# Okresy mają NAZWY, nie tylko wartości: gdy MEiN ogłosi zasady kolejnego
# okresu (2026+, cztero- albo pięcioletniego), dopisujemy tutaj NOWĄ stałą,
# a stara ZOSTAJE. Dane i raporty policzone pod poprzednie zasady muszą dać
# się odtworzyć, więc ``OKRES_2022_2025`` nigdy nie zmieni wartości — to
# okres zamknięty, liczony wg zasad sprzed nowelizacji.
OKRES_2022_2025 = (2022, 2025)

# Okres, którego domyślnie używa kod produkcyjny. TO JEST TA JEDNA LINIA,
# którą przestawia się przy zmianie okresu ewaluacyjnego — wystarczy
# wskazać tu inną stałą ``OKRES_*``. Nie wpisuj tutaj krotki wprost;
# nazwany okres wyżej mówi, WEDŁUG JAKICH ZASAD liczymy.
OKRES_DOMYSLNY = OKRES_2022_2025

# Zakres lat dla wyszukiwania prac do ewaluacji.
#
# Stałe te mieszkały historycznie w ``ewaluacja2021.const`` (apka raportów
# "3N"), ale to ``ewaluacja_common`` jest współdzielonym modułem żywych apek
# ewaluacyjnych (liczba_n, optymalizacja, metryki) i to on faktycznie używa
# zakresu lat w ``get_lista_prac``. Przeniesione tutaj, by żywy kod nie zależał
# od uśpionej apki ``ewaluacja2021`` (patrz ``ewaluacja2021/README.md``).
ROK_MIN = 2022
ROK_MAX = 2026
#
# Wyprowadzone z ``OKRES_DOMYSLNY``, żeby nie mogły ponownie zdryfować
# względem reszty repo (przed refaktorem ``ROK_MAX`` wynosił tu 2026, choć
# cała reszta kodu ewaluacyjnego liczyła do 2025).
ROK_MIN, ROK_MAX = OKRES_DOMYSLNY


def lata_okresu(okres=None):
"""Zwraca listę kolejnych lat okresu — dla filtrów ``rok__in`` i list w UI.

Okres jest przedziałem DOMKNIĘTYM (``(2022, 2025)`` to cztery lata,
z rokiem 2025 włącznie), a ``range`` ma prawy koniec wyłączny — stąd
``+ 1``. Ta jedynka mieszka wyłącznie tutaj, żeby nie trzeba jej było
powtarzać (i mylić się w niej) w każdym miejscu budującym listę lat.

Args:
okres: krotka ``(rok_min, rok_max)``; domyślnie ``OKRES_DOMYSLNY``
odczytywany w momencie WYWOŁANIA (a nie definicji funkcji), żeby
testy mogły podmienić okres przez ``mock.patch``.
"""
if okres is None:
okres = OKRES_DOMYSLNY
rok_min, rok_max = okres
return list(range(rok_min, rok_max + 1))
6 changes: 4 additions & 2 deletions src/ewaluacja_dwudyscyplinowcy/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from bpp.models import Autor_Dyscyplina, Wydawnictwo_Ciagle_Autor
from bpp.models.sloty.core import CannotAdapt, ISlot
from ewaluacja_common.const import lata_okresu

logger = logging.getLogger(__name__)

Expand All @@ -18,7 +19,8 @@ def pobierz_autorow_z_dwiema_dyscyplinami(lata=None):
Pobiera autorów z dokładnie dwiema dyscyplinami dla podanych lat.

Args:
lata: Lista lat do sprawdzenia. Domyślnie 2022-2025.
lata: Lista lat do sprawdzenia. Domyślnie lata bieżącego okresu
ewaluacji (``OKRES_DOMYSLNY``).

Returns:
Słownik {autor_id: {
Expand All @@ -34,7 +36,7 @@ def pobierz_autorow_z_dwiema_dyscyplinami(lata=None):
}}
"""
if lata is None:
lata = range(2022, 2026) # 2022-2025
lata = lata_okresu()

autorzy_dict = defaultdict(lambda: {"autor": None, "lata": {}})

Expand Down
4 changes: 2 additions & 2 deletions src/ewaluacja_liczba_n/excel_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from bpp.models import Uczelnia

from .models import LiczbaNDlaUczelni
from .utils import oblicz_liczbe_n_na_koniec_2025
from .utils import oblicz_liczbe_n_na_koniec_okresu


class LiczbaNExcelExporter:
Expand Down Expand Up @@ -84,7 +84,7 @@ def _create_nieraportowane_worksheet(
.select_related("dyscyplina_naukowa")
.order_by("dyscyplina_naukowa__nazwa")
)
liczby_n_2025 = oblicz_liczbe_n_na_koniec_2025(uczelnia)
liczby_n_2025 = oblicz_liczbe_n_na_koniec_okresu(uczelnia)

# Filter only non-reported (N < 12 at end of 2025)
row_num = 2
Expand Down
4 changes: 2 additions & 2 deletions src/ewaluacja_liczba_n/management/commands/przelicz_n.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.core.management import BaseCommand

from bpp.models import Uczelnia
from ewaluacja_liczba_n.utils import oblicz_liczby_n_dla_ewaluacji_2022_2025
from ewaluacja_liczba_n.utils import oblicz_liczby_n_dla_okresu


class Command(BaseCommand):
Expand All @@ -24,5 +24,5 @@ def handle(self, *args, **options):
uczelnia = Uczelnia.objects.get()

self.stdout.write("Przeliczam liczby N dla uczelni...")
oblicz_liczby_n_dla_ewaluacji_2022_2025(uczelnia=uczelnia)
oblicz_liczby_n_dla_okresu(uczelnia=uczelnia)
self.stdout.write(self.style.SUCCESS("Przeliczono liczby N pomyślnie!"))
41 changes: 26 additions & 15 deletions src/ewaluacja_liczba_n/tests/test_core_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ def test_get_lista_prac_zakres_lat(
denorms,
typy_odpowiedzialnosci,
charaktery_formalne,
rodzaj_autora_n,
):
"""Sprawdza, czy lista prac odrzuca prace spoza zakresu 2022-2026"""
"""Sprawdza, czy lista prac odrzuca prace spoza okresu ewaluacji 2022-2025"""
from bpp.models import Charakter_Formalny

# Zrob dane testowe od 2015 do 2026
# Dane testowe od 2015 do 2026 — z zapasem po OBU stronach okresu
# ewaluacji, żeby test wyłapał zjechanie zarówno dolnej, jak i górnej
# granicy zakresu.

LiczbaNDlaUczelni.objects.create(
dyscyplina_naukowa=dyscyplina1, uczelnia=uczelnia, liczba_n=100
Expand All @@ -29,8 +32,14 @@ def test_get_lista_prac_zakres_lat(
).first()

for ROK in range(2015, 2027):
# rodzaj_autora jest OBOWIĄZKOWY: bez rodzaju z ``licz_sloty=True``
# ``SlotMixin.autorzy_z_dyscypliny`` pomija autora, przez co rekord
# nie trafia do ``Cache_Punktacja_Autora`` i test nie ma czego badać.
Autor_Dyscyplina.objects.create(
autor=autor_jan_nowak, rok=ROK, dyscyplina_naukowa=dyscyplina1
autor=autor_jan_nowak,
rok=ROK,
dyscyplina_naukowa=dyscyplina1,
rodzaj_autora=rodzaj_autora_n,
)

# Create IloscUdzialowDlaAutoraZaRok only for years 2022-2026
Expand All @@ -46,6 +55,10 @@ def test_get_lista_prac_zakres_lat(
wc: Wydawnictwo_Ciagle = baker.make(
Wydawnictwo_Ciagle,
rok=ROK,
# 5 pkt wpada w próg 3 kalkulatora slotów, ale DOPIERO od 2017 r.
# (patrz ``bpp.models.sloty.core._dopasuj_kalkulator``); lata
# 2015-2016 nie dają się przeliczyć i cache ich nie zawiera —
# to nie szkodzi, bo i tak są poza okresem ewaluacji.
punkty_kbn=5,
tytul_oryginalny=f"Test 123 - praca za rok {ROK}",
charakter_formalny=charakter_formalny,
Expand All @@ -54,19 +67,17 @@ def test_get_lista_prac_zakres_lat(

denorms.flush()

# Debug: Check if cache entries were created
# Bez wpisów w cache test nie badałby niczego — sprawdzamy to JAWNIE,
# zamiast (jak dawniej) po cichu robić ``pytest.skip``, przez co asercja
# niżej nigdy się nie wykonywała.
from bpp.models import Cache_Punktacja_Autora_Query

cache_entries = Cache_Punktacja_Autora_Query.objects.filter(
assert Cache_Punktacja_Autora_Query.objects.filter(
dyscyplina__nazwa=dyscyplina1.nazwa, autor=autor_jan_nowak
)
# If no cache entries, the test cannot pass
if cache_entries.count() == 0:
# The cache was not properly populated, skip this test for now
import pytest

pytest.skip(
"Cache_Punktacja_Autora_Query not populated - this appears to be a test infrastructure issue"
)
).exists(), "Cache_Punktacja_Autora_Query pusty — test nie ma czego sprawdzać"

assert (len(list(get_lista_prac(dyscyplina1.nazwa)))) == 5
# Asercja na KONKRETNYCH latach, nie na samej ich liczbie: gdyby zakres
# zjechał o rok w którąkolwiek stronę (np. 2023-2026), sama liczba prac
# nadal wynosiłaby 4 i test by tego nie zauważył.
lata = sorted(praca.rok for praca in get_lista_prac(dyscyplina1.nazwa))
assert lata == [2022, 2023, 2024, 2025]
79 changes: 79 additions & 0 deletions src/ewaluacja_liczba_n/tests/test_okres_w_zapytaniach.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Strażnik: zapytania LICZĄCE muszą brać zakres lat z ``OKRES_DOMYSLNY``.

Okres 2022-2025 był wpisany na sztywno w kilkudziesięciu filtrach ORM.
To groźniejsza kategoria niż literał w etykiecie: po przestawieniu
``OKRES_DOMYSLNY`` na kolejny okres takie miejsce dalej liczyłoby po cichu
2022-2025 — bez wyjątku, bez ostrzeżenia, tylko ze złym wynikiem.

Test celuje w najcięższe miejsce w apce ``ewaluacja_liczba_n`` — queryset
listy udziałów, z którego wyrasta cała liczba N — i sprawdza, że podmiana
stałej faktycznie zmienia to, co widok pobiera z bazy. Sam fakt, że w kodzie
stoi ``OKRES_DOMYSLNY[0]``, jest tu niewystarczający: liczy się, czy wartość
jest czytana w momencie WYWOŁANIA (a nie zapieczona np. w domyślnym
argumencie funkcji, gdzie ``mock.patch`` już by nie pomógł).
"""

from decimal import Decimal
from unittest import mock

import pytest
from django.test import RequestFactory
from model_bakery import baker

from bpp.models import Autor, Dyscyplina_Naukowa, Jednostka, Uczelnia
from ewaluacja_liczba_n.models import IloscUdzialowDlaAutoraZaRok
from ewaluacja_liczba_n.views import list as widok_listy

# Okres celowo rozłączny z 2022-2025 — gdyby widok dalej filtrował po starych
# latach, przecięcie zbiorów byłoby puste i test padnie jednoznacznie.
OKRES_TESTOWY = (2026, 2029)

LATA_STAREGO_OKRESU = [2022, 2023, 2024, 2025]
LATA_TESTOWEGO_OKRESU = [2026, 2027, 2028, 2029]


@pytest.fixture
def uczelnia_z_udzialami_z_dwoch_okresow(db):
"""Udziały za oba okresy naraz — po jednym rekordzie na każdy rok."""
uczelnia = baker.make(Uczelnia)
jednostka = baker.make(Jednostka, uczelnia=uczelnia, skupia_pracownikow=True)
autor = baker.make(Autor, aktualna_jednostka=jednostka)
dyscyplina = baker.make(Dyscyplina_Naukowa)

for rok in LATA_STAREGO_OKRESU + LATA_TESTOWEGO_OKRESU:
IloscUdzialowDlaAutoraZaRok.objects.create(
autor=autor,
dyscyplina_naukowa=dyscyplina,
uczelnia=uczelnia,
rok=rok,
ilosc_udzialow=Decimal("1.0"),
ilosc_udzialow_monografie=Decimal("0.5"),
)

return uczelnia


def _lata_widziane_przez_widok(uczelnia):
"""Lata, które widok listy faktycznie wyciąga z bazy."""
request = RequestFactory().get("/")
# Podstawiamy uczelnię wprost — ``get_for_request`` czyta ten cache jako
# pierwszy, więc omijamy rozwiązywanie domena → Site → Uczelnia.
request._uczelnia = uczelnia

widok = widok_listy.AutorzyLiczbaNListView()
widok.request = request
return sorted(widok.get_queryset().values_list("rok", flat=True))


@pytest.mark.django_db
def test_lista_udzialow_filtruje_po_okresie_domyslnym(
uczelnia_z_udzialami_z_dwoch_okresow,
):
uczelnia = uczelnia_z_udzialami_z_dwoch_okresow

# Stan bazowy: bieżący OKRES_DOMYSLNY to 2022-2025.
assert _lata_widziane_przez_widok(uczelnia) == LATA_STAREGO_OKRESU

# A teraz sedno: po podmianie okresu widok MUSI pokazać nowe lata.
with mock.patch.object(widok_listy, "OKRES_DOMYSLNY", OKRES_TESTOWY):
assert _lata_widziane_przez_widok(uczelnia) == LATA_TESTOWEGO_OKRESU
10 changes: 5 additions & 5 deletions src/ewaluacja_liczba_n/tests/test_per_uczelnia.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_pipeline_izolacja_dwie_uczelnie(db):
IloscUdzialowDlaAutoraZaRok,
LiczbaNDlaUczelni,
)
from ewaluacja_liczba_n.utils import oblicz_liczby_n_dla_ewaluacji_2022_2025
from ewaluacja_liczba_n.utils import oblicz_liczby_n_dla_okresu

u1 = baker.make(Uczelnia, skrot="U1", nazwa="U1")
u2 = baker.make(Uczelnia, skrot="U2", nazwa="U2")
Expand All @@ -88,8 +88,8 @@ def test_pipeline_izolacja_dwie_uczelnie(db):
for rok in (2022, 2023, 2024, 2025):
_make_autor_dyscyplina(autor, rok, dyscyplina)

oblicz_liczby_n_dla_ewaluacji_2022_2025(u1)
oblicz_liczby_n_dla_ewaluacji_2022_2025(u2) # second run must NOT wipe u1
oblicz_liczby_n_dla_okresu(u1)
oblicz_liczby_n_dla_okresu(u2) # second run must NOT wipe u1

assert IloscUdzialowDlaAutoraZaRok.objects.filter(uczelnia=u1, autor=a1).exists()
assert IloscUdzialowDlaAutoraZaRok.objects.filter(uczelnia=u2, autor=a2).exists()
Expand All @@ -108,7 +108,7 @@ def test_pipeline_pomija_nieprzypisanych(db):
from bpp.models import Autor, Jednostka, Uczelnia
from bpp.models.dyscyplina_naukowa import Dyscyplina_Naukowa
from ewaluacja_liczba_n.models import IloscUdzialowDlaAutoraZaRok
from ewaluacja_liczba_n.utils import oblicz_liczby_n_dla_ewaluacji_2022_2025
from ewaluacja_liczba_n.utils import oblicz_liczby_n_dla_okresu

u1 = baker.make(Uczelnia, skrot="U1", nazwa="U1")
obca = baker.make(Jednostka, uczelnia=u1, skupia_pracownikow=False)
Expand All @@ -118,7 +118,7 @@ def test_pipeline_pomija_nieprzypisanych(db):
for autor in (a_null, a_obca):
_make_autor_dyscyplina(autor, 2022, dyscyplina)

oblicz_liczby_n_dla_ewaluacji_2022_2025(u1)
oblicz_liczby_n_dla_okresu(u1)

assert not IloscUdzialowDlaAutoraZaRok.objects.filter(autor=a_null).exists()
assert not IloscUdzialowDlaAutoraZaRok.objects.filter(autor=a_obca).exists()
Expand Down
16 changes: 8 additions & 8 deletions src/ewaluacja_liczba_n/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
LiczbaNDlaUczelni,
)
from ewaluacja_liczba_n.utils import (
oblicz_liczby_n_dla_ewaluacji_2022_2025,
oblicz_liczby_n_dla_okresu,
oblicz_srednia_liczbe_n_dla_dyscyplin,
)


@pytest.mark.parametrize("zaokraglaj", [True, False])
def test_oblicz_liczby_n_dla_ewaluacji_2022_2025_prosty(
def test_oblicz_liczby_n_dla_okresu_prosty(
uczelnia,
jednostka,
autor_jan_nowak,
Expand Down Expand Up @@ -57,7 +57,7 @@ def test_oblicz_liczby_n_dla_ewaluacji_2022_2025_prosty(
uczelnia.przydzielaj_1_slot_gdy_udzial_mniejszy = zaokraglaj
uczelnia.save()

oblicz_liczby_n_dla_ewaluacji_2022_2025(uczelnia)
oblicz_liczby_n_dla_okresu(uczelnia)

assert (
IloscUdzialowDlaAutoraZaRok.objects.get(autor=autor_jan_nowak).ilosc_udzialow
Expand Down Expand Up @@ -351,7 +351,7 @@ def test_autor_typu_z_ma_udzialy_zero(
)

# Uruchom obliczenia
oblicz_liczby_n_dla_ewaluacji_2022_2025(uczelnia)
oblicz_liczby_n_dla_okresu(uczelnia)

# Sprawdź że autor ma wpis w IloscUdzialowDlaAutoraZaRok
assert IloscUdzialowDlaAutoraZaRok.objects.filter(autor=autor, rok=2025).exists()
Expand Down Expand Up @@ -396,15 +396,15 @@ def test_autor_typu_z_nie_wliczany_do_liczby_n(
)

# Uruchom obliczenia
oblicz_liczby_n_dla_ewaluacji_2022_2025(uczelnia)
oblicz_liczby_n_dla_okresu(uczelnia)

# Sprawdź że wszystkie 25 autorów ma wpisy
assert IloscUdzialowDlaAutoraZaRok.objects.filter(rok=2025).count() == 25

# Sprawdź że liczba N = 15 (tylko autorzy typu N)
from ewaluacja_liczba_n.utils import oblicz_liczbe_n_na_koniec_2025
from ewaluacja_liczba_n.utils import oblicz_liczbe_n_na_koniec_okresu

liczby_n_2025 = oblicz_liczbe_n_na_koniec_2025(uczelnia)
liczby_n_2025 = oblicz_liczbe_n_na_koniec_okresu(uczelnia)
assert liczby_n_2025.get(dyscyplina1.id, 0) == Decimal("15.0")


Expand Down Expand Up @@ -440,7 +440,7 @@ def test_autor_zmienia_typ_z_n_na_z(
)

# Uruchom obliczenia
oblicz_liczby_n_dla_ewaluacji_2022_2025(uczelnia)
oblicz_liczby_n_dla_okresu(uczelnia)

# Sprawdź że w 2024 ma udziały > 0
udzial_2024 = IloscUdzialowDlaAutoraZaRok.objects.get(autor=autor, rok=2024)
Expand Down
Loading