fix: keep council criteria tied to source publication years
This commit is contained in:
1 parent
0af278247a
commit
30ea1c9257
5 files changed
+44
-15
No files matched your search
+20
-9
@@ -163,27 +163,38 @@ def _unique_articles(articles: list[dict], start: date, end: date) -> list[dict]
|
||||
key = identifier or fallback
|
||||
if key not in unique:
|
||||
unique[key] = {**article, "doi": identifier,
|
||||
**{field: list(article[field]) for field in ("types", "quartiles", "journals", "origins", "rows")}}
|
||||
**{field: list(article[field]) for field in ("types", "quartiles", "journals", "origins", "rows")},
|
||||
"evidence": []}
|
||||
merged = unique[key]
|
||||
merged["evidence"].append(article)
|
||||
for field in ("types", "quartiles", "journals", "origins", "rows"):
|
||||
merged[field] = list(dict.fromkeys([*merged[field], *article[field]]))
|
||||
merged["in_csv"] |= article["in_csv"]
|
||||
merged["in_db"] |= article["in_db"]
|
||||
merged["url"] = merged["url"] or article["url"]
|
||||
merged["year"] = merged["year"] or article["year"]
|
||||
for article in unique.values():
|
||||
year = article["year"]
|
||||
evidence = article.pop("evidence")
|
||||
years = sorted({item["year"] for item in evidence if item["year"] is not None})
|
||||
# Never pick a conflicting source year merely because that row came first.
|
||||
year = article["year"] = years[0] if len(years) == 1 else None
|
||||
article["criteria"] = list(article["quartiles"])
|
||||
matches = list(dict.fromkeys(k1_journals[key] for name in article["journals"]
|
||||
if (key := _journal_key(name)) in k1_journals))
|
||||
article["criteria"].extend(f"К1 · {name} (справочник ВАК)" for name in matches)
|
||||
eligible = (year is not None and start.year <= year <= end.year
|
||||
and (not article["types"] or article["types"] == ["Статья"]))
|
||||
article["q1"] = eligible and any(q.endswith(" Q1") for q in article["quartiles"])
|
||||
article["q2"] = eligible and any(q.endswith(" Q2") for q in article["quartiles"])
|
||||
article["k1"] = eligible and bool(matches)
|
||||
eligible_type = not article["types"] or article["types"] == ["Статья"]
|
||||
article["q1"] = article["q2"] = article["k1"] = False
|
||||
for item in evidence:
|
||||
source_year = item["year"] if item["year"] is not None else year
|
||||
if not eligible_type or source_year is None or not start.year <= source_year <= end.year:
|
||||
continue
|
||||
article["q1"] |= any(q.endswith(" Q1") for q in item["quartiles"])
|
||||
article["q2"] |= any(q.endswith(" Q2") for q in item["quartiles"])
|
||||
article["k1"] |= any(_journal_key(name) in k1_journals for name in item["journals"])
|
||||
article["qualifies"] = article["q1"] or article["q2"] or article["k1"]
|
||||
if year is None:
|
||||
if len(years) > 1:
|
||||
article["criteria"].append("Годы источников расходятся: " + ", ".join(map(str, years))
|
||||
+ "; критерии учитываются по году каждой исходной записи")
|
||||
elif year is None:
|
||||
article["criteria"].append("Год не указан — период не подтверждён")
|
||||
elif start.year <= year <= end.year:
|
||||
article["criteria"].append("В пределах пяти лет по году; точную дату нужно проверить" if year in {start.year, end.year}
|
||||
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
APP_VERSION = "0.14.0"
|
||||
FRONTEND_VERSION = "0.14.0"
|
||||
BACKEND_VERSION = "0.14.0"
|
||||
APP_VERSION = "0.14.1"
|
||||
FRONTEND_VERSION = "0.14.1"
|
||||
BACKEND_VERSION = "0.14.1"
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "miem-workers"
|
||||
version = "0.14.0"
|
||||
version = "0.14.1"
|
||||
description = "MIEM employees parser, admin API, and web admin"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ def test_health_returns_versions():
|
||||
response = TestClient(app).get("/api/health")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["backend_version"] == "0.14.0"
|
||||
assert response.json()["backend_version"] == "0.14.1"
|
||||
|
||||
|
||||
def test_api_employees_and_stats_require_admin_session():
|
||||
|
||||
+19
-1
@@ -211,7 +211,7 @@ def test_public_page_data_minimization_and_safe_links(db_session, export):
|
||||
assert "<script>" in response.text and "<script>" not in response.text
|
||||
assert "javascript:" not in response.text and "private@example.org" not in response.text
|
||||
assert "/admin/employees/" not in response.text and "/admin/logout" not in response.text
|
||||
assert "Backend 0.14.0 · Frontend 0.14.0" in response.text
|
||||
assert "Backend 0.14.1 · Frontend 0.14.1" in response.text
|
||||
assert "Вход в админку" in response.text
|
||||
assert client.get("/admin/directory", follow_redirects=False).status_code in (302, 303, 401, 403)
|
||||
assert "Сотрудники не найдены" in client.get("/admin/council?q=missing").text
|
||||
@@ -350,6 +350,24 @@ def test_k1_reference_integrity():
|
||||
assert "бизнес информатика" in council._k1_journals()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reverse", [False, True])
|
||||
def test_conflicting_source_years_keep_categories_with_their_year(db_session, export, reverse):
|
||||
rows = [row(year="2020", **{"Название журнала": "Бизнес-информатика"}),
|
||||
row(year="2024", **{"Квартиль scopus": "Q2"})]
|
||||
export(list(reversed(rows)) if reverse else rows)
|
||||
person = employee(db_session)
|
||||
# Evidence with no year must not inherit an arbitrary conflicting year.
|
||||
publication(db_session, person, doi="https://doi.org/10.1234/shared", year=None,
|
||||
raw_data={"csv_enrichment": {"Квартиль scopus": "Q1", "Журнал": "Бизнес-информатика"}})
|
||||
db_session.commit()
|
||||
candidate = council.council_candidates(db_session, date(2026, 10, 8))["candidates"][0]
|
||||
assert [candidate[f"{key}_count"] for key in ("q1", "q2", "k1", "total")] == [0, 1, 0, 1]
|
||||
assert candidate["count"] == 1
|
||||
article = candidate["articles"][0]
|
||||
assert article["year"] is None
|
||||
assert any("Годы источников расходятся: 2020, 2024" in criterion for criterion in article["criteria"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sort", ["q1_count", "q2_count", "k1_count", "total_count"])
|
||||
@pytest.mark.parametrize("direction", ["asc", "desc"])
|
||||
def test_sort_category_counters_and_xlsx(db_session, export, sort, direction):
|
||||
|
||||
Reference in new issue
Block a user