Commit 8c5cb3c

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-06-19 13:08:03
fix model/ tests and code
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent fe5e146
app/lib/model/export_import/column.dart
@@ -1,7 +1,5 @@
 import 'dart:convert';
-import 'dart:ui';
 
-import 'package:blood_pressure_app/model/blood_pressure/needle_pin.dart';
 import 'package:blood_pressure_app/model/export_import/export_configuration.dart';
 import 'package:blood_pressure_app/model/export_import/import_field_type.dart';
 import 'package:blood_pressure_app/model/export_import/record_formatter.dart';
@@ -62,18 +60,18 @@ class NativeColumn extends ExportColumn {
   );
   static final NativeColumn color = NativeColumn._create(
     'color',
-    RowDataFieldType.needlePin,
+    RowDataFieldType.color,
     (_, note, __) => note.color?.toString() ?? '',
     (pattern) {
       final value = int.tryParse(pattern);
       if (value == null) return null;
-      return MeasurementNeedlePin(Color(value));
+      return value;
     }
   );
   static final NativeColumn needlePin = NativeColumn._create(
     'needlePin',
-    RowDataFieldType.needlePin,
-    (_, note, __) => '{"color":${note.color}',
+    RowDataFieldType.color,
+    (_, note, __) => '{"color":${note.color}}',
     (pattern) {
       try {
         final json = jsonDecode(pattern);
@@ -81,7 +79,7 @@ class NativeColumn extends ExportColumn {
         if (json.containsKey('color')) {
           final value = json['color'];
           return (value is int)
-            ? MeasurementNeedlePin(Color(value))
+            ? value
             : null;
         }
       } on FormatException {
app/lib/model/export_import/csv_converter.dart
@@ -1,5 +1,3 @@
-
-import 'package:blood_pressure_app/model/blood_pressure/needle_pin.dart';
 import 'package:blood_pressure_app/model/export_import/column.dart';
 import 'package:blood_pressure_app/model/export_import/import_field_type.dart' show RowDataFieldType;
 import 'package:blood_pressure_app/model/export_import/record_parsing_result.dart';
@@ -139,8 +137,8 @@ class CsvConverter {
             (piece) => piece.$1 == RowDataFieldType.pul,)?.$2;
       final String noteText = recordPieces.firstWhereOrNull(
             (piece) => piece.$1 == RowDataFieldType.notes,)?.$2 ?? '';
-      final MeasurementNeedlePin? needlePin = recordPieces.firstWhereOrNull(
-            (piece) => piece.$1 == RowDataFieldType.needlePin,)?.$2;
+      final int? color = recordPieces.firstWhereOrNull(
+            (piece) => piece.$1 == RowDataFieldType.color,)?.$2;
 
       final record = BloodPressureRecord(
         time: timestamp,
@@ -151,7 +149,7 @@ class CsvConverter {
       final note = Note(
         time: timestamp,
         note: noteText,
-        color: needlePin?.color.value,
+        color: color,
       );
       entries.add((record, note, []));
       currentLineNumber++;
app/lib/model/export_import/import_field_type.dart
@@ -1,5 +1,6 @@
 import 'package:blood_pressure_app/model/blood_pressure/needle_pin.dart';
 import 'package:blood_pressure_app/model/export_import/record_formatter.dart';
+import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 
 
@@ -15,8 +16,10 @@ enum RowDataFieldType {
   pul,
   /// Guarantees [String] is returned.
   notes,
-  /// Guarantees that the returned type is of type [MeasurementNeedlePin].
-  needlePin; // TODO: replace with color
+  /// Guarantees that a [int] containing a [Color.value] is returned.
+  ///
+  /// Backwards compatability with [MeasurementNeedlePin] json is maintained.
+  color; // TODO: replace with color
 
   /// Selection of a displayable string from [localizations].
   String localize(AppLocalizations localizations) {
@@ -31,7 +34,7 @@ enum RowDataFieldType {
         return localizations.pulLong;
       case RowDataFieldType.notes:
         return localizations.notes;
-      case RowDataFieldType.needlePin:
+      case RowDataFieldType.color:
         return localizations.color;
     }
   }
app/lib/model/export_import/record_formatter.dart
@@ -2,7 +2,6 @@ import 'dart:convert';
 
 import 'package:blood_pressure_app/model/blood_pressure/needle_pin.dart';
 import 'package:blood_pressure_app/model/export_import/import_field_type.dart';
-import 'package:flutter/material.dart';
 import 'package:function_tree/function_tree.dart';
 import 'package:health_data_store/health_data_store.dart';
 import 'package:intl/intl.dart';
@@ -56,13 +55,14 @@ class ScriptedFormatter implements Formatter {
         return int.tryParse(text);
       case RowDataFieldType.notes:
         return text;
-      case RowDataFieldType.needlePin:
+      case RowDataFieldType.color:
         final num = int.tryParse(text);
-        if (num != null) return MeasurementNeedlePin(Color(num));
+        if (num != null) return num;
         try {
-          return MeasurementNeedlePin.fromMap(jsonDecode(text));
-        } catch (e) {
-          assert(e is FormatException || e is TypeError);
+          return MeasurementNeedlePin.fromMap(jsonDecode(text)).color.value;
+        } on FormatException {
+          return null;
+        } on TypeError {
           return null;
         }
     }}();
@@ -128,7 +128,7 @@ class ScriptedFormatter implements Formatter {
       } else if (pattern == r'$TIMESTAMP') {
         _restoreAbleType = RowDataFieldType.timestamp;
       } else if (pattern == r'$COLOR') {
-        _restoreAbleType = RowDataFieldType.needlePin;
+        _restoreAbleType = RowDataFieldType.color;
       } else if (pattern == r'$NOTE') {
         _restoreAbleType = RowDataFieldType.notes;
       } else if (replaced.contains(RegExp(r'[^{},$]*\$(PUL|DIA|SYS)[^{},$]*'))) {
app/lib/model/blood_pressure_analyzer.dart
@@ -40,7 +40,7 @@ class BloodPressureAnalyser {
   int? get minPul => _records.map((r) => r.pul).tryMin?.toInt();
 
   /// The minimal systolic values of all records.
-  Pressure? get minSys => _records.map((r) => r.sys?.kPa).tryMax?.asKPa;
+  Pressure? get minSys => _records.map((r) => r.sys?.kPa).tryMin?.asKPa;
 
   /// The earliest timestamp of all records.
   DateTime? get firstDay {
app/lib/screens/subsettings/foreign_db_import_screen.dart
@@ -115,7 +115,7 @@ class _ForeignDBImportScreenState extends State<ForeignDBImportScreen> {
                   /*re cord = record.copyWith( FIXME
                     notes: ConvertUtil.parseString(row[colType.$1]),
                   );*/
-                case RowDataFieldType.needlePin:
+                case RowDataFieldType.color:
                   /*try { FIXME
                     final json = jsonDecode(row[colType.$1].toString());
                     if (json is! Map<String, dynamic>) continue;
app/test/model/export_import/column_test.dart
@@ -15,9 +15,9 @@ void main() {
         NativeColumn.systolic,
         NativeColumn.diastolic,
         NativeColumn.pulse,
-        /*NativeColumn.notes, fixme
+        NativeColumn.notes,
         NativeColumn.color,
-        NativeColumn.needlePin,*/
+        NativeColumn.needlePin,
       ]),);
     });
     test('should have internalIdentifier prefixed with "native."', () {
@@ -28,7 +28,8 @@ void main() {
     test('should encode into non-empty string', () {
       // Use BuildInColumn for utility columns
       for (final c in NativeColumn.allColumns) {
-        expect(c.encode(_filledRecord()), isNotEmpty, reason: '${c.internalIdentifier} is NativeColumn');
+        final r = _filledRecord();
+        expect(c.encode(r.$1, r.$2, r.$3), isNotEmpty, reason: '${c.internalIdentifier} is NativeColumn');
       }
     });
     test('should only contain restoreable types', () {
@@ -40,33 +41,33 @@ void main() {
     test('should decode correctly', () {
       final r = _filledRecord();
       for (final c in NativeColumn.allColumns) {
-        final txt = c.encode(r);
+        final txt = c.encode(r.$1, r.$2, r.$3);
         final decoded = c.decode(txt);
         expect(decoded, isNotNull, reason: 'a real value was encoded: ${c.internalIdentifier}: $r > $txt');
         switch (decoded!.$1) {
           case RowDataFieldType.timestamp:
             expect(decoded.$2, isA<DateTime>().having(
-                    (p0) => p0.millisecondsSinceEpoch, 'milliseconds', r.time.millisecondsSinceEpoch,),);
+              (p0) => p0.millisecondsSinceEpoch, 'milliseconds', r.$1.time.millisecondsSinceEpoch,),);
             break;
           case RowDataFieldType.sys:
             expect(decoded.$2, isA<int>().having(
-                    (p0) => p0, 'systolic', r.sys,),);
+                    (p0) => p0, 'systolic', r.$1.sys?.mmHg,),);
             break;
           case RowDataFieldType.dia:
             expect(decoded.$2, isA<int>().having(
-                    (p0) => p0, 'diastolic', r.dia,),);
+                    (p0) => p0, 'diastolic', r.$1.dia?.mmHg,),);
             break;
           case RowDataFieldType.pul:
             expect(decoded.$2, isA<int>().having(
-                    (p0) => p0, 'pulse', r.pul,),);
+                    (p0) => p0, 'pulse', r.$1.pul,),);
             break;
           case RowDataFieldType.notes:
-            /*expect(decoded.$2, isA<String>().having( fixme
-                    (p0) => p0, 'pulse', r.notes,),);*/
+            expect(decoded.$2, isA<String>().having(
+                    (p0) => p0, 'pulse', r.$2.note,),);
             break;
-          case RowDataFieldType.needlePin:
-            /*expect(decoded.$2, isA<MeasurementNeedlePin>().having( fixme
-                    (p0) => p0.toMap(), 'pin', r.needlePin?.toMap(),),);*/
+          case RowDataFieldType.color:
+            expect(decoded.$2, isA<int>().having(
+                    (p0) => p0, 'color', r.$2.color,),);
             break;
         }
       }
@@ -94,46 +95,47 @@ void main() {
     });
     test('should encode without problems', () {
       for (final c in BuildInColumn.allColumns) {
-        expect(c.encode(_filledRecord()), isNotNull);
+        final r = _filledRecord();
+        expect(c.encode(r.$1, r.$2, r.$3), isNotNull);
       }
     });
     test('should decode correctly', () {
       final r = _filledRecord();
       for (final c in BuildInColumn.allColumns) {
-        final txt = c.encode(r);
+        final txt = c.encode(r.$1, r.$2, r.$3);
         final decoded = c.decode(txt);
         switch (decoded?.$1) {
           case RowDataFieldType.timestamp:
             if (c is TimeColumn) {
               // This ensures no columns with useless conversions get introduced.
               expect(decoded?.$2, isA<DateTime>().having(
-                  (p0) => p0.difference(r.time).inDays,
+                  (p0) => p0.difference(r.$1.time).inDays,
                   'inaccuracy',
                   lessThan(1),),);
             } else {
               expect(decoded?.$2, isA<DateTime>().having(
-                  (p0) => p0.millisecondsSinceEpoch, 'milliseconds', r.time.millisecondsSinceEpoch,),);
+                  (p0) => p0.millisecondsSinceEpoch, 'milliseconds', r.$1.time.millisecondsSinceEpoch,),);
             }
             break;
           case RowDataFieldType.sys:
             expect(decoded?.$2, isA<int>().having(
-                    (p0) => p0, 'systolic', r.sys,),);
+                    (p0) => p0, 'systolic', r.$1.sys?.mmHg,),);
             break;
           case RowDataFieldType.dia:
             expect(decoded?.$2, isA<int>().having(
-                    (p0) => p0, 'diastolic', r.dia,),);
+                    (p0) => p0, 'diastolic', r.$1.dia?.mmHg,),);
             break;
           case RowDataFieldType.pul:
             expect(decoded?.$2, isA<int>().having(
-                    (p0) => p0, 'pulse', r.pul,),);
+                    (p0) => p0, 'pulse', r.$1.pul,),);
             break;
           case RowDataFieldType.notes:
