Merge branch 'dev' into smoothing

This commit is contained in:
Milos Kozak 2022-12-26 11:10:57 +01:00
commit 9c0775c826
22 changed files with 582 additions and 101 deletions

View file

@ -7,10 +7,10 @@ import info.nightscout.database.entities.TotalDailyDose
interface TddCalculator {
fun calculate(days: Long): LongSparseArray<TotalDailyDose>
fun calculateToday(): TotalDailyDose
fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose
fun calculate(startTime: Long, endTime: Long): TotalDailyDose
fun averageTDD(tdds: LongSparseArray<TotalDailyDose>): TotalDailyDose?
fun calculate(days: Long, allowMissingDays: Boolean): LongSparseArray<TotalDailyDose>?
fun calculateToday(): TotalDailyDose?
fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose?
fun calculate(startTime: Long, endTime: Long, allowMissingData: Boolean): TotalDailyDose?
fun averageTDD(tdds: LongSparseArray<TotalDailyDose>?): TotalDailyDose?
fun stats(context: Context): TableLayout
}

View file

@ -21,7 +21,8 @@ data class NSBolus(
override val pumpSerial: String?,
override var app: String? = null,
val insulin: Double,
val type: BolusType
val type: BolusType,
val isBasalInsulin: Boolean
) : NSTreatment {
enum class BolusType {

View file

@ -17,6 +17,17 @@ import info.nightscout.sdk.remotemodel.RemoteTreatment
import org.json.JSONObject
import java.util.concurrent.TimeUnit
/**
* Convert to [RemoteTreatment] and back to [NSTreatment]
* testing purpose only
*
* @param treatment original
*
* @return treatment after double conversion
*/
fun NSTreatment.convertToRemoteAndBack(): NSTreatment? =
toRemoteTreatment()?.toTreatment()
internal fun RemoteTreatment.toTreatment(): NSTreatment? {
val treatmentTimestamp = timestamp()
when {
@ -40,6 +51,7 @@ internal fun RemoteTreatment.toTreatment(): NSTreatment? {
pumpSerial = this.pumpSerial,
insulin = this.insulin,
type = NSBolus.BolusType.fromString(this.type),
isBasalInsulin = isBasalInsulin ?: false
)
carbs != null && carbs > 0 ->
@ -355,7 +367,8 @@ internal fun NSTreatment.toRemoteTreatment(): RemoteTreatment? =
pumpType = pumpType,
pumpSerial = pumpSerial,
insulin = insulin,
type = type.name
type = type.name,
isBasalInsulin = isBasalInsulin
)
is NSCarbs -> RemoteTreatment(

View file

@ -76,7 +76,8 @@ internal data class RemoteTreatment(
@SerializedName("rate") val rate: Double? = null, // Double "Temp Basal" absolute rate (could be calculated with percent and profile information...)
@SerializedName("extendedEmulated") val extendedEmulated: RemoteTreatment? = null, // Gson of emulated EB
@SerializedName("timeshift") val timeshift: Long? = null, // integer "Profile Switch"
@SerializedName("percentage") val percentage: Int? = null // integer "Profile Switch"
@SerializedName("percentage") val percentage: Int? = null, // integer "Profile Switch"
@SerializedName("isBasalInsulin") val isBasalInsulin: Boolean? = null // boolean "Bolus"
) {
fun timestamp(): Long {

View file

@ -49,7 +49,7 @@ data class Bolus(
var insulinConfiguration: InsulinConfiguration? = null
) : TraceableDBEntry, DBEntryWithTime {
private fun contentEqualsTo(other: Bolus): Boolean =
fun contentEqualsTo(other: Bolus): Boolean =
isValid == other.isValid &&
timestamp == other.timestamp &&
utcOffset == other.utcOffset &&

View file

@ -38,7 +38,7 @@ data class Carbs(
var notes: String? = null
) : TraceableDBEntry, DBEntryWithTimeAndDuration {
private fun contentEqualsTo(other: Carbs): Boolean =
fun contentEqualsTo(other: Carbs): Boolean =
isValid == other.isValid &&
timestamp == other.timestamp &&
utcOffset == other.utcOffset &&

View file

@ -49,12 +49,12 @@ data class EffectiveProfileSwitch(
var originalTimeshift: Long, // [milliseconds]
var originalPercentage: Int, // 1 ~ XXX [%]
var originalDuration: Long, // [milliseconds]
var originalEnd: Long,
var originalEnd: Long, // not used (calculated from duration)
@Embedded
var insulinConfiguration: InsulinConfiguration
) : TraceableDBEntry, DBEntryWithTime {
private fun contentEqualsTo(other: EffectiveProfileSwitch): Boolean =
fun contentEqualsTo(other: EffectiveProfileSwitch): Boolean =
isValid == other.isValid &&
timestamp == other.timestamp &&
utcOffset == other.utcOffset &&

View file

@ -52,7 +52,25 @@ data class ProfileSwitch(
var insulinConfiguration: InsulinConfiguration
) : TraceableDBEntry, DBEntryWithTimeAndDuration {
private fun contentEqualsTo(other: ProfileSwitch): Boolean =
fun copy(): ProfileSwitch =
ProfileSwitch(
isValid = isValid,
timestamp = timestamp,
utcOffset = utcOffset,
basalBlocks = basalBlocks,
isfBlocks = isfBlocks,
icBlocks = icBlocks,
targetBlocks = targetBlocks,
glucoseUnit = glucoseUnit,
profileName = profileName,
timeshift = timeshift,
percentage = percentage,
duration = duration,
insulinConfiguration = insulinConfiguration,
interfaceIDs_backing = interfaceIDs_backing
)
fun contentEqualsTo(other: ProfileSwitch): Boolean =
isValid == other.isValid &&
timestamp == other.timestamp &&
utcOffset == other.utcOffset &&

View file

@ -25,4 +25,15 @@ interface TraceableDBEntry: DBEntry {
set(value) {
interfaceIDs_backing = value
}
fun interfaceIdsEqualsTo(other: TraceableDBEntry): Boolean =
interfaceIDs.nightscoutId == interfaceIDs.nightscoutId &&
interfaceIDs.nightscoutSystemId == interfaceIDs.nightscoutSystemId &&
interfaceIDs.pumpType == interfaceIDs.pumpType &&
interfaceIDs.pumpSerial == interfaceIDs.pumpSerial &&
interfaceIDs.temporaryId == interfaceIDs.temporaryId &&
interfaceIDs.pumpId == interfaceIDs.pumpId &&
interfaceIDs.startId == interfaceIDs.startId &&
interfaceIDs.endId == interfaceIDs.endId
}

View file

@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import androidx.core.util.size
import info.nightscout.database.ValueWrapper
import info.nightscout.database.entities.Bolus
import info.nightscout.database.entities.TotalDailyDose
@ -37,7 +38,7 @@ class TddCalculatorImpl @Inject constructor(
private val repository: AppRepository
) : TddCalculator {
override fun calculate(days: Long): LongSparseArray<TotalDailyDose> {
override fun calculate(days: Long, allowMissingDays: Boolean): LongSparseArray<TotalDailyDose>? {
var startTime = MidnightTime.calc(dateUtil.now() - T.days(days).msecs())
val endTime = MidnightTime.calc(dateUtil.now())
//val stepSize = T.hours(24).msecs() // this is not true on DST change
@ -55,8 +56,8 @@ class TddCalculatorImpl @Inject constructor(
if (endTime > startTime) {
var midnight = startTime
while (midnight < endTime) {
val tdd = calculate(midnight, midnight + T.hours(24).msecs())
result.put(midnight, tdd)
val tdd = calculate(midnight, midnight + T.hours(24).msecs(), allowMissingData = false)
if (tdd != null) result.put(midnight, tdd)
midnight = MidnightTime.calc(midnight + T.hours(27).msecs()) // be sure we find correct midnight
}
}
@ -68,25 +69,28 @@ class TddCalculatorImpl @Inject constructor(
repository.insertTotalDailyDose(tdd)
}
}
return result
if (result.size.toLong() == days || allowMissingDays) return result
return null
}
override fun calculateToday(): TotalDailyDose {
override fun calculateToday(): TotalDailyDose? {
val startTime = MidnightTime.calc(dateUtil.now())
val endTime = dateUtil.now()
return calculate(startTime, endTime)
return calculate(startTime, endTime, allowMissingData = true)
}
override fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose {
override fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose? {
val startTime = dateUtil.now() + T.hours(hour = startHours).msecs()
val endTime = dateUtil.now() + T.hours(hour = endHours).msecs()
return calculate(startTime, endTime)
return calculate(startTime, endTime, allowMissingData = false)
}
override fun calculate(startTime: Long, endTime: Long): TotalDailyDose {
override fun calculate(startTime: Long, endTime: Long, allowMissingData: Boolean): TotalDailyDose? {
val isWholeDay = (endTime - startTime) >= T.days(1).msecs()
val startTimeAligned = startTime - startTime % (5 * 60 * 1000)
val endTimeAligned = endTime - endTime % (5 * 60 * 1000)
val tdd = TotalDailyDose(timestamp = startTimeAligned)
var tbrFound = false
repository.getBolusesDataFromTimeToTime(startTime, endTime, true).blockingGet()
.filter { it.type != Bolus.Type.PRIMING }
.forEach { t ->
@ -98,8 +102,9 @@ class TddCalculatorImpl @Inject constructor(
val calculationStep = T.mins(5).msecs()
for (t in startTimeAligned until endTimeAligned step calculationStep) {
val profile = profileFunction.getProfile(t) ?: continue
val profile = profileFunction.getProfile(t) ?: if (allowMissingData) continue else return null
val tbr = iobCobCalculator.getBasalData(profile, t)
if (tbr.isTempBasalRunning) tbrFound = true
val absoluteRate = tbr.tempBasalAbsolute
tdd.basalAmount += absoluteRate / 60.0 * 5.0
@ -111,11 +116,13 @@ class TddCalculatorImpl @Inject constructor(
}
tdd.totalAmount = tdd.bolusAmount + tdd.basalAmount
aapsLogger.debug(LTag.CORE, tdd.toString())
return tdd
if (tdd.bolusAmount > 0 || tdd.basalAmount > 0 || tbrFound) return tdd
return null
}
override fun averageTDD(tdds: LongSparseArray<TotalDailyDose>): TotalDailyDose? {
override fun averageTDD(tdds: LongSparseArray<TotalDailyDose>?): TotalDailyDose? {
val totalTdd = TotalDailyDose(timestamp = dateUtil.now())
tdds ?: return null
if (tdds.size() == 0) return null
for (i in 0 until tdds.size()) {
val tdd = tdds.valueAt(i)
@ -132,7 +139,7 @@ class TddCalculatorImpl @Inject constructor(
}
override fun stats(context: Context): TableLayout {
val tdds = calculate(7)
val tdds = calculate(7, allowMissingDays = true) ?: return TableLayout(context)
val averageTdd = averageTDD(tdds)
val todayTdd = calculateToday()
val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT)
@ -156,6 +163,7 @@ class TddCalculatorImpl @Inject constructor(
})
layout.addView(averageTdd.toTableRow(context, rh, tdds.size(), includeCarbs = true))
}
todayTdd?.let {
layout.addView(TextView(context).apply {
text = rh.gs(info.nightscout.shared.R.string.today)
setTypeface(typeface, Typeface.BOLD)
@ -165,4 +173,5 @@ class TddCalculatorImpl @Inject constructor(
layout.addView(todayTdd.toTableRow(context, rh, dateUtil, includeCarbs = true))
}
}
}
}

View file

@ -577,14 +577,12 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
// for IOBpredBGs, predicted deviation impact drops linearly from current deviation down to zero
// over 60 minutes (data points every 5m)
var predDev = ci * ( 1 - Math.min(1,IOBpredBGs.length/(60/5)) );
//IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + predBGI + predDev;
IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + (round(( -iobTick.activity * (1800 / ( TDD * (Math.log((Math.max( IOBpredBGs[IOBpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) ))
* 5 ),2)) + predDev;
if (!TDD) IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + predBGI + predDev;
else IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + (round(( -iobTick.activity * (1800 / ( TDD * (Math.log((Math.max( IOBpredBGs[IOBpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) )) * 5 ),2)) + predDev;
// calculate predBGs with long zero temp without deviations
//var ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + predZTBGI;
var ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + (round(( -iobTick.iobWithZeroTemp.activity * (1800 / ( TDD * (Math.log(( Math.max(ZTpredBGs[ZTpredBGs.length-1],39) /
insulinDivisor ) + 1 ) ) )) * 5 ), 2));
if (!TDD) var ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + predZTBGI;
else var ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + (round(( -iobTick.iobWithZeroTemp.activity * (1800 / ( TDD * (Math.log(( Math.max(ZTpredBGs[ZTpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) )) * 5 ), 2));
// for COBpredBGs, predicted carb impact drops linearly from current carb impact down to zero
// eventually accounting for all carbs (if they can be absorbed over DIA)
var predCI = Math.max(0, Math.max(0,ci) * ( 1 - COBpredBGs.length/Math.max(cid*2,1) ) );
@ -613,9 +611,8 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
//console.error(UAMpredBGs.length,slopeFromDeviations, predUCI);
UAMduration=round((UAMpredBGs.length+1)*5/60,1);
}
//UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + predBGI + Math.min(0, predDev) + predUCI;
UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + (round(( -iobTick.activity * (1800 / ( TDD
* (Math.log(( Math.max(UAMpredBGs[UAMpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) )) * 5 ),2)) + Math.min(0, predDev) + predUCI;
if (!TDD) UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + predBGI + Math.min(0, predDev) + predUCI;
else UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + (round(( -iobTick.activity * (1800 / ( TDD * (Math.log(( Math.max(UAMpredBGs[UAMpredBGs.length-1],39) / insulinDivisor ) + 1 ) ) )) * 5 ),2)) + Math.min(0, predDev) + predUCI;
//console.error(predBGI, predCI, predUCI);
// truncate all BG predictions at 4 hours
if ( IOBpredBGs.length < 48) { IOBpredBGs.push(IOBpredBG); }
@ -727,6 +724,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
var fSensBG = Math.min(minPredBG,bg);
if (TDD) {
if (bg > target_bg && glucose_status.delta < 3 && glucose_status.delta > -3 && glucose_status.short_avgdelta > -3 && glucose_status.short_avgdelta < 3 && eventualBG > target_bg && eventualBG < bg ) {
var future_sens = ( 1800 / (Math.log((((fSensBG * 0.5) + (bg * 0.5))/insulinDivisor)+1)*TDD));
//var future_sens_old = ( 277700 / (TDD * ((bg * 0.5) + (eventualBG * 0.5 ))));
@ -748,6 +746,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;";
}
future_sens = round(future_sens,1);
} else future_sens = variable_sens
var fractionCarbsLeft = meal_data.mealCOB/meal_data.carbs;

View file

@ -262,23 +262,11 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
this.mealData.put("lastBolusTime", mealData.lastBolusTime)
this.mealData.put("lastCarbTime", mealData.lastCarbTime)
val tdd1D = tddCalculator.averageTDD(tddCalculator.calculate(1))?.totalAmount
val tdd7D = tddCalculator.averageTDD(tddCalculator.calculate(7))?.totalAmount
val tddLast24H = tddCalculator.calculateDaily(-24, 0).totalAmount
val tddLast4H = tddCalculator.calculateDaily(-4, 0).totalAmount
val tddLast8to4H = tddCalculator.calculateDaily(-8, -4).totalAmount
val tddWeightedFromLast8H = ((1.4 * tddLast4H) + (0.6 * tddLast8to4H)) * 3
// console.error("Rolling 8 hours weight average: " + tdd_last8_wt + "; ");
// console.error("1-day average TDD is: " + tdd1 + "; ");
// console.error("7-day average TDD is: " + tdd7 + "; ");
var tdd =
if (tdd1D != null && tdd7D != null) (tddWeightedFromLast8H * 0.33) + (tdd7D * 0.34) + (tdd1D * 0.33)
else tddWeightedFromLast8H
// console.log("TDD = " + TDD + " using average of 7-day, 1-day and weighted 8hr average");
// console.log("Insulin Peak = " + insulin.peak + "; ");
val tdd1D = tddCalculator.averageTDD(tddCalculator.calculate(1, allowMissingDays = false))?.totalAmount
val tdd7D = tddCalculator.averageTDD(tddCalculator.calculate(7, allowMissingDays = false))?.totalAmount
val tddLast24H = tddCalculator.calculateDaily(-24, 0)?.totalAmount
val tddLast4H = tddCalculator.calculateDaily(-4, 0)?.totalAmount
val tddLast8to4H = tddCalculator.calculateDaily(-8, -4)?.totalAmount
val insulin = activePlugin.activeInsulin
val insulinDivisor = when {
@ -286,25 +274,35 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
insulin.peak > 50 -> 65 // ultra rapid peak: 55
else -> 75 // rapid peak: 75
}
// console.log("For " + insulin.friendlyName + " (insulin peak: " + insulin.peak + ") insulin divisor is: " + ins_val + "; ");
var tdd: Double? = null
var variableSensitivity: Double
if (tddLast24H != null && tddLast4H != null && tddLast8to4H != null) {
val tddWeightedFromLast8H = ((1.4 * tddLast4H) + (0.6 * tddLast8to4H)) * 3
// console.error("Rolling 8 hours weight average: " + tdd_last8_wt + "; ");
// console.error("1-day average TDD is: " + tdd1 + "; ");
// console.error("7-day average TDD is: " + tdd7 + "; ");
tdd =
if (tdd1D != null && tdd7D != null) (tddWeightedFromLast8H * 0.33) + (tdd7D * 0.34) + (tdd1D * 0.33)
else tddWeightedFromLast8H
// console.log("TDD = " + TDD + " using average of 7-day, 1-day and weighted 8hr average");
val dynISFadjust = SafeParse.stringToDouble(sp.getString(R.string.key_DynISFAdjust, "100")) / 100.0
tdd *= dynISFadjust
var variableSensitivity = 1800 / (tdd * (ln((glucoseStatus.glucose / insulinDivisor) + 1)))
variableSensitivity = 1800 / (tdd * (ln((glucoseStatus.glucose / insulinDivisor) + 1)))
variableSensitivity = Round.roundTo(variableSensitivity, 0.1)
if (dynISFadjust != 1.0) {
// console.log("TDD adjusted to " + TDD + " using adjustment factor of " + dynISFadjust + "; ");
} else {
variableSensitivity = profile.getIsfMgdl()
}
// console.log("Current sensitivity for predictions is " + variable_sens + " based on current bg");
this.profile.put("variable_sens", variableSensitivity)
this.profile.put("insulinDivisor", insulinDivisor)
this.profile.put("TDD", tdd)
tdd?.let { this.profile.put("TDD", tdd) }
if (sp.getBoolean(R.string.key_adjust_sensitivity, false) && tdd7D != null && tdd7D != 0.0)
if (sp.getBoolean(R.string.key_adjust_sensitivity, false) && tdd7D != null && tddLast24H != null)
autosensData.put("ratio", tddLast24H / tdd7D)
else
autosensData.put("ratio", 1.0)

View file

@ -134,11 +134,10 @@ class StatusLightHandler @Inject constructor(
private fun handleUsage(view: TextView?, units: String) {
handler.post {
val therapyEvent = repository.getLastTherapyRecordUpToNow(TherapyEvent.Type.CANNULA_CHANGE).blockingGet()
var usage = 0.0
var usage =
if (therapyEvent is ValueWrapper.Existing) {
val tdd = tddCalculator.calculate(therapyEvent.value.timestamp, dateUtil.now())
usage = tdd.totalAmount
}
tddCalculator.calculate(therapyEvent.value.timestamp, dateUtil.now(), allowMissingData = false)?.totalAmount ?: 0.0
} else 0.0
runOnUiThread {
view?.text = DecimalFormatter.to0Decimal(usage, units)
}

View file

@ -26,6 +26,7 @@ dependencies {
implementation project(':core:utils')
implementation project(':core:validators')
testImplementation project(':implementation')
// NSClient, Tidepool
api("io.socket:socket.io-client:1.0.2") {

View file

@ -611,6 +611,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
offlineEvents.clear()
result.inserted.forEach { oe ->
if (config.NSCLIENT.not()) userEntries.add(
UserEntry(
@ -679,6 +680,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
extendedBoluses.clear()
result.inserted.forEach {
if (config.NSCLIENT.not()) userEntries.add(
UserEntry(
@ -775,6 +777,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdTemporaryTargets.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of TemporaryTarget $it")
nsIdUpdated.inc(TemporaryTarget::class.java.simpleName)
@ -787,6 +790,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdGlucoseValues.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of GlucoseValue $it")
nsIdUpdated.inc(GlucoseValue::class.java.simpleName)
@ -799,6 +803,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdFoods.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of Food $it")
nsIdUpdated.inc(Food::class.java.simpleName)
@ -811,6 +816,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdTherapyEvents.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of TherapyEvent $it")
nsIdUpdated.inc(TherapyEvent::class.java.simpleName)
@ -823,6 +829,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdBoluses.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of Bolus $it")
nsIdUpdated.inc(Bolus::class.java.simpleName)
@ -835,6 +842,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdCarbs.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of Carbs $it")
nsIdUpdated.inc(Carbs::class.java.simpleName)
@ -847,6 +855,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdBolusCalculatorResults.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of BolusCalculatorResult $it")
nsIdUpdated.inc(BolusCalculatorResult::class.java.simpleName)
@ -859,6 +868,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdTemporaryBasals.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of TemporaryBasal $it")
nsIdUpdated.inc(TemporaryBasal::class.java.simpleName)
@ -871,6 +881,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdExtendedBoluses.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of ExtendedBolus $it")
nsIdUpdated.inc(ExtendedBolus::class.java.simpleName)
@ -883,6 +894,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdProfileSwitches.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of ProfileSwitch $it")
nsIdUpdated.inc(ProfileSwitch::class.java.simpleName)
@ -895,6 +907,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdEffectiveProfileSwitches.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of EffectiveProfileSwitch $it")
nsIdUpdated.inc(EffectiveProfileSwitch::class.java.simpleName)
@ -907,6 +920,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdDeviceStatuses.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of DeviceStatus $it")
nsIdUpdated.inc(DeviceStatus::class.java.simpleName)
@ -919,6 +933,7 @@ class StoreDataForDbImpl @Inject constructor(
}
.blockingGet()
.also { result ->
nsIdOfflineEvents.clear()
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId of OfflineEvent $it")
nsIdUpdated.inc(OfflineEvent::class.java.simpleName)

View file

@ -14,6 +14,7 @@ fun NSBolus.toBolus(): Bolus =
amount = insulin,
type = type.toBolusType(),
notes = notes,
isBasalInsulin = isBasalInsulin,
interfaceIDs_backing = InterfaceIDs(nightscoutId = identifier, pumpId = pumpId, pumpType = InterfaceIDs.PumpType.fromString(pumpType), pumpSerial = pumpSerial, endId = endId)
)
@ -34,6 +35,7 @@ fun Bolus.toNSBolus(): NSBolus =
insulin = amount,
type = type.toBolusType(),
notes = notes,
isBasalInsulin = isBasalInsulin,
identifier = interfaceIDs.nightscoutId,
pumpId = interfaceIDs.pumpId,
pumpType = interfaceIDs.pumpType?.name,

View file

@ -41,23 +41,22 @@ fun NSProfileSwitch.toProfileSwitch(activePlugin: ActivePlugin, dateUtil: DateUt
fun ProfileSwitch.toNSProfileSwitch(dateUtil: DateUtil): NSProfileSwitch {
val unmodifiedCustomizedName = getCustomizedName()
// ProfileSealed.PS doesn't provide unmodified json -> reset it
val unmodifiedTimeshift = timeshift
val unmodifiedPercentage = percentage
timeshift = 0
percentage = 100
val notCustomized = this.copy()
notCustomized.timeshift = 0
notCustomized.percentage = 100
return NSProfileSwitch(
eventType = EventType.fromString(TherapyEvent.Type.PROFILE_SWITCH.text),
isValid = isValid,
date = timestamp,
utcOffset = utcOffset,
timeShift = unmodifiedTimeshift,
percentage = unmodifiedPercentage,
timeShift = timeshift,
percentage = percentage,
duration = T.mins(duration).msecs(),
profile = unmodifiedCustomizedName,
originalProfileName = profileName,
originalDuration = duration,
profileJson = ProfileSealed.PS(this).toPureNsJson(dateUtil),
profileJson = ProfileSealed.PS(notCustomized).toPureNsJson(dateUtil),
identifier = interfaceIDs.nightscoutId,
pumpId = interfaceIDs.pumpId,
pumpType = interfaceIDs.pumpType?.name,

View file

@ -0,0 +1,193 @@
package info.nightscout.androidaps
import android.content.Context
import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.core.extensions.pureProfileFromJson
import info.nightscout.core.profile.ProfileSealed
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.database.entities.EffectiveProfileSwitch
import info.nightscout.database.entities.embedments.InsulinConfiguration
import info.nightscout.database.impl.AppRepository
import info.nightscout.implementation.profile.ProfileFunctionImpl
import info.nightscout.implementation.profile.ProfileStoreObject
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.insulin.Insulin
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.nsclient.ProcessedDeviceStatusData
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.profile.ProfileStore
import info.nightscout.interfaces.utils.HardLimits
import info.nightscout.rx.bus.RxBus
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import org.json.JSONObject
import org.junit.jupiter.api.BeforeEach
import org.mockito.ArgumentMatchers.anyDouble
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.invocation.InvocationOnMock
@Suppress("SpellCheckingInspection")
open class TestBaseWithProfile : TestBase() {
@Mock lateinit var activePlugin: ActivePlugin
@Mock lateinit var rh: ResourceHelper
@Mock lateinit var iobCobCalculator: IobCobCalculator
@Mock lateinit var fabricPrivacy: FabricPrivacy
@Mock lateinit var config: Config
@Mock lateinit var context: Context
@Mock lateinit var sp: SP
@Mock lateinit var repository: AppRepository
@Mock lateinit var hardLimits: HardLimits
@Mock lateinit var processedDeviceStatusData: ProcessedDeviceStatusData
@Mock lateinit var insulin: Insulin
lateinit var profileFunction: ProfileFunction
lateinit var dateUtil: DateUtil
var insulinConfiguration: InsulinConfiguration = InsulinConfiguration("Insulin", 360 * 60 * 1000, 60 * 60 * 1000)
val rxBus = RxBus(aapsSchedulers, aapsLogger)
val profileInjector = HasAndroidInjector { AndroidInjector { } }
private lateinit var validProfileJSON: String
lateinit var validProfile: ProfileSealed.Pure
lateinit var effectiveProfileSwitch: EffectiveProfileSwitch
@Suppress("PropertyName") val TESTPROFILENAME = "someProfile"
@BeforeEach
fun prepareMock() {
validProfileJSON = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"3\"}," +
"{\"time\":\"2:00\",\"value\":\"3.4\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4.5\"}]," +
"\"target_high\":[{\"time\":\"00:00\",\"value\":\"7\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
dateUtil = Mockito.spy(DateUtil(context))
`when`(dateUtil.now()).thenReturn(1656358822000)
`when`(insulin.insulinConfiguration).thenReturn(insulinConfiguration)
`when`(activePlugin.activeInsulin).thenReturn(insulin)
profileFunction = ProfileFunctionImpl(aapsLogger, sp, rxBus, rh, activePlugin, repository, dateUtil, config, hardLimits, aapsSchedulers, fabricPrivacy, processedDeviceStatusData)
validProfile = ProfileSealed.Pure(pureProfileFromJson(JSONObject(validProfileJSON), dateUtil)!!)
effectiveProfileSwitch = EffectiveProfileSwitch(
timestamp = dateUtil.now(),
basalBlocks = validProfile.basalBlocks,
isfBlocks = validProfile.isfBlocks,
icBlocks = validProfile.icBlocks,
targetBlocks = validProfile.targetBlocks,
glucoseUnit = EffectiveProfileSwitch.GlucoseUnit.MMOL,
originalProfileName = "",
originalCustomizedName = "",
originalTimeshift = 0,
originalPercentage = 100,
originalDuration = 0,
originalEnd = 0,
insulinConfiguration = InsulinConfiguration("", 0, 0)
)
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<Int?>(1)
String.format(rh.gs(string), arg1)
}.`when`(rh).gs(anyInt(), anyInt())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<Double?>(1)
String.format(rh.gs(string), arg1)
}.`when`(rh).gs(anyInt(), anyDouble())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<String?>(1)
String.format(rh.gs(string), arg1)
}.`when`(rh).gs(anyInt(), anyString())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<String?>(1)
val arg2 = invocation.getArgument<String?>(2)
String.format(rh.gs(string), arg1, arg2)
}.`when`(rh).gs(anyInt(), anyString(), anyString())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<String?>(1)
val arg2 = invocation.getArgument<Int?>(2)
String.format(rh.gs(string), arg1, arg2)
}.`when`(rh).gs(anyInt(), anyString(), anyInt())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<Double?>(1)
val arg2 = invocation.getArgument<String?>(2)
String.format(rh.gs(string), arg1, arg2)
}.`when`(rh).gs(anyInt(), anyDouble(), anyString())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<Double?>(1)
val arg2 = invocation.getArgument<Int?>(2)
String.format(rh.gs(string), arg1, arg2)
}.`when`(rh).gs(anyInt(), anyDouble(), anyInt())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<Int?>(1)
val arg2 = invocation.getArgument<Int?>(2)
String.format(rh.gs(string), arg1, arg2)
}.`when`(rh).gs(anyInt(), anyInt(), anyInt())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<Int?>(1)
val arg2 = invocation.getArgument<String?>(2)
String.format(rh.gs(string), arg1, arg2)
}.`when`(rh).gs(anyInt(), anyInt(), anyString())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<Int?>(1)
val arg2 = invocation.getArgument<Int?>(2)
val arg3 = invocation.getArgument<String?>(3)
String.format(rh.gs(string), arg1, arg2, arg3)
}.`when`(rh).gs(anyInt(), anyInt(), anyInt(), anyString())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<Int?>(1)
val arg2 = invocation.getArgument<String?>(2)
val arg3 = invocation.getArgument<String?>(3)
String.format(rh.gs(string), arg1, arg2, arg3)
}.`when`(rh).gs(anyInt(), anyInt(), anyString(), anyString())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<Double?>(1)
val arg2 = invocation.getArgument<Int?>(2)
val arg3 = invocation.getArgument<String?>(3)
String.format(rh.gs(string), arg1, arg2, arg3)
}.`when`(rh).gs(anyInt(), anyDouble(), anyInt(), anyString())
Mockito.doAnswer { invocation: InvocationOnMock ->
val string = invocation.getArgument<Int>(0)
val arg1 = invocation.getArgument<String?>(1)
val arg2 = invocation.getArgument<Int?>(2)
val arg3 = invocation.getArgument<String?>(3)
String.format(rh.gs(string), arg1, arg2, arg3)
}.`when`(rh).gs(anyInt(), anyString(), anyInt(), anyString())
}
fun getValidProfileStore(): ProfileStore {
val json = JSONObject()
val store = JSONObject()
store.put(TESTPROFILENAME, JSONObject(validProfileJSON))
json.put("defaultProfile", TESTPROFILENAME)
json.put("store", store)
return ProfileStoreObject(profileInjector, json, dateUtil)
}
}

View file

@ -0,0 +1,54 @@
package info.nightscout.plugins.sync.nsclientV3.extensions
import info.nightscout.database.entities.Bolus
import info.nightscout.database.entities.embedments.InterfaceIDs
import info.nightscout.sdk.localmodel.treatment.NSBolus
import info.nightscout.sdk.mapper.convertToRemoteAndBack
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@Suppress("SpellCheckingInspection")
internal class BolusExtensionKtTest {
@Test
fun toBolus() {
var bolus = Bolus(
timestamp = 10000,
isValid = true,
amount = 1.0,
type = Bolus.Type.SMB,
notes = "aaaa",
isBasalInsulin = false,
interfaceIDs_backing = InterfaceIDs(
nightscoutId = "nightscoutId",
pumpId = 11000,
pumpType = InterfaceIDs.PumpType.DANA_I,
pumpSerial = "bbbb"
)
)
var bolus2 = (bolus.toNSBolus().convertToRemoteAndBack() as NSBolus).toBolus()
Assertions.assertTrue(bolus.contentEqualsTo(bolus2))
Assertions.assertTrue(bolus.interfaceIdsEqualsTo(bolus2))
bolus = Bolus(
timestamp = 10000,
isValid = false,
amount = 1.0,
type = Bolus.Type.NORMAL,
notes = "aaaa",
isBasalInsulin = true,
interfaceIDs_backing = InterfaceIDs(
nightscoutId = "nightscoutId",
pumpId = 11000,
pumpType = InterfaceIDs.PumpType.DANA_I,
pumpSerial = "bbbb"
)
)
bolus2 = (bolus.toNSBolus().convertToRemoteAndBack() as NSBolus).toBolus()
Assertions.assertTrue(bolus.contentEqualsTo(bolus2))
Assertions.assertTrue(bolus.interfaceIdsEqualsTo(bolus2))
}
}

View file

@ -0,0 +1,51 @@
package info.nightscout.plugins.sync.nsclientV3.extensions
import info.nightscout.database.entities.Carbs
import info.nightscout.database.entities.embedments.InterfaceIDs
import info.nightscout.sdk.localmodel.treatment.NSCarbs
import info.nightscout.sdk.mapper.convertToRemoteAndBack
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@Suppress("SpellCheckingInspection")
internal class CarbsExtensionKtTest {
@Test
fun toCarbs() {
var carbs = Carbs(
timestamp = 10000,
isValid = true,
amount = 1.0,
duration = 0,
notes = "aaaa",
interfaceIDs_backing = InterfaceIDs(
nightscoutId = "nightscoutId",
pumpId = 11000,
pumpType = InterfaceIDs.PumpType.DANA_I,
pumpSerial = "bbbb"
)
)
var carbs2 = (carbs.toNSCarbs().convertToRemoteAndBack() as NSCarbs).toCarbs()
Assertions.assertTrue(carbs.contentEqualsTo(carbs2))
Assertions.assertTrue(carbs.interfaceIdsEqualsTo(carbs2))
carbs = Carbs(
timestamp = 10000,
isValid = false,
amount = 1.0,
duration = 60000,
notes = null,
interfaceIDs_backing = InterfaceIDs(
nightscoutId = "nightscoutId",
pumpId = 11000,
pumpType = InterfaceIDs.PumpType.DANA_I,
pumpSerial = "bbbb"
)
)
carbs2 = (carbs.toNSCarbs().convertToRemoteAndBack() as NSCarbs).toCarbs()
Assertions.assertTrue(carbs.contentEqualsTo(carbs2))
Assertions.assertTrue(carbs.interfaceIdsEqualsTo(carbs2))
}
}

View file

@ -0,0 +1,46 @@
package info.nightscout.plugins.sync.nsclientV3.extensions
import info.nightscout.androidaps.TestBaseWithProfile
import info.nightscout.database.entities.EffectiveProfileSwitch
import info.nightscout.database.entities.embedments.InterfaceIDs
import info.nightscout.plugins.sync.nsclient.extensions.fromConstant
import info.nightscout.sdk.localmodel.treatment.NSEffectiveProfileSwitch
import info.nightscout.sdk.mapper.convertToRemoteAndBack
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@Suppress("SpellCheckingInspection")
internal class EffectiveProfileSwitchExtensionKtTest : TestBaseWithProfile() {
@Test
fun toEffectiveProfileSwitch() {
val profileSwitch = EffectiveProfileSwitch(
timestamp = 10000,
isValid = true,
basalBlocks = validProfile.basalBlocks,
isfBlocks = validProfile.isfBlocks,
icBlocks = validProfile.icBlocks,
targetBlocks = validProfile.targetBlocks,
glucoseUnit = EffectiveProfileSwitch.GlucoseUnit.fromConstant(validProfile.units),
originalProfileName = "SomeProfile",
originalCustomizedName = "SomeProfile (150%, 1h)",
originalTimeshift = 3600000,
originalPercentage = 150,
originalDuration = 3600000,
originalEnd = 0,
insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration.also {
it.insulinEndTime = (validProfile.dia * 3600 * 1000).toLong()
},
interfaceIDs_backing = InterfaceIDs(
nightscoutId = "nightscoutId",
pumpId = 11000,
pumpType = InterfaceIDs.PumpType.DANA_I,
pumpSerial = "bbbb"
)
)
val profileSwitch2 = (profileSwitch.toNSEffectiveProfileSwitch(dateUtil).convertToRemoteAndBack() as NSEffectiveProfileSwitch).toEffectiveProfileSwitch(dateUtil)!!
Assertions.assertTrue(profileSwitch.contentEqualsTo(profileSwitch2))
Assertions.assertTrue(profileSwitch.interfaceIdsEqualsTo(profileSwitch2))
}
}

View file

@ -0,0 +1,71 @@
package info.nightscout.plugins.sync.nsclientV3.extensions
import info.nightscout.androidaps.TestBaseWithProfile
import info.nightscout.core.extensions.fromConstant
import info.nightscout.database.entities.ProfileSwitch
import info.nightscout.database.entities.embedments.InterfaceIDs
import info.nightscout.sdk.localmodel.treatment.NSProfileSwitch
import info.nightscout.sdk.mapper.convertToRemoteAndBack
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@Suppress("SpellCheckingInspection")
internal class ProfileSwitchExtensionKtTest : TestBaseWithProfile() {
@Test
fun toProfileSwitch() {
var profileSwitch = ProfileSwitch(
timestamp = 10000,
isValid = true,
basalBlocks = validProfile.basalBlocks,
isfBlocks = validProfile.isfBlocks,
icBlocks = validProfile.icBlocks,
targetBlocks = validProfile.targetBlocks,
glucoseUnit = ProfileSwitch.GlucoseUnit.fromConstant(validProfile.units),
profileName = "SomeProfile",
timeshift = 0,
percentage = 100,
duration = 0,
insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration.also {
it.insulinEndTime = (validProfile.dia * 3600 * 1000).toLong()
},
interfaceIDs_backing = InterfaceIDs(
nightscoutId = "nightscoutId",
pumpId = 11000,
pumpType = InterfaceIDs.PumpType.DANA_I,
pumpSerial = "bbbb"
)
)
var profileSwitch2 = (profileSwitch.toNSProfileSwitch(dateUtil).convertToRemoteAndBack() as NSProfileSwitch).toProfileSwitch(activePlugin, dateUtil)!!
Assertions.assertTrue(profileSwitch.contentEqualsTo(profileSwitch2))
Assertions.assertTrue(profileSwitch.interfaceIdsEqualsTo(profileSwitch2))
profileSwitch = ProfileSwitch(
timestamp = 10000,
isValid = true,
basalBlocks = validProfile.basalBlocks,
isfBlocks = validProfile.isfBlocks,
icBlocks = validProfile.icBlocks,
targetBlocks = validProfile.targetBlocks,
glucoseUnit = ProfileSwitch.GlucoseUnit.fromConstant(validProfile.units),
profileName = "SomeProfile",
timeshift = -3600000,
percentage = 150,
duration = 3600000,
insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration.also {
it.insulinEndTime = (validProfile.dia * 3600 * 1000).toLong()
},
interfaceIDs_backing = InterfaceIDs(
nightscoutId = "nightscoutId",
pumpId = 11000,
pumpType = InterfaceIDs.PumpType.DANA_I,
pumpSerial = "bbbb"
)
)
profileSwitch2 = (profileSwitch.toNSProfileSwitch(dateUtil).convertToRemoteAndBack() as NSProfileSwitch).toProfileSwitch(activePlugin, dateUtil)!!
Assertions.assertTrue(profileSwitch.contentEqualsTo(profileSwitch2))
Assertions.assertTrue(profileSwitch.interfaceIdsEqualsTo(profileSwitch2))
}
}