Skip to content

Commit

Permalink
Refactor occupancy calculation to backend
Browse files Browse the repository at this point in the history
  • Loading branch information
patari committed Jan 20, 2025
1 parent 7bb3e37 commit 1149a88
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import React, { useMemo } from 'react'
import { Link } from 'react-router'
import styled from 'styled-components'

import { AssistanceFactor } from 'lib-common/generated/api-types/occupancy'
import {
Child,
ChildRecordOfDay,
Expand Down Expand Up @@ -46,7 +45,6 @@ interface Props {
unitId: UUID
days: OperationalDay[]
childBasics: Child[]
assistanceFactors: AssistanceFactor[]
onMakeReservationForChild: (child: Child) => void
onOpenEditForChildDate: (childId: UUID, date: LocalDate) => void
selectedDate: LocalDate
Expand Down Expand Up @@ -115,7 +113,6 @@ const occupancyFormatter = new Intl.NumberFormat('fi-FI', {
export default React.memo(function ChildReservationsTable({
days,
childBasics,
assistanceFactors,
onMakeReservationForChild,
onOpenEditForChildDate,
selectedDate,
Expand Down Expand Up @@ -160,30 +157,19 @@ export default React.memo(function ChildReservationsTable({
)
})
.map((child) => child.childId)
const occupancy = childBasics.reduce((occupancy, child) => {
if (!presentChildren.includes(child.id)) return occupancy
const coefficient = child.serviceNeeds.reduce((occupancy, sn) => {
if (!sn.validDuring.includes(day.date)) return occupancy
return occupancy * day.date.differenceInYears(child.dateOfBirth) < 3
? sn.occupancyCoefficientUnder3y
: sn.occupancyCoefficient
}, 1)
const factor = assistanceFactors.reduce((factor, af) => {
if (
!presentChildren.includes(af.childId) ||
!af.period.includes(day.date)
)
return factor
return factor * af.capacityFactor
}, 1)
return occupancy + coefficient * factor
}, 0)
const occupancy = day.children.reduce(
(occupancy, child) =>
presentChildren.includes(child.childId)
? occupancy + child.occupancy
: occupancy,
0
)
return {
headcount: presentChildren.length,
occupancy
}
}),
[days, childBasics, selectedGroup, assistanceFactors]
[days, childBasics, selectedGroup]
)

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ export default React.memo(function UnitAttendanceReservationsView({
unitId={unitId}
days={childData.days}
childBasics={childData.children}
assistanceFactors={childData.assistanceFactors}
onMakeReservationForChild={setCreatingReservationChild}
onOpenEditForChildDate={(childId, date) => {
const child = childData.children.find((c) => c.id === childId)
Expand Down
17 changes: 0 additions & 17 deletions frontend/src/lib-common/generated/api-types/occupancy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,6 @@ import { GroupId } from './shared'
import { JsonOf } from '../../json'
import { PersonId } from './shared'

/**
* Generated from fi.espoo.evaka.occupancy.AssistanceFactor
*/
export interface AssistanceFactor {
capacityFactor: number
childId: PersonId
period: FiniteDateRange
}

/**
* Generated from fi.espoo.evaka.occupancy.ChildCapacityPoint
*/
Expand Down Expand Up @@ -146,14 +137,6 @@ export interface UnitOccupancies {
}


export function deserializeJsonAssistanceFactor(json: JsonOf<AssistanceFactor>): AssistanceFactor {
return {
...json,
period: FiniteDateRange.parseJson(json.period)
}
}


export function deserializeJsonChildCapacityPoint(json: JsonOf<ChildCapacityPoint>): ChildCapacityPoint {
return {
...json,
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/lib-common/generated/api-types/reservations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import TimeInterval from '../../time-interval'
import TimeRange from '../../time-range'
import { AbsenceCategory } from './absence'
import { AbsenceType } from './absence'
import { AssistanceFactor } from './occupancy'
import { ChildImageId } from './shared'
import { ChildServiceNeedInfo } from './absence'
import { DailyServiceTimesValue } from './dailyservicetimes'
Expand All @@ -23,7 +22,6 @@ import { JsonOf } from '../../json'
import { PersonId } from './shared'
import { PlacementType } from './placement'
import { ScheduleType } from './placement'
import { deserializeJsonAssistanceFactor } from './occupancy'
import { deserializeJsonChildServiceNeedInfo } from './absence'
import { deserializeJsonDailyServiceTimesValue } from './dailyservicetimes'
import { deserializeJsonHolidayPeriodEffect } from './holidayperiod'
Expand Down Expand Up @@ -107,6 +105,7 @@ export interface ChildRecordOfDay {
dailyServiceTimes: DailyServiceTimesValue | null
groupId: GroupId | null
inOtherUnit: boolean
occupancy: number
possibleAbsenceCategories: AbsenceCategory[]
reservations: ReservationResponse[]
scheduleType: ScheduleType
Expand Down Expand Up @@ -412,7 +411,6 @@ export interface ReservationsResponse {
* Generated from fi.espoo.evaka.reservations.UnitAttendanceReservations
*/
export interface UnitAttendanceReservations {
assistanceFactors: AssistanceFactor[]
children: Child[]
days: OperationalDay[]
groups: ReservationGroup[]
Expand Down Expand Up @@ -705,7 +703,6 @@ export function deserializeJsonReservationsResponse(json: JsonOf<ReservationsRes
export function deserializeJsonUnitAttendanceReservations(json: JsonOf<UnitAttendanceReservations>): UnitAttendanceReservations {
return {
...json,
assistanceFactors: json.assistanceFactors.map(e => deserializeJsonAssistanceFactor(e)),
children: json.children.map(e => deserializeJsonChild(e)),
days: json.days.map(e => deserializeJsonOperationalDay(e))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal("1.00"),
)
),
monChildren,
Expand All @@ -486,6 +487,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal("1.00"),
)
),
tueChildren,
Expand All @@ -511,6 +513,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal("1.00"),
),
wedChildren.first { it.childId == testChild_1.id },
)
Expand All @@ -527,6 +530,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
),
wedChildren.first { it.childId == testChild_4.id },
)
Expand All @@ -543,6 +547,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
),
wedChildren.first { it.childId == testChild_6.id },
)
Expand All @@ -566,6 +571,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal("1.00"),
),
thuChildren.first { it.childId == testChild_1.id },
)
Expand All @@ -582,6 +588,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
),
thuChildren.first { it.childId == testChild_4.id },
)
Expand All @@ -606,6 +613,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = testGroup2.id,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
),
thuChildren.first { it.childId == testChild_6.id },
)
Expand All @@ -629,6 +637,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal("1.00"),
),
friChildren.first { it.childId == testChild_1.id },
)
Expand All @@ -649,6 +658,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = testGroup2.id,
inOtherUnit = false,
scheduleType = ScheduleType.FIXED_SCHEDULE,
occupancy = BigDecimal("1.75"),
),
friChildren.first { it.childId == testChild_5.id },
)
Expand All @@ -665,6 +675,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = true,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
),
friChildren.first { it.childId == testChild_6.id },
)
Expand Down Expand Up @@ -773,6 +784,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
)
),
response.days.first { it.date == mon }.children,
Expand Down Expand Up @@ -820,6 +832,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
)
),
response.days.first { it.date == tue }.children,
Expand Down Expand Up @@ -847,6 +860,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
)
),
response.days.first { it.date == wed }.children,
Expand Down Expand Up @@ -1066,6 +1080,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
),
getAttendanceReservations(testClock).days[2].children.first(),
)
Expand Down Expand Up @@ -1128,6 +1143,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
),
getAttendanceReservations(testClock).days[2].children.first(),
)
Expand Down Expand Up @@ -1173,6 +1189,7 @@ class AttendanceReservationsControllerIntegrationTest :
backupGroupId = null,
inOtherUnit = false,
scheduleType = ScheduleType.RESERVATION_REQUIRED,
occupancy = BigDecimal.ONE,
),
getAttendanceReservations(testClock).days[2].children.first(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import fi.espoo.evaka.daycare.getPreschoolTerms
import fi.espoo.evaka.holidayperiod.HolidayPeriod
import fi.espoo.evaka.holidayperiod.getHolidayPeriods
import fi.espoo.evaka.holidayperiod.getHolidayPeriodsInRange
import fi.espoo.evaka.occupancy.AssistanceFactor
import fi.espoo.evaka.placement.PlacementType
import fi.espoo.evaka.placement.ScheduleType
import fi.espoo.evaka.placement.getPlacementsForChildDuring
Expand Down Expand Up @@ -55,6 +54,7 @@ import fi.espoo.evaka.user.EvakaUser
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalTime
import java.time.temporal.ChronoUnit
import org.jdbi.v3.core.mapper.PropagateNull
import org.jdbi.v3.json.Json
import org.springframework.format.annotation.DateTimeFormat
Expand Down Expand Up @@ -151,6 +151,29 @@ class AttendanceReservationController(
val placementStatus =
placementInfo[childId]?.getValue(date)
?: return@mapNotNull null
val age =
ChronoUnit.YEARS.between(
childData.child.dateOfBirth,
date,
)
val coefficient =
childData.child.serviceNeeds.fold(BigDecimal.ONE) {
coefficient,
sn ->
if (sn.validDuring.includes(date))
coefficient *
if (age < 3)
sn.occupancyCoefficientUnder3y
else sn.occupancyCoefficient
else coefficient
}
val factor =
assistanceFactors.fold(BigDecimal.ONE) { factor, af
->
if (af.validDuring.includes(date))
factor * af.capacityFactor.toBigDecimal()
else factor
}

UnitAttendanceReservations.ChildRecordOfDay(
childId = childData.child.id,
Expand Down Expand Up @@ -198,18 +221,11 @@ class AttendanceReservationController(
clubTerms,
preschoolTerms,
),
occupancy = coefficient * factor,
)
}
)
},
assistanceFactors =
assistanceFactors.map {
AssistanceFactor(
childId = it.childId,
capacityFactor = it.capacityFactor.toBigDecimal(),
period = it.validDuring,
)
},
)
}
}
Expand Down Expand Up @@ -717,7 +733,6 @@ data class UnitAttendanceReservations(
val groups: List<ReservationGroup>,
val children: List<Child>,
val days: List<OperationalDay>,
val assistanceFactors: List<AssistanceFactor>,
) {
data class ReservationGroup(@PropagateNull val id: GroupId, val name: String)

Expand Down Expand Up @@ -747,6 +762,7 @@ data class UnitAttendanceReservations(
val backupGroupId: GroupId?,
val inOtherUnit: Boolean,
val scheduleType: ScheduleType,
val occupancy: BigDecimal,
)

data class Child(
Expand Down

0 comments on commit 1149a88

Please sign in to comment.