Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Legg til hjelpefunksjoner for tidslinje fra familie-felles #5073

Merged
merged 12 commits into from
Feb 14, 2025
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@
<groupId>no.nav.familie.felles</groupId>
<artifactId>metrikker</artifactId>
</dependency>
<dependency>
<groupId>no.nav.familie.felles</groupId>
<artifactId>tidslinje</artifactId>
</dependency>
<dependency>
<groupId>no.nav.familie.kontrakter</groupId>
<artifactId>felles</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon

import no.nav.familie.tidslinje.Tidslinje
import no.nav.familie.tidslinje.tomTidslinje
import no.nav.familie.tidslinje.utvidelser.kombinerMed

/**
* Extension-metode for å kombinere to nøkkel-verdi-map'er der verdiene er tidslinjer
* Nøkkelen må være av samme type K
* Verdiene i tidslinjene i map'en på venstre side må alle være av typen V
* Verdiene i tidslinjene i map'en på høyre side må alle være av typen H
* Kombinator-funksjonen kalles med verdiene av fra venstre og høyre tidslinje for samme nøkkel og tidspunkt.
* <null> blir sendt som verdier hvis venstre, høyre eller begge tidslinjer mangler verdi for et tidspunkt
* Resultatet er en ny map der nøklene er av type K, og tidslinjene har innhold av typen (nullable) R.
* Bare nøkler som finnes i begge map'ene vil finnes i den resulterende map'en
*/
fun <K, V, H, R> Map<K, Tidslinje<V>>.join(
høyreTidslinjer: Map<K, Tidslinje<H>>,
kombinator: (V?, H?) -> R?,
): Map<K, Tidslinje<R>> {
val venstreTidslinjer = this
val alleNøkler = venstreTidslinjer.keys.intersect(høyreTidslinjer.keys)

return alleNøkler.associateWith { nøkkel ->
val venstreTidslinje = venstreTidslinjer.getOrDefault(nøkkel, tomTidslinje())
val høyreTidslinje = høyreTidslinjer.getOrDefault(nøkkel, tomTidslinje())

venstreTidslinje.kombinerMed(høyreTidslinje, kombinator)
}
}

