Cyclic and tapering dose schedules (schema v3)
Extends DoseTime with two orthogonal filters, as the brief foresaw
('cyclic/taper extend from the same model'):
- Cyclic (cycleActiveDays/cycleLengthDays from anchor): N-on/M-off, e.g.
21/28 contraception. occursOn gains a cycle-window check; nextOccurrence
is now a uniform day-by-day scan over occursOn (replacing the interval
fast-path) so weekly/interval/cyclic/windowed all fall out of one
correct path.
- Validity window (startEpochDay/endEpochDay, inclusive): a taper is
several daily rows with descending amount and adjacent windows.
dailyConsumption now takes : cyclic scales by active/length, and
rows outside their window contribute 0 so a finished taper step stops
inflating days-of-supply.
UI: dose-time dialog gains a Syklisk section (på/av days) and is now
scrollable; a 'Lag nedtrapping' generator creates the windowed rows from
start dose / step-down / days-per-step / step-count. ScheduleText renders
'· syklus 21/28' and '· 11. juni–13. juni'.
Migration 2→3 is additive ADD COLUMNs with defaults matching the entity
(@ColumnInfo defaultValue), proven by an instrumented MigrationTestHelper
test. Backup DTO extended with defaults so v2 files still parse. 9 new
engine unit tests; versionCode 3 / 0.3.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
dae09ff9fb
commit
eeed098b1a
13 changed files with 782 additions and 50 deletions
|
|
@ -1,5 +1,8 @@
|
|||
package no.naiv.meddetsamme.domain
|
||||
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import no.naiv.meddetsamme.data.DoseTime
|
||||
|
||||
/**
|
||||
|
|
@ -12,9 +15,30 @@ object ScheduleText {
|
|||
/** Mask bit order matches ScheduleEngine: bit 0 = Sunday. */
|
||||
private val DAY_ABBREV = listOf("søn", "man", "tir", "ons", "tor", "fre", "lør")
|
||||
|
||||
fun describe(doseTime: DoseTime): String {
|
||||
val time = "%02d:%02d".format(doseTime.minuteOfDay / 60, doseTime.minuteOfDay % 60)
|
||||
return "$time ${daysText(doseTime)}"
|
||||
// Locale-pinned to nb so month names don't come out English on a foreign locale.
|
||||
private val dateFmt = DateTimeFormatter.ofPattern("d. MMM", Locale.forLanguageTag("nb"))
|
||||
|
||||
fun describe(doseTime: DoseTime): String =
|
||||
"${"%02d:%02d".format(doseTime.minuteOfDay / 60, doseTime.minuteOfDay % 60)} " +
|
||||
"${daysText(doseTime)}${cycleText(doseTime)}${windowText(doseTime)}"
|
||||
|
||||
/** " · syklus 21/28" when cyclic, else "". */
|
||||
fun cycleText(doseTime: DoseTime): String {
|
||||
val length = doseTime.cycleLengthDays
|
||||
val active = doseTime.cycleActiveDays
|
||||
return if (length > 0 && active in 1 until length) " · syklus $active/$length" else ""
|
||||
}
|
||||
|
||||
/** " · 1.–14. jun", " · f.o.m. 1. jun", " · t.o.m. 14. jun" for a validity window. */
|
||||
fun windowText(doseTime: DoseTime): String {
|
||||
val start = doseTime.startEpochDay?.let { LocalDate.ofEpochDay(it) }
|
||||
val end = doseTime.endEpochDay?.let { LocalDate.ofEpochDay(it) }
|
||||
return when {
|
||||
start != null && end != null -> " · ${dateFmt.format(start)}–${dateFmt.format(end)}"
|
||||
start != null -> " · f.o.m. ${dateFmt.format(start)}"
|
||||
end != null -> " · t.o.m. ${dateFmt.format(end)}"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
fun daysText(doseTime: DoseTime): String {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue