All files / src/learning outcomes.ts

98.66% Statements 148/150
90.62% Branches 29/32
100% Functions 5/5
98.66% Lines 148/150

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 1511x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 32x 32x 32x 32x 32x 32x 32x 32x 32x 32x 32x 32x 32x 32x 32x 32x 32x 32x 32x 12x 12x 12x 2x 2x 10x 10x 10x 10x 10x 12x 12x 12x 12x 12x 12x 12x 12x 12x     10x 10x 10x 10x 10x 12x 12x 12x 12x 12x 12x 12x 32x 32x 32x 32x 32x 32x 32x 32x 4x 4x 2x 2x 2x 2x 2x 3x 4x 4x 4x 4x 4x 4x 32x 32x 32x 32x 32x 32x 32x 32x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 1x 1x 1x 1x 1x 2x 2x 2x 2x 32x  
/**
 * Outcome Recording
 *
 * Handles recording and processing of run outcomes.
 *
 * @packageDocumentation
 */
 
import type { Store } from '../storage/store.js';
import type { OutcomeInput, Outcome, OutcomeRecord } from '../types.js';
 
/**
 * Outcome recorder for processing user feedback.
 */
export class OutcomeRecorder {
  private store: Store;
 
  /**
   * Creates a new OutcomeRecorder.
   *
   * @param store - The storage instance to use
   */
  constructor(store: Store) {
    this.store = store;
  }
 
  /**
   * Records an outcome for a run.
   *
   * @param input - The outcome input
   * @returns The recorded outcome
   * @throws If the run ID is not found
   */
  record(input: OutcomeInput): Outcome {
    // Verify the run exists
    const run = this.store.getRun(input.runId);
    if (!run) {
      throw new Error(`Run not found: ${input.runId}`);
    }
 
    // Record the outcome
    const id = this.store.recordOutcome({
      runId: input.runId,
      success: input.success,
      quality: input.quality ?? null,
      latencySatisfactory: input.latencySatisfactory ?? null,
      costSatisfactory: input.costSatisfactory ?? null,
      feedback: input.feedback ?? null,
    });
 
    // Get the recorded outcome
    const outcome = this.store.getOutcome(input.runId);
    if (!outcome) {
      throw new Error('Failed to record outcome');
    }
 
    return {
      id: outcome.id,
      runId: outcome.runId,
      success: outcome.success,
      quality: outcome.quality ?? undefined,
      latencySatisfactory: outcome.latencySatisfactory ?? undefined,
      costSatisfactory: outcome.costSatisfactory ?? undefined,
      feedback: outcome.feedback ?? undefined,
      recordedAt: outcome.createdAt,
    };
  }
 
  /**
   * Gets an outcome for a run.
   *
   * @param runId - The run ID
   * @returns The outcome, or null if not found
   */
  get(runId: string): Outcome | null {
    const outcome = this.store.getOutcome(runId);
    if (!outcome) return null;
 
    return {
      id: outcome.id,
      runId: outcome.runId,
      success: outcome.success,
      quality: outcome.quality ?? undefined,
      latencySatisfactory: outcome.latencySatisfactory ?? undefined,
      costSatisfactory: outcome.costSatisfactory ?? undefined,
      feedback: outcome.feedback ?? undefined,
      recordedAt: outcome.createdAt,
    };
  }
 
  /**
   * Gets outcome statistics for a task type.
   *
   * @param taskType - The task type to get stats for
   * @returns Outcome statistics
   */
  getTaskStats(taskType: string): {
    totalOutcomes: number;
    successRate: number;
    qualityDistribution: Record<string, number>;
    latencySatisfactionRate: number;
    costSatisfactionRate: number;
  } {
    const outcomes = this.store.getOutcomes({ taskType: taskType as any, limit: 1000 });
 
    if (outcomes.length === 0) {
      return {
        totalOutcomes: 0,
        successRate: 0,
        qualityDistribution: {},
        latencySatisfactionRate: 0,
        costSatisfactionRate: 0,
      };
    }
 
    // Calculate statistics
    let successCount = 0;
    let latencySatisfiedCount = 0;
    let latencyRatedCount = 0;
    let costSatisfiedCount = 0;
    let costRatedCount = 0;
    const qualityDistribution: Record<string, number> = {};
 
    for (const outcome of outcomes) {
      if (outcome.success) successCount++;
 
      if (outcome.quality) {
        qualityDistribution[outcome.quality] = (qualityDistribution[outcome.quality] ?? 0) + 1;
      }
 
      if (outcome.latencySatisfactory != null) {
        latencyRatedCount++;
        if (outcome.latencySatisfactory) latencySatisfiedCount++;
      }
 
      if (outcome.costSatisfactory != null) {
        costRatedCount++;
        if (outcome.costSatisfactory) costSatisfiedCount++;
      }
    }
 
    return {
      totalOutcomes: outcomes.length,
      successRate: successCount / outcomes.length,
      qualityDistribution,
      latencySatisfactionRate: latencyRatedCount > 0 ? latencySatisfiedCount / latencyRatedCount : 0,
      costSatisfactionRate: costRatedCount > 0 ? costSatisfiedCount / costRatedCount : 0,
    };
  }
}