diff --git a/src/scoring/model.ts b/src/scoring/model.ts index 1ec4675e75..35f930064b 100644 --- a/src/scoring/model.ts +++ b/src/scoring/model.ts @@ -140,12 +140,16 @@ export async function getOrCreateScoringModelSnapshot(env: Env): Promise { const constants: Record = {}; for (const line of source.split("\n")) { - const match = line.match(/^([A-Z][A-Z0-9_]+)\s*=\s*([-+]?\d+(?:\.\d+)?)/); + // Match Python numeric literals including underscore separators (1_000_000), floats, and exponents + // (1e-9, 5.8e1). The previous /[-+]?\d+(?:\.\d+)?/ stopped at `_`/`e`, truncating 1_000_000 -> 1 and + // 1e-9 -> 1, which silently misparsed any such upstream constant and polluted the unmodeled list (#810). + const match = line.match(/^([A-Z][A-Z0-9_]+)\s*=\s*([-+]?(?:\d[\d_]*\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)/); if (!match) continue; const name = match[1]!; const raw = match[2]!; if (options.knownOnly !== false && !SCORING_CONSTANT_NAMES.has(name)) continue; - constants[name] = Number(raw); + // Number() rejects underscore separators, so strip them before parsing. + constants[name] = Number(raw.replace(/_/g, "")); } return constants; } diff --git a/test/unit/scoring.test.ts b/test/unit/scoring.test.ts index 0ab930517c..2502255382 100644 --- a/test/unit/scoring.test.ts +++ b/test/unit/scoring.test.ts @@ -72,6 +72,20 @@ IGNORED = "not numeric" expect(detectActiveModel({})).toBe("unknown"); }); + it("parses underscore separators, floats, and scientific notation without truncating (#810)", () => { + const parsed = parsePythonNumberConstants(` +CONTRIBUTION_SCORE_FOR_FULL_BONUS = 1_500_000 +SRC_TOK_SATURATION_SCALE = 5.8e1 +MERGED_PR_BASE_SCORE = 1e-9 +OSS_EMISSION_SHARE = 0.90 +`); + // The previous /[-+]?\\d+(?:\\.\\d+)?/ regex stopped at `_`/`e`: 1_500_000 -> 1, 5.8e1 -> 5.8, 1e-9 -> 1. + expect(parsed.CONTRIBUTION_SCORE_FOR_FULL_BONUS).toBe(1500000); + expect(parsed.SRC_TOK_SATURATION_SCALE).toBe(58); + expect(parsed.MERGED_PR_BASE_SCORE).toBe(1e-9); + expect(parsed.OSS_EMISSION_SHARE).toBe(0.9); + }); + it("prefers exponential saturation when mixed upstream constants are present", () => { const parsed = parsePythonNumberConstants(` MERGED_PR_BASE_SCORE = 25