Skip to content

Commit 1c7d9d4

Browse files
ronshapiroclaude
andcommitted
Enumify StalenessStatus and StalenessTier
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent a0c7029 commit 1c7d9d4

3 files changed

Lines changed: 56 additions & 31 deletions

File tree

precomputed/rsi_state/staleness.test.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
editDistance,
77
hashNormalizedText,
88
resolveWithClassification,
9+
StalenessStatus,
10+
StalenessTier,
911
} from "./staleness";
1012

1113
test("editDistance is 0 for identical strings", () => {
@@ -41,24 +43,24 @@ test("hashNormalizedText differs on a real word change", () => {
4143

4244
test("checkTextStaleness Tier 0: cosmetic-only change (punctuation/whitespace) is fresh", () => {
4345
const result = checkTextStaleness("שלום, עולם!", "שלום עולם");
44-
expect(result).toEqual({status: "fresh", tier: 0});
46+
expect(result).toEqual({status: StalenessStatus.Fresh, tier: StalenessTier.Fingerprint});
4547
});
4648

4749
test("checkTextStaleness Tier 1: small real edit under the cosmetic threshold is fresh", () => {
4850
const stored = "a".repeat(100);
4951
const current = `${"a".repeat(97)}bbb`; // ~3% edit ratio once padded — see ratio math below
5052
const result = checkTextStaleness(stored, current, DEFAULT_STALENESS_THRESHOLDS);
51-
expect(result.tier).toBe(1);
52-
expect(result.status).toBe("fresh");
53+
expect(result.tier).toBe(StalenessTier.EditDistance);
54+
expect(result.status).toBe(StalenessStatus.Fresh);
5355
expect(result.editRatio).toBeLessThanOrEqual(DEFAULT_STALENESS_THRESHOLDS.cosmeticMaxRatio);
5456
});
5557

5658
test("checkTextStaleness Tier 2: mid-range edit needs classification", () => {
5759
const stored = "The quick brown fox jumps over the lazy dog and keeps running";
5860
const current = "The quick brown fox leaps over the lazy dog and keeps walking";
5961
const result = checkTextStaleness(stored, current, DEFAULT_STALENESS_THRESHOLDS);
60-
expect(result.status).toBe("needsClassification");
61-
expect(result.tier).toBe(2);
62+
expect(result.status).toBe(StalenessStatus.NeedsClassification);
63+
expect(result.tier).toBe(StalenessTier.AgenticClassification);
6264
expect(result.editRatio).toBeGreaterThan(DEFAULT_STALENESS_THRESHOLDS.cosmeticMaxRatio);
6365
expect(result.editRatio).toBeLessThan(DEFAULT_STALENESS_THRESHOLDS.structuralMinRatio);
6466
});
@@ -69,20 +71,19 @@ test("checkTextStaleness Tier 3: large rewrite is stale without classification",
6971
"Completely different content that shares almost nothing with before.",
7072
DEFAULT_STALENESS_THRESHOLDS,
7173
);
72-
expect(result.status).toBe("stale");
73-
expect(result.tier).toBe(3);
74+
expect(result.status).toBe(StalenessStatus.Stale);
75+
expect(result.tier).toBe(StalenessTier.StructuralBreak);
7476
});
7577

7678
test("resolveWithClassification marks stillValid as fresh at tier 2", () => {
7779
const pending = checkTextStaleness(
7880
"The quick brown fox jumps over the lazy dog and keeps running",
79-
"The quick brown fox leaps over the lazy dog and keeps walking",
80-
);
81+
"The quick brown fox leaps over the lazy dog and keeps walking");
8182
const resolved = resolveWithClassification(
8283
pending, {stillValid: true, reason: "meaning preserved"});
8384
expect(resolved).toEqual({
84-
status: "fresh",
85-
tier: 2,
85+
status: StalenessStatus.Fresh,
86+
tier: StalenessTier.AgenticClassification,
8687
editRatio: pending.editRatio,
8788
reason: "meaning preserved",
8889
});
@@ -91,11 +92,10 @@ test("resolveWithClassification marks stillValid as fresh at tier 2", () => {
9192
test("resolveWithClassification marks !stillValid as stale at tier 2", () => {
9293
const pending = checkTextStaleness(
9394
"The quick brown fox jumps over the lazy dog and keeps running",
94-
"The quick brown fox leaps over the lazy dog and keeps walking",
95-
);
95+
"The quick brown fox leaps over the lazy dog and keeps walking");
9696
const resolved = resolveWithClassification(
9797
pending, {stillValid: false, reason: "meaning changed"});
98-
expect(resolved.status).toBe("stale");
98+
expect(resolved.status).toBe(StalenessStatus.Stale);
9999
});
100100

101101
test("resolveWithClassification throws when called on a non-pending result", () => {
@@ -105,13 +105,14 @@ test("resolveWithClassification throws when called on a non-pending result", ()
105105
});
106106

107107
test("checkSegmentCountStaleness: unchanged count is fresh", () => {
108-
expect(checkSegmentCountStaleness(5, 5)).toEqual({status: "fresh", tier: 0});
108+
expect(checkSegmentCountStaleness(5, 5))
109+
.toEqual({status: StalenessStatus.Fresh, tier: StalenessTier.Fingerprint});
109110
});
110111

111112
test("checkSegmentCountStaleness: changed count is a Tier 3 structural break", () => {
112113
const result = checkSegmentCountStaleness(5, 6);
113-
expect(result.status).toBe("stale");
114-
expect(result.tier).toBe(3);
114+
expect(result.status).toBe(StalenessStatus.Stale);
115+
expect(result.tier).toBe(StalenessTier.StructuralBreak);
115116
expect(result.reason).toContain("5");
116117
expect(result.reason).toContain("6");
117118
});

precomputed/rsi_state/staleness.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,22 @@ function normalizeForStalenessFingerprint(text: string): string {
2626
.trim();
2727
}
2828

29-
export type StalenessStatus = "fresh" | "stale" | "needsClassification";
29+
export enum StalenessStatus {
30+
Fresh = "fresh",
31+
Stale = "stale",
32+
NeedsClassification = "needsClassification",
33+
}
34+
35+
export enum StalenessTier {
36+
Fingerprint = 0,
37+
EditDistance = 1,
38+
AgenticClassification = 2,
39+
StructuralBreak = 3,
40+
}
3041

3142
export interface StalenessResult {
3243
status: StalenessStatus;
33-
tier: 0 | 1 | 2 | 3;
44+
tier: StalenessTier;
3445
editRatio?: number;
3546
reason?: string;
3647
}
@@ -99,17 +110,26 @@ export function checkTextStaleness(
99110
thresholds: StalenessThresholds = DEFAULT_STALENESS_THRESHOLDS,
100111
): StalenessResult {
101112
if (hashNormalizedText(currentSourceText) === hashNormalizedText(storedSourceText)) {
102-
return {status: "fresh", tier: 0};
113+
return {status: StalenessStatus.Fresh, tier: StalenessTier.Fingerprint};
103114
}
104115

105116
const editRatio = computeEditRatio(storedSourceText, currentSourceText);
106117
if (editRatio <= thresholds.cosmeticMaxRatio) {
107-
return {status: "fresh", tier: 1, editRatio};
118+
return {status: StalenessStatus.Fresh, tier: StalenessTier.EditDistance, editRatio};
108119
}
109120
if (editRatio >= thresholds.structuralMinRatio) {
110-
return {status: "stale", tier: 3, editRatio, reason: "edit ratio above structural threshold"};
121+
return {
122+
status: StalenessStatus.Stale,
123+
tier: StalenessTier.StructuralBreak,
124+
editRatio,
125+
reason: "edit ratio above structural threshold",
126+
};
111127
}
112-
return {status: "needsClassification", tier: 2, editRatio};
128+
return {
129+
status: StalenessStatus.NeedsClassification,
130+
tier: StalenessTier.AgenticClassification,
131+
editRatio,
132+
};
113133
}
114134

115135
/**
@@ -123,13 +143,13 @@ export function resolveWithClassification(
123143
pending: StalenessResult,
124144
classifierVerdict: {stillValid: boolean; reason: string},
125145
): StalenessResult {
126-
if (pending.status !== "needsClassification") {
146+
if (pending.status !== StalenessStatus.NeedsClassification) {
127147
throw new Error(`resolveWithClassification called on a tier-${pending.tier} result that `
128148
+ `wasn't needsClassification`);
129149
}
130150
return {
131-
status: classifierVerdict.stillValid ? "fresh" : "stale",
132-
tier: 2,
151+
status: classifierVerdict.stillValid ? StalenessStatus.Fresh : StalenessStatus.Stale,
152+
tier: StalenessTier.AgenticClassification,
133153
editRatio: pending.editRatio,
134154
reason: classifierVerdict.reason,
135155
};
@@ -145,11 +165,11 @@ export function checkSegmentCountStaleness(
145165
currentSegmentCount: number,
146166
): StalenessResult {
147167
if (storedSegmentCount === currentSegmentCount) {
148-
return {status: "fresh", tier: 0};
168+
return {status: StalenessStatus.Fresh, tier: StalenessTier.Fingerprint};
149169
}
150170
return {
151-
status: "stale",
152-
tier: 3,
171+
status: StalenessStatus.Stale,
172+
tier: StalenessTier.StructuralBreak,
153173
reason: `segment count changed from ${storedSegmentCount} to ${currentSegmentCount}`,
154174
};
155175
}

rsi_orchestrator/rashi_tosafot_translation.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import {cachedOutputFilePath} from "../cached_outputs";
55
import {readUtf8} from "../files";
66
import {Edit} from "../precomputed/ai_edits";
77
import {readGenerationRecord, upsertGenerationRecord} from "../precomputed/rsi_state/generation_record";
8-
import {checkTextStaleness, DEFAULT_STALENESS_THRESHOLDS} from "../precomputed/rsi_state/staleness";
8+
import {
9+
checkTextStaleness,
10+
DEFAULT_STALENESS_THRESHOLDS,
11+
StalenessStatus,
12+
} from "../precomputed/rsi_state/staleness";
913
import {toFlatArray} from "../sefariaTextType";
1014
import {HeadlessClaudeError, runHeadlessClaude} from "./headless_claude";
1115

@@ -141,7 +145,7 @@ export function isFreshTranslation(candidate: TranslationCandidate): boolean {
141145
if (!record) return false;
142146
const result = checkTextStaleness(
143147
record.sourceText, candidate.hebrewSource, DEFAULT_STALENESS_THRESHOLDS);
144-
return result.status === "fresh";
148+
return result.status === StalenessStatus.Fresh;
145149
}
146150

147151
function generationPrompt(candidate: TranslationCandidate, priorFeedback?: string): string {

0 commit comments

Comments
 (0)