cyberhybridhub/server/lib/question_source_mix.dart
2026-06-06 19:23:18 +00:00

69 lines
2.1 KiB
Dart

import 'dart:math';
import 'question_source_preferences.dart';
import 'questions_db.dart';
/// Picks the next question source randomly matching configured mix percentages.
abstract final class QuestionSourceMix {
/// Source id selected using weighted random selection based on configured mix ratios.
static String pickNextSourceId({
required Map<String, int> effectiveMixPercent,
required Map<String, int> deliveredCounts,
Random? random,
}) {
if (effectiveMixPercent.isEmpty) {
return 'market_history';
}
final int totalWeight =
effectiveMixPercent.values.fold<int>(0, (int a, int b) => a + b);
if (totalWeight <= 0) {
return effectiveMixPercent.keys.first;
}
// If one source has 100% of the active weight, bypass the random roll completely.
for (final MapEntry<String, int> entry in effectiveMixPercent.entries) {
if (entry.value == totalWeight) {
return entry.key;
}
}
final Random rng = random ?? Random();
var roll = rng.nextInt(totalWeight);
for (final MapEntry<String, int> entry in effectiveMixPercent.entries) {
roll -= entry.value;
if (roll < 0) {
return entry.key;
}
}
return effectiveMixPercent.keys.first;
}
static Map<String, int> effectiveMixFromResolved(
List<ResolvedQuestionSourcePreference> prefs,
) {
return QuestionSourcePreferences.effectiveMix(prefs);
}
/// Orders unanswered rows so [preferredSourceId] is shown first (stable FIFO within source).
static List<Map<String, dynamic>> orderUnansweredByPreferredSource(
List<Map<String, dynamic>> rows, {
required String preferredSourceId,
}) {
if (rows.length <= 1) {
return rows;
}
final List<Map<String, dynamic>> preferred = <Map<String, dynamic>>[];
final List<Map<String, dynamic>> rest = <Map<String, dynamic>>[];
for (final Map<String, dynamic> row in rows) {
if (QuestionsDb.resolveSourceId(row) == preferredSourceId) {
preferred.add(row);
} else {
rest.add(row);
}
}
return <Map<String, dynamic>>[...preferred, ...rest];
}
}