import { describe, expect, it } from 'vitest' import { tag } from '@/lib/fristen/datum' import { anteilCent, leistungszuschlagBerechnen, stufeFuerMonate, verweildauerMonate, } from '@/lib/pflege/leistungszuschlag' import { LEISTUNGSZUSCHLAG_STAFFEL, LEISTUNGSZUSCHLAG_STAND } from '@/lib/pflege/konstanten' import { abwesenheitstageJeJahr, abwesenheitstageGesamt, tageEines } from '@/lib/pflege/abwesenheit' import { exazerbationenJeJahr, krankenhaustageJeJahr, verlaufAuswerten, verlaufZusammenfassung, verschlechterungErkannt, } from '@/lib/pflege/verlauf' import { impfstatusBerechnen, impfungErkennen, offeneImpfungen } from '@/lib/pflege/impfungen' import { detailsFormatieren, detailsLesen } from '@/lib/pflege/ereignis-details' describe('Leistungszuschlag nach § 43c SGB XI', () => { it('führt ein Stand-Datum mit, weil der Gesetzgeber die Staffel ändert', () => { expect(LEISTUNGSZUSCHLAG_STAND).toBe('01.01.2024') expect(LEISTUNGSZUSCHLAG_STAFFEL.map((s) => s.prozent)).toEqual([15, 30, 50, 75]) }) it('zählt die vollendeten Monate seit dem Einzug', () => { expect(verweildauerMonate(tag(2026, 1, 15), tag(2026, 1, 15))).toBe(0) expect(verweildauerMonate(tag(2026, 1, 15), tag(2026, 2, 14))).toBe(0) expect(verweildauerMonate(tag(2026, 1, 15), tag(2026, 2, 15))).toBe(1) expect(verweildauerMonate(tag(2026, 1, 31), tag(2026, 3, 30))).toBe(1) }) it('trifft die Stufengrenzen genau', () => { expect(stufeFuerMonate(0).prozent).toBe(15) expect(stufeFuerMonate(11).prozent).toBe(15) expect(stufeFuerMonate(12).prozent).toBe(30) expect(stufeFuerMonate(23).prozent).toBe(30) expect(stufeFuerMonate(24).prozent).toBe(50) expect(stufeFuerMonate(35).prozent).toBe(50) expect(stufeFuerMonate(36).prozent).toBe(75) expect(stufeFuerMonate(240).prozent).toBe(75) }) it('springt am Tag des Jahrestages auf die nächste Stufe', () => { const einzugAm = tag(2025, 1, 15) const eigenanteilCent = 150_000n // 1.500,00 Euro const vorher = leistungszuschlagBerechnen({ einzugAm, eigenanteilCent, stichtag: tag(2026, 1, 14), }) const nachher = leistungszuschlagBerechnen({ einzugAm, eigenanteilCent, stichtag: tag(2026, 1, 15), }) expect(vorher.prozent).toBe(15) expect(vorher.zuschlagCent).toBe(22_500n) expect(nachher.prozent).toBe(30) expect(nachher.zuschlagCent).toBe(45_000n) }) it('sagt, ab wann die nächste Stufe greift und was sie bringt', () => { const ergebnis = leistungszuschlagBerechnen({ einzugAm: tag(2026, 1, 15), eigenanteilCent: 150_000n, stichtag: tag(2026, 8, 22), }) expect(ergebnis.naechsteStufe?.stufe.prozent).toBe(30) expect(ergebnis.naechsteStufe?.abDatum).toEqual(tag(2027, 1, 15)) expect(ergebnis.naechsteStufe?.mehrCent).toBe(22_500n) expect(ergebnis.naechsteStufe?.tageBis).toBeGreaterThan(0) }) it('hat ab dem vierten Jahr keine nächste Stufe mehr', () => { const ergebnis = leistungszuschlagBerechnen({ einzugAm: tag(2020, 1, 1), eigenanteilCent: 150_000n, stichtag: tag(2026, 1, 1), }) expect(ergebnis.prozent).toBe(75) expect(ergebnis.naechsteStufe).toBeNull() }) it('rechnet ohne Einzugsdatum nicht, sondern sagt warum', () => { const ergebnis = leistungszuschlagBerechnen({ einzugAm: null, eigenanteilCent: 150_000n }) expect(ergebnis.berechenbar).toBe(false) expect(ergebnis.hinweise[0]).toMatch(/Einzugsdatum/) }) it('weist darauf hin, dass der Zuschlag nur den Pflegeanteil betrifft', () => { const ergebnis = leistungszuschlagBerechnen({ einzugAm: tag(2025, 1, 15), eigenanteilCent: 150_000n, stichtag: tag(2026, 3, 1), }) expect(ergebnis.hinweise.join(' ')).toMatch(/Unterkunft, Verpflegung/) }) it('rundet Prozentanteile kaufmännisch auf ganze Cent', () => { expect(anteilCent(101n, 15)).toBe(15n) expect(anteilCent(100_000n, 30)).toBe(30_000n) expect(anteilCent(333n, 15)).toBe(50n) }) it('warnt bei Pflegegrad 1, dass § 43c nicht greift', () => { const ergebnis = leistungszuschlagBerechnen({ einzugAm: tag(2025, 1, 15), eigenanteilCent: 150_000n, pflegegrad: 1, stichtag: tag(2026, 3, 1), }) expect(ergebnis.hinweise.join(' ')).toMatch(/Pflegegrad 2/) }) }) describe('Abwesenheitstage', () => { it('summiert die Tage je Kalenderjahr', () => { const jahre = abwesenheitstageJeJahr([ { datum: tag(2026, 2, 3), bisDatum: tag(2026, 2, 13), abwesenheitstage: null, art: 'KRANKENHAUS' }, { datum: tag(2026, 7, 1), bisDatum: tag(2026, 7, 8), abwesenheitstage: null, art: 'KRANKENHAUS' }, { datum: tag(2025, 11, 4), bisDatum: tag(2025, 11, 9), abwesenheitstage: null, art: 'KRANKENHAUS' }, ]) expect(jahre).toEqual([ { jahr: 2026, tage: 17, ereignisse: 2 }, { jahr: 2025, tage: 5, ereignisse: 1 }, ]) expect(abwesenheitstageGesamt(jahre)).toBe(22) }) it('teilt einen Aufenthalt über den Jahreswechsel auf beide Jahre auf', () => { const jahre = abwesenheitstageJeJahr([ { datum: tag(2025, 12, 28), bisDatum: tag(2026, 1, 4), abwesenheitstage: null, art: 'KRANKENHAUS' }, ]) expect(jahre.find((j) => j.jahr === 2025)?.tage).toBe(4) expect(jahre.find((j) => j.jahr === 2026)?.tage).toBe(3) expect(abwesenheitstageGesamt(jahre)).toBe(7) }) it('lässt eine von Hand eingetragene Tageszahl unangetastet', () => { const jahre = abwesenheitstageJeJahr([ { datum: tag(2026, 2, 3), bisDatum: tag(2026, 2, 13), abwesenheitstage: 9, art: 'KRANKENHAUS' }, ]) expect(jahre[0]?.tage).toBe(9) }) it('zählt einen laufenden Aufenthalt ohne Entlassdatum nicht mit', () => { const jahre = abwesenheitstageJeJahr([ { datum: tag(2026, 8, 1), bisDatum: null, abwesenheitstage: null, art: 'KRANKENHAUS' }, ]) expect(jahre).toEqual([]) }) it('rechnet die Tage eines einzelnen Ereignisses', () => { expect( tageEines({ datum: tag(2026, 2, 3), bisDatum: tag(2026, 2, 13), abwesenheitstage: null, art: 'KRANKENHAUS' }), ).toBe(10) expect( tageEines({ datum: tag(2026, 2, 3), bisDatum: null, abwesenheitstage: 4, art: 'KRANKENHAUS' }), ).toBe(4) }) }) describe('Gesundheitsverlauf bei COPD', () => { const ereignisse = [ { datum: tag(2025, 1, 20), bisDatum: null, art: 'EXAZERBATION' as const, abwesenheitstage: null }, { datum: tag(2025, 11, 2), bisDatum: null, art: 'EXAZERBATION' as const, abwesenheitstage: null }, { datum: tag(2026, 1, 5), bisDatum: null, art: 'EXAZERBATION' as const, abwesenheitstage: null }, { datum: tag(2026, 2, 3), bisDatum: null, art: 'EXAZERBATION' as const, abwesenheitstage: null }, { datum: tag(2026, 2, 3), bisDatum: tag(2026, 2, 13), art: 'KRANKENHAUS' as const, abwesenheitstage: null }, { datum: tag(2026, 5, 9), bisDatum: null, art: 'STURZ' as const, abwesenheitstage: null }, { datum: tag(2026, 9, 14), bisDatum: null, art: 'EXAZERBATION' as const, abwesenheitstage: null }, { datum: tag(2026, 6, 1), bisDatum: null, art: 'IMPFUNG' as const, abwesenheitstage: null }, ] it('zählt Exazerbationen je Jahr', () => { expect(exazerbationenJeJahr(ereignisse)).toEqual([ { jahr: 2026, anzahl: 3 }, { jahr: 2025, anzahl: 2 }, ]) }) it('zählt Krankenhaustage je Jahr', () => { expect(krankenhaustageJeJahr(ereignisse)).toEqual([{ jahr: 2026, tage: 10 }]) }) it('stellt Schübe, Krankenhaustage und Stürze je Jahr zusammen', () => { const jahre = verlaufAuswerten(ereignisse) const zweitausendsechsundzwanzig = jahre.find((j) => j.jahr === 2026)! expect(zweitausendsechsundzwanzig.exazerbationen).toBe(3) expect(zweitausendsechsundzwanzig.krankenhausaufenthalte).toBe(1) expect(zweitausendsechsundzwanzig.krankenhaustage).toBe(10) expect(zweitausendsechsundzwanzig.stuerze).toBe(1) expect(zweitausendsechsundzwanzig.ereignisseGesamt).toBe(6) expect(jahre.find((j) => j.jahr === 2025)!.exazerbationen).toBe(2) }) it('erkennt die Zunahme gegenüber dem Vorjahr als Argument für eine Neubegutachtung', () => { const jahre = verlaufAuswerten(ereignisse) const befund = verschlechterungErkannt(jahre, 2026) expect(befund.erkannt).toBe(true) expect(befund.begruendung).toMatch(/Zunahme/) }) it('erkennt keine Zunahme, wenn kein Vorjahr erfasst ist', () => { expect(verschlechterungErkannt(verlaufAuswerten(ereignisse), 2025).erkannt).toBe(false) }) it('formuliert einen Satz für den Jahresbericht', () => { const jahre = verlaufAuswerten(ereignisse) const satz = verlaufZusammenfassung(jahre.find((j) => j.jahr === 2026)!) expect(satz).toContain('2026') expect(satz).toContain('3 akute Verschlechterungen') expect(satz).toContain('10 Tagen') }) }) describe('Impfungen bei COPD', () => { it('erkennt die Impfung am Titel', () => { expect(impfungErkennen('Grippeimpfung 2026')).toBe('grippe') expect(impfungErkennen('Pneumokokken (PCV20)')).toBe('pneumokokken') expect(impfungErkennen('COVID-19 Auffrischung')).toBe('covid') expect(impfungErkennen('RSV-Impfung')).toBe('rsv') expect(impfungErkennen('Zahnarzt')).toBeNull() }) it('meldet eine nicht dokumentierte Impfung als unbekannt', () => { const status = impfstatusBerechnen([], tag(2026, 8, 22)) expect(status).toHaveLength(4) expect(status.every((s) => s.lage === 'unbekannt')).toBe(true) expect(offeneImpfungen(status)).toHaveLength(4) }) it('macht die Grippeimpfung nach einem Jahr wieder fällig, frühestens im Oktober', () => { const status = impfstatusBerechnen( [{ datum: tag(2025, 10, 8), titel: 'Grippeimpfung' }], tag(2026, 8, 22), ) const grippe = status.find((s) => s.empfehlung.schluessel === 'grippe')! expect(grippe.faelligAm).toEqual(tag(2026, 10, 8)) expect(grippe.lage).toBe('bald') }) it('führt die einmalige Pneumokokkenimpfung als erledigt', () => { const status = impfstatusBerechnen( [{ datum: tag(2024, 3, 1), titel: 'Pneumokokken PCV20' }], tag(2026, 8, 22), ) const pneumo = status.find((s) => s.empfehlung.schluessel === 'pneumokokken')! expect(pneumo.lage).toBe('erledigt') expect(pneumo.faelligAm).toBeNull() }) }) describe('Zusatzangaben der Schnellerfassung', () => { it('schreibt Schweregrad und Auslöser in die verschlüsselte Beschreibung', () => { const text = detailsFormatieren( 'EXAZERBATION', { schweregrad: 'schwer', ausloeser: 'Atemwegsinfekt', stationaer: 'ja' }, 'Notarzt gerufen.', ) expect(text).toContain('Schweregrad: schwer') expect(text).toContain('Auslöser: Atemwegsinfekt') expect(text).toContain('Notarzt gerufen.') }) it('liest die Angaben verlustfrei zurück', () => { const text = detailsFormatieren( 'SAUERSTOFFVERSORGUNG', { fluss: '2', stundenProTag: '16', geraet: 'Konzentrator Philips' }, 'Nachts durchgehend.', ) const gelesen = detailsLesen('SAUERSTOFFVERSORGUNG', text) expect(gelesen.werte.fluss).toBe('2') expect(gelesen.werte.stundenProTag).toBe('16') expect(gelesen.werte.geraet).toBe('Konzentrator Philips') expect(gelesen.freitext).toBe('Nachts durchgehend.') }) it('gibt null zurück, wenn nichts eingetragen wurde', () => { expect(detailsFormatieren('STURZ', {}, null)).toBeNull() }) })