device infos

This commit is contained in:
2026-08-28 15:07:30 +05:30
parent 5723d373b2
commit 69f4f3e909
84 changed files with 4337 additions and 2339 deletions

View File

@@ -102,7 +102,60 @@ class MilkRun {
// fabricated kitchen.
/// The stable key a stop groups under for pickup.
static String sourceKeyOf(Map<String, dynamic> stop) {
///
/// ── The address is the last witness, and it was not being asked ──
///
/// Id, then name, then `'none'` — and `'none'` is its own bucket per the note
/// above, on the sound principle that the app must not fabricate a kitchen.
///
/// What that missed is that a stop with no source id and no source name is
/// not a stop with no *place*. It still says where it is collected from: the
/// pickup address, which is on every row, and which two orders off the same
/// counter share exactly because it is the same counter.
///
/// The cost of not asking was visible on a real morning. Eight orders came
/// from one kitchen, seven carrying its id and one not, and the eighth could
/// not be grouped with its siblings — so it fell out of the dropdown into a
/// flat header of its own, titled with the customer's name, reading as a
/// second place the rider had to ride to. Seven bags in a list and one
/// stranded above it, off the same shelf.
///
/// Grouping by address is not a fabricated kitchen: it is the weakest true
/// statement available — *these are collected from the same address* — and it
/// is only reached when the two stronger ones are absent. A row with no
/// address at all still gets `'none'`, and still stands alone.
/// [within] is the rest of the day, when the caller has it. A stop that
/// names no counter **adopts the one its neighbours name at the same
/// address** — which is the half of this that the reported case actually
/// needed: seven of the eight carried the kitchen's id and the eighth carried
/// nothing, so keying the odd one out by its address alone still left it in a
/// bucket of its own, correctly grouped with nobody.
///
/// Only ever *adopts*, never overrides: a stop with its own identity is
/// keyed by it and does not consult the day at all. So two counters that
/// share a street cannot merge, and a row cannot be pulled away from the id
/// the hub gave it.
static String sourceKeyOf(
Map<String, dynamic> stop, {
List<Map<String, dynamic>> within = const [],
}) {
final own = _identityKeyOf(stop);
if (own.isNotEmpty) return own;
final place = _addressKey(stop);
if (place.isEmpty) return 'none';
for (final other in within) {
if (_addressKey(other) != place) continue;
final identified = _identityKeyOf(other);
if (identified.isNotEmpty) return identified;
}
return 'at:$place';
}
/// The counter this stop names for itself — `id:…`, `name:…`, or `''` when
/// the payload names none.
static String _identityKeyOf(Map<String, dynamic> stop) {
final id =
(stop['sourceid'] ??
stop['kitchenid'] ??
@@ -113,7 +166,25 @@ class MilkRun {
if (id.isNotEmpty && id != '0') return 'id:$id';
final name = sourceNameOf(stop).toLowerCase();
if (name.isNotEmpty) return 'name:$name';
return 'none';
return '';
}
/// A pickup address reduced to something two rows can be compared on.
///
/// Case, spacing and punctuation all vary between rows the same hub wrote —
/// `12, SNS Colony` and `12 SNS COLONY ` are one place — so everything but
/// the letters and digits is dropped before comparing. Deliberately not
/// `compactAddress`: that is written to be *read*, and a key should not
/// change because a display rule was tuned.
static String _addressKey(Map<String, dynamic> stop) {
final raw =
(stop['pickupaddress'] ??
stop['PickupAddress'] ??
stop['pickup_address'] ??
'')
.toString()
.toLowerCase();
return raw.replaceAll(RegExp(r'[^a-z0-9]'), '');
}
/// The kitchen's name as the rider reads it, or '' when the payload has none.
@@ -177,10 +248,14 @@ class MilkRun {
Map<String, dynamic> stop,
List<Map<String, dynamic>> stops,
) {
final key = sourceKeyOf(stop);
// The day is passed on both sides so a row that names no counter is
// gathered with the ones that do — see [sourceKeyOf]. Without it the bulk
// collect would leave behind exactly the order the route card had just
// learned to group in, which is the two-readers failure in a new place.
final key = sourceKeyOf(stop, within: stops);
return [
for (final s in stops)
if (sourceKeyOf(s) == key) s,
if (sourceKeyOf(s, within: stops) == key) s,
];
}