Skip to content

Commit

Permalink
NAV-24038: Forskyve søkers vilkår basert på lovverk tidslinje (#1061)
Browse files Browse the repository at this point in the history
### 💰 Hva skal gjøres, og hvorfor?
For å kunne begynne å behandle saker med barn født i 2024, legger vi her
inn støtte for å kunne behandle alle fagsaker hvor ingen barn med ulike
lovverk overlapper.

To barn overlapper dersom det er mindre enn 7 måneder mellom de og et av
barna treffer lovverk før februar 2025 og det andre treffer lovverk
etter 2025. Dette er svært lite sannsynlig at vil kunne skje, og er
muligens noe vi aldri kommer til å støtte fullt ut.

Løsningen ligner litt på hvordan eksisterende forskyvning gjøres for
`LOVVERK_FØR_FEBRUAR_2025`. Vi forskyver søkers vilkår etter alle
lovverk og kombinerer resultatene ved å "klippe og lime" i overgangen
mellom ulike lovverk ved hjelp av en lovverk-tidslinje.

Nå er forskyvningen av søkers vilkår tett knyttet mot barnas vilkår, og
det må alltid finnes `PersonResultater` for søker og minst ett barn ved
forskyvning. En rekke tester er korrigert som følge av dette.


### ✅ Checklist
- [ ] Jeg har testet mine endringer i henhold til akseptansekriteriene
🕵️
- [ ] Jeg har config- eller sql-endringer.
- [x] Jeg har skrevet tester.

### 💬 Ønsker du en muntlig gjennomgang?
- [ ] Ja
- [ ] Nei
  • Loading branch information
bragejahren authored Jan 31, 2025
1 parent 8c64988 commit 48fa1a4
Show file tree
Hide file tree
Showing 19 changed files with 900 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import no.nav.familie.ks.sak.common.util.storForbokstav
import no.nav.familie.ks.sak.common.util.tilMånedÅr
import no.nav.familie.ks.sak.common.util.toLocalDate
import no.nav.familie.ks.sak.common.util.toYearMonth
import no.nav.familie.ks.sak.config.featureToggle.FeatureToggleConfig
import no.nav.familie.ks.sak.config.featureToggle.UnleashNextMedContextService
import no.nav.familie.ks.sak.integrasjon.familieintegrasjon.IntegrasjonClient
import no.nav.familie.ks.sak.integrasjon.sanity.SanityService
import no.nav.familie.ks.sak.kjerne.behandling.domene.Behandling
Expand Down Expand Up @@ -70,6 +72,7 @@ class VedtaksperiodeService(
private val integrasjonClient: IntegrasjonClient,
private val refusjonEøsRepository: RefusjonEøsRepository,
private val kompetanseService: KompetanseService,
private val unleashNextMedContextService: UnleashNextMedContextService,
) {
fun oppdaterVedtaksperiodeMedFritekster(
vedtaksperiodeId: Long,
Expand Down Expand Up @@ -407,6 +410,7 @@ class VedtaksperiodeService(
kompetanser = utfylteKompetanser,
andelerTilkjentYtelse = andeler,
overgangsordningAndeler = overgangsordningAndelService.hentOvergangsordningAndeler(behandling.id),
skalBestemmeLovverkBasertPåFødselsdato = unleashNextMedContextService.isEnabled(FeatureToggleConfig.STØTTER_LOVENDRING_2025),
).hentGyldigeBegrunnelserForVedtaksperiode(),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package no.nav.familie.ks.sak.kjerne.behandling.steg.vedtak.vedtaksperiode.utbetalingsperiodeMedBegrunnelser

import no.nav.familie.ks.sak.config.featureToggle.FeatureToggleConfig
import no.nav.familie.ks.sak.config.featureToggle.UnleashNextMedContextService
import no.nav.familie.ks.sak.kjerne.behandling.steg.vedtak.domene.Vedtak
import no.nav.familie.ks.sak.kjerne.behandling.steg.vedtak.vedtaksperiode.domene.VedtaksperiodeMedBegrunnelser
import no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.VilkårsvurderingService
Expand All @@ -15,6 +17,7 @@ class UtbetalingsperiodeMedBegrunnelserService(
private val andelerTilkjentYtelseOgEndreteUtbetalingerService: AndelerTilkjentYtelseOgEndreteUtbetalingerService,
private val personopplysningGrunnlagService: PersonopplysningGrunnlagService,
private val kompetanseService: KompetanseService,
private val unleashNextMedContextService: UnleashNextMedContextService,
) {
fun hentUtbetalingsperioder(
vedtak: Vedtak,
Expand All @@ -29,7 +32,10 @@ class UtbetalingsperiodeMedBegrunnelserService(
personopplysningGrunnlagService.hentAktivPersonopplysningGrunnlagThrows(behandlingId = vedtak.behandling.id)

val forskjøvetVilkårResultatTidslinjeMap =
vilkårsvurdering.personResultater.tilForskjøvetOppfylteVilkårResultatTidslinjeMap(personopplysningGrunnlag)
vilkårsvurdering.personResultater.tilForskjøvetOppfylteVilkårResultatTidslinjeMap(
personopplysningGrunnlag = personopplysningGrunnlag,
skalBestemmeLovverkBasertPåFødselsdato = unleashNextMedContextService.isEnabled(FeatureToggleConfig.STØTTER_LOVENDRING_2025),
)

return hentPerioderMedUtbetaling(
andelerTilkjentYtelse = andelerTilkjentYtelse,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.forskyvning

import no.nav.familie.ks.sak.common.exception.Feil
import no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.domene.PersonResultat
import no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.domene.Regelverk
import no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.domene.Resultat
Expand All @@ -8,59 +9,86 @@ import no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.domene.Vil
import no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.forskyvning.lovverkFebruar2025.ForskyvVilkårLovendringFebruar2025
import no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.forskyvning.lovverkFørFebruar2025.ForskyvVilkårFørFebruar2025
import no.nav.familie.ks.sak.kjerne.lovverk.Lovverk
import no.nav.familie.ks.sak.kjerne.lovverk.LovverkUtleder
import no.nav.familie.ks.sak.kjerne.personident.Aktør
import no.nav.familie.ks.sak.kjerne.personopplysninggrunnlag.domene.Person
import no.nav.familie.ks.sak.kjerne.personopplysninggrunnlag.domene.PersonType
import no.nav.familie.ks.sak.kjerne.personopplysninggrunnlag.domene.PersonopplysningGrunnlag
import no.nav.familie.tidslinje.Periode
import no.nav.familie.tidslinje.Tidslinje
import no.nav.familie.tidslinje.tilTidslinje
import no.nav.familie.tidslinje.tomTidslinje
import no.nav.familie.tidslinje.utvidelser.kombiner
import no.nav.familie.tidslinje.utvidelser.kombinerMed
import no.nav.familie.tidslinje.utvidelser.tilPerioderIkkeNull

fun Collection<PersonResultat>.tilForskjøvetOppfylteVilkårResultatTidslinjeMap(
personopplysningGrunnlag: PersonopplysningGrunnlag,
skalBestemmeLovverkBasertPåFødselsdato: Boolean,
): Map<Aktør, Tidslinje<List<VilkårResultat>>> =
personopplysningGrunnlag.personer.associate { person ->
Pair(
person.aktør,
this.tilForskjøvetVilkårResultatTidslinjeDerVilkårErOppfyltForPerson(person),
)
}
this
.forskyvVilkårResultater(
personopplysningGrunnlag = personopplysningGrunnlag,
skalBestemmeLovverkBasertPåFødselsdato = skalBestemmeLovverkBasertPåFødselsdato,
).mapValues { entry ->
val person = personopplysningGrunnlag.personer.single { it.aktør == entry.key }
entry.value.tilOppfylteVilkårTidslinje(person.type)
}

fun Collection<PersonResultat>.tilForskjøvetVilkårResultatTidslinjeMap(
personopplysningGrunnlag: PersonopplysningGrunnlag,
skalBestemmeLovverkBasertPåFødselsdato: Boolean,
): Map<Aktør, Tidslinje<List<VilkårResultat>>> =
personopplysningGrunnlag.personer.associate { person ->
Pair(
person.aktør,
this.tilForskjøvetVilkårResultatTidslinjeForPerson(person),
)
}
this
.forskyvVilkårResultater(
personopplysningGrunnlag = personopplysningGrunnlag,
skalBestemmeLovverkBasertPåFødselsdato = skalBestemmeLovverkBasertPåFødselsdato,
).mapValues {
it.value.tilVilkårResultaterTidslinje()
}

private fun Collection<PersonResultat>.tilForskjøvetVilkårResultatTidslinjeForPerson(
fun Collection<PersonResultat>.tilForskjøvetVilkårResultatTidslinjeDerVilkårErOppfyltForPerson(
person: Person,
): Tidslinje<List<VilkårResultat>> {
val forskjøvedeVilkårResultater = this.find { it.aktør == person.aktør }?.forskyvVilkårResultater() ?: emptyMap()
lovverk: Lovverk = Lovverk.FØR_LOVENDRING_2025,
): Tidslinje<List<VilkårResultat>> =
this
.single { it.aktør == person.aktør }
.forskyvVilkårResultater(lovverk = lovverk)
.tilOppfylteVilkårTidslinje(person.type)

return forskjøvedeVilkårResultater
private fun Map<Vilkår, List<Periode<VilkårResultat>>>.tilOppfylteVilkårTidslinje(personType: PersonType) =
this
.map { it.value.tilTidslinje() }
.kombiner { it.toList() }
.kombiner { alleVilkårOppfyltEllerNull(it, personType) }
.tilPerioderIkkeNull()
.tilTidslinje()
}

fun Collection<PersonResultat>.tilForskjøvetVilkårResultatTidslinjeDerVilkårErOppfyltForPerson(
person: Person,
lovverk: Lovverk = Lovverk.FØR_LOVENDRING_2025,
): Tidslinje<List<VilkårResultat>> {
val forskjøvedeVilkårResultater = this.find { it.aktør == person.aktør }?.forskyvVilkårResultater(lovverk = lovverk) ?: emptyMap()

return forskjøvedeVilkårResultater
private fun Map<Vilkår, List<Periode<VilkårResultat>>>.tilVilkårResultaterTidslinje() =
this
.map { it.value.tilTidslinje() }
.kombiner { alleVilkårOppfyltEllerNull(it, person.type) }
.kombiner { it.toList() }
.tilPerioderIkkeNull()
.tilTidslinje()

fun Collection<PersonResultat>.forskyvVilkårResultater(
personopplysningGrunnlag: PersonopplysningGrunnlag,
skalBestemmeLovverkBasertPåFødselsdato: Boolean,
): Map<Aktør, Map<Vilkår, List<Periode<VilkårResultat>>>> {
// Forskyver barnas vilkår basert på barnets lovverk
val barnasForskjøvedeVilkårResultater = this.forskyvBarnasVilkårResultater(personopplysningGrunnlag = personopplysningGrunnlag, skalBestemmeLovverkBasertPåFødselsdato)

// Lager lovverk-tidslinje basert på barnas forskjøvede VilkårResultater
val lovverkTidslinje =
LovverkTidslinjeGenerator.generer(
barnasForskjøvedeVilkårResultater = barnasForskjøvedeVilkårResultater,
personopplysningGrunnlag = personopplysningGrunnlag,
skalBestemmeLovverkBasertPåFødselsdato = skalBestemmeLovverkBasertPåFødselsdato,
)

// Forskyver søker etter alle lovverk og kombinerer med lovverk-tidslinje
val søkersForskjøvedeVilkårResultater = this.single { it.erSøkersResultater() }.forskyvSøkersVilkårResultater(lovverkTidslinje = lovverkTidslinje)

return barnasForskjøvedeVilkårResultater.plus(Pair(this.single { it.erSøkersResultater() }.aktør, søkersForskjøvedeVilkårResultater))
}

fun PersonResultat.forskyvVilkårResultater(
Expand All @@ -71,6 +99,53 @@ fun PersonResultat.forskyvVilkårResultater(
Lovverk.LOVENDRING_FEBRUAR_2025 -> ForskyvVilkårLovendringFebruar2025.forskyvVilkårResultater(this.vilkårResultater)
}

private fun Collection<PersonResultat>.forskyvBarnasVilkårResultater(
personopplysningGrunnlag: PersonopplysningGrunnlag,
skalBestemmeLovverkBasertPåFødselsdato: Boolean,
): Map<Aktør, Map<Vilkår, List<Periode<VilkårResultat>>>> =
this.filter { !it.erSøkersResultater() }.associate { personResultat ->
val lovverk =
LovverkUtleder.utledLovverkForBarn(
personopplysningGrunnlag.barna.single { it.aktør == personResultat.aktør }.fødselsdato,
skalBestemmeLovverkBasertPåFødselsdato = skalBestemmeLovverkBasertPåFødselsdato,
)
personResultat.aktør to personResultat.forskyvVilkårResultater(lovverk)
}

private fun PersonResultat.forskyvSøkersVilkårResultater(lovverkTidslinje: Tidslinje<Lovverk>): Map<Vilkår, List<Periode<VilkårResultat>>> {
if (!this.erSøkersResultater()) throw Feil("PersonResultat må være søkers resultat")
// Forskyver alle VilkårResultater etter alle lovverk og kombinerer resulterende VilkårResultat-tidslinje med Lovverk-tidslinja.
// Dette fører til at VilkårResultat-tidslinja per lovverk blir avgrenset til hvor lenge et bestemt lovverk er gjeldende.
val lovverkIBehandling = lovverkTidslinje.tilPerioderIkkeNull().map { it.verdi }.toSet()
val forskjøvedeVilkårResultaterEtterMuligeLovverkAvgrensetAvLovverkTidslinje =
lovverkIBehandling
.map { forskyvningsLovverk ->
this
.forskyvVilkårResultater(forskyvningsLovverk)
.mapValues {
it.value
.tilTidslinje()
.kombinerMed(lovverkTidslinje) { vilkårResultat, lovverk ->
if (forskyvningsLovverk == lovverk) {
vilkårResultat
} else {
null
}
}
}
}

// Kombinerer alle tidslinjer per lovverk og vilkår til én tidslinje per vilkår
return forskjøvedeVilkårResultaterEtterMuligeLovverkAvgrensetAvLovverkTidslinje
.flatMap { it.keys }
.associateWith { key ->
forskjøvedeVilkårResultaterEtterMuligeLovverkAvgrensetAvLovverkTidslinje
.map { it.getOrDefault(key, tomTidslinje()) }
.reduce { kombinertTidslinje, tidslinjeForLovverk -> kombinertTidslinje.kombinerMed(tidslinjeForLovverk) { a, b -> a ?: b } }
.tilPerioderIkkeNull()
}
}

fun alleVilkårOppfyltEllerNull(
vilkårResultater: Iterable<VilkårResultat?>,
personType: PersonType,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.forskyvning

import no.nav.familie.ks.sak.common.exception.Feil
import no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.domene.Vilkår
import no.nav.familie.ks.sak.kjerne.behandling.steg.vilkårsvurdering.domene.VilkårResultat
import no.nav.familie.ks.sak.kjerne.lovverk.Lovverk
import no.nav.familie.ks.sak.kjerne.lovverk.LovverkUtleder
import no.nav.familie.ks.sak.kjerne.personident.Aktør
import no.nav.familie.ks.sak.kjerne.personopplysninggrunnlag.domene.Person
import no.nav.familie.ks.sak.kjerne.personopplysninggrunnlag.domene.PersonopplysningGrunnlag
import no.nav.familie.tidslinje.Periode
import no.nav.familie.tidslinje.Tidslinje
import no.nav.familie.tidslinje.tilTidslinje
import no.nav.familie.tidslinje.utvidelser.kombiner
import no.nav.familie.tidslinje.utvidelser.slåSammenLikePerioder
import no.nav.familie.tidslinje.utvidelser.tilPerioderIkkeNull

object LovverkTidslinjeGenerator {
fun generer(
barnasForskjøvedeVilkårResultater: Map<Aktør, Map<Vilkår, List<Periode<VilkårResultat>>>>,
personopplysningGrunnlag: PersonopplysningGrunnlag,
skalBestemmeLovverkBasertPåFødselsdato: Boolean,
): Tidslinje<Lovverk> =
barnasForskjøvedeVilkårResultater
.map { (aktør, forskjøvedeVilkårResultater) ->
// Konverterer forskjøvede VilkårResultater til Lovverk-tidslinje per barn
forskjøvedeVilkårResultater.tilLovverkTidslinje(
barn = personopplysningGrunnlag.barna.single { it.aktør == aktør },
skalBestemmeLovverkBasertPåFødselsdato = skalBestemmeLovverkBasertPåFødselsdato,
)
}
// Kombinerer alle Lovverk-tidslinjer til en felles Lovverk-tidslinje.
// Lovverk-tidslinjene kan overlappe, men lovverket i de overlappende periodene må være det samme.
.kombiner {
val lovverkIPeriode = it.toSet()
if (lovverkIPeriode.size > 1) {
throw Feil("Støtter ikke overlappende lovverk")
}
lovverkIPeriode.single()
}.tilPerioderIkkeNull()
.sortedBy { it.fom }
.erstattFørsteFomOgSisteTomMedNull()
.kombinerEtterfølgendeElementer()
// Sørger for at et lovverk strekker seg helt frem til neste startdato for neste lovverk
.map { (lovverkPeriode, nesteLovverkPeriode) -> Periode(verdi = lovverkPeriode.verdi, fom = lovverkPeriode.fom, tom = nesteLovverkPeriode?.fom?.minusDays(1)) }
.tilTidslinje()

private fun Map<Vilkår, List<Periode<VilkårResultat>>>.tilLovverkTidslinje(
barn: Person,
skalBestemmeLovverkBasertPåFødselsdato: Boolean,
): Tidslinje<Lovverk> =
// Konverterer alle VilkårResultatPerioder per vilkår til Lovverk-tidslinjer og kombinerer disse til en felles tidslinje.
this.values
.map { perioder ->
perioder
.map { periode ->
Periode(
fom = periode.fom,
tom = periode.tom,
verdi =
LovverkUtleder.utledLovverkForBarn(
fødselsdato = barn.fødselsdato,
skalBestemmeLovverkBasertPåFødselsdato = skalBestemmeLovverkBasertPåFødselsdato,
),
)
}.tilTidslinje()
.slåSammenLikePerioder()
}.kombiner {
it.toSet().single()
}

private fun List<Periode<Lovverk>>.erstattFørsteFomOgSisteTomMedNull(): List<Periode<Lovverk>> =
// Sørger for at lovverk-tidslinje strekker seg fra TIDENES_MORGEN til TIDENES_ENDE.
// Da er vi sikre på at tidslinja dekker søkers vilkår.
this.mapIndexed { index, periode ->
when (index) {
0 -> Periode(verdi = periode.verdi, fom = null, tom = periode.tom)
this.lastIndex -> Periode(verdi = periode.verdi, fom = periode.fom, tom = null)
else -> periode
}
}

private fun List<Periode<Lovverk>>.kombinerEtterfølgendeElementer(): List<Pair<Periode<Lovverk>, Periode<Lovverk>?>> {
if (this.isEmpty()) return emptyList()

return this.zipWithNext() + Pair(this.last(), null)
}
}
Loading

0 comments on commit 48fa1a4

Please sign in to comment.