fix: normalize K1 reference checksum for clean Linux checkouts
This commit is contained in:
1 parent
00c818e681
commit
86f0ee1137
10 files changed
+49
-13
No files matched your search
@@ -1,2 +1,4 @@
|
||||
app/data/council_articles.csv text eol=lf
|
||||
app/data/council_articles.json text eol=lf
|
||||
app/data/k1_journals.json text eol=lf
|
||||
app/data/k1_specialties.json text eol=lf
|
||||
@@ -155,7 +155,7 @@ node --check app/static/admin.js
|
||||
|
||||
Тесты с данными используют временную SQLite; отдельные API smoke-тесты запускают приложение с его текущей конфигурацией. Браузерная проверка: `pip install playwright`, `python -m playwright install chromium`, затем `python tests/browser_admin.py`. Она проверяет реальный рендеринг страниц, клавиатуру, диалог, фильтры и восстановление прогресса после ошибки. Скриншоты сохраняются во временную папку; путь выводится в конце.
|
||||
|
||||
Версия сервиса: `0.15.3`. Админка всегда показывает версии backend и frontend в footer.
|
||||
Версия сервиса: `0.15.4`. Админка всегда показывает версии backend и frontend в footer.
|
||||
|
||||
## Разовый импорт публикаций
|
||||
|
||||
@@ -196,7 +196,9 @@ python scripts/extract_k1_specialties.py "app/data/ВАК Перечень.pdf"
|
||||
Скрипт проверяет последовательность всех 3231 строк. Проверенные различия названий
|
||||
сохранены в `scripts/k1_specialty_matches.json`; каждое сопоставление проверяется по
|
||||
номеру строки и точному исходному названию. Нечёткий поиск не применяется.
|
||||
Сервис читает готовый JSON и не требует PDF или `pdfplumber` при запуске.
|
||||
Сервис читает готовый JSON и не требует PDF или `pdfplumber` при запуске. Хеш
|
||||
`k1_source_sha256` рассчитывается для исходного JSON с окончаниями строк LF; оба
|
||||
JSON-справочника записываются с LF для воспроизводимости на Windows и Linux.
|
||||
|
||||
### Подготовка публикаций для портала
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"source": "ВАК Перечень.pdf",
|
||||
"source_sha256": "d16ab69668fb4c4cab80eb75809928cca63b447df5ee246e4f9d56cae4d3273d",
|
||||
"as_of": "2026-09-15",
|
||||
"k1_source_sha256": "ae6c18ae283d89b7703264bd0047f0466207fd535b59c8c50288f0315c546392",
|
||||
"k1_source_sha256": "f0f4514b132e486e821ae64c92cc23dccc55b641d590946de53bc7199e6f4caa",
|
||||
"k1_source_hash_eol": "LF",
|
||||
"source_rows": 3231,
|
||||
"specialties": [
|
||||
{
|
||||
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
APP_VERSION = "0.15.3"
|
||||
FRONTEND_VERSION = "0.15.3"
|
||||
BACKEND_VERSION = "0.15.3"
|
||||
APP_VERSION = "0.15.4"
|
||||
FRONTEND_VERSION = "0.15.4"
|
||||
BACKEND_VERSION = "0.15.4"
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "miem-workers"
|
||||
version = "0.15.3"
|
||||
version = "0.15.4"
|
||||
description = "MIEM employees parser, admin API, and web admin"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
|
||||
@@ -54,5 +54,5 @@ if __name__ == "__main__":
|
||||
parser.add_argument("--output", type=Path, default=Path("app/data/k1_journals.json"))
|
||||
args = parser.parse_args()
|
||||
result = extract(args.source)
|
||||
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", newline="\n")
|
||||
print(f"Saved {len(result['journals'])} K1 journals; verified {result['source_rows']} source rows")
|
||||
@@ -169,8 +169,8 @@ if __name__ == "__main__":
|
||||
entries = build_reference(journals, k1, json.loads(args.matches.read_text(encoding="utf-8")), args.as_of)
|
||||
result = {"schema_version": 1, "source": args.source.name,
|
||||
"source_sha256": hashlib.sha256(args.source.read_bytes()).hexdigest(), "as_of": args.as_of,
|
||||
"k1_source_sha256": hashlib.sha256(args.k1.read_bytes()).hexdigest(), "source_rows": len(journals),
|
||||
"k1_source_sha256": hashlib.sha256(args.k1.read_bytes().replace(b"\r\n", b"\n")).hexdigest(), "k1_source_hash_eol": "LF", "source_rows": len(journals),
|
||||
"specialties": [{"code": code, "name": name} for code, name in SPECIALTIES], "journals": entries}
|
||||
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", newline="\n")
|
||||
from collections import Counter
|
||||
print(len(entries), dict(Counter(j['status'] for j in entries)))
|
||||
+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.15.3"
|
||||
assert response.json()["backend_version"] == "0.15.4"
|
||||
|
||||
|
||||
def test_api_employees_and_stats_require_admin_session():
|
||||
|
||||
@@ -215,7 +215,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.15.3 · Frontend 0.15.3" in response.text
|
||||
assert "Backend 0.15.4 · Frontend 0.15.4" 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
|
||||
@@ -519,7 +519,7 @@ def test_specialty_reference_integrity():
|
||||
from scripts.extract_k1_specialties import active_codes
|
||||
data = json.loads(council.K1_SPECIALTIES.read_text(encoding="utf-8"))
|
||||
k1 = json.loads(council.K1_JOURNALS.read_text(encoding="utf-8"))
|
||||
assert data["k1_source_sha256"] == hashlib.sha256(council.K1_JOURNALS.read_bytes()).hexdigest()
|
||||
assert data["k1_source_sha256"] == hashlib.sha256(council.K1_JOURNALS.read_bytes().replace(b"\r\n", b"\n")).hexdigest()
|
||||
assert [j["name"] for j in data["journals"]] == [j["name"] for j in k1["journals"]]
|
||||
assert len(data["journals"]) == 773 and len(data["specialties"]) == 12
|
||||
assert data["source_rows"] == 3231
|
||||
|
||||
@@ -36,3 +36,34 @@ def test_malformed_source_start_date_is_preserved_without_inventing_date():
|
||||
assert assignment["source_dates"] == "с 01.02.200"
|
||||
assert assignment["valid_from"] is None and assignment["date_note"]
|
||||
assert active_codes(journal, "2026-09-15") == ["1.3.8"]
|
||||
|
||||
|
||||
def test_k1_extraction_is_reproducible_for_lf_and_crlf(tmp_path):
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
source = tmp_path / 'list.pdf'
|
||||
source.write_bytes(b'PDF source bytes for cached-row extraction')
|
||||
rows = tmp_path / 'rows.json'
|
||||
rows.write_text(json.dumps([{'page': 1, 'cells': ['1.', 'Journal', '1234-5678', '1.2.2. Test', 'с 01.02.2022']}]), encoding='utf8')
|
||||
matches = tmp_path / 'matches.json'
|
||||
matches.write_text('[]', encoding='utf8')
|
||||
canonical = (json.dumps({'journals': [{'name': 'Journal'}]}, indent=2) + '\n').encode('utf8')
|
||||
outputs = []
|
||||
for name, content in [('lf', canonical), ('crlf', canonical.replace(b'\n', b'\r\n'))]:
|
||||
directory = tmp_path / name
|
||||
directory.mkdir()
|
||||
k1 = directory / 'k1.json'
|
||||
k1.write_bytes(content)
|
||||
output = directory / 'specialties.json'
|
||||
subprocess.run([sys.executable, 'scripts/extract_k1_specialties.py', str(source), '--k1', str(k1),
|
||||
'--matches', str(matches), '--rows-cache', str(rows), '--output', str(output)], check=True, capture_output=True)
|
||||
data = json.loads(output.read_text(encoding='utf8'))
|
||||
assert data['k1_source_sha256'] == hashlib.sha256(canonical).hexdigest()
|
||||
assert data['source_sha256'] == hashlib.sha256(source.read_bytes()).hexdigest()
|
||||
assert data['journals'][0]['specialty_codes'] == ['1.2.2']
|
||||
assert k1.read_bytes() == content
|
||||
assert b'\r\n' not in output.read_bytes()
|
||||
outputs.append(output.read_bytes())
|
||||
assert outputs[0] == outputs[1]
|
||||
Reference in new issue
Block a user