import { AdminVoteOutcome } from '../entities/AdminVoteSession'; import { RatingEvent } from '../entities/RatingEvent'; import { RatingDelta } from '../value-objects/RatingDelta'; /** * Input for vote outcome calculation */ export interface VoteOutcomeInput { outcome: AdminVoteOutcome; eligibleVoterCount: number; voteCount: number; percentPositive: number; } /** * Input for system signal calculation */ export interface SystemSignalInput { actionType: 'sla_response' | 'reversal' | 'rule_clarity' | 'abuse_report'; details: Record; severity?: 'minor' | 'major'; } /** * Domain Service: AdminTrustRatingCalculator * * Pure, stateless calculator for admin trust rating. * Implements full logic per ratings-architecture-concept.md sections 5.2 and 7.1.1 */ export class AdminTrustRatingCalculator { /** * Calculate admin trust rating delta from events * * Logic: * - Vote outcomes: weighted by participation and percentage * - System signals: fixed deltas based on action type * - All events are summed with their weights */ static calculate(events: RatingEvent[]): number { return events.reduce((sum, event) => { // Apply weight if present, otherwise use delta directly const weightedDelta = event.weight ? event.delta.value * event.weight : event.delta.value; return sum + weightedDelta; }, 0); } /** * Calculate delta from vote outcome * * Based on section 5.2.1: * - Votes produce events with reference to voteSessionId * - Delta is weighted by eligible voter count and participation * - Range: -20 to +20 based on percentage * * @param input - Vote outcome data * @returns Rating delta */ static calculateFromVote(input: VoteOutcomeInput): RatingDelta { const { outcome, eligibleVoterCount, voteCount, percentPositive } = input; // If no votes, no change if (voteCount === 0) { return RatingDelta.create(0); } // Calculate base delta from percentage // Positive outcome: +1 to +20 // Negative outcome: -1 to -20 // Tie: 0 let baseDelta: number; if (outcome.outcome === 'positive') { baseDelta = (percentPositive / 100) * 20; // 0 to +20 } else if (outcome.outcome === 'negative') { baseDelta = -((100 - percentPositive) / 100) * 20; // -20 to 0 } else { baseDelta = 0; // Tie } // Weight by participation rate (higher participation = more trust in result) // Minimum 50% participation for full weight const participationRate = voteCount / eligibleVoterCount; const participationMultiplier = Math.max(0.5, Math.min(1, participationRate)); const weightedDelta = baseDelta * participationMultiplier; // Round to 2 decimal places const roundedDelta = Math.round(weightedDelta * 100) / 100; return RatingDelta.create(roundedDelta); } /** * Calculate delta from system signal * * Based on section 5.2.2: * - ADMIN_ACTION_SLA_BONUS: +5 * - ADMIN_ACTION_REVERSAL_PENALTY: -10 (minor) or -20 (major) * - ADMIN_ACTION_RULE_CLARITY_BONUS: +3 * - ADMIN_ACTION_ABUSE_REPORT_PENALTY: -15 (minor) or -30 (major) * * @param input - System signal data * @returns Rating delta */ static calculateFromSystemSignal(input: SystemSignalInput): RatingDelta { const { actionType, severity } = input; switch (actionType) { case 'sla_response': return RatingDelta.create(5); case 'reversal': return RatingDelta.create(severity === 'major' ? -20 : -10); case 'rule_clarity': return RatingDelta.create(3); case 'abuse_report': return RatingDelta.create(severity === 'major' ? -30 : -15); default: return RatingDelta.create(0); } } /** * Calculate combined delta from multiple vote outcomes * Useful for batch processing */ static calculateFromMultipleVotes(inputs: VoteOutcomeInput[]): RatingDelta { const totalDelta = inputs.reduce((sum, input) => { const delta = this.calculateFromVote(input); return sum + delta.value; }, 0); return RatingDelta.create(totalDelta); } /** * Calculate combined delta from multiple system signals */ static calculateFromMultipleSystemSignals(inputs: SystemSignalInput[]): RatingDelta { const totalDelta = inputs.reduce((sum, input) => { const delta = this.calculateFromSystemSignal(input); return sum + delta.value; }, 0); return RatingDelta.create(totalDelta); } /** * Calculate total delta from mixed sources * Combines votes and system signals */ static calculateTotalDelta( voteInputs: VoteOutcomeInput[], systemInputs: SystemSignalInput[] ): RatingDelta { const voteDelta = this.calculateFromMultipleVotes(voteInputs); const systemDelta = this.calculateFromMultipleSystemSignals(systemInputs); return RatingDelta.create(voteDelta.value + systemDelta.value); } }