DeciderAnswer constructor
- required String? choice,
- required List<
RankedOption> ranked, - required ScoreKind scoreKind,
- required double margin,
- required DeciderProvenance provenance,
- double? confidence,
- AbstainReason? abstainReason,
- Map<
String, double> ? probabilities, - DeciderQuestionType questionType = DeciderQuestionType.choice,
- Map<
String, String> ? legend,
For deciders implemented outside core (and test fakes). Lists are copied.
Throws ArgumentError for an answer that contradicts itself: exactly
one of choice and abstainReason is set; a choice is the first of a
non-empty ranked; ranked has each id once, no NaN score, and is
sorted by score, highest first; margin is not NaN and not negative;
confidence is in 0, 1 and only given with probabilities;
probabilities are each in 0, 1, sum to 1 (within 1e-6) and cover
every ranked option, whose RankedOption.probability is its entry there
— and every RankedOption.probability is null without them; the
provenance's abstain
signal and its kind are both set or both null; an answer that abstains
with AbstainReason.belowThreshold has its signal below the threshold
(both set), and one that chooses with both set has it at or above;
AbstainReason.noneOption comes with candidates in ranked and may
have its signal on either side (the none option wins first);
AbstainReason.noCandidates comes with nothing ranked, no signal, no
probabilities and no confidence.
questionType is DeciderQuestionType.choice unless given; then
legend is null. A DeciderQuestionType.noul or
DeciderQuestionType.score answer never abstains, has no threshold,
has probabilities, and chooses its most probable option. A Noul
answer's probabilities are over no and yes exactly; a Score answer's
over the levels 0 … K − 1 (K ≥ 2), and its legend has a non-empty
text for each level and nothing else. legend is copied.
Implementation
DeciderAnswer({
required this.choice,
required List<RankedOption> ranked,
required this.scoreKind,
required this.margin,
required this.provenance,
this.confidence,
this.abstainReason,
Map<String, double>? probabilities,
this.questionType = DeciderQuestionType.choice,
Map<String, String>? legend,
}) : ranked = List.unmodifiable(ranked),
probabilities = probabilities == null
? null
: Map.unmodifiable(probabilities),
legend = legend == null ? null : Map.unmodifiable(legend) {
if ((choice == null) == (abstainReason == null)) {
throw ArgumentError(
'A DeciderAnswer has either a choice or an abstainReason, exactly '
'one of them; got choice $choice, abstainReason $abstainReason.',
);
}
if (choice != null && this.ranked.isNotEmpty) {
if (this.ranked.first.id != choice) {
throw ArgumentError.value(
choice,
'choice',
'is not the best ranked option (${this.ranked.first.id})',
);
}
}
for (var i = 1; i < this.ranked.length; i++) {
if (!(this.ranked[i - 1].score >= this.ranked[i].score)) {
throw ArgumentError.value(
this.ranked.map((r) => r.score).toList(),
'ranked',
'must be sorted by score, highest first',
);
}
}
final p = this.probabilities;
if (confidence case final c?) {
if (p == null) {
throw ArgumentError.value(
c,
'confidence',
'is only given with calibrated probabilities',
);
}
if (!(c >= 0 && c <= 1)) {
throw ArgumentError.value(c, 'confidence', 'must be in [0, 1]');
}
}
_checkRanked();
_checkProbabilities();
_checkAbstention();
_checkQuestionType();
}