-            /*expect(decoded?.$2, isA<String>().having( fixme
-                    (p0) => p0, 'pulse', r.notes,),);*/
+            expect(decoded?.$2, isA<String>().having(
+                    (p0) => p0, 'note', r.$2.note,),);
             break;
-          case RowDataFieldType.needlePin:
-            /*expect(decoded?.$2, isA<MeasurementNeedlePin>().having( fixme
-                    (p0) => p0.toMap(), 'pin', r.needlePin?.toMap(),),);*/
+          case RowDataFieldType.color:
+            expect(decoded?.$2, isA<int>().having(
+                    (p0) => p0, 'pin', r.$2.color,),);
             break;
           case null:
             break;
@@ -149,11 +151,26 @@ void main() {
     });
     test('should encode like ScriptedFormatter', () {
       final r = _filledRecord();
-      expect(UserColumn('','', 'TEST').encode(r), ScriptedFormatter('TEST').encode(r));
-      expect(UserColumn('','', r'$SYS').encode(r), ScriptedFormatter(r'$SYS').encode(r));
-      expect(UserColumn('','', r'$SYS-$DIA').encode(r), ScriptedFormatter(r'$SYS-$DIA').encode(r));
-      expect(UserColumn('','', r'$TIMESTAMP').encode(r), ScriptedFormatter(r'$TIMESTAMP').encode(r));
-      expect(UserColumn('','', '').encode(r), ScriptedFormatter('').encode(r));
+      expect(
+        UserColumn('','', 'TEST').encode(r.$1, r.$2, r.$3),
+        ScriptedFormatter('TEST').encode(r.$1, r.$2, r.$3),
+      );
+      expect(
+        UserColumn('','', r'$SYS').encode(r.$1, r.$2, r.$3),
+        ScriptedFormatter(r'$SYS').encode(r.$1, r.$2, r.$3),
+      );
+      expect(
+        UserColumn('','', r'$SYS-$DIA').encode(r.$1, r.$2, r.$3),
+        ScriptedFormatter(r'$SYS-$DIA').encode(r.$1, r.$2, r.$3),
+      );
+      expect(
+        UserColumn('','', r'$TIMESTAMP').encode(r.$1, r.$2, r.$3),
+        ScriptedFormatter(r'$TIMESTAMP').encode(r.$1, r.$2, r.$3),
+      );
+      expect(
+        UserColumn('','', '').encode(r.$1, r.$2, r.$3),
+        ScriptedFormatter('').encode(r.$1, r.$2, r.$3),
+      );
     });
     test('should decode like ScriptedFormatter', () {
       final r = _filledRecord();
@@ -162,7 +179,10 @@ void main() {
       for (final pattern in testPatterns) {
         final column = UserColumn('','', pattern);
         final formatter = ScriptedFormatter(pattern);
-        expect(column.decode(column.encode(r)), formatter.decode(formatter.encode(r)));
+        expect(
+          column.decode(column.encode(r.$1, r.$2, r.$3)),
+          formatter.decode(formatter.encode(r.$1, r.$2, r.$3)),
+        );
       }
     });
   });
@@ -175,7 +195,7 @@ void main() {
   });
 }
 
