Spaces:
Sleeping
Sleeping
| /** | |
| * Heuristic diff classifier for feedback analysis. | |
| * Analyzes editDiffs from a completed job and produces feedback classifications. | |
| * Layer 1: rule-based. Layer 2 (Claude skill) will enrich with source code analysis. | |
| */ | |
| type EditDiff = { | |
| id: string; | |
| subtitleIndex: number; | |
| field: string; | |
| oldValue: string | null; | |
| newValue: string | null; | |
| rationale: string; | |
| changeCategory: string; | |
| }; | |
| type FeedbackClassification = { | |
| errorType: "input_error" | "cicd_needed" | "style_preference" | "false_positive"; | |
| confidenceScore: number; | |
| description: string; | |
| affectedInput: "neon" | "alto_curated" | "lsx_prompt" | "lsx_logic" | "arc_config" | null; | |
| }; | |
| // Keyword patterns for rationale analysis | |
| const INPUT_ERROR_KEYWORDS = ["wrong", "incorrect", "mistranslat", "error in source", "bad input", "original is wrong", "source error"]; | |
| const STYLE_KEYWORDS = ["style", "preference", "natural", "sounds better", "flow", "readability", "tone", "nuance"]; | |
| const NEON_KEYWORDS = ["neon", "source", "template", "original", "input text", "transcription"]; | |
| const ALTO_KEYWORDS = ["alto", "curated", "reference", "base translation"]; | |
| const LSX_LOGIC_KEYWORDS = ["timing", "cpl", "cps", "overlap", "gap", "duration", "frame", "sync"]; | |
| const LSX_PROMPT_KEYWORDS = ["format", "style guide", "capitalization", "punctuation", "tag", "italic"]; | |
| function matchesKeywords(text: string, keywords: string[]): boolean { | |
| const lower = text.toLowerCase(); | |
| return keywords.some((k) => lower.includes(k)); | |
| } | |
| function classifySingleDiff(diff: EditDiff): FeedbackClassification { | |
| const { changeCategory, rationale, field } = diff; | |
| const rat = rationale.toLowerCase(); | |
| // timing_fix → usually cicd_needed (LSX should handle timing) | |
| if (changeCategory === "timing_fix") { | |
| return { | |
| errorType: "cicd_needed", | |
| confidenceScore: matchesKeywords(rat, LSX_LOGIC_KEYWORDS) ? 0.85 : 0.65, | |
| description: `Timing correction on subtitle ${diff.subtitleIndex + 1}: ${rationale}`, | |
| affectedInput: "lsx_logic", | |
| }; | |
| } | |
| // cpl_fix → cicd_needed (LSX should auto-fix CPL violations) | |
| if (changeCategory === "cpl_fix") { | |
| return { | |
| errorType: "cicd_needed", | |
| confidenceScore: 0.8, | |
| description: `CPL violation fix on subtitle ${diff.subtitleIndex + 1}: ${rationale}`, | |
| affectedInput: "lsx_logic", | |
| }; | |
| } | |
| // split_merge → cicd_needed (LSX cueing should handle this) | |
| if (changeCategory === "split_merge") { | |
| return { | |
| errorType: "cicd_needed", | |
| confidenceScore: 0.75, | |
| description: `Split/merge on subtitle ${diff.subtitleIndex + 1}: ${rationale}`, | |
| affectedInput: "lsx_logic", | |
| }; | |
| } | |
| // style_correction → style_preference | |
| if (changeCategory === "style_correction") { | |
| return { | |
| errorType: "style_preference", | |
| confidenceScore: 0.7, | |
| description: `Style correction on subtitle ${diff.subtitleIndex + 1}: ${rationale}`, | |
| affectedInput: matchesKeywords(rat, LSX_PROMPT_KEYWORDS) ? "lsx_prompt" : null, | |
| }; | |
| } | |
| // translation_fix → depends on rationale | |
| if (changeCategory === "translation_fix") { | |
| if (matchesKeywords(rat, INPUT_ERROR_KEYWORDS)) { | |
| return { | |
| errorType: "input_error", | |
| confidenceScore: 0.8, | |
| description: `Translation error on subtitle ${diff.subtitleIndex + 1}: ${rationale}`, | |
| affectedInput: matchesKeywords(rat, ALTO_KEYWORDS) | |
| ? "alto_curated" | |
| : matchesKeywords(rat, NEON_KEYWORDS) | |
| ? "neon" | |
| : "neon", | |
| }; | |
| } | |
| if (matchesKeywords(rat, STYLE_KEYWORDS)) { | |
| return { | |
| errorType: "style_preference", | |
| confidenceScore: 0.65, | |
| description: `Style preference on subtitle ${diff.subtitleIndex + 1}: ${rationale}`, | |
| affectedInput: "lsx_prompt", | |
| }; | |
| } | |
| // Default translation fix → could be input error but low confidence | |
| return { | |
| errorType: "input_error", | |
| confidenceScore: 0.5, | |
| description: `Translation change on subtitle ${diff.subtitleIndex + 1}: ${rationale}`, | |
| affectedInput: "neon", | |
| }; | |
| } | |
| // other / fallback → false_positive with low confidence | |
| return { | |
| errorType: "false_positive", | |
| confidenceScore: 0.4, | |
| description: `Edit on subtitle ${diff.subtitleIndex + 1} (${field}): ${rationale}`, | |
| affectedInput: null, | |
| }; | |
| } | |
| /** | |
| * Classify all diffs from a completed job. | |
| * Groups similar patterns to avoid 1:1 diff→analysis noise. | |
| * Returns deduplicated feedback classifications. | |
| */ | |
| export function classifyDiffs(diffs: EditDiff[]): FeedbackClassification[] { | |
| if (diffs.length === 0) return []; | |
| // Classify each diff | |
| const classified = diffs.map(classifySingleDiff); | |
| // Group by errorType + affectedInput to reduce noise | |
| const groups = new Map<string, FeedbackClassification[]>(); | |
| for (const c of classified) { | |
| const key = `${c.errorType}:${c.affectedInput || "none"}`; | |
| if (!groups.has(key)) groups.set(key, []); | |
| groups.get(key)!.push(c); | |
| } | |
| // Produce one analysis per group with aggregated description | |
| const results: FeedbackClassification[] = []; | |
| for (const [, items] of groups) { | |
| if (items.length === 0) continue; | |
| const first = items[0]; | |
| const avgConfidence = | |
| items.reduce((sum, i) => sum + i.confidenceScore, 0) / items.length; | |
| results.push({ | |
| errorType: first.errorType, | |
| confidenceScore: Math.round(avgConfidence * 100) / 100, | |
| description: | |
| items.length === 1 | |
| ? first.description | |
| : `${items.length} ${first.errorType.replace("_", " ")} issues detected. Examples: ${items | |
| .slice(0, 3) | |
| .map((i) => i.description) | |
| .join("; ")}`, | |
| affectedInput: first.affectedInput, | |
| }); | |
| } | |
| // Sort by confidence descending | |
| results.sort((a, b) => b.confidenceScore - a.confidenceScore); | |
| return results; | |
| } | |