/**
* Extension-metode for å kombinere to nøkkel-verdi-map'er der verdiene er tidslinjer
* Nøkkelen må være av samme type K
* Verdiene i tidslinjene i map'en på venstre side må alle være av typen V
* Verdiene i tidslinjene i map'en på høyre side må alle være av typen H
* Kombinator-funksjonen kalles med verdiene av fra venstre og høyre tidslinje for samme nøkkel og tidspunkt.
* Kombinator-funksjonen blir IKKE kalt hvis venstre, høyre eller begge tidslinjer mangler verdi for et tidspunkt
* Resultatet er en ny map der nøklene er av type K, og tidslinjene har innhold av typen (nullable) R.
* Bare nøkler som finnes i begge map'ene vil finnes i den resulterende map'en
*/
fun <K, V, H, R> Map<K, Tidslinje<V>>.joinIkkeNull(
høyreTidslinjer: Map<K, Tidslinje<H>>,
kombinator: (V, H) -> R?,
): Map<K, Tidslinje<R>> {
val venstreTidslinjer = this
val alleNøkler = venstreTidslinjer.keys.intersect(høyreTidslinjer.keys)

return alleNøkler.associateWith { nøkkel ->
val venstreTidslinje = venstreTidslinjer.getOrDefault(nøkkel, tomTidslinje())
val høyreTidslinje = høyreTidslinjer.getOrDefault(nøkkel, tomTidslinje())

venstreTidslinje.kombinerUtenNullMed(høyreTidslinje, kombinator)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon

import no.nav.familie.tidslinje.Null
import no.nav.familie.tidslinje.Tidslinje
import no.nav.familie.tidslinje.Udefinert
import no.nav.familie.tidslinje.Verdi
import no.nav.familie.tidslinje.tilPeriodeVerdi
import no.nav.familie.tidslinje.utvidelser.biFunksjon
import no.nav.familie.tidslinje.utvidelser.kombinerMed
import no.nav.familie.tidslinje.utvidelser.map
import no.nav.familie.tidslinje.utvidelser.slåSammen
import no.nav.familie.tidslinje.utvidelser.trim

/**
* Extension-metode for å kombinere to tidslinjer der begge har verdi
* Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene
* Hver av tidslinjene kan ha ulik type, hhv V og H
* Hvis V eller H mangler verdi, så vil ikke resulterende tidslinje få en verdi for det tidspunktet
* Kombintor-funksjonen tar ellers V og H og returnerer (nullable) R
* Hvis kombinator-funksjonen returner <null>, antas det at tidslinjen ikke skal ha verdi for tidspunktet
* Resultatet er en tidslinje med verdi R
*/
fun <V, H, R> Tidslinje<V>.kombinerUtenNullMed(
høyreTidslinje: Tidslinje<H>,
kombineringsfunksjon: (V, H) -> R?,
): Tidslinje<R> =
this.biFunksjon(høyreTidslinje) { periodeverdiVenstre, periodeverdiHøyre ->
when {
periodeverdiVenstre is Verdi && periodeverdiHøyre is Verdi ->
kombineringsfunksjon(periodeverdiVenstre.verdi, periodeverdiHøyre.verdi).tilPeriodeVerdi()

else -> Null()
}
}

/**
* Extension-metode for å kombinere liste av tidslinjer
* Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene fra alle tidslinjene
* Verdi (V) må være av samme type
* Kombintor-funksjonen tar inn Iterable<V> og returner (nullable) R
* Null-verdier fjernes før de sendes til kombinator-funksjonen, som betyr at en tom iterator kan bli sendt
* Hvis resultatet fra kombinatoren er null, tolkes det som at det ikke skal være en verdi
* Resultatet er en tidslinje med verdi R
*/
fun <V, R> Collection<Tidslinje<V>>.kombinerUtenNull(
listeKombinator: (Iterable<V>) -> R?,
): Tidslinje<R> = kombinerNullableKombinator { it.filterNotNull().let(listeKombinator) }

/**
* Extension-metode for å kombinere liste av tidslinjer
* Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene fra alle tidslinjene
* Verdi (V) må være av samme type
* Kombintor-funksjonen tar inn Iterable<V> og returner (nullable) R
* Null-verdier fjernes, og listen av verdier sendes til kombinator-funksjonen bare hvis den inneholder verdier
* Hvis reesultatet fra kombinatoren er null, tolkes det som at det ikke skal være en verdi
* Resultatet er en tidslinje med verdi R
*/
fun <V, R> Collection<Tidslinje<V>>.kombinerUtenNullOgIkkeTom(
listeKombinator: (Iterable<V>) -> R?,
): Tidslinje<R> = kombinerNullableKombinator { it.filterNotNull().takeIf { it.isNotEmpty() }?.let(listeKombinator) }

/**
* Extension-metode for å kombinere liste av tidslinjer
* Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene fra alle tidslinjene
* Verdien V må være av samme type
* Resultatet er en tidslinje med verdi Iterable<V>
*/
fun <V> Collection<Tidslinje<V>>.kombiner() = this.kombinerNullableKombinator { if (it.toList().isNotEmpty()) it else null }

/**
* Extension-metode for å kombinere en nøkkel-verdi-map'er der verdiene er tidslinjer, med en enkelt tidslinje
* Verdien i tidslinjene i map'en på venstre side må alle være av typen V
* Verdien i tidslinjen på høyre side er av typen H
* Kombinator-funksjonen kalles for hvert tidspunkt med med verdien for det tidspunktet fra høyre tidslinje og
* verdien fra den enkelte av venstre tidslinjer etter tur.
* Kombinator-funksjonen blir IKKE kalt hvis venstre, høyre eller begge tidslinjer mangler verdi for et tidspunkt
* Resultatet er en ny map der nøklene er av type K, og tidslinjene har verdi av typen (nullable) R.
*/
fun <K, V, H, R> Map<K, Tidslinje<V>>.kombinerKunVerdiMed(
høyreTidslinje: Tidslinje<H>,
kombinator: (V, H) -> R?,
): Map<K, Tidslinje<R>> {
val venstreTidslinjer = this

return venstreTidslinjer.mapValues { (_, venstreTidslinje) ->
venstreTidslinje.kombinerUtenNullMed(høyreTidslinje, kombinator)
}
}

/**
* Extension-metode for å kombinere tre tidslinjer
* Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene fra alle tidslinjene
* Hver av tidslinjene kan ha ulik type, hhv A, B og C
* Kombintor-funksjonen tar inn av A, B og C og returner (nullable) R
* Resultatet er en tidslinje med verdi R
*/
fun <A, B, C, R> Tidslinje<A>.kombinerKunVerdiMed(
tidslinjeB: Tidslinje<B>,
tidslinjeC: Tidslinje<C>,
kombinator: (A, B, C) -> R?,
): Tidslinje<R> =
this.kombinerMed(tidslinjeB, tidslinjeC) { a, b, c ->
when {
a != null && b != null && c != null -> kombinator(a, b, c)
else -> null
}
}

fun <V> Tidslinje<V>.erIkkeTom() = !this.erTom()

fun <V, H> Tidslinje<V>.harOverlappMed(tidslinje: Tidslinje<H>) = this.kombinerUtenNullMed(tidslinje) { v, h -> true }.trim(Null()).erIkkeTom()

fun <V, H> Tidslinje<V>.harIkkeOverlappMed(tidslinje: Tidslinje<H>) = !this.harOverlappMed(tidslinje)

fun <V, H> Tidslinje<V>.kombinerMedNullable(
høyreTidslinje: Tidslinje<H>?,
kombinator: (V?, H?) -> V?,
): Tidslinje<V> =
if (høyreTidslinje != null) {
kombinerMed(høyreTidslinje, kombinator)
} else {
this
}

/**
* Extension-metode for å kombinere liste av tidslinjer
* Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene fra alle tidslinjene
* Verdi (V) må være av samme type
* Kombintor-funksjonen tar inn Iterable<V> og returner (nullable) R
* Resultatet er en tidslinje med verdi R
*/
fun <V, R> Collection<Tidslinje<V>>.kombinerNullableKombinator(listeKombinator: (Iterable<V>) -> R?): Tidslinje<R> =
this.slåSammen().map {
when (it) {
is Verdi -> {
val resultat = listeKombinator(it.verdi)
if (resultat != null) Verdi(resultat) else Null()
}

is Null -> Null()
is Udefinert -> Udefinert()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon

import no.nav.familie.ba.sak.common.førsteDagIInneværendeMåned
import no.nav.familie.ba.sak.common.førsteDagINesteMåned
import no.nav.familie.ba.sak.common.sisteDagIForrigeMåned
import no.nav.familie.ba.sak.common.sisteDagIInneværendeMåned
import no.nav.familie.ba.sak.common.sisteDagIMåned
import no.nav.familie.ba.sak.kjerne.grunnlag.personopplysninger.Person
import no.nav.familie.tidslinje.Periode
import no.nav.familie.tidslinje.Tidslinje
import no.nav.familie.tidslinje.tilTidslinje
import java.time.LocalDate
import java.time.YearMonth

fun erUnder18ÅrVilkårTidslinje(fødselsdato: LocalDate): Tidslinje<Boolean> =
opprettBooleanTidslinje(
fraDato = fødselsdato.førsteDagINesteMåned(),
tilDato = fødselsdato.plusYears(18).sisteDagIForrigeMåned(),
)

fun erUnder6ÅrTidslinje(person: Person): Tidslinje<Boolean> =
opprettBooleanTidslinje(
fraDato = person.fødselsdato.førsteDagIInneværendeMåned(),
tilDato = person.fødselsdato.plusYears(6).sisteDagIForrigeMåned(),
)

fun erTilogMed3ÅrTidslinje(fødselsdato: LocalDate): Tidslinje<Boolean> =
opprettBooleanTidslinje(
fraDato = fødselsdato.førsteDagINesteMåned(),
tilDato = fødselsdato.plusYears(3).sisteDagIMåned(),
)

fun opprettBooleanTidslinje(
fraÅrMåned: YearMonth,
tilÅrMåned: YearMonth,
) = listOf(
Periode(
verdi = true,
fom = fraÅrMåned.førsteDagIInneværendeMåned(),
tom = tilÅrMåned.sisteDagIInneværendeMåned(),
),
).tilTidslinje()

fun opprettBooleanTidslinje(
fraDato: LocalDate,
tilDato: LocalDate,
) = listOf(Periode(verdi = true, fom = fraDato, tom = tilDato)).tilTidslinje()
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.matematikk

import no.nav.familie.ba.sak.kjerne.personident.Aktør
import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon.join
import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon.kombinerUtenNullOgIkkeTom
import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon.mapIkkeNull
import no.nav.familie.tidslinje.Tidslinje
import java.math.BigDecimal
import java.math.RoundingMode

fun <K> Map<K, Tidslinje<BigDecimal>>.minus(
bTidslinjer: Map<K, Tidslinje<BigDecimal>>,
) = this.join(bTidslinjer) { a, b ->
when {
a != null && b != null -> a - b
else -> a
}
}

fun Map<Aktør, Tidslinje<BigDecimal>>.sum() = values.kombinerUtenNullOgIkkeTom { it.reduce { sum, verdi -> sum.plus(verdi) } }

fun Tidslinje<BigDecimal>.rundAvTilHeltall() = this.mapIkkeNull { it.setScale(0, RoundingMode.HALF_UP) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.matematikk

import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon.joinIkkeNull
import no.nav.familie.tidslinje.Tidslinje

fun <K, V : Comparable<V>> minsteAvHver(
aTidslinjer: Map<K, Tidslinje<V>>,
bTidslinjer: Map<K, Tidslinje<V>>,
) = aTidslinjer.joinIkkeNull(bTidslinjer) { a, b -> minOf(a, b) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon

import no.nav.familie.tidslinje.Tidslinje
import no.nav.familie.tidslinje.tomTidslinje
import no.nav.familie.tidslinje.utvidelser.klipp
import java.time.LocalDate

/**
* Extension-metode for å beskjære (forkorte) en tidslinje etter til-og-med fra en annen tidslinje
* Etter beskjæringen vil tidslinjen maksimalt strekke seg fra [this]s startTidspunkt og til [tidslinje]s sluttTidspunkt
* Perioder som ligger helt utenfor grensene vil forsvinne.
* Perioden i hver ende som ligger delvis innenfor, vil forkortes.
* Hvis ny og eksisterende grenseverdi begge er uendelige, vil den nye benyttes
* Beskjæring mot tom tidslinje vil gi tom tidslinje
*/
fun <I> Tidslinje<I>.beskjærTilOgMedEtter(tidslinje: Tidslinje<*>): Tidslinje<I> =
when {
tidslinje.erTom() -> tomTidslinje()
else ->
klipp(
startsTidspunkt = startsTidspunkt,
sluttTidspunkt = tidslinje.kalkulerSluttTidspunkt(),
)
}

/**
* Extension-metode for å beskjære (forkorte) en tidslinje
* Etter beskjæringen vil tidslinjen maksimalt strekke seg fra innsendt [fraOgMed] og til [tilOgMed]
* Perioder som ligger helt utenfor grensene vil forsvinne.
* Perioden i hver ende som ligger delvis innenfor, vil forkortes.
* Uendelige endepunkter vil beskjæres til endelig
*/
fun <V> Tidslinje<V>.beskjær(
fraOgMed: LocalDate,
tilOgMed: LocalDate,
): Tidslinje<V> =
when {
erTom() -> tomTidslinje()
else ->
klipp(
startsTidspunkt = fraOgMed,
sluttTidspunkt = tilOgMed,
)
}

/**
* Extension-metode for å beskjære fom dato på en tidslinje
* Etter beskjæringen vil tidslinjen maksimalt strekke seg fra innsendt [fraOgMed] og til eksisterende tilOgMed
*/
fun <V> Tidslinje<V>.beskjærFraOgMed(
fraOgMed: LocalDate,
): Tidslinje<V> =
when {
erTom() -> tomTidslinje()
else ->
klipp(
startsTidspunkt = fraOgMed,
sluttTidspunkt = kalkulerSluttTidspunkt(),
)
}

/**
* Extension-metode for å beskjære tom dato på en tidslinje
* Etter beskjæringen vil tidslinjen maksimalt strekke seg fra eksisterende fraOgMed og til innsendt [tilOgMed]
*/
fun <V> Tidslinje<V>.beskjærTilOgMed(
tilOgMed: LocalDate,
): Tidslinje<V> =
when {
erTom() -> tomTidslinje()
else ->
klipp(
startsTidspunkt = startsTidspunkt,
sluttTidspunkt = tilOgMed,
)
}

/**
* Extension-metode for å beskjære tom dato på et map av tidslinjer
* Etter beskjæringen vil tidslinjen maksimalt strekke seg fra eksisterende fraOgMed og til innsendt [tilOgMed]
*/
fun <K, V> Map<K, Tidslinje<V>>.beskjærTilOgMed(
tilOgMed: LocalDate,
): Map<K, Tidslinje<V>> = this.mapValues { (_, tidslinje) -> tidslinje.beskjærTilOgMed(tilOgMed) }
Loading