all profiles as lists, better handling, stop using a fallback profile for broken submissions without one

This commit is contained in:
stopflock
2025-10-24 17:31:04 -05:00
parent f1f145a35f
commit 0db4c0f80d
5 changed files with 143 additions and 259 deletions

View File

@@ -20,161 +20,149 @@ class NodeProfile {
this.editable = true,
});
/// Builtin default: Generic ALPR camera (customizable template, not submittable)
factory NodeProfile.genericAlpr() => NodeProfile(
id: 'builtin-generic-alpr',
name: 'Generic ALPR',
tags: const {
'man_made': 'surveillance',
'surveillance:type': 'ALPR',
},
builtin: true,
requiresDirection: true,
submittable: false,
editable: false,
);
/// Get all built-in default node profiles
static List<NodeProfile> getDefaults() => [
NodeProfile(
id: 'builtin-generic-alpr',
name: 'Generic ALPR',
tags: const {
'man_made': 'surveillance',
'surveillance:type': 'ALPR',
},
builtin: true,
requiresDirection: true,
submittable: false,
editable: false,
),
NodeProfile(
id: 'builtin-flock',
name: 'Flock',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'ALPR',
'surveillance:zone': 'traffic',
'camera:type': 'fixed',
'manufacturer': 'Flock Safety',
'manufacturer:wikidata': 'Q108485435',
},
builtin: true,
requiresDirection: true,
submittable: true,
editable: true,
),
NodeProfile(
id: 'builtin-motorola',
name: 'Motorola/Vigilant',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'ALPR',
'surveillance:zone': 'traffic',
'camera:type': 'fixed',
'manufacturer': 'Motorola Solutions',
'manufacturer:wikidata': 'Q634815',
},
builtin: true,
requiresDirection: true,
submittable: true,
editable: true,
),
NodeProfile(
id: 'builtin-genetec',
name: 'Genetec',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'ALPR',
'surveillance:zone': 'traffic',
'camera:type': 'fixed',
'manufacturer': 'Genetec',
'manufacturer:wikidata': 'Q30295174',
},
builtin: true,
requiresDirection: true,
submittable: true,
editable: true,
),
NodeProfile(
id: 'builtin-leonardo',
name: 'Leonardo/ELSAG',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'ALPR',
'surveillance:zone': 'traffic',
'camera:type': 'fixed',
'manufacturer': 'Leonardo',
'manufacturer:wikidata': 'Q910379',
},
builtin: true,
requiresDirection: true,
submittable: true,
editable: true,
),
NodeProfile(
id: 'builtin-neology',
name: 'Neology',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'ALPR',
'surveillance:zone': 'traffic',
'camera:type': 'fixed',
'manufacturer': 'Neology, Inc.',
},
builtin: true,
requiresDirection: true,
submittable: true,
editable: true,
),
NodeProfile(
id: 'builtin-generic-gunshot',
name: 'Generic Gunshot Detector',
tags: const {
'man_made': 'surveillance',
'surveillance:type': 'gunshot_detector',
},
builtin: true,
requiresDirection: false,
submittable: false,
editable: false,
),
NodeProfile(
id: 'builtin-shotspotter',
name: 'ShotSpotter',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'gunshot_detector',
'surveillance:brand': 'ShotSpotter',
'surveillance:brand:wikidata': 'Q107740188',
},
builtin: true,
requiresDirection: false,
submittable: true,
editable: true,
),
NodeProfile(
id: 'builtin-flock-raven',
name: 'Flock Raven',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'gunshot_detector',
'brand': 'Flock Safety',
'brand:wikidata': 'Q108485435',
},
builtin: true,
requiresDirection: false,
submittable: true,
editable: true,
),
];
/// Builtin: Flock Safety ALPR camera
factory NodeProfile.flock() => NodeProfile(
id: 'builtin-flock',
name: 'Flock',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'ALPR',
'surveillance:zone': 'traffic',
'camera:type': 'fixed',
'manufacturer': 'Flock Safety',
'manufacturer:wikidata': 'Q108485435',
},
builtin: true,
requiresDirection: true,
submittable: true,
editable: true,
);
/// Builtin: Motorola Solutions/Vigilant ALPR camera
factory NodeProfile.motorola() => NodeProfile(
id: 'builtin-motorola',
name: 'Motorola/Vigilant',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'ALPR',
'surveillance:zone': 'traffic',
'camera:type': 'fixed',
'manufacturer': 'Motorola Solutions',
'manufacturer:wikidata': 'Q634815',
},
builtin: true,
requiresDirection: true,
submittable: true,
editable: true,
);
/// Builtin: Genetec ALPR camera
factory NodeProfile.genetec() => NodeProfile(
id: 'builtin-genetec',
name: 'Genetec',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'ALPR',
'surveillance:zone': 'traffic',
'camera:type': 'fixed',
'manufacturer': 'Genetec',
'manufacturer:wikidata': 'Q30295174',
},
builtin: true,
requiresDirection: true,
submittable: true,
editable: true,
);
/// Builtin: Leonardo/ELSAG ALPR camera
factory NodeProfile.leonardo() => NodeProfile(
id: 'builtin-leonardo',
name: 'Leonardo/ELSAG',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'ALPR',
'surveillance:zone': 'traffic',
'camera:type': 'fixed',
'manufacturer': 'Leonardo',
'manufacturer:wikidata': 'Q910379',
},
builtin: true,
requiresDirection: true,
submittable: true,
editable: true,
);
/// Builtin: Neology ALPR camera
factory NodeProfile.neology() => NodeProfile(
id: 'builtin-neology',
name: 'Neology',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'ALPR',
'surveillance:zone': 'traffic',
'camera:type': 'fixed',
'manufacturer': 'Neology, Inc.',
},
builtin: true,
requiresDirection: true,
submittable: true,
editable: true,
);
/// Builtin: Generic gunshot detector (customizable template, not submittable)
factory NodeProfile.genericGunshotDetector() => NodeProfile(
id: 'builtin-generic-gunshot',
name: 'Generic Gunshot Detector',
tags: const {
'man_made': 'surveillance',
'surveillance:type': 'gunshot_detector',
},
builtin: true,
requiresDirection: false,
submittable: false,
editable: false,
);
/// Builtin: ShotSpotter gunshot detector
factory NodeProfile.shotspotter() => NodeProfile(
id: 'builtin-shotspotter',
name: 'ShotSpotter',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'gunshot_detector',
'surveillance:brand': 'ShotSpotter',
'surveillance:brand:wikidata': 'Q107740188',
},
builtin: true,
requiresDirection: false,
submittable: true,
editable: true,
);
/// Builtin: Flock Raven gunshot detector
factory NodeProfile.flockRaven() => NodeProfile(
id: 'builtin-flock-raven',
name: 'Flock Raven',
tags: const {
'man_made': 'surveillance',
'surveillance': 'public',
'surveillance:type': 'gunshot_detector',
'brand': 'Flock Safety',
'brand:wikidata': 'Q108485435',
},
builtin: true,
requiresDirection: false,
submittable: true,
editable: true,
);
/// Returns true if this profile can be used for submissions
bool get isSubmittable => submittable;

