112 lines
3.1 KiB
Dart
112 lines
3.1 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'user_trading_state_db.dart';
|
|
|
|
/// Runtime scoring mode for prospective / guess-the-move answers.
|
|
///
|
|
/// Set from [ServerEnv.prospectiveAnswerClosenessEnabled] at server startup.
|
|
class ProspectiveAnswerScoring {
|
|
ProspectiveAnswerScoring._();
|
|
|
|
/// When false (default): correct sign `+1`, wrong sign `-1`.
|
|
///
|
|
/// When true: correct sign `+1` plus up to `+1` closeness; wrong sign `-2`.
|
|
static bool closenessExtraPointsEnabled = false;
|
|
}
|
|
|
|
/// Breakdown of one prospective / guess-the-move answer grade.
|
|
class ProspectiveAnswerGrade {
|
|
const ProspectiveAnswerGrade({
|
|
required this.directionPoint,
|
|
required this.closenessPoint,
|
|
required this.answerScore,
|
|
required this.absError,
|
|
required this.sameDirection,
|
|
});
|
|
|
|
final num directionPoint;
|
|
final num closenessPoint;
|
|
final num answerScore;
|
|
final num absError;
|
|
final bool sameDirection;
|
|
}
|
|
|
|
/// Full credit when absolute error is within this many percentage points.
|
|
const num prospectiveAnswerFullCreditTolerance = 1;
|
|
|
|
/// Grades a slider answer against the expected percent move.
|
|
ProspectiveAnswerGrade gradeProspectiveAnswer({
|
|
required num userResponse,
|
|
required num correctAnswer,
|
|
}) {
|
|
final bool sameDirection = _sameDirection(
|
|
userResponse: userResponse,
|
|
correctAnswer: correctAnswer,
|
|
);
|
|
final num absError = (userResponse - correctAnswer).abs();
|
|
|
|
if (!ProspectiveAnswerScoring.closenessExtraPointsEnabled) {
|
|
final num directionPoint = sameDirection ? 1 : -1;
|
|
return ProspectiveAnswerGrade(
|
|
directionPoint: directionPoint,
|
|
closenessPoint: 0,
|
|
answerScore: directionPoint,
|
|
absError: absError,
|
|
sameDirection: sameDirection,
|
|
);
|
|
}
|
|
|
|
final num directionPoint = sameDirection ? 1 : -2;
|
|
final num closenessPoint = sameDirection
|
|
? _closenessPoint(
|
|
userResponse: userResponse,
|
|
correctAnswer: correctAnswer,
|
|
)
|
|
: 0;
|
|
return ProspectiveAnswerGrade(
|
|
directionPoint: directionPoint,
|
|
closenessPoint: closenessPoint,
|
|
answerScore: directionPoint + closenessPoint,
|
|
absError: absError,
|
|
sameDirection: sameDirection,
|
|
);
|
|
}
|
|
|
|
/// Adds [grade] to cumulative `guess_score` in [user_trading_state].
|
|
Future<void> recordProspectiveGuessScore({
|
|
required UserTradingStateDb tradingStateDb,
|
|
required String firebaseUid,
|
|
required ProspectiveAnswerGrade grade,
|
|
required String symbol,
|
|
required DateTime at,
|
|
}) async {
|
|
await tradingStateDb.recordGuessScore(
|
|
firebaseUid: firebaseUid,
|
|
scoreDelta: grade.answerScore,
|
|
symbol: symbol,
|
|
at: at,
|
|
directionCorrect: grade.sameDirection,
|
|
);
|
|
}
|
|
|
|
bool _sameDirection({
|
|
required num userResponse,
|
|
required num correctAnswer,
|
|
}) {
|
|
return userResponse.compareTo(0) == correctAnswer.compareTo(0);
|
|
}
|
|
|
|
num _closenessPoint({
|
|
required num userResponse,
|
|
required num correctAnswer,
|
|
}) {
|
|
final num absError = (userResponse - correctAnswer).abs();
|
|
if (absError <= prospectiveAnswerFullCreditTolerance) {
|
|
return 1;
|
|
}
|
|
final num scale = math.max(1, correctAnswer.abs());
|
|
final num normalizedError =
|
|
(absError - prospectiveAnswerFullCreditTolerance) / scale;
|
|
return math.max(0, 1 - normalizedError);
|
|
}
|