-BloodPressureRecord _filledRecord() => mockRecord(
+FullEntry _filledRecord() => mockEntry(
   sys: 123,
   dia: 456,
   pul: 789,
app/test/model/export_import/csv_converter_test.dart
@@ -42,13 +42,13 @@ void main() {
     final parsedRecords = converter.parse(csv).getOr(failParse);
 
     expect(parsedRecords, pairwiseCompare(initialRecords,
-      (p0, BloodPressureRecord p1) =>
-        p0.time == p1.time &&
-        p0.sys == p1.sys &&
-        p0.dia == p1.dia &&
-        p0.pul == p1.pul /*&&
-        p0.notes == p1.notes && fixme
-        p0.needlePin?.color == p1.needlePin?.color,*/,
+      (FullEntry p0, FullEntry p1) =>
+        p0.$1.time == p1.$1.time &&
+        p0.$1.sys == p1.$1.sys &&
+        p0.$1.dia == p1.$1.dia &&
+        p0.$1.pul == p1.$1.pul &&
+        p0.$2.note == p1.$2.note &&
+        p0.$2.color == p1.$2.color,
       'equal to',),);
   });
   test('should allow partial imports', () {
@@ -62,29 +62,29 @@ void main() {
     final records = parsed.getOr(failParse);
     expect(records, isNotNull);
     expect(records.length, 3);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703239921194)
-        .having((p0) => p0.sys, 'systolic', null)
-        .having((p0) => p0.dia, 'diastolic', null)
-        .having((p0) => p0.pul, 'pulse', null)
-      //fixme .having((p0) => p0.notes, 'notes', 'note')
-      //fixme .having((p0) => p0.needlePin, 'pin', null),
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703239921194)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', null)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', null)
+      .having((p0) => p0.$1.pul, 'pulse', null)
+      .having((p0) => p0.$2.note, 'notes', 'note')
+      .having((p0) => p0.$2.color, 'pin', null),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703239908244)
-        .having((p0) => p0.sys, 'systolic', null)
-        .having((p0) => p0.dia, 'diastolic', 45)
-        .having((p0) => p0.pul, 'pulse', null)
-      //fixme .having((p0) => p0.notes, 'notes', 'test')
-      //fixme .having((p0) => p0.needlePin, 'pin', null),
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703239908244)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', null)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 45)
+      .having((p0) => p0.$1.pul, 'pulse', null)
+      .having((p0) => p0.$2.note, 'notes', 'test')
+      .having((p0) => p0.$2.color, 'pin', null),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703239905395)
-        .having((p0) => p0.sys, 'systolic', 123)
-        .having((p0) => p0.dia, 'diastolic', null)
-        .having((p0) => p0.pul, 'pulse', null)
-        /*.having((p0) => p0.notes, 'notes', '') fixme
-        .having((p0) => p0.needlePin, 'pin', null)*/,
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703239905395)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 123)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', null)
+      .having((p0) => p0.$1.pul, 'pulse', null)
+      .having((p0) => p0.$2.note, 'notes', '')
+      .having((p0) => p0.$2.color, 'pin', null),
     ),);
   });
 
@@ -100,20 +100,20 @@ void main() {
     final records = parsed.getOr(failParse);
     expect(records, isNotNull);
     expect(records.length, 2);
-    expect(records, everyElement(isA<BloodPressureRecord>()));
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175660000)
-        .having((p0) => p0.sys, 'systolic', 312)
-        .having((p0) => p0.dia, 'diastolic', 315)
-        .having((p0) => p0.pul, 'pulse', 46)
-      //fixme .having((p0) => p0.notes.trim(), 'notes', 'testfkajkfb'),
+    expect(records, everyElement(isA<FullEntry>()));
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175660000)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 312)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 315)
+      .having((p0) => p0.$1.pul, 'pulse', 46)
+      .having((p0) => p0.$2.note?.trim(), 'notes', 'testfkajkfb'),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175600000)
-        .having((p0) => p0.sys, 'systolic', 123)
-        .having((p0) => p0.dia, 'diastolic', 41)
-        .having((p0) => p0.pul, 'pulse', 43)
-      //fixme .having((p0) => p0.notes.trim(), 'notes', '1214s3'),
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175600000)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 123)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 41)
+      .having((p0) => p0.$1.pul, 'pulse', 43)
+      .having((p0) => p0.$2.note?.trim(), 'notes', '1214s3'),
     ),);
   });
   test('should import v1.1.0 measurements', () {
@@ -127,20 +127,20 @@ void main() {
     final records = parsed.getOr(failParse);
     expect(records, isNotNull);
     expect(records.length, 4);
-    expect(records, everyElement(isA<BloodPressureRecord>()));
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175660000)
-        .having((p0) => p0.sys, 'systolic', 312)
-        .having((p0) => p0.dia, 'diastolic', 315)
-        .having((p0) => p0.pul, 'pulse', 46)
-      //fixme .having((p0) => p0.notes.trim(), 'notes', 'testfkajkfb'),
+    expect(records, everyElement(isA<FullEntry>()));
+    expect(records, anyElement(isA<FullEntry>()
+        .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175660000)
+        .having((p0) => p0.$1.sys?.mmHg, 'systolic', 312)
+        .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 315)
+        .having((p0) => p0.$1.pul, 'pulse', 46)
+        .having((p0) => p0.$2.note?.trim(), 'notes', 'testfkajkfb'),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175600000)
-        .having((p0) => p0.sys, 'systolic', 123)
-        .having((p0) => p0.dia, 'diastolic', 41)
-        .having((p0) => p0.pul, 'pulse', 43)
-      //fixme .having((p0) => p0.notes.trim(), 'notes', '1214s3'),
+    expect(records, anyElement(isA<FullEntry>()
+        .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175600000)
+        .having((p0) => p0.$1.sys?.mmHg, 'systolic', 123)
+        .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 41)
+        .having((p0) => p0.$1.pul, 'pulse', 43)
+        .having((p0) => p0.$2.note?.trim(), 'notes', '1214s3'),
     ),);
   });
   test('should import v1.4.0 measurements', () {
@@ -154,27 +154,27 @@ void main() {
     final records = parsed.getOr(failParse);
     expect(records, isNotNull);
     expect(records.length, 186);
-    expect(records, everyElement(isA<BloodPressureRecord>()));
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175660000)
-        .having((p0) => p0.sys, 'systolic', 312)
-        .having((p0) => p0.dia, 'diastolic', 315)
-        .having((p0) => p0.pul, 'pulse', 46)
-        //fixme .having((p0) => p0.notes, 'notes', 'testfkajkfb'),
+    expect(records, everyElement(isA<FullEntry>()));
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175660000)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 312)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 315)
+      .having((p0) => p0.$1.pul, 'pulse', 46)
+      .having((p0) => p0.$2.note, 'notes', 'testfkajkfb'),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175600000)
-        .having((p0) => p0.sys, 'systolic', 123)
-        .having((p0) => p0.dia, 'diastolic', 41)
-        .having((p0) => p0.pul, 'pulse', 43)
-        //.having((p0) => p0.notes, 'notes', '1214s3'),fixme
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175600000)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 123)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 41)
+      .having((p0) => p0.$1.pul, 'pulse', 43)
+      .having((p0) => p0.$2.note, 'notes', '1214s3'),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 10893142303200)
-        .having((p0) => p0.sys, 'systolic', 106)
-        .having((p0) => p0.dia, 'diastolic', 77)
-        .having((p0) => p0.pul, 'pulse', 53)
-        //.having((p0) => p0.notes, 'notes', ''),fixme
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 10893142303200)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 106)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 77)
+      .having((p0) => p0.$1.pul, 'pulse', 53)
+      .having((p0) => p0.$2.note, 'notes', ''),
     ),);
   });
   test('should import v1.5.1 measurements', () {
@@ -188,30 +188,30 @@ void main() {
     final records = parsed.getOr(failParse);
     expect(records, isNotNull);
     expect(records.length, 185);
-    expect(records, everyElement(isA<BloodPressureRecord>()));
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175660000)
-        .having((p0) => p0.sys, 'systolic', 312)
-        .having((p0) => p0.dia, 'diastolic', 315)
-        .having((p0) => p0.pul, 'pulse', 46)
-        /*.having((p0) => p0.notes, 'notes', 'testfkajkfb')fixme
-        .having((p0) => p0.needlePin, 'pin', null),*/
+    expect(records, everyElement(isA<FullEntry>()));
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175660000)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 312)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 315)
+      .having((p0) => p0.$1.pul, 'pulse', 46)
+      .having((p0) => p0.$2.note, 'notes', 'testfkajkfb')
+      .having((p0) => p0.$2.color, 'pin', null),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175600000)
-        .having((p0) => p0.sys, 'systolic', 123)
-        .having((p0) => p0.dia, 'diastolic', 41)
-        .having((p0) => p0.pul, 'pulse', 43)
-        /*.having((p0) => p0.notes, 'notes', '1214s3')fixme
-        .having((p0) => p0.needlePin, 'pin', null),*/
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175600000)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 123)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 41)
+      .having((p0) => p0.$1.pul, 'pulse', 43)
+      .having((p0) => p0.$2.note, 'notes', '1214s3')
+      .having((p0) => p0.$2.color, 'pin', null),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1077625200000)
-        .having((p0) => p0.sys, 'systolic', 100)
-        .having((p0) => p0.dia, 'diastolic', 82)
-        .having((p0) => p0.pul, 'pulse', 63)
-        /*.having((p0) => p0.notes, 'notes', '') fixme
-        .having((p0) => p0.needlePin, 'pin', null)*/,
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1077625200000)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 100)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 82)
+      .having((p0) => p0.$1.pul, 'pulse', 63)
+      .having((p0) => p0.$2.note, 'notes', '')
+      .having((p0) => p0.$2.color, 'pin', null),
     ),);
   });
   test('should import v1.5.7 measurements', () {
@@ -225,30 +225,30 @@ void main() {
     final records = parsed.getOr(failParse);
     expect(records, isNotNull);
     expect(records.length, 185);
-    expect(records, everyElement(isA<BloodPressureRecord>()));
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175660000)
-        .having((p0) => p0.sys, 'systolic', 312)
-        .having((p0) => p0.dia, 'diastolic', 315)
-        .having((p0) => p0.pul, 'pulse', 46)
-        /*.having((p0) => p0.notes, 'notes', 'testfkajkfb') fixme
-        .having((p0) => p0.needlePin, 'pin', null),*/
+    expect(records, everyElement(isA<FullEntry>()));
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175660000)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 312)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 315)
+      .having((p0) => p0.$1.pul, 'pulse', 46)
+      .having((p0) => p0.$2.note, 'notes', 'testfkajkfb')
+      .having((p0) => p0.$2.color, 'pin', null),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175600000)
-        .having((p0) => p0.sys, 'systolic', 123)
-        .having((p0) => p0.dia, 'diastolic', 41)
-        .having((p0) => p0.pul, 'pulse', 43)
-        /*.having((p0) => p0.notes, 'notes', '1214s3')fixme
-        .having((p0) => p0.needlePin, 'pin', null),*/
+    expect(records, anyElement(isA<FullEntry>()
+        .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175600000)
+        .having((p0) => p0.$1.sys?.mmHg, 'systolic', 123)
+        .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 41)
+        .having((p0) => p0.$1.pul, 'pulse', 43)
+        .having((p0) => p0.$2.note, 'notes', '1214s3')
+        .having((p0) => p0.$2.color, 'pin', null),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1077625200000)
-        .having((p0) => p0.sys, 'systolic', 100)
-        .having((p0) => p0.dia, 'diastolic', 82)
-        .having((p0) => p0.pul, 'pulse', 63)
-        /*.having((p0) => p0.notes, 'notes', '') fixme
-        .having((p0) => p0.needlePin, 'pin', null)*/,
+    expect(records, anyElement(isA<FullEntry>()
+        .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1077625200000)
+        .having((p0) => p0.$1.sys?.mmHg, 'systolic', 100)
+        .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 82)
+        .having((p0) => p0.$1.pul, 'pulse', 63)
+        .having((p0) => p0.$2.note, 'notes', '')
+        .having((p0) => p0.$2.color, 'pin', null),
     ),);
     // TODO: test color
   });