View File

@@ -44,15 +44,6 @@ class OperatorProfile {
),
];
/// Built-in default: Lowe's operator profile
factory OperatorProfile.lowes() => getDefaults()[0];
/// Built-in default: The Home Depot operator profile
factory OperatorProfile.homeDepot() => getDefaults()[1];
/// Built-in default: Simon Property Group operator profile
factory OperatorProfile.simonPropertyGroup() => getDefaults()[2];
OperatorProfile copyWith({
String? id,
String? name,

View File

@@ -91,7 +91,7 @@ class PendingUpload {
direction: j['dir'],
profile: j['profile'] is Map<String, dynamic>
? NodeProfile.fromJson(j['profile'])
: NodeProfile.genericAlpr(),
: throw Exception('PendingUpload missing required profile data - this should never happen'),
operatorProfile: j['operatorProfile'] != null
? OperatorProfile.fromJson(j['operatorProfile'])
: null,

View File

@@ -23,19 +23,7 @@ class ProfileState extends ChangeNotifier {
// Add built-in profiles if this is first launch
if (addDefaults) {
final builtinProfiles = [
NodeProfile.genericAlpr(),
NodeProfile.flock(),
NodeProfile.motorola(),
NodeProfile.genetec(),
NodeProfile.leonardo(),
NodeProfile.neology(),
NodeProfile.genericGunshotDetector(),
NodeProfile.shotspotter(),
NodeProfile.flockRaven(),
];
_profiles.addAll(builtinProfiles);
_profiles.addAll(NodeProfile.getDefaults());
await ProfileService().save(_profiles);
}

View File

@@ -1,83 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:latlong2/latlong.dart';
import 'package:deflockapp/models/pending_upload.dart';
import 'package:deflockapp/models/node_profile.dart';
import 'package:deflockapp/state/settings_state.dart';
void main() {
group('PendingUpload', () {
test('should serialize and deserialize upload mode correctly', () {
// Test each upload mode
final testModes = [
UploadMode.production,
UploadMode.sandbox,
UploadMode.simulate,
];
for (final mode in testModes) {
final original = PendingUpload(
coord: LatLng(37.7749, -122.4194),
direction: 90.0,
profile: NodeProfile.flock(),
uploadMode: mode,
);
// Serialize to JSON
final json = original.toJson();
// Deserialize from JSON
final restored = PendingUpload.fromJson(json);
// Verify upload mode is preserved
expect(restored.uploadMode, equals(mode));
expect(restored.uploadModeDisplayName, equals(original.uploadModeDisplayName));
// Verify other fields too
expect(restored.coord.latitude, equals(original.coord.latitude));
expect(restored.coord.longitude, equals(original.coord.longitude));
expect(restored.direction, equals(original.direction));
expect(restored.profile.id, equals(original.profile.id));
}
});
test('should handle legacy JSON without uploadMode', () {
// Simulate old JSON format without uploadMode field
final legacyJson = {
'lat': 37.7749,
'lon': -122.4194,
'dir': 90.0,
'profile': NodeProfile.flock().toJson(),
'originalNodeId': null,
'attempts': 0,
'error': false,
// Note: no 'uploadMode' field
};
final upload = PendingUpload.fromJson(legacyJson);
// Should default to production mode for legacy entries
expect(upload.uploadMode, equals(UploadMode.production));
expect(upload.uploadModeDisplayName, equals('Production'));
});
test('should correctly identify edits vs new cameras', () {
final newCamera = PendingUpload(
coord: LatLng(37.7749, -122.4194),
direction: 90.0,
profile: NodeProfile.flock(),
uploadMode: UploadMode.production,
);
final editCamera = PendingUpload(
coord: LatLng(37.7749, -122.4194),
direction: 90.0,
profile: CameraProfile.flock(),
uploadMode: UploadMode.production,
originalNodeId: 12345,
);
expect(newCamera.isEdit, isFalse);
expect(editCamera.isEdit, isTrue);
});
});
}