Skip to content

Commit

Permalink
Legg til hjelpefunksjoner for tidslinje fra familie-felles (#5073)
Browse files Browse the repository at this point in the history
### 💰 Hva skal gjøres, og hvorfor?

Favro: NAV-23270

Vi ønsker å ta i bruk tidslinjebiblioteket fra familie-felles i ba-sak.
Starter migreringen med å importere biblioteket, og oversette
hjelpefunksjonene fra det gamle tidlsinjebiblioteket til det nye.
Kopierer og oversetter testene av de gamle funksjonene, for å sjekke at
funksjonaliteten er lik.

Sammenlikn gjerne nye og gamle funksjoner, for å sjekke at logikken er
den samme.
  • Loading branch information
MagnusTonnessen authored Feb 14, 2025
1 parent 36fc15d commit abfb509
Show file tree
Hide file tree
Showing 28 changed files with 1,656 additions and 0 deletions.
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(
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(
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(
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(
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(
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

0 comments on commit abfb509

Please sign in to comment.