@@ -256,57 +256,57 @@ void main() {
     final text = File('test/model/export_import/exported_formats/v1.5.8.csv').readAsStringSync();
 
     final converter = CsvConverter(
-        CsvExportSettings(),
-        ExportColumnsManager(),
+      CsvExportSettings(),
+      ExportColumnsManager(),
     );
     final parsed = converter.parse(text);
     final records = parsed.getOr(failParse);
     expect(records, isNotNull);
     expect(records.length, 9478);
-    expect(records, everyElement(isA<BloodPressureRecord>()));
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1703175193324)
-        .having((p0) => p0.sys, 'systolic', 123)
-        .having((p0) => p0.dia, 'diastolic', 43)
-        .having((p0) => p0.pul, 'pulse', 53)
-        /*.having((p0) => p0.notes, 'notes', 'sdfsdfds')fixme
-        .having((p0) => p0.needlePin?.color, 'pin', const Color(0xff69f0ae)),*/
+    expect(records, everyElement(isA<FullEntry>()));
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1703175193324)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 123)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 43)
+      .having((p0) => p0.$1.pul, 'pulse', 53)
+      .having((p0) => p0.$2.note, 'notes', 'sdfsdfds')
+      .having((p0) => p0.$2.color, 'color', 0xff69f0ae),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1702883511000)
-        .having((p0) => p0.sys, 'systolic', 114)
-        .having((p0) => p0.dia, 'diastolic', 71)
-        .having((p0) => p0.pul, 'pulse', 66)
-        /*.having((p0) => p0.notes, 'notes', 'fsaf &_*ยข|^โœ“[=%ยฎยฉ')fixme
-        .having((p0) => p0.needlePin?.color.value, 'pin', Colors.lightGreen.value),*/
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1702883511000)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 114)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 71)
+      .having((p0) => p0.$1.pul, 'pulse', 66)
+      .having((p0) => p0.$2.note, 'notes', 'fsaf &_*ยข|^โœ“[=%ยฎยฉ')
+      .having((p0) => p0.$2.color, 'color', Colors.lightGreen.value),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1701034952000)
-        .having((p0) => p0.sys, 'systolic', 125)
-        .having((p0) => p0.dia, 'diastolic', 77)
-        .having((p0) => p0.pul, 'pulse', 60)
-        /*.having((p0) => p0.notes, 'notes', '') fixme
-        .having((p0) => p0.needlePin, 'pin', null)*/,
+    expect(records, anyElement(isA<FullEntry>()
+      .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1701034952000)
+      .having((p0) => p0.$1.sys?.mmHg, 'systolic', 125)
+      .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 77)
+      .having((p0) => p0.$1.pul, 'pulse', 60)
+      .having((p0) => p0.$2.note, 'notes', '')
+      .having((p0) => p0.$2.color, 'color', null),
     ),);
-    expect(records, anyElement(isA<BloodPressureRecord>()
-        .having((p0) => p0.time.millisecondsSinceEpoch, 'timestamp', 1077625200000)
-        .having((p0) => p0.sys, 'systolic', 100)
-        .having((p0) => p0.dia, 'diastolic', 82)
-        .having((p0) => p0.pul, 'pulse', 63)
-        /*.having((p0) => p0.notes, 'notes', '') fixme
-        .having((p0) => p0.needlePin, 'pin', null)*/,
+    expect(records, anyElement(isA<FullEntry>()
+        .having((p0) => p0.$1.time.millisecondsSinceEpoch, 'timestamp', 1077625200000)
+        .having((p0) => p0.$1.sys?.mmHg, 'systolic', 100)
+        .having((p0) => p0.$1.dia?.mmHg, 'diastolic', 82)
+        .having((p0) => p0.$1.pul, 'pulse', 63)
+        .having((p0) => p0.$2.note, 'notes', '')
+        .having((p0) => p0.$2.color, 'pin', null),
     ),);
     // TODO: test time columns
   });
 }
 
