From 5e132dd7fe4260180c117757351742ab59fde6a0 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:05:41 -0500 Subject: [PATCH] feat: make construct aggregated case-insensitive (#5137) --- .../analytics_data_service.dart | 4 +- .../analytics_data/construct_merge_table.dart | 81 +++++++++++-------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/lib/pangea/analytics_data/analytics_data_service.dart b/lib/pangea/analytics_data/analytics_data_service.dart index 5a0b2ce1e..f3084c061 100644 --- a/lib/pangea/analytics_data/analytics_data_service.dart +++ b/lib/pangea/analytics_data/analytics_data_service.dart @@ -273,7 +273,7 @@ class AnalyticsDataService { Future getConstructUse(ConstructIdentifier id) async { await _ensureInitialized(); final blocked = blockedConstructs; - final ids = _mergeTable.groupedIds(id, blocked); + final ids = _mergeTable.groupedIds(_mergeTable.resolve(id), blocked); if (ids.isEmpty) { return ConstructUses( uses: [], @@ -294,7 +294,7 @@ class AnalyticsDataService { final blocked = blockedConstructs; for (final id in ids) { if (blocked.contains(id)) continue; - request[id] = _mergeTable.groupedIds(id, blocked); + request[id] = _mergeTable.groupedIds(_mergeTable.resolve(id), blocked); } return _analyticsClientGetter.database.getConstructUses(request); diff --git a/lib/pangea/analytics_data/construct_merge_table.dart b/lib/pangea/analytics_data/construct_merge_table.dart index 97d18f67c..b84786568 100644 --- a/lib/pangea/analytics_data/construct_merge_table.dart +++ b/lib/pangea/analytics_data/construct_merge_table.dart @@ -8,6 +8,7 @@ import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; class ConstructMergeTable { Map> lemmaTypeGroups = {}; Map otherToSpecific = {}; + final Map caseInsensitive = {}; void addConstructs( List constructs, @@ -30,6 +31,18 @@ class ConstructMergeTable { (lemmaTypeGroups[composite] ??= {}).add(id); } + for (final use in uses) { + final id = use.identifier; + if (exclude.contains(id)) continue; + final group = lemmaTypeGroups[id.compositeKey]; + if (group == null) continue; + final matches = group.where((m) => m != id && m.string == id.string); + for (final match in matches) { + caseInsensitive[match] = id; + caseInsensitive[id] = id; + } + } + for (final use in uses) { if (exclude.contains(use.identifier)) continue; final id = use.identifier; @@ -39,7 +52,7 @@ class ConstructMergeTable { (k) => k.category != 'other', ); if (specific != null) { - otherToSpecific[id] = specific; + otherToSpecific[id] = caseInsensitive[specific] ?? specific; } } } @@ -65,10 +78,18 @@ class ConstructMergeTable { } else { otherToSpecific.remove(id); } + + final caseEntry = caseInsensitive[id]; + if (caseEntry != null && caseEntry != id) { + caseInsensitive.remove(caseEntry); + } + caseInsensitive.remove(id); } - ConstructIdentifier resolve(ConstructIdentifier key) => - otherToSpecific[key] ?? key; + ConstructIdentifier resolve(ConstructIdentifier key) { + final specific = otherToSpecific[key] ?? key; + return caseInsensitive[specific] ?? specific; + } List groupedIds( ConstructIdentifier id, @@ -79,6 +100,16 @@ class ConstructMergeTable { keys.add(id); } + // if this key maps to a different case variant, include that as well + final differentCase = caseInsensitive[id]; + if (differentCase != null && differentCase != id) { + if (!exclude.contains(differentCase)) { + keys.add(differentCase); + } + } + + // if this is an broad ('other') key, find the specific key it maps to + // and include it if available if (id.category == 'other') { final specificKey = otherToSpecific[id]; if (specificKey != null) { @@ -87,21 +118,17 @@ class ConstructMergeTable { return keys; } - final group = lemmaTypeGroups[id.compositeKey]; - if (group == null) return keys; + // if this is a specific key, and there existing an 'other' construct + // in the same group, and that 'other' construct maps to this specific key, + // include the 'other' construct as well + final otherEntry = lemmaTypeGroups[id.compositeKey] + ?.firstWhereOrNull((k) => k.category == 'other'); + if (otherEntry == null) { + return keys; + } - final otherEntry = group.firstWhereOrNull((k) => k.category == 'other'); - if (otherEntry == null) return keys; - - final otherSpecificEntry = otherToSpecific[otherEntry]; - if (otherSpecificEntry == id) { - keys.add( - ConstructIdentifier( - lemma: id.lemma, - type: id.type, - category: 'other', - ), - ); + if (otherToSpecific[otherEntry] == id) { + keys.add(otherEntry); } return keys; } @@ -111,26 +138,13 @@ class ConstructMergeTable { (composite) => composite.endsWith('|${type.name}'), ); - int count = 0; + final Set unique = {}; for (final composite in keys) { final group = lemmaTypeGroups[composite]!; - if (group.any((e) => e.category == 'other')) { - // if this is the only entry in the group, it's a unique construct - if (group.length == 1) { - count += 1; - continue; - } - // otherwise, count all but the 'other' entry, - // which is merged into a more specific construct - count += group.length - 1; - continue; - } - - // all specific constructs, count them all - count += group.length; + unique.addAll(group.map((c) => resolve(c))); } - return count; + return unique.length; } bool constructUsed(ConstructIdentifier id) => @@ -139,5 +153,6 @@ class ConstructMergeTable { void clear() { lemmaTypeGroups.clear(); otherToSpecific.clear(); + caseInsensitive.clear(); } }