Skip to content

Commit

Permalink
Handle zero duration meetings and add contextual logging
Browse files Browse the repository at this point in the history
Exception handling to be removed
  • Loading branch information
balysv committed Jul 20, 2021
1 parent e97de11 commit 58824c6
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 91 deletions.
3 changes: 3 additions & 0 deletions app/src/main/java/com/sama/slotsuggestion/domain/Block.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.sama.slotsuggestion.domain

import java.time.Duration
import java.time.LocalTime
import java.time.ZonedDateTime
import kotlin.streams.asSequence
Expand All @@ -17,6 +18,8 @@ data class Block(
*/
fun multiDay() = !startDateTime.toLocalDate().isEqual(endDateTime.toLocalDate())

fun zeroDuration() = Duration.between(startDateTime, endDateTime) == Duration.ZERO

/**
* Splits this [Block] into multiple [Block]s such that each block is within a single [java.time.LocalDate]. If
* the [Block] is not [Block.multiDay] then a list containing itself is returned
Expand Down
221 changes: 130 additions & 91 deletions app/src/main/java/com/sama/slotsuggestion/domain/Weights.kt
Original file line number Diff line number Diff line change
@@ -1,73 +1,92 @@
package com.sama.slotsuggestion.domain

import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import kotlin.streams.asSequence

private var logger: Logger = LoggerFactory.getLogger(Weigher::class.java)

interface Weigher {
fun weigh(weightContext: WeightContext): Vector
}

@JvmInline
value class PastBlockWeigher(private val block: Block?) : Weigher {
override fun weigh(weightContext: WeightContext): Vector {
if (block == null || block.multiDay() || block.allDay) {
return weightContext.zeroes()
}
try {
if (block == null || block.multiDay() || block.allDay || block.zeroDuration()) {
return weightContext.zeroes()
}

val startTime = block.startDateTime.toLocalTime()
val endTime = block.endDateTime.toLocalTime()
val startTime = block.startDateTime.toLocalTime()
val endTime = block.endDateTime.toLocalTime()

val weight = if (block.hasRecipients) {
// there's a meeting with a person, we should add weight
0.05
} else {
// if it's a self-blocked time, we count as we DON'T want meetings for the time
-0.1
} / block.recurrenceCount
val weight = if (block.hasRecipients) {
// there's a meeting with a person, we should add weight
0.05
} else {
// if it's a self-blocked time, we count as we DON'T want meetings for the time
-0.1
} / block.recurrenceCount

return weightContext.cliff(startTime, endTime, 0.0, weight)
return weightContext.cliff(startTime, endTime, 0.0, weight)
} catch (e: Exception) {
logger.error("past-block weigh error", e)
return weightContext.zeroes()
}
}
}

@JvmInline
value class FutureBlockWeigher(private val block: Block?) : Weigher {
override fun weigh(weightContext: WeightContext): Vector {
if (block == null) {
return weightContext.ones()
}
val startTime = block.startDateTime.toLocalTime()
val endTime = block.endDateTime.toLocalTime()
try {
if (block == null || block.zeroDuration()) {
return weightContext.zeroes()
}
val startTime = block.startDateTime.toLocalTime()
val endTime = block.endDateTime.toLocalTime()

val weight = weightContext.cliff(startTime, endTime, 0.0, -100.0)
val weight = weightContext.cliff(startTime, endTime, 0.0, -100.0)

// Prefer meetings close to an existing one
val startTimeIndex = weightContext.timeToIndex(startTime)
if (startTimeIndex >= 1) {
weight[startTimeIndex - 1] += 0.1
}
val endTimeIndex = weightContext.timeToIndex(endTime)
if (endTimeIndex <= weight.size - 1) {
weight[endTimeIndex] += 0.1
}
// Prefer meetings close to an existing one
val startTimeIndex = weightContext.timeToIndex(startTime)
if (startTimeIndex >= 1) {
weight[startTimeIndex - 1] += 0.1
}
val endTimeIndex = weightContext.timeToIndex(endTime)
if (endTimeIndex <= weight.size - 1) {
weight[endTimeIndex] += 0.1
}

return weight
return weight
} catch (e: Exception) {
logger.error("future-block weigh error", e)
return weightContext.zeroes()
}
}
}

@JvmInline
value class WorkingHoursWeigher(private val wh: WorkingHours?) : Weigher {
override fun weigh(weightContext: WeightContext): Vector {
if (wh == null) {
return weightContext.line(-5.0)
}
if (wh.isAllDay()) {
try {
if (wh == null) {
return weightContext.line(-5.0)
}
if (wh.isAllDay()) {
return weightContext.zeroes()
}

return weightContext.linearCurve(wh.startTime, wh.endTime, -5.0, 0.0, -4 to 0)
} catch (e: Exception) {
logger.error("working-hours weigh error", e)
return weightContext.zeroes()
}

return weightContext.linearCurve(wh.startTime, wh.endTime, -5.0, 0.0, -4 to 0)
}
}

Expand All @@ -77,29 +96,34 @@ data class SearchBoundaryWeigher(
private val initiatorTimeZone: ZoneId
) : Weigher {
override fun weigh(weightContext: WeightContext): Vector {
val endDate = searchStartDate.plusDays(weightContext.days.toLong())
val searchEndDate = searchStartDate.plusDays(suggestionDayCount.toLong())
return searchStartDate.datesUntil(endDate).asSequence()
.map { date ->
when {
date.isEqual(searchStartDate) -> {
val startTime = LocalDateTime.now(initiatorTimeZone).toLocalTime()
weightContext.cliff(startTime, LocalTime.MAX, -100.0, 0.0)
}
date.isBefore(searchEndDate) -> {
weightContext.zeroes()
}
date.isEqual(searchEndDate) -> {
val endTime = LocalDateTime.now(initiatorTimeZone)
.plusDays(suggestionDayCount.toLong()).toLocalTime()
weightContext.cliff(LocalTime.MIDNIGHT, endTime, 0.0, -100.0)
}
else -> {
weightContext.line(-100.0)
try {
val endDate = searchStartDate.plusDays(weightContext.days.toLong())
val searchEndDate = searchStartDate.plusDays(suggestionDayCount.toLong())
return searchStartDate.datesUntil(endDate).asSequence()
.map { date ->
when {
date.isEqual(searchStartDate) -> {
val startTime = LocalDateTime.now(initiatorTimeZone).toLocalTime()
weightContext.cliff(startTime, LocalTime.MAX, -100.0, 0.0)
}
date.isBefore(searchEndDate) -> {
weightContext.zeroes()
}
date.isEqual(searchEndDate) -> {
val endTime = LocalDateTime.now(initiatorTimeZone)
.plusDays(suggestionDayCount.toLong()).toLocalTime()
weightContext.cliff(LocalTime.MIDNIGHT, endTime, 0.0, -100.0)
}
else -> {
weightContext.line(-100.0)
}
}
}
}
.reduce { acc, vector -> acc.plus(vector) }
.reduce { acc, vector -> acc.plus(vector) }
} catch (e: Exception) {
logger.error("search-boundary weigh error", e)
return weightContext.multiDayZeroes()
}
}

}
Expand All @@ -113,23 +137,28 @@ data class RecipientTimeZoneWeigher(
private val requestTimeZone: ZoneId
) : Weigher {
override fun weigh(weightContext: WeightContext): Vector {
val now = LocalDateTime.now()
val userOffsetSeconds = initiatorTimeZone.rules.getOffset(now).totalSeconds
val requestOffsetSeconds = requestTimeZone.rules.getOffset(now).totalSeconds
if (userOffsetSeconds == requestOffsetSeconds) {
return weightContext.multiDayZeroes()
}
try {
val now = LocalDateTime.now()
val userOffsetSeconds = initiatorTimeZone.rules.getOffset(now).totalSeconds
val requestOffsetSeconds = requestTimeZone.rules.getOffset(now).totalSeconds
if (userOffsetSeconds == requestOffsetSeconds) {
return weightContext.multiDayZeroes()
}

val startTime = LocalTime.of(8, 0)
val endTime = LocalTime.of(20, 0)
val startTime = LocalTime.of(8, 0)
val endTime = LocalTime.of(20, 0)

val offsetMinutes = (userOffsetSeconds - requestOffsetSeconds) / 60
val slotOffset = weightContext.minutesToSlotOffset(offsetMinutes)
val offsetMinutes = (userOffsetSeconds - requestOffsetSeconds) / 60
val slotOffset = weightContext.minutesToSlotOffset(offsetMinutes)

return (0..weightContext.days)
.map { weightContext.linearCurve(startTime, endTime, -3.0, 0.0, -1 to 2) }
.reduce { acc, vector -> acc.plus(vector) }
.rotate(slotOffset)
return (0..weightContext.days)
.map { weightContext.linearCurve(startTime, endTime, -3.0, 0.0, -1 to 2) }
.reduce { acc, vector -> acc.plus(vector) }
.rotate(slotOffset)
} catch (e: Exception) {
logger.error("recipient-time-zone weigh error", e)
return weightContext.multiDayZeroes()
}
}
}

Expand All @@ -138,16 +167,21 @@ data class RecipientTimeZoneWeigher(
*/
class RecencyWeigher : Weigher {
override fun weigh(weightContext: WeightContext): Vector {
val vector = curve(
-1.0,
0.5,
weightContext.multiDayVectorSize,
0,
weightContext.singleDayVectorSize,
0 to 0, { x -> x },
-weightContext.multiDayVectorSize + weightContext.singleDayVectorSize to 0, { x -> x }
)
return vector
return try {
val vector = curve(
-1.0,
0.5,
weightContext.multiDayVectorSize,
0,
weightContext.singleDayVectorSize,
0 to 0, { x -> x },
-weightContext.multiDayVectorSize + weightContext.singleDayVectorSize to 0, { x -> x }
)
vector
} catch (e: Exception) {
logger.error("recency weigh error", e)
weightContext.multiDayZeroes()
}
}
}

Expand All @@ -159,19 +193,24 @@ data class SuggestedSlotWeigher(
private val startDate: LocalDate
) : Weigher {
override fun weigh(weightContext: WeightContext): Vector {
val daySinceStart = startDate.until(ss.startDateTime.toLocalDate()).days
try {
val daySinceStart = startDate.until(ss.startDateTime.toLocalDate()).days

val startTime = ss.startDateTime.toLocalTime()
val endTime = ss.endDateTime.toLocalTime()
val startTime = ss.startDateTime.toLocalTime()
val endTime = ss.endDateTime.toLocalTime()

return (0 until weightContext.days)
.map {
if (it == daySinceStart) {
weightContext.linearCurve(startTime, endTime, 0.0, -10.0, -4 to 0)
} else {
weightContext.zeroes()
return (0 until weightContext.days)
.map {
if (it == daySinceStart) {
weightContext.linearCurve(startTime, endTime, 0.0, -10.0, -4 to 0)
} else {
weightContext.zeroes()
}
}
}
.reduce { acc, vector -> acc.plus(vector) }
.reduce { acc, vector -> acc.plus(vector) }
} catch (e: Exception) {
logger.error("suggested-slot weigh error", e)
return weightContext.multiDayZeroes()
}
}
}
19 changes: 19 additions & 0 deletions app/src/test/java/com/sama/slotsuggestion/domain/BlockTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.sama.slotsuggestion.domain

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.time.ZonedDateTime


internal class BlockTest {

@Test
fun testZeroDuration() {
val now = ZonedDateTime.now()
assertThat(Block(now, now, false, false, 0, null).zeroDuration())
.isTrue()

assertThat(Block(now, now.plusMinutes(1), false, false, 0, null).zeroDuration())
.isFalse()
}
}

0 comments on commit 58824c6

Please sign in to comment.