mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 18:23:42 +00:00
feat: use KVNR instead of Nachname in fall_id generation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d33fc7d242
commit
a436580b03
2 changed files with 68 additions and 27 deletions
|
|
@ -1,13 +1,15 @@
|
|||
"""Import service for DAK Zweitmeinungs-Portal.
|
||||
|
||||
Handles:
|
||||
- fall_id generation: YYYY-KW02d-fallgruppe-Nachname
|
||||
- fall_id generation: YYYY-KW02d-fallgruppe-KVNR (or random suffix)
|
||||
- Duplicate detection: by fall_id or (nachname, fallgruppe, datum, vorname, geburtsdatum)
|
||||
- Preview/confirm flow: preview_import() checks for duplicates, confirm_import() inserts
|
||||
- Import logging: writes ImportLog entry on each confirmed import
|
||||
"""
|
||||
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
|
@ -19,15 +21,24 @@ from app.services.csv_parser import ParsedCase
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def generate_random_suffix(length: int = 6) -> str:
|
||||
"""Generate a random alphanumeric suffix (uppercase + digits)."""
|
||||
charset = string.ascii_uppercase + string.digits
|
||||
return "".join(random.choices(charset, k=length))
|
||||
|
||||
|
||||
def generate_fall_id(parsed: ParsedCase) -> str:
|
||||
"""Generate unique fall_id: YYYY-KW02d-fallgruppe-Nachname.
|
||||
"""Generate unique fall_id: YYYY-KW02d-fallgruppe-KVNR.
|
||||
|
||||
Uses KVNR as identifier. Falls back to 6-char random suffix if
|
||||
KVNR is missing or empty.
|
||||
|
||||
Examples:
|
||||
- 2026-06-onko-Tonn
|
||||
- 2026-12-kardio-Mueller
|
||||
- 2026-06-intensiv-Daum
|
||||
- 2026-06-onko-A123456789
|
||||
- 2026-12-kardio-X7K9M2 (random fallback)
|
||||
"""
|
||||
return f"{parsed.jahr}-{parsed.kw:02d}-{parsed.fallgruppe}-{parsed.nachname}"
|
||||
suffix = parsed.kvnr if parsed.kvnr else generate_random_suffix()
|
||||
return f"{parsed.jahr}-{parsed.kw:02d}-{parsed.fallgruppe}-{suffix}"
|
||||
|
||||
|
||||
def check_duplicate(db: Session, parsed: ParsedCase) -> bool:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from app.services.import_service import (
|
|||
check_duplicate,
|
||||
confirm_import,
|
||||
generate_fall_id,
|
||||
generate_random_suffix,
|
||||
preview_import,
|
||||
)
|
||||
|
||||
|
|
@ -55,10 +56,10 @@ def _make_parsed_case(
|
|||
|
||||
class TestGenerateFallId:
|
||||
def test_format(self):
|
||||
"""fall_id matches YYYY-KW-fallgruppe-Nachname format."""
|
||||
pc = _make_parsed_case(nachname="Tonn", fallgruppe="onko", jahr=2026, kw=6)
|
||||
"""fall_id matches YYYY-KW-fallgruppe-KVNR format."""
|
||||
pc = _make_parsed_case(nachname="Tonn", kvnr="D410126355", fallgruppe="onko", jahr=2026, kw=6)
|
||||
result = generate_fall_id(pc)
|
||||
assert result == "2026-06-onko-Tonn"
|
||||
assert result == "2026-06-onko-D410126355"
|
||||
|
||||
def test_kw_padding_single_digit(self):
|
||||
"""KW < 10 is zero-padded to 2 digits."""
|
||||
|
|
@ -79,31 +80,40 @@ class TestGenerateFallId:
|
|||
assert "-01-" in result
|
||||
|
||||
def test_different_cases_produce_different_ids(self):
|
||||
"""Different patients/fallgruppen produce unique fall_ids."""
|
||||
pc1 = _make_parsed_case(nachname="Tonn", fallgruppe="onko")
|
||||
pc2 = _make_parsed_case(nachname="Daum", fallgruppe="intensiv")
|
||||
pc3 = _make_parsed_case(nachname="Tonn", fallgruppe="kardio")
|
||||
|
||||
"""Different KVNRs/fallgruppen produce unique fall_ids."""
|
||||
pc1 = _make_parsed_case(kvnr="A111111111", fallgruppe="onko")
|
||||
pc2 = _make_parsed_case(kvnr="B222222222", fallgruppe="intensiv")
|
||||
pc3 = _make_parsed_case(kvnr="A111111111", fallgruppe="kardio")
|
||||
ids = {generate_fall_id(pc1), generate_fall_id(pc2), generate_fall_id(pc3)}
|
||||
assert len(ids) == 3
|
||||
|
||||
def test_same_patient_same_week_same_fallgruppe(self):
|
||||
"""Same patient in same week and fallgruppe produces same fall_id."""
|
||||
pc1 = _make_parsed_case(nachname="Mueller", fallgruppe="onko", kw=8)
|
||||
pc2 = _make_parsed_case(nachname="Mueller", fallgruppe="onko", kw=8)
|
||||
"""Same KVNR in same week and fallgruppe produces same fall_id."""
|
||||
pc1 = _make_parsed_case(kvnr="A111111111", fallgruppe="onko", kw=8)
|
||||
pc2 = _make_parsed_case(kvnr="A111111111", fallgruppe="onko", kw=8)
|
||||
assert generate_fall_id(pc1) == generate_fall_id(pc2)
|
||||
|
||||
def test_umlauts_preserved(self):
|
||||
"""German umlauts in Nachname are preserved in fall_id."""
|
||||
pc = _make_parsed_case(nachname="Krölls", fallgruppe="onko")
|
||||
def test_random_suffix_when_no_kvnr(self):
|
||||
"""fall_id uses 6-char random suffix when KVNR is missing."""
|
||||
pc = _make_parsed_case(kvnr=None, fallgruppe="onko", jahr=2026, kw=6)
|
||||
result = generate_fall_id(pc)
|
||||
assert "Krölls" in result
|
||||
parts = result.split("-")
|
||||
assert parts[0] == "2026"
|
||||
assert parts[1] == "06"
|
||||
assert parts[2] == "onko"
|
||||
suffix = parts[3]
|
||||
assert len(suffix) == 6
|
||||
assert suffix.isalnum()
|
||||
assert suffix == suffix.upper()
|
||||
|
||||
def test_hyphenated_name(self):
|
||||
"""Hyphenated names are preserved in fall_id."""
|
||||
pc = _make_parsed_case(nachname="Hähle-Jakelski", fallgruppe="sd")
|
||||
def test_random_suffix_when_empty_kvnr(self):
|
||||
"""fall_id uses random suffix when KVNR is empty string."""
|
||||
pc = _make_parsed_case(kvnr="", fallgruppe="onko", jahr=2026, kw=6)
|
||||
result = generate_fall_id(pc)
|
||||
assert "Hähle-Jakelski" in result
|
||||
parts = result.split("-")
|
||||
suffix = parts[3]
|
||||
assert len(suffix) == 6
|
||||
assert suffix.isalnum()
|
||||
|
||||
def test_all_fallgruppen(self):
|
||||
"""fall_id works for all valid fallgruppen."""
|
||||
|
|
@ -120,6 +130,26 @@ class TestGenerateFallId:
|
|||
assert result.startswith("2027-")
|
||||
|
||||
|
||||
# ── generate_random_suffix tests ───────────────────────────────────────
|
||||
|
||||
|
||||
class TestGenerateRandomSuffix:
|
||||
def test_length(self):
|
||||
assert len(generate_random_suffix()) == 6
|
||||
|
||||
def test_charset(self):
|
||||
"""Only uppercase letters and digits."""
|
||||
import re
|
||||
for _ in range(50):
|
||||
s = generate_random_suffix()
|
||||
assert re.match(r'^[A-Z0-9]{6}$', s)
|
||||
|
||||
def test_uniqueness(self):
|
||||
"""Different calls produce different suffixes."""
|
||||
suffixes = {generate_random_suffix() for _ in range(20)}
|
||||
assert len(suffixes) >= 15
|
||||
|
||||
|
||||
# ── ImportRow schema tests ──────────────────────────────────────────────
|
||||
|
||||
|
||||
|
|
@ -149,10 +179,10 @@ class TestImportRowSchema:
|
|||
fallgruppe="kardio",
|
||||
datum=date(2026, 2, 2),
|
||||
is_duplicate=True,
|
||||
fall_id="2026-06-kardio-Tonn",
|
||||
fall_id="2026-06-kardio-D410126355",
|
||||
)
|
||||
assert row.is_duplicate is True
|
||||
assert row.fall_id == "2026-06-kardio-Tonn"
|
||||
assert row.fall_id == "2026-06-kardio-D410126355"
|
||||
|
||||
|
||||
# ── ImportPreview schema tests ──────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Reference in a new issue