diff --git a/app/lib/search/mem_index.dart b/app/lib/search/mem_index.dart index bebe57cbe7..fd279bfd1f 100644 --- a/app/lib/search/mem_index.dart +++ b/app/lib/search/mem_index.dart @@ -24,6 +24,11 @@ class InMemoryPackageIndex { final TokenIndex _descrIndex = TokenIndex(); final TokenIndex _readmeIndex = TokenIndex(); final TokenIndex _apiSymbolIndex = TokenIndex(); + + /// Adjusted score takes the overall score and transforms + /// it linearly into the [0.4-1.0] range. + final _adjustedOverallScores = {}; + late final List _overallOrderedHits; late final List _createdOrderedHits; late final List _updatedOrderedHits; late final List _popularityOrderedHits; @@ -42,7 +47,10 @@ class InMemoryPackageIndex { if (_packages.values.any((e) => e.likeScore == null)) { _packages.values.updateLikeScores(); } + _updateOverallScores(); _lastUpdated = clock.now().toUtc(); + _overallOrderedHits = _rankWithComparator(_compareOverall, + score: (doc) => doc.overallScore ?? 0.0); _createdOrderedHits = _rankWithComparator(_compareCreated); _updatedOrderedHits = _rankWithComparator(_compareUpdated); _popularityOrderedHits = _rankWithComparator(_comparePopularity, @@ -143,11 +151,16 @@ class InMemoryPackageIndex { late List packageHits; switch (query.effectiveOrder ?? SearchOrder.top) { case SearchOrder.top: - final scores = [ - _getOverallScore(packages), - if (textResults != null) textResults.pkgScore, - ]; - final overallScore = Score.multiply(scores); + if (textResults == null) { + packageHits = _overallOrderedHits.whereInSet(packages); + break; + } + + /// Adjusted score takes the overall score and transforms + /// it linearly into the [0.4-1.0] range, to allow better + /// multiplication outcomes. + final overallScore = textResults.pkgScore + .map((key, value) => value * _adjustedOverallScores[key]!); // If the search hits have an exact name match, we move it to the front of the result list. final parsedQueryText = query.parsedQuery.text; final priorityPackageName = @@ -201,18 +214,18 @@ class InMemoryPackageIndex { ); } - Score _getOverallScore(Iterable packages) { - final values = Map.fromEntries(packages.map((package) { - final doc = _packages[package]!; + /// Update the overall score both on [PackageDocument] and in the [_adjustedOverallScores] map. + void _updateOverallScores() { + for (final doc in _packages.values) { final downloadScore = doc.popularityScore ?? 0.0; final likeScore = doc.likeScore ?? 0.0; final popularity = (downloadScore + likeScore) / 2; final points = doc.grantedPoints / math.max(1, doc.maxPoints); final overall = popularity * 0.5 + points * 0.5; - // don't multiply with zero. - return MapEntry(package, 0.4 + 0.6 * overall); - })); - return Score(values); + doc.overallScore = overall; + // adding a base score prevents later multiplication with zero + _adjustedOverallScores[doc.package] = 0.4 + 0.6 * overall; + } } _TextResults? _searchText(Set packages, String? text) { @@ -359,6 +372,12 @@ class InMemoryPackageIndex { return -a.updated.compareTo(b.updated); } + int _compareOverall(PackageDocument a, PackageDocument b) { + final x = -(a.overallScore ?? 0.0).compareTo(b.overallScore ?? 0.0); + if (x != 0) return x; + return _compareUpdated(a, b); + } + int _comparePopularity(PackageDocument a, PackageDocument b) { final x = -(a.popularityScore ?? 0.0).compareTo(b.popularityScore ?? 0.0); if (x != 0) return x; diff --git a/app/lib/search/search_service.dart b/app/lib/search/search_service.dart index 31ee464335..43154ec52b 100644 --- a/app/lib/search/search_service.dart +++ b/app/lib/search/search_service.dart @@ -83,6 +83,9 @@ class PackageDocument { final int grantedPoints; final int maxPoints; + /// The normalized overall score between [0.0-1.0] for default package listing. + double? overallScore; + final Map dependencies; final List? apiDocPages; diff --git a/app/lib/search/search_service.g.dart b/app/lib/search/search_service.g.dart index 19d80bdcbb..9af824fd37 100644 --- a/app/lib/search/search_service.g.dart +++ b/app/lib/search/search_service.g.dart @@ -38,7 +38,7 @@ PackageDocument _$PackageDocumentFromJson(Map json) => sourceUpdated: json['sourceUpdated'] == null ? null : DateTime.parse(json['sourceUpdated'] as String), - ); + )..overallScore = (json['overallScore'] as num?)?.toDouble(); Map _$PackageDocumentToJson(PackageDocument instance) => { @@ -54,6 +54,7 @@ Map _$PackageDocumentToJson(PackageDocument instance) => 'popularityScore': instance.popularityScore, 'grantedPoints': instance.grantedPoints, 'maxPoints': instance.maxPoints, + 'overallScore': instance.overallScore, 'dependencies': instance.dependencies, 'apiDocPages': instance.apiDocPages, 'timestamp': instance.timestamp.toIso8601String(), diff --git a/app/test/search/mem_index_test.dart b/app/test/search/mem_index_test.dart index 490c7bf42a..b389aa8e66 100644 --- a/app/test/search/mem_index_test.dart +++ b/app/test/search/mem_index_test.dart @@ -193,7 +193,7 @@ server.dart adds a small, prescriptive server (PicoServer) that can be configure 'packageHits': [ { 'package': 'chrome_net', - 'score': closeTo(0.54, 0.1), + 'score': closeTo(0.08, 0.1), }, ], }); @@ -328,8 +328,8 @@ server.dart adds a small, prescriptive server (PicoServer) that can be configure 'totalCount': 2, 'sdkLibraryHits': [], 'packageHits': [ - {'package': 'http', 'score': closeTo(0.96, 0.01)}, - {'package': 'async', 'score': closeTo(0.65, 0.01)}, + {'package': 'http', 'score': closeTo(0.92, 0.01)}, + {'package': 'async', 'score': closeTo(0.41, 0.01)}, ], }); @@ -347,7 +347,7 @@ server.dart adds a small, prescriptive server (PicoServer) that can be configure 'totalCount': 1, 'sdkLibraryHits': [], 'packageHits': [ - {'package': 'chrome_net', 'score': closeTo(0.45, 0.01)}, + {'package': 'chrome_net', 'score': closeTo(0.08, 0.01)}, ], }); }); @@ -360,8 +360,8 @@ server.dart adds a small, prescriptive server (PicoServer) that can be configure 'totalCount': 2, 'sdkLibraryHits': [], 'packageHits': [ - {'package': 'http', 'score': closeTo(0.96, 0.01)}, - {'package': 'chrome_net', 'score': closeTo(0.45, 0.01)}, + {'package': 'http', 'score': closeTo(0.92, 0.01)}, + {'package': 'chrome_net', 'score': closeTo(0.08, 0.01)}, ], }); @@ -392,7 +392,7 @@ server.dart adds a small, prescriptive server (PicoServer) that can be configure 'totalCount': 1, 'sdkLibraryHits': [], 'packageHits': [ - {'package': 'http', 'score': closeTo(0.96, 0.01)}, + {'package': 'http', 'score': closeTo(0.92, 0.01)}, ], }); }); @@ -416,7 +416,7 @@ server.dart adds a small, prescriptive server (PicoServer) that can be configure 'totalCount': 1, 'sdkLibraryHits': [], 'packageHits': [ - {'package': 'async', 'score': closeTo(0.65, 0.01)}, + {'package': 'async', 'score': closeTo(0.41, 0.01)}, ], }); diff --git a/app/test/search/result_combiner_test.dart b/app/test/search/result_combiner_test.dart index dc329ca998..101300c9fe 100644 --- a/app/test/search/result_combiner_test.dart +++ b/app/test/search/result_combiner_test.dart @@ -101,7 +101,7 @@ void main() { 'totalCount': 1, 'sdkLibraryHits': [], 'packageHits': [ - {'package': 'stringutils', 'score': closeTo(0.91, 0.01)}, + {'package': 'stringutils', 'score': closeTo(0.85, 0.01)}, ], }); });