-List<BloodPressureRecord> createRecords([int count = 20]) => [
+List<FullEntry> createRecords([int count = 20]) => [
   for (int i = 0; i<count; i++)
-    mockRecordPos(DateTime.fromMillisecondsSinceEpoch(123456 + i),
+    mockEntryPos(DateTime.fromMillisecondsSinceEpoch(123456 + i),
         i, 100+i, 200+1, 'note $i', Color(123+i),),
 ];
 
-List<BloodPressureRecord>? failParse(EntryParsingError error) {
+List<FullEntry>? failParse(EntryParsingError error) {
   switch (error) {
     case RecordParsingErrorEmptyFile():
       fail('Parsing failed due to insufficient data.');
app/test/model/export_import/pdf_converter_test.dart
@@ -1,4 +1,3 @@
-
 import 'package:blood_pressure_app/model/export_import/pdf_converter.dart';
 import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
 import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
@@ -14,15 +13,15 @@ void main() {
   test('should not return empty data', () async {
     final localizations = await AppLocalizations.delegate.load(const Locale('en'));
     final converter = PdfConverter(PdfExportSettings(), localizations, Settings(), ExportColumnsManager());
-    final pdf = await converter.create(createRecords());
+    final pdf = await converter.create(_createRecords());
     expect(pdf.length, isNonZero);
   });
   test('generated data length should be consistent', () async {
     final localizations = await AppLocalizations.delegate.load(const Locale('en'));
     final converter = PdfConverter(PdfExportSettings(), localizations, Settings(), ExportColumnsManager());
-    final pdf = await converter.create(createRecords());
+    final pdf = await converter.create(_createRecords());
     final converter2 = PdfConverter(PdfExportSettings(), localizations, Settings(), ExportColumnsManager());
-    final pdf2 = await converter2.create(createRecords());
+    final pdf2 = await converter2.create(_createRecords());
     expect(pdf.length, pdf2.length);
   });
 
@@ -35,29 +34,29 @@ void main() {
     );
 
     final converter = PdfConverter(pdfSettings, localizations, Settings(), ExportColumnsManager());
-    final pdf1 = await converter.create(createRecords());
+    final pdf1 = await converter.create(_createRecords());
 
     pdfSettings.exportData = false;
-    final pdf2 = await converter.create(createRecords());
+    final pdf2 = await converter.create(_createRecords());
     expect(pdf1.length, isNot(pdf2.length));
     expect(pdf1.length, greaterThan(pdf2.length));
 
     pdfSettings.exportStatistics = false;
-    final pdf3 = await converter.create(createRecords());
+    final pdf3 = await converter.create(_createRecords());
     expect(pdf3.length, isNot(pdf2.length));
     expect(pdf3.length, isNot(pdf1.length));
     expect(pdf2.length, greaterThan(pdf3.length));
 
     pdfSettings.exportTitle = false;
     pdfSettings.exportData = true;
-    final pdf4 = await converter.create(createRecords());
+    final pdf4 = await converter.create(_createRecords());
     expect(pdf4.length, isNot(pdf1.length));
     expect(pdf1.length, greaterThan(pdf4.length));
   });
 }
 
-List<BloodPressureRecord> createRecords([int count = 20]) => [
+List<FullEntry> _createRecords([int count = 20]) => [
   for (int i = 0; i<count; i++)
-    mockRecordPos(DateTime.fromMillisecondsSinceEpoch(123456 + i),
-        i, 100+i, 200+1, 'note $i', Color(123+i)),
+    mockEntryPos(DateTime.fromMillisecondsSinceEpoch(123456 + i),
+      i, 100+i, 200+1, 'note $i', Color(123+i),),
 ];
app/test/model/export_import/record_formatter_test.dart
@@ -1,4 +1,3 @@
-import 'package:blood_pressure_app/model/blood_pressure/needle_pin.dart';
 import 'package:blood_pressure_app/model/export_import/import_field_type.dart';
 import 'package:blood_pressure_app/model/export_import/record_formatter.dart';
 import 'package:flutter/material.dart';
@@ -12,33 +11,40 @@ void main() {
       final f = ScriptedFormatter(r'$SYS');
       
       expect(f.formatPattern, r'$SYS');
-      f.encode(mockRecordPos(DateTime.now(), 123, 456, 789, 'test text'));
-      f.encode(mockRecord());
+      final r1 = mockEntryPos(DateTime.now(), 123, 456, 789, 'test text');
+      f.encode(r1.$1, r1.$2, r1.$3);
+      final r2 = mockEntry();
+      f.encode(r2.$1, r2.$2, r2.$3);
       f.decode('123');
     });
     test('should create correct strings', () {
-      final testRecord = mockRecordPos(DateTime.fromMillisecondsSinceEpoch(31415926), 123, 45, 67, 'Test', Colors.red);
+      final r = mockEntryPos(DateTime.fromMillisecondsSinceEpoch(31415926), 123, 45, 67, 'Test', Colors.red);
 
-      expect(ScriptedFormatter(r'constant text',).encode(testRecord), 'constant text');
-      expect(ScriptedFormatter(r'$SYS',).encode(testRecord), testRecord.sys.toString());
-      expect(ScriptedFormatter(r'$DIA',).encode(testRecord), testRecord.dia.toString());
-      expect(ScriptedFormatter(r'$PUL',).encode(testRecord), testRecord.pul.toString());
-      /*expect(ScriptedFormatter(r'$COLOR',).encode(testRecord), jsonEncode(testRecord.needlePin!.toMap())); FIXME
-      expect(ScriptedFormatter(r'$NOTE',).encode(testRecord), testRecord.notes);*/
-      expect(ScriptedFormatter(r'$TIMESTAMP',).encode(testRecord), testRecord.time.millisecondsSinceEpoch.toString());
-      expect(ScriptedFormatter(r'$SYS$DIA$PUL',).encode(testRecord), (testRecord.sys!.mmHg.toString()
-        + testRecord.dia!.mmHg.toString() + testRecord.pul.toString()),);
-      expect(ScriptedFormatter(r'$SYS$SYS',).encode(testRecord), (testRecord.sys!.mmHg.toString()
-        + testRecord.sys!.mmHg.toString()),);
-      expect(ScriptedFormatter(r'{{$SYS-$DIA}}',).encode(testRecord),
-        (testRecord.sys!.mmHg - testRecord.dia!.mmHg).toDouble().toString(),);
-      expect(ScriptedFormatter(r'{{$SYS*$DIA-$PUL}}',).encode(testRecord),
-          (testRecord.sys!.mmHg * testRecord.dia!.mmHg - testRecord.pul!).toDouble().toString(),);
-      expect(ScriptedFormatter(r'$SYS-$DIA',).encode(testRecord), ('${testRecord.sys}-${testRecord.dia}'));
+      expect(ScriptedFormatter(r'constant text',).encode(r.$1, r.$2, r.$3), 'constant text');
+      expect(ScriptedFormatter(r'$SYS',).encode(r.$1, r.$2, r.$3), r.$1.sys?.mmHg.toString());
+      expect(ScriptedFormatter(r'$DIA',).encode(r.$1, r.$2, r.$3), r.$1.dia?.mmHg.toString());
+      expect(ScriptedFormatter(r'$PUL',).encode(r.$1, r.$2, r.$3), r.$1.pul.toString());
+      expect(ScriptedFormatter(r'$COLOR',).encode(r.$1, r.$2, r.$3), r.$2.color.toString());
+      expect(ScriptedFormatter(r'$NOTE',).encode(r.$1, r.$2, r.$3), r.$2.note);
+      expect(ScriptedFormatter(r'$TIMESTAMP',).encode(r.$1, r.$2, r.$3), r.$1.time.millisecondsSinceEpoch.toString());
+      expect(
+        ScriptedFormatter(r'$SYS$DIA$PUL',).encode(r.$1, r.$2, r.$3),
+        (r.$1.sys!.mmHg.toString() + r.$1.dia!.mmHg.toString() + r.$1.pul.toString()),);
+      expect(
+        ScriptedFormatter(r'$SYS$SYS',).encode(r.$1, r.$2, r.$3),
+        (r.$1.sys!.mmHg.toString() + r.$1.sys!.mmHg.toString()),);
+      expect(
+        ScriptedFormatter(r'{{$SYS-$DIA}}',).encode(r.$1, r.$2, r.$3),
+        (r.$1.sys!.mmHg - r.$1.dia!.mmHg).toDouble().toString(),);
+      expect(
+        ScriptedFormatter(r'{{$SYS*$DIA-$PUL}}',).encode(r.$1, r.$2, r.$3),
+          (r.$1.sys!.mmHg * r.$1.dia!.mmHg - r.$1.pul!).toDouble().toString(),);
+      expect(
+          ScriptedFormatter(r'$SYS-$DIA',).encode(r.$1, r.$2, r.$3), ('${r.$1.sys?.mmHg}-${r.$1.dia?.mmHg}'));
 
       final formatter = DateFormat.yMMMMEEEEd();
-      expect(ScriptedFormatter('\$FORMAT{\$TIMESTAMP,${formatter.pattern}}',).encode(testRecord),
-          formatter.format(testRecord.time),);
+      expect(ScriptedFormatter('\$FORMAT{\$TIMESTAMP,${formatter.pattern}}',).encode(r.$1, r.$2, r.$3),
+          formatter.format(r.$1.time),);
     });
     test('should report correct reversibility', () {
       expect(ScriptedFormatter(r'$SYS',).restoreAbleType, RowDataFieldType.sys);
@@ -46,7 +52,7 @@ void main() {
       expect(ScriptedFormatter(r'$PUL',).restoreAbleType, RowDataFieldType.pul);
       expect(ScriptedFormatter(r'$TIMESTAMP',).restoreAbleType, RowDataFieldType.timestamp);
       expect(ScriptedFormatter(r'$NOTE',).restoreAbleType, RowDataFieldType.notes);
-      expect(ScriptedFormatter(r'$COLOR',).restoreAbleType, RowDataFieldType.needlePin);
+      expect(ScriptedFormatter(r'$COLOR',).restoreAbleType, RowDataFieldType.color);
       expect(ScriptedFormatter(r'test$SYS123',).restoreAbleType, RowDataFieldType.sys);
       expect(ScriptedFormatter(r'test$DIA123',).restoreAbleType, RowDataFieldType.dia);
       expect(ScriptedFormatter(r'test$PUL123',).restoreAbleType, RowDataFieldType.pul);
@@ -63,11 +69,10 @@ void main() {
       expect(ScriptedFormatter(r'$PUL',).decode('789'), (RowDataFieldType.pul, 789));
       expect(ScriptedFormatter(r'$TIMESTAMP',).decode('12345678'), (RowDataFieldType.timestamp, DateTime.fromMillisecondsSinceEpoch(12345678)));
       expect(ScriptedFormatter(r'$NOTE',).decode('test note'), (RowDataFieldType.notes, 'test note'));
-      final encodedPurple = ScriptedFormatter(r'$COLOR',)
-        .encode(mockRecordPos(DateTime.now(), null, null, null, '', Colors.purple));
-      expect(ScriptedFormatter(r'$COLOR',).decode(encodedPurple)?.$1, RowDataFieldType.needlePin);
-      expect(ScriptedFormatter(r'$COLOR',).decode(encodedPurple)?.$2, isA<MeasurementNeedlePin>()
-          .having((p0) => p0.color.value, 'color', Colors.purple.value),);
+      final r = mockEntryPos(DateTime.now(), null, null, null, '', Colors.purple);
+      final encodedPurple = ScriptedFormatter(r'$COLOR',).encode(r.$1, r.$2, r.$3);
+      expect(ScriptedFormatter(r'$COLOR',).decode(encodedPurple)?.$1, RowDataFieldType.color);
+      expect(ScriptedFormatter(r'$COLOR',).decode(encodedPurple)?.$2, Colors.purple.value);
       expect(ScriptedFormatter(r'test$SYS',).decode('test567'), (RowDataFieldType.sys, 567));
       expect(ScriptedFormatter(r'test$SYS123',).decode('test567123'), (RowDataFieldType.sys, 567));
       expect(ScriptedFormatter(r'test$DIA123',).decode('test567123'), (RowDataFieldType.dia, 567));
@@ -83,9 +88,11 @@ void main() {
     });
 
     test('should when ignore groups in format strings', () {
-      expect(ScriptedFormatter(r'($SYS)',).encode(mockRecord(sys: 123)), '(123)');
-      expect(ScriptedFormatter(r'($SYS',).encode(mockRecord(sys: 123)), '(123');
-      expect(ScriptedFormatter(r'($NOTE',).encode(mockRecord(note: 'test')), '(test');
+      final r1 = mockEntry(sys: 123);
+      expect(ScriptedFormatter(r'($SYS)',).encode(r1.$1, r1.$2, r1.$3), '(123)');
+      expect(ScriptedFormatter(r'($SYS',).encode(r1.$1, r1.$2, r1.$3), '(123');
+      final r2 = mockEntry(note: 'test');
+      expect(ScriptedFormatter(r'($NOTE',).encode(r2.$1, r2.$2, r2.$3), '(test');
 
       expect(ScriptedFormatter(r'($SYS)',).restoreAbleType, RowDataFieldType.sys);
       expect(ScriptedFormatter(r'($SYS',).restoreAbleType, RowDataFieldType.sys);
@@ -106,47 +113,57 @@ void main() {
 
   group('ScriptedTimeFormatter', () {
     test('should create non-empty string', () {
-      expect(ScriptedTimeFormatter('dd').encode(mockRecord()), isNotNull);
-      expect(ScriptedTimeFormatter('dd').encode(mockRecord()), isNotEmpty);
+      final r1 = mockEntry();
+      expect(ScriptedTimeFormatter('dd').encode(r1.$1, r1.$2, r1.$3), isNotNull);
+      expect(ScriptedTimeFormatter('dd').encode(r1.$1, r1.$2, r1.$3), isNotEmpty);
     });
     test('should decode rough time', () {
       final formatter = ScriptedTimeFormatter('yyyy.MMMM.dd GGG hh:mm.ss aaa');
-      final r = mockRecord();
-      expect(formatter.encode(r), isNotNull);
-      expect(formatter.decode(formatter.encode(r))?.$2, isA<DateTime>()
-        .having((p0) => p0.millisecondsSinceEpoch, 'time(up to one second difference)', closeTo(r.time.millisecondsSinceEpoch, 1000)),);
+      final r = mockEntry();
+      expect(formatter.encode(r.$1, r.$2, r.$3), isNotNull);
+      expect(formatter.decode(formatter.encode(r.$1, r.$2, r.$3))?.$2, isA<DateTime>()
+        .having((p0) => p0.millisecondsSinceEpoch, 'time(up to one second difference)', closeTo(r.$1.time.millisecondsSinceEpoch, 1000)),);
     });
   });
 }
 
-BloodPressureRecord mockRecordPos([
+FullEntry mockEntryPos([
   DateTime? time,
   int? sys,
   int? dia,
   int? pul,
-  String? notes,
+  String? note,
   Color? pin,
-]) => BloodPressureRecord(
-  time: time ?? DateTime.now(),
-  sys: sys == null ? null : Pressure.mmHg(sys),
-  dia: dia == null ? null : Pressure.mmHg(dia),
+]) => mockEntry(
+  time: time,
+  sys: sys,
+  dia: dia,
   pul: pul,
-  //note ?? '', FIXME
-  //needlePin: pin == null ? null : MeasurementNeedlePin(pin),
+  note: note,
+  pin: pin,
 );
 
-BloodPressureRecord mockRecord({
+FullEntry mockEntry({
   DateTime? time,
   int? sys,
   int? dia,
   int? pul,
   String? note,
   Color? pin,
-}) => BloodPressureRecord(
-  time: time ?? DateTime.now(),
-  sys: sys == null ? null : Pressure.mmHg(sys),
-  dia: dia == null ? null : Pressure.mmHg(dia),
-  pul: pul,
-  // note ?? '', FIXME
-  // needlePin: pin == null ? null : MeasurementNeedlePin(pin),
-);
+}) {
+  time ??= DateTime.now();
+  return (
+    BloodPressureRecord(
+      time: time,
+      sys: sys == null ? null : Pressure.mmHg(sys),
+      dia: dia == null ? null : Pressure.mmHg(dia),
+      pul: pul,
+    ),
+    Note(
+      time: time,
+      note: note,
+      color: pin?.value,
+    ),
+    [],
+  );
+}
app/test/model/export_import/record_parsing_result_test.dart
@@ -10,11 +10,11 @@ void main() {
     expect(result.hasError(), isTrue);
   });
   test('should indicate when no error is present', () async {
-    final result = RecordParsingResult.ok([mockRecord()]);
+    final result = RecordParsingResult.ok([mockEntry()]);
     expect(result.hasError(), isFalse);
   });
   test('should return error through getter', () async {
-    final okResult = RecordParsingResult.ok([mockRecord()]);
+    final okResult = RecordParsingResult.ok([mockEntry()]);
     expect(okResult.error, isNull);
 
     final errResult = RecordParsingResult.err(RecordParsingErrorUnparsableField(42, 'fieldContents'));
@@ -24,7 +24,7 @@ void main() {
       .having((p0) => p0.fieldContents, 'contents', 'fieldContents'),);
   });
   test('should normally return value when no error is present', () async {
-    final record = mockRecord();
+    final record = mockEntry();
 
     final result = RecordParsingResult.ok([record]);
     final value = result.getOr((error) {
@@ -38,10 +38,10 @@ void main() {
     final value = result.getOr((error) {
       expect(error, isA<RecordParsingErrorExpectedMoreFields>()
           .having((p0) => p0.lineNumber, 'line number', 123),);
-      return [mockRecord(sys: 1234567)];
+      return [mockEntry(sys: 1234567)];
     });
     expect(value.length, 1);
-    expect(value.first.sys, 1234567);
+    expect(value.first.$1.sys?.mmHg, 1234567);
   });
   test('should return empty list when error function returns null', () async {
     final result = RecordParsingResult.err(RecordParsingErrorExpectedMoreFields(123));
app/test/model/analyzer_test.dart
@@ -1,64 +1,75 @@
 
 import 'package:blood_pressure_app/model/blood_pressure_analyzer.dart';
 import 'package:flutter_test/flutter_test.dart';
-
-import 'export_import/record_formatter_test.dart';
+import 'package:health_data_store/health_data_store.dart';
 
 void main() {
   test('should return averages', () async {
     final m = BloodPressureAnalyser([
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(1), 122, 87, 65, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, ''),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(1), 122, 87, 65),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73),
     ]);
 
-    expect(m.avgSys, 111);
-    expect(m.avgDia, 73);
+    expect(m.avgSys?.mmHg, 111);
+    expect(m.avgDia?.mmHg, 73);
     expect(m.avgPul, 66);
   });
 
   test('should return max', () async {
     final a = BloodPressureAnalyser([
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(1), 123, 87, 65, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(4), 111, 73, 73, ''),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(1), 123, 87, 65),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(4), 111, 73, 73),
     ]);
 
-    expect(a.maxSys, 123);
-    expect(a.maxDia, 87);
+    expect(a.maxSys?.mmHg, 123);
+    expect(a.maxDia?.mmHg, 87);
     expect(a.maxPul, 73);
   });
 
   test('should return min', () async {
     final a = BloodPressureAnalyser([
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(1), 123, 87, 65, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(4), 100, 60, 62, ''),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(1), 123, 87, 65),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(4), 100, 60, 62),
     ]);
 
-    expect(a.minSys, 100);
-    expect(a.minDia, 60);
+    expect(a.minSys?.mmHg, 100);
+    expect(a.minDia?.mmHg, 60);
     expect(a.minPul, 62);
   });
 
   test('should know count', () async {
     final m = BloodPressureAnalyser([
       for (int i = 1; i < 101; i++)
-        mockRecordPos(DateTime.fromMillisecondsSinceEpoch(i), 0, 0, 0, ''),
+        mockRecordPos(DateTime.fromMillisecondsSinceEpoch(i), 0, 0, 0),
     ]);
     expect(m.count, 100);
   });
 
   test('should determine special days', () async {
-    final m = BloodPressureAnalyser([mockRecordPos(DateTime.fromMillisecondsSinceEpoch(100), 0, 0, 0, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(20), 0, 0, 0, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(9000000), 0, 0, 0, ''),
-      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(3124159), 0, 0, 0, ''),
+    final m = BloodPressureAnalyser([mockRecordPos(DateTime.fromMillisecondsSinceEpoch(100), 0, 0, 0),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(20), 0, 0, 0),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(9000000), 0, 0, 0),
+      mockRecordPos(DateTime.fromMillisecondsSinceEpoch(3124159), 0, 0, 0),
     ]);
 
     expect((m.firstDay), DateTime.fromMillisecondsSinceEpoch(20));
     expect((m.lastDay), DateTime.fromMillisecondsSinceEpoch(9000000));
   });
 }
+
+BloodPressureRecord mockRecordPos([
+  DateTime? time,
+  int? sys,
+  int? dia,
+  int? pul,
+]) => BloodPressureRecord(
+  time: time ?? DateTime.now(),
+  sys: sys == null ? null : Pressure.mmHg(sys),
+  dia: dia == null ? null : Pressure.mmHg(dia),
+  pul: pul,
+);
app/test/ui/components/statistics/blood_pressure_distribution_test.dart
@@ -40,12 +40,12 @@ void main() {
   testWidgets('should report records to ValueDistribution', (tester) async {
     await tester.pumpWidget(materialApp(BloodPressureDistribution(
       records: [
-        mockRecord(sys: 123),
-        mockRecord(dia: 123),
-        mockRecord(dia: 124),
-        mockRecord(pul: 123),
-        mockRecord(pul: 124),
-        mockRecord(pul: 125),
+        mockEntry(sys: 123),
+        mockEntry(dia: 123),
+        mockEntry(dia: 124),
+        mockEntry(pul: 123),
+        mockEntry(pul: 124),
+        mockEntry(pul: 125),
       ],
       settings: Settings(
         sysColor: Colors.red,
app/test/ui/components/add_measurement_dialoge_test.dart
@@ -35,7 +35,7 @@ void main() {
       await tester.pumpWidget(materialApp(
         AddEntryDialoge(
           settings: Settings(),
-          initialRecord: mockRecordPos(
+          initialRecord: mockEntryPos(
             DateTime.now(), 123, 56, 43, 'Test note', Colors.teal,
           ),
           medRepo: medRepo(),
@@ -156,7 +156,7 @@ void main() {
       await loadDialoge(tester, (context) async
       => result = await showAddEntryDialoge(context, Settings(),
         medRepo(),
-        mockRecord(sys: 123, dia: 56, pul: 43, note: 'Test note', pin: Colors.teal),),);
+        mockEntry(sys: 123, dia: 56, pul: 43, note: 'Test note', pin: Colors.teal),),);
       expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
 
       expect(find.byType(AddEntryDialoge), findsOneWidget);
@@ -168,7 +168,7 @@ void main() {
     });
     testWidgets('should return values on edit cancel', (tester) async {
       dynamic result = 'result before save';
-      final record = mockRecord(sys: 123, dia: 56, pul: 43, note: 'Test note', pin: Colors.teal);
+      final record = mockEntry(sys: 123, dia: 56, pul: 43, note: 'Test note', pin: Colors.teal);
       await loadDialoge(tester, (context) async
       => result = await showAddEntryDialoge(context, Settings(), medRepo(), record),);
       expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
@@ -392,7 +392,7 @@ void main() {
     testWidgets('should start with sys input focused', (tester) async {
       final mRep = medRepo();
       await loadDialoge(tester, (context) =>
-          showAddEntryDialoge(context, Settings(), mRep, mockRecord(sys: 12)),);
+          showAddEntryDialoge(context, Settings(), mRep, mockEntry(sys: 12)),);
       expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
 
       final primaryFocus = FocusManager.instance.primaryFocus;
@@ -408,7 +408,7 @@ void main() {
     testWidgets('should focus next on input finished', (tester) async {
       final mRep = medRepo();
       await loadDialoge(tester, (context) =>
-          showAddEntryDialoge(context, Settings(), mRep, mockRecord(sys: 12, dia: 3, pul: 4, note: 'note')),);
+          showAddEntryDialoge(context, Settings(), mRep, mockEntry(sys: 12, dia: 3, pul: 4, note: 'note')),);
       expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
 
       await tester.enterText(find.ancestor(of: find.text('Systolic').first, matching: find.byType(TextFormField)), '123');
@@ -451,7 +451,7 @@ void main() {
     testWidgets('should focus last input field on backspace pressed in empty input field', (tester) async {
       final mRep = medRepo();
       await loadDialoge(tester, (context) =>
-          showAddEntryDialoge(context, Settings(), mRep, mockRecord(sys: 12, dia: 3, pul: 4, note: 'note')),);
+          showAddEntryDialoge(context, Settings(), mRep, mockEntry(sys: 12, dia: 3, pul: 4, note: 'note')),);
       expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
 
       await tester.enterText(find.ancestor(of: find.text('note').first, matching: find.byType(TextFormField)), '');
@@ -587,7 +587,7 @@ void main() {
     testWidgets('should not go back to last field when the current field is still filled', (tester) async {
       final mRep = medRepo([mockMedicine(designation: 'testmed')]);
       await loadDialoge(tester, (context) =>
-          showAddEntryDialoge(context, Settings(), mRep, mockRecord(sys: 12, dia: 3, pul: 4, note: 'note')),);
+          showAddEntryDialoge(context, Settings(), mRep, mockEntry(sys: 12, dia: 3, pul: 4, note: 'note')),);
       expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
 
       await tester.enterText(find.ancestor(
app/test/ui/components/measurement_list_entry_test.dart
@@ -11,21 +11,21 @@ void main() {
   testWidgets('should initialize without errors', (tester) async {
     await tester.pumpWidget(materialApp(MeasurementListRow(
       settings: Settings(),
-      record: mockRecordPos(DateTime(2023), 123, 80, 60, 'test'),),),);
+      record: mockEntryPos(DateTime(2023), 123, 80, 60, 'test'),),),);
     expect(tester.takeException(), isNull);
     await tester.pumpWidget(materialApp(MeasurementListRow(
       settings: Settings(),
-      record: mockRecordPos(DateTime.fromMillisecondsSinceEpoch(31279811), null, null, null, 'null test'),),),);
+      record: mockEntryPos(DateTime.fromMillisecondsSinceEpoch(31279811), null, null, null, 'null test'),),),);
     expect(tester.takeException(), isNull);
     await tester.pumpWidget(materialApp(MeasurementListRow(
       settings: Settings(),
-      record: mockRecordPos(DateTime(2023), 124, 85, 63, 'color',Colors.cyan))));
+      record: mockEntryPos(DateTime(2023), 124, 85, 63, 'color',Colors.cyan))));
     expect(tester.takeException(), isNull);
   });
   testWidgets('should expand correctly', (tester) async {
     await tester.pumpWidget(materialApp(MeasurementListRow(
         settings: Settings(),
-        record: mockRecordPos(DateTime(2023), 123, 78, 56, 'Test texts'),),),);
+        record: mockEntryPos(DateTime(2023), 123, 78, 56, 'Test texts'),),),);
     expect(find.byIcon(Icons.expand_more), findsOneWidget);
     await tester.tap(find.byIcon(Icons.expand_more));
     await tester.pumpAndSettle();
@@ -36,7 +36,7 @@ void main() {
   testWidgets('should display correct information', (tester) async {
     await tester.pumpWidget(materialApp(MeasurementListRow(
         settings: Settings(),
-        record: mockRecordPos(DateTime(2023), 123, 78, 56, 'Test text'),),),);
+        record: mockEntryPos(DateTime(2023), 123, 78, 56, 'Test text'),),),);
     expect(find.text('123'), findsOneWidget);
     expect(find.text('78'), findsOneWidget);
     expect(find.text('56'), findsOneWidget);
@@ -52,7 +52,7 @@ void main() {
   });
   testWidgets('should not display null values', (tester) async {
     await tester.pumpWidget(materialApp(MeasurementListRow(
-      settings: Settings(), record: mockRecord(time: DateTime(2023)),),),);
+      settings: Settings(), record: mockEntry(time: DateTime(2023)),),),);
     expect(find.text('null'), findsNothing);
     expect(find.byIcon(Icons.expand_more), findsOneWidget);
     await tester.tap(find.byIcon(Icons.expand_more));
@@ -61,7 +61,7 @@ void main() {
   });
   testWidgets('should open edit dialoge', (tester) async {
     await tester.pumpWidget(await appBase(MeasurementListRow(
-      settings: Settings(), record: mockRecord(time: DateTime(2023),
+      settings: Settings(), record: mockEntry(time: DateTime(2023),
         sys:1, dia: 2, pul: 3, note: 'testTxt',),),),);
     expect(find.byIcon(Icons.expand_more), findsOneWidget);
     await tester.tap(find.byIcon(Icons.expand_more));
app/test/ui/statistics_test.dart
@@ -26,7 +26,7 @@ void main() {
   testWidgets('should report measurement count', (tester) async {
     await _initStatsPage(tester, [
       for (int i = 1; i<51; i++) // can't safe entries at or before epoch
-        mockRecord(time: DateTime.fromMillisecondsSinceEpoch(1582991592 + i),
+        mockEntry(time: DateTime.fromMillisecondsSinceEpoch(1582991592 + i),
           sys: i, dia: 60+i, pul: 110+i,),
     ], intervallStoreManager: IntervallStoreManager(IntervallStorage(),
         IntervallStorage(), IntervallStorage(stepSize: TimeStep.lifetime,),),);
@@ -54,8 +54,8 @@ void main() {
   });
   testWidgets("should not display 'null' or -1", (tester) async {
     await _initStatsPage(tester, [
-      mockRecord(time: DateTime.fromMillisecondsSinceEpoch(1), sys: 40, dia: 60),
-      mockRecord(time: DateTime.fromMillisecondsSinceEpoch(2),),
+      mockEntry(time: DateTime.fromMillisecondsSinceEpoch(1), sys: 40, dia: 60),
+      mockEntry(time: DateTime.fromMillisecondsSinceEpoch(2),),
     ], intervallStoreManager: IntervallStoreManager(IntervallStorage(),
         IntervallStorage(), IntervallStorage(stepSize: TimeStep.lifetime),),);
     expect(find.textContaining('-1'), findsNothing);