package com.eveningoutpost.dexdrip.Models; /** * Created by jamorham on 01/02/2017. */ import android.provider.BaseColumns; import com.activeandroid.annotation.Column; import com.activeandroid.annotation.Table; import com.activeandroid.query.Select; import com.eveningoutpost.dexdrip.BestGlucose; import com.eveningoutpost.dexdrip.UtilityModels.Constants; import com.eveningoutpost.dexdrip.UtilityModels.Pref; import com.google.gson.annotations.Expose; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Table(name = "Accuracy", id = BaseColumns._ID) public class Accuracy extends PlusModel { private static final String TAG = "Accuracy"; private static boolean patched = false; static final String[] schema = { "CREATE TABLE Accuracy (_id INTEGER PRIMARY KEY AUTOINCREMENT);", "ALTER TABLE Accuracy ADD COLUMN timestamp INTEGER;", "ALTER TABLE Accuracy ADD COLUMN bg REAL;", "ALTER TABLE Accuracy ADD COLUMN bgtimestamp INTEGER;", "ALTER TABLE Accuracy ADD COLUMN bgsource TEXT;", "ALTER TABLE Accuracy ADD COLUMN plugin TEXT;", "ALTER TABLE Accuracy ADD COLUMN calculated REAL;", "ALTER TABLE Accuracy ADD COLUMN lag INTEGER;", "ALTER TABLE Accuracy ADD COLUMN difference REAL;", "CREATE INDEX index_Accuracy_timestamp on Accuracy(timestamp);", "CREATE INDEX index_Accuracy_bgtimestamp on Accuracy(bgtimestamp);" }; @Expose @Column(name = "timestamp", index = true) public long timestamp; @Expose @Column(name = "bg") public double bg; @Expose @Column(name = "bgtimestamp", index = true) public long bgtimestamp; @Expose @Column(name = "bgsource") public String bgsource; @Expose @Column(name = "plugin") public String plugin; @Expose @Column(name = "calculated") public double calculated; @Expose @Column(name = "lag") public boolean lag; @Expose @Column(name = "difference") public double difference; private static final boolean d = false; public static Accuracy create(BloodTest bloodTest, BestGlucose.DisplayGlucose dg) { if (dg == null) return null; final BgReading from_dg = new BgReading(); from_dg.timestamp = dg.timestamp; from_dg.calculated_value = dg.mgdl; return create(bloodTest, from_dg, dg.plugin_name); } public static Accuracy create(BloodTest bloodTest, BgReading bgReading, String plugin) { if ((bloodTest == null) || (bgReading == null)) return null; patched = fixUpTable(schema, patched); if (getForPreciseTimestamp(bgReading.timestamp, Constants.MINUTE_IN_MS, plugin) != null) { UserError.Log.d(TAG, "Duplicate accuracy timestamp for: " + JoH.dateTimeText(bgReading.timestamp)); return null; } final Accuracy ac = new Accuracy(); ac.timestamp = bgReading.timestamp; ac.bg = bloodTest.mgdl; ac.bgtimestamp = bloodTest.timestamp; ac.bgsource = bloodTest.source; ac.plugin = plugin; ac.calculated = bgReading.calculated_value; //ac.lag = bgReading.timestamp-bloodTest.timestamp; ac.difference = bgReading.calculated_value - bloodTest.mgdl; ac.save(); return ac; } static Accuracy getForPreciseTimestamp(double timestamp, double precision, String plugin) { patched = fixUpTable(schema, patched); final Accuracy accuracy = new Select() .from(Accuracy.class) .where("timestamp <= ?", (timestamp + precision)) .where("timestamp >= ?", (timestamp - precision)) .where("plugin = ?", plugin) .orderBy("abs(timestamp - " + timestamp + ") asc") .executeSingle(); if (accuracy != null && Math.abs(accuracy.timestamp - timestamp) < precision) { return accuracy; } return null; } public static List<Accuracy> latestForGraph(int number, long startTime, long endTime) { try { return new Select() .from(Accuracy.class) .where("timestamp >= " + Math.max(startTime, 0)) .where("timestamp <= " + endTime) .orderBy("timestamp desc, _id asc") .limit(number) .execute(); } catch (android.database.sqlite.SQLiteException e) { patched = fixUpTable(schema, patched); return new ArrayList<>(); } } public static String evaluateAccuracy(long period) { // TODO CACHE ? final boolean domgdl = Pref.getString("units", "mgdl").equals("mgdl"); final Map<String, Double> totals = new HashMap<>(); final Map<String, Double> signed_totals = new HashMap<>(); final Map<String, Integer> count = new HashMap<>(); final List<Accuracy> alist = latestForGraph(500, JoH.tsl() - period, JoH.tsl()); // total up differences for (Accuracy entry : alist) { if (totals.containsKey(entry.plugin)) { totals.put(entry.plugin, totals.get(entry.plugin) + Math.abs(entry.difference)); signed_totals.put(entry.plugin, signed_totals.get(entry.plugin) + entry.difference); count.put(entry.plugin, count.get(entry.plugin) + 1); } else { totals.put(entry.plugin, Math.abs(entry.difference)); signed_totals.put(entry.plugin, entry.difference); count.put(entry.plugin, 1); } } String result = ""; int plugin_count = 0; for (Map.Entry<String, Double> total : totals.entrySet()) { plugin_count++; final String plugin = total.getKey(); final int this_count = count.get(plugin); final double this_total = total.getValue(); // calculate the abs mean, 0 = perfect final double this_mean = this_total / this_count; final double signed_total = signed_totals.get(plugin); final double signed_mean = signed_total / this_count; // calculate the bias ratio. 0% means totally unbiased, 100% means all data skewed towards signed mean final double signed_ratio = (Math.abs(signed_mean) / this_mean) * 100; if (d) UserError.Log.d(TAG, plugin + ": total: " + JoH.qs(this_total) + " count: " + this_count + " avg: " + JoH.qs(this_mean) + " mmol: " + JoH.qs((this_mean) * Constants.MGDL_TO_MMOLL) + " bias: " + JoH.qs(signed_mean) + " " + JoH.qs(signed_ratio, 0) + "%"); String plugin_result = plugin.substring(0, 1).toLowerCase() + ": " + asString(this_mean, signed_mean, signed_ratio, domgdl); UserError.Log.d(TAG, plugin_result); if (result.length() > 0) result += " "; result += plugin_result; } return plugin_count == 1 ? result : result.replaceFirst(" mmol", "").replaceFirst(" mgdl", " "); } private static String asString(double mean, double signed_mean, double signed_ratio, boolean domgdl) { String symbol = "err"; if (signed_ratio < 90) { symbol = "\u00B1"; // +- symbol } else { if (signed_mean < 0) { symbol = "\u207B"; // superscript minus } else { symbol = "\u207A"; // superscript plus } } return symbol + (!domgdl ? JoH.qs(mean * Constants.MGDL_TO_MMOLL, 2) + " mmol" : JoH.qs(mean, 1) + " mgdl"); } }