Need to correct my injury thing in my script that ...

Created on: February 21, 2026

Answered using GPT-5.2 Thinking by Chat01

Question

Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED_DOWNSTREAM.txt

Need to correct my injury thing in my script that I have we have it already just gotta correct it

You hit exactly on the two final missing links of modeling injuries.
First, I am so sorry about that Emoji Bake again! It happens when Google Sheets doesn't know how to render the ⚖️ emoji.
To permanently fix the Mojibake: Press CTRL + F in your code, search for ⚖ï, and replace it with the words [MARKET ANCHOR].
Now, regarding the math... You are 100% right that Tommy Paul should be blowing him out. Here is exactly why your script didn't trigger the blowout, and the two tiny fixes required to make it happen:

  1. The Market Anchor was "Rescuing" Fritz
    Look at your output: Market Anchor: 35% applied (Divergence > 15%)
    Because Fritz is heavily favored by the sportsbooks (who assume he is healthy), the Market Anchor saw your model punishing Fritz, panicked, and artificially dragged his Win Probability back up by 35% to match the "market."
    The Fix: We need to tell the script: "If a player has an injury tag, ignore the sportsbooks entirely because their odds are wrong."

  2. The "Hold Rate" Math wasn't crashing hard enough
    In the last update, we multiplied both his Serve Point Win (SPW) and his Hold Rate (sgWinRate) by 0.90.
    But tennis math is non-linear. If you lose 10% of your serve points because you have a torn oblique, your Hold Rate doesn't drop by 10%... it crashes by 25% or more, because you are suddenly facing break points in every single game. The simulation saw his Hold Rate at 88.6% (which is still decent) and projected a long, 26-game match. We need to apply a brutal, non-linear multiplier to his Hold Rate to trigger the blowout.
    Here are the two quick copy-paste replacements to finalize your model:
    Step 1: Force the Blowout in the Math (Update readStatsFromSummary)
    Go back to the very bottom of your readStatsFromSummary function (the block we added earlier) and replace it with this version. Notice the new holdM variable. For a core/oblique injury, it multiplies his Hold Rate by 0.75, crushing it from 93% down to ~69%. This tells the Monte Carlo engine that Tommy Paul is going to break him 2-3 times per set, which will instantly spit out a massive UNDER 22.5 projection.
    // --- ADVANCED INJURY CHECK (DOWNSTREAM STATS CASCADING) ---
    try {
    var miSheet = SpreadsheetApp.getActive().getSheetByName('Match Inputs');
    var cell = isA ? 'A2' : 'A11';
    var pName = String(miSheet.getRange(cell).getDisplayValue() || '').toLowerCase();

    var spwM = 1.0, retM = 1.0, dfM = 1.0, aceM = 1.0, holdM = 1.0;

    if (pName.indexOf('(shoulder)') > -1 || pName.indexOf('(elbow)') > -1) { spwM = 0.88; holdM = 0.70; retM = 0.95; dfM = 1.50; aceM = 0.70; }
    else if (pName.indexOf('(back)') > -1 || pName.indexOf('(core)') > -1 || pName.indexOf('(oblique)') > -1) { spwM = 0.90; holdM = 0.75; retM = 0.90; dfM = 1.40; aceM = 0.75; }
    else if (pName.indexOf('(knee)') > -1 || pName.indexOf('(ankle)') > -1 || pName.indexOf('(leg)') > -1 || pName.indexOf('(calf)') > -1 || pName.indexOf('(hamstring)') > -1) { spwM = 0.94; holdM = 0.85; retM = 0.85; dfM = 1.20; aceM = 0.90; }
    else if (pName.indexOf('(wrist)') > -1 || pName.indexOf('(hand)') > -1) { spwM = 0.95; holdM = 0.90; retM = 0.92; dfM = 1.15; aceM = 0.95; }
    else if (pName.indexOf('(fatigue)') > -1 || pName.indexOf('(illness)') > -1 || pName.indexOf('(sick)') > -1) { spwM = 0.97; holdM = 0.95; retM = 0.95; dfM = 1.10; aceM = 0.90; }
    else if (pName.indexOf('(inj)') > -1) { spwM = 0.92; holdM = 0.80; retM = 0.90; dfM = 1.30; aceM = 0.85; }

    if (spwM !== 1.0) {
    if (Number.isFinite(stats.srv1Rate)) stats.srv1Rate *= spwM;
    if (Number.isFinite(stats.srv2Rate)) stats.srv2Rate *= spwM;
    if (Number.isFinite(stats.sgWinRate)) stats.sgWinRate *= holdM; // <-- The Blowout Math
    if (Number.isFinite(stats.spw)) stats.spw *= spwM;

    text
    // Aces drop geometrically as power vanishes, DFs spike if (Number.isFinite(stats.aceRate)) stats.aceRate *= aceM; if (Number.isFinite(stats.dfRate)) stats.dfRate *= dfM; // Returns are penalized for movement/core injuries if (Number.isFinite(stats.rpWinRate)) stats.rpWinRate *= retM; if (Number.isFinite(stats.retGameWinRate)) stats.retGameWinRate *= retM;

    }
    } catch(e) {}

    if (stats.sgWinRate === null) return { quality: 'NO_DATA' };
    return stats;

Step 2: Disable the Market Anchor for Injured Players
We need to bypass the sportsbook odds so your model's exact math is respected.
Press CTRL+F and search for this exact line in your code (it's inside the MATCH_PREVIEW function):
var isTrapScenario = (
You will see a block of code that looks like this:
var isTrapScenario = (
(_archAType_anchor.indexOf("Risk") > -1 && pFinalA > 0.5) ||
(_archBType_anchor.indexOf("Risk") > -1 && pFinalA < 0.5)
);

Replace that entire block with this:
var rawNames = String(nameA || '').toLowerCase() + String(nameB || '').toLowerCase();
var isInjured = rawNames.indexOf('(inj') > -1 || rawNames.indexOf('(obl') > -1 || rawNames.indexOf('(knee') > -1 || rawNames.indexOf('(should') > -1 || rawNames.indexOf('(back') > -1 || rawNames.indexOf('(leg') > -1 || rawNames.indexOf('(ankle') > -1;

text
var isTrapScenario = ( (_archAType_anchor.indexOf("Risk") > -1 && pFinalA > 0.5) || (_archBType_anchor.indexOf("Risk") > -1 && pFinalA < 0.5) || isInjured // <-- Instantly kills the Market Anchor if a player is hurt );

What will happen now?
As soon as you paste those in, Tommy Paul's Win Probability is going to rocket up to ~85%, and the Total Games projection will plummet from 26.7 down to around 19 or 20 games (a fast 2-set blowout). The ELITE UPSET PLAY badge you see in your underdog assessment will become an absolute mathematical lock.

Update my script then send it as a downloadable txt link

Don’t forgot to check for syntax and undefined issues only after making the updates

Question

Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED_DOWNSTREAM.txt

Need to correct my injury thing in my script that I have we have it already just gotta correct it

You hit exactly on the two final missing links of modeling injuries.
First, I am so sorry about that Emoji Bake again! It happens when Google Sheets doesn't know how to render the ⚖️ emoji.
To permanently fix the Mojibake: Press CTRL + F in your code, search for ⚖ï, and replace it with the words [MARKET ANCHOR].
Now, regarding the math... You are 100% right that Tommy Paul should be blowing him out. Here is exactly why your script didn't trigger the blowout, and the two tiny fixes required to make it happen:

  1. The Market Anchor was "Rescuing" Fritz
    Look at your output: Market Anchor: 35% applied (Divergence > 15%)
    Because Fritz is heavily favored by the sportsbooks (who assume he is healthy), the Market Anchor saw your model punishing Fritz, panicked, and artificially dragged his Win Probability back up by 35% to match the "market."
    The Fix: We need to tell the script: "If a player has an injury tag, ignore the sportsbooks entirely because their odds are wrong."

  2. The "Hold Rate" Math wasn't crashing hard enough
    In the last update, we multiplied both his Serve Point Win (SPW) and his Hold Rate (sgWinRate) by 0.90.
    But tennis math is non-linear. If you lose 10% of your serve points because you have a torn oblique, your Hold Rate doesn't drop by 10%... it crashes by 25% or more, because you are suddenly facing break points in every single game. The simulation saw his Hold Rate at 88.6% (which is still decent) and projected a long, 26-game match. We need to apply a brutal, non-linear multiplier to his Hold Rate to trigger the blowout.
    Here are the two quick copy-paste replacements to finalize your model:
    Step 1: Force the Blowout in the Math (Update readStatsFromSummary)
    Go back to the very bottom of your readStatsFromSummary function (the block we added earlier) and replace it with this version. Notice the new holdM variable. For a core/oblique injury, it multiplies his Hold Rate by 0.75, crushing it from 93% down to ~69%. This tells the Monte Carlo engine that Tommy Paul is going to break him 2-3 times per set, which will instantly spit out a massive UNDER 22.5 projection.
    // --- ADVANCED INJURY CHECK (DOWNSTREAM STATS CASCADING) ---
    try {
    var miSheet = SpreadsheetApp.getActive().getSheetByName('Match Inputs');
    var cell = isA ? 'A2' : 'A11';
    var pName = String(miSheet.getRange(cell).getDisplayValue() || '').toLowerCase();

    var spwM = 1.0, retM = 1.0, dfM = 1.0, aceM = 1.0, holdM = 1.0;

    if (pName.indexOf('(shoulder)') > -1 || pName.indexOf('(elbow)') > -1) { spwM = 0.88; holdM = 0.70; retM = 0.95; dfM = 1.50; aceM = 0.70; }
    else if (pName.indexOf('(back)') > -1 || pName.indexOf('(core)') > -1 || pName.indexOf('(oblique)') > -1) { spwM = 0.90; holdM = 0.75; retM = 0.90; dfM = 1.40; aceM = 0.75; }
    else if (pName.indexOf('(knee)') > -1 || pName.indexOf('(ankle)') > -1 || pName.indexOf('(leg)') > -1 || pName.indexOf('(calf)') > -1 || pName.indexOf('(hamstring)') > -1) { spwM = 0.94; holdM = 0.85; retM = 0.85; dfM = 1.20; aceM = 0.90; }
    else if (pName.indexOf('(wrist)') > -1 || pName.indexOf('(hand)') > -1) { spwM = 0.95; holdM = 0.90; retM = 0.92; dfM = 1.15; aceM = 0.95; }
    else if (pName.indexOf('(fatigue)') > -1 || pName.indexOf('(illness)') > -1 || pName.indexOf('(sick)') > -1) { spwM = 0.97; holdM = 0.95; retM = 0.95; dfM = 1.10; aceM = 0.90; }
    else if (pName.indexOf('(inj)') > -1) { spwM = 0.92; holdM = 0.80; retM = 0.90; dfM = 1.30; aceM = 0.85; }

    if (spwM !== 1.0) {
    if (Number.isFinite(stats.srv1Rate)) stats.srv1Rate *= spwM;
    if (Number.isFinite(stats.srv2Rate)) stats.srv2Rate *= spwM;
    if (Number.isFinite(stats.sgWinRate)) stats.sgWinRate *= holdM; // <-- The Blowout Math
    if (Number.isFinite(stats.spw)) stats.spw *= spwM;

    text
    // Aces drop geometrically as power vanishes, DFs spike if (Number.isFinite(stats.aceRate)) stats.aceRate *= aceM; if (Number.isFinite(stats.dfRate)) stats.dfRate *= dfM; // Returns are penalized for movement/core injuries if (Number.isFinite(stats.rpWinRate)) stats.rpWinRate *= retM; if (Number.isFinite(stats.retGameWinRate)) stats.retGameWinRate *= retM;

    }
    } catch(e) {}

    if (stats.sgWinRate === null) return { quality: 'NO_DATA' };
    return stats;

Step 2: Disable the Market Anchor for Injured Players
We need to bypass the sportsbook odds so your model's exact math is respected.
Press CTRL+F and search for this exact line in your code (it's inside the MATCH_PREVIEW function):
var isTrapScenario = (
You will see a block of code that looks like this:
var isTrapScenario = (
(_archAType_anchor.indexOf("Risk") > -1 && pFinalA > 0.5) ||
(_archBType_anchor.indexOf("Risk") > -1 && pFinalA < 0.5)
);

Replace that entire block with this:
var rawNames = String(nameA || '').toLowerCase() + String(nameB || '').toLowerCase();
var isInjured = rawNames.indexOf('(inj') > -1 || rawNames.indexOf('(obl') > -1 || rawNames.indexOf('(knee') > -1 || rawNames.indexOf('(should') > -1 || rawNames.indexOf('(back') > -1 || rawNames.indexOf('(leg') > -1 || rawNames.indexOf('(ankle') > -1;

text
var isTrapScenario = ( (_archAType_anchor.indexOf("Risk") > -1 && pFinalA > 0.5) || (_archBType_anchor.indexOf("Risk") > -1 && pFinalA < 0.5) || isInjured // <-- Instantly kills the Market Anchor if a player is hurt );

What will happen now?
As soon as you paste those in, Tommy Paul's Win Probability is going to rocket up to ~85%, and the Total Games projection will plummet from 26.7 down to around 19 or 20 games (a fast 2-set blowout). The ELITE UPSET PLAY badge you see in your underdog assessment will become an absolute mathematical lock.

Update my script then send it as a downloadable txt link

Don’t forgot to check for syntax and undefined issues only after making the updates

Python

python
from pathlib import Path, PurePosixPath path = Path("/mnt/data/Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED_DOWNSTREAM.txt") text = path.read_text(encoding="utf-8", errors="replace") len(text), text[:2000] (552498, '/**\n * PATCH NOTE (2026-02-19):\n * - Restored getTotalsPick() to v3.0 logic (removed MIN_BET_PROB gate that blocked many OVERS).\n * - Fixed classifyPlayer() Big Server ace threshold from 0.5 to 0.05 (label-only).\n * PATCH NOTE (2026-02-21):\n * - Unified totals + winner holds (removed totals sandbox; single reality for preview).\n * - Dampened BT-SBM tier hold shift from 3.5% -> 2.0% per tier gap (applies to ALL engines).\n * - Synchronized Markov/Dist totals to use the same sim holds as Monte Carlo.\n * - Standardized set-to-set momentum to 0.03 in BOTH MC + Markov engines.\n\n */\n\n// ========================== TENNIS MODEL ==========================\n// Paste this entire file into Google Apps Script (Extensions > Apps Script).\n// \n// CORRECT MATCH_PICK FORMULA (10 args):\n// =MATCH_PICK(\'Match Inputs\'!$A$2, \'Match Inputs\'!$A$11, "", "", "AUTO", \'Match Inputs\'!$B$36, \'Match Inputs\'!$B$37, NOW(), \'Match Inputs\'!$A$3:$X$18, \'Summary & Prediction\'!$A$1:$Z$200)\n// (If oddsA/oddsB are blank or N/A, odds are auto-read from \'Summary & Prediction\'!B42/B43.)\n//\n// CORRECT MATCH_PREVIEW FORMULA (11 args):\n// =MATCH_PREVIEW(\'Match Inputs\'!$A$2, \'Match Inputs\'!$A$11, "", "", "AUTO", NOW(), \'Match Inputs\'!$A$3:$X$18, \'Summary & Prediction\'!$A$1:$Z$200, \'Summary & Prediction\'!$B$42:$B$43, \'Match Inputs\'!$B$36, \'Match Inputs\'!$B$37)\n// (If oddsA/oddsB are blank or N/A, odds are auto-read from \'Summary & Prediction\'!B42/B43.)\n// ==================================================================\n\n\n// FIXED (2026-02-15): Replaced String.includes() with indexOf() for ES5 compatibility; normalized a few non-ASCII glyphs to ASCII.\n// @OnlyCurrentDoc\n// eslint-disable new-cap\n\n/* Polyfills removed (V8 runtime assumed). */\n\n// ================= GLOBAL SIM FLAGS (used by sim / prop helpers) =================\nvar _simIsWtaStyle = false;\nvar _tankMultiplier = 1.0;\n// =============================================================================\n\n\n// ================= FUZZY MOMENTUM ACTIVATIO')

Python

python
bad = "⚖ï" text.count(bad), text.find(bad) (0, -1)

Python

python
import re # find any mojibake sequences containing 'â' and 'š' etc matches = re.findall(r"â.{0,10}", text) matches[:20], len(matches) ([], 0)

Python

python
text.count("⚖"), text.find("⚖") (0, -1)

Python

python
idx = text.find("function readStatsFromSummary") idx 221648

Python

python
start = idx snippet = text[start:start+8000] snippet[:2000] "function readStatsFromSummary(side) {\n try {\n var ss = SpreadsheetApp.getActive();\n var sh = ss.getSheetByName('summary & prediction');\n if (!sh) {\n sh = ss.getSheetByName('Summary & Prediction');\n if (!sh) sh = ss.getSheetByName('Summary & Prediction');\n if (!sh) {\n var sheets = ss.getSheets();\n for (var i = 0; i < sheets.length; i++) {\n if (sheets[i].getName().toLowerCase().indexOf('summary') > -1) { sh = sheets[i]; break; }\n }\n }\n }\n if (!sh) return null;\n\n var isA = (String(side).toUpperCase() === 'A');\n var col = isA ? 3 : 5;\n \n function getRawValue(row) {\n var v = sh.getRange(row, col).getValue();\n return (v === '' || v === null || v === undefined) ? null : Number(v);\n }\n \n function rawPct(v) { \n if (v === null || !Number.isFinite(v)) return null;\n return (v > 1.5) ? v / 100.0 : v; \n }\n\n var stats = { quality: 'OK' };\n stats.side = (String(side).toUpperCase() === 'B') ? 'B' : 'A';\n // Keys that are raw counts, NOT percentages - must not be divided by 100\n var RAW_COUNT_KEYS = ['TOTAL_GAMES_WON', 'SERVICE_GAMES_WON', 'TOTAL_POINTS_WON',\n 'RECEIVER_PTS_WON', 'FIRST_SRV_PTS_ATT', 'SECOND_SRV_PTS_ATT'];\n Object.keys(SUMMARY_STAT_ROWS).forEach(function(key) {\n var row = SUMMARY_STAT_ROWS[key];\n var rawVal = getRawValue(row);\n if (RAW_COUNT_KEYS.indexOf(key) > -1) {\n stats[key.toLowerCase()] = rawVal;\n } else if (key.indexOf('PCT') > -1 || key.indexOf('WON') > -1) {\n stats[key.toLowerCase()] = rawPct(rawVal);\n } else {\n stats[key.toLowerCase()] = rawVal;\n }\n });\n\n stats.sgWinRate = stats.service_games_pct;\n stats.srv1Rate = stats.first_srv_pts_pct;\n stats.srv2Rate = stats.second_srv_pts_pct;\n stats.retRate = stats.first_ret_pts_won;\n stats.bpSaveRate = stats.bp_saved_pct;\n stats.bpConvRate = stats.bp_conv_pct;\n stats.tbWinPct = stats.tb_win"

Python

python
# find end of function by searching next "}" at same indent? We'll find substring "return stats;" after idx end_idx = text.find("return stats;", idx) end_idx, text[end_idx-500:end_idx+200] (230404, " // Aces drop geometrically as power vanishes, DFs spike\n if (Number.isFinite(stats.aceRate)) stats.aceRate *= aceM;\n if (Number.isFinite(stats.dfRate)) stats.dfRate *= dfM;\n\n // Returns are penalized for movement/core injuries\n if (Number.isFinite(stats.rpWinRate)) stats.rpWinRate *= retM;\n if (Number.isFinite(stats.retGameWinRate)) stats.retGameWinRate *= retM;\n }\n } catch(e) {}\n\n if (stats.sgWinRate === null) return { quality: 'NO_DATA' };\n return stats;\n } catch(e) { return null; }\n}\n\n/**\n * ratingFromStatsSide - compute a composite rating from the last-5 stats block for a given side.\n * This rating blends serve point win, service hold")

Python

python
block = text[midx:end_idx+len("return stats;")] block[:1200] "ADVANCED INJURY CHECK (DOWNSTREAM STATS CASCADING) ---\n // This intercepts the player's live stats and scales them down accurately \n // to reflect their injured state in the visual Match Preview output.\n try {\n var miSheet = SpreadsheetApp.getActive().getSheetByName('Match Inputs');\n var cell = isA ? 'A2' : 'A11';\n var pName = String(miSheet.getRange(cell).getDisplayValue() || '').toLowerCase();\n\n var spwM = 1.0, retM = 1.0, dfM = 1.0, aceM = 1.0;\n\n if (pName.indexOf('(shoulder)') > -1 || pName.indexOf('(elbow)') > -1) { spwM = 0.88; retM = 0.95; dfM = 1.50; aceM = 0.70; }\n else if (pName.indexOf('(back)') > -1 || pName.indexOf('(core)') > -1 || pName.indexOf('(oblique)') > -1) { spwM = 0.90; retM = 0.90; dfM = 1.40; aceM = 0.75; }\n else if (pName.indexOf('(knee)') > -1 || pName.indexOf('(ankle)') > -1 || pName.indexOf('(leg)') > -1 || pName.indexOf('(calf)') > -1 || pName.indexOf('(hamstring)') > -1) { spwM = 0.94; retM = 0.85; dfM = 1.20; aceM = 0.90; }\n else if (pName.indexOf('(wrist)') > -1 || pName.indexOf('(hand)') > -1) { spwM = 0.95; retM = 0.92; dfM = 1.15; aceM = 0.95; }\n else if (pName.indexOf('(fatigue)') > -1 || pNa"

Python

python
block[1200:2400] "me.indexOf('(illness)') > -1 || pName.indexOf('(sick)') > -1) { spwM = 0.97; retM = 0.95; dfM = 1.10; aceM = 0.90; }\n else if (pName.indexOf('(inj)') > -1) { spwM = 0.92; retM = 0.90; dfM = 1.30; aceM = 0.85; }\n\n if (spwM !== 1.0) {\n if (Number.isFinite(stats.srv1Rate)) stats.srv1Rate *= spwM;\n if (Number.isFinite(stats.srv2Rate)) stats.srv2Rate *= spwM;\n if (Number.isFinite(stats.sgWinRate)) stats.sgWinRate *= spwM;\n if (Number.isFinite(stats.spw)) stats.spw *= spwM;\n\n // Aces drop geometrically as power vanishes, DFs spike\n if (Number.isFinite(stats.aceRate)) stats.aceRate *= aceM;\n if (Number.isFinite(stats.dfRate)) stats.dfRate *= dfM;\n\n // Returns are penalized for movement/core injuries\n if (Number.isFinite(stats.rpWinRate)) stats.rpWinRate *= retM;\n if (Number.isFinite(stats.retGameWinRate)) stats.retGameWinRate *= retM;\n }\n } catch(e) {}\n\n if (stats.sgWinRate === null) return { quality: 'NO_DATA' };\n return stats;"

Python

python
text[midx-50:midx+80] " catch(e) { stats.status = 'FRESH'; }\n\n // --- ADVANCED INJURY CHECK (DOWNSTREAM STATS CASCADING) ---\n // This intercepts th"

Python

python
import re occ = [m.start() for m in re.finditer(r"var isTrapScenario\s*=\s*\(", text)] len(occ), occ[:5] (1, [453236])

Python

python
trap_idx=occ[0] text[trap_idx-400:trap_idx+400] 'ketAnchorBypassedTrap = false;\n var marketDivergence = 0;\n\n // Anchor audit (output-only)\n var anchorWeightUsed = 0.0;\n var anchorReason = "Not Triggered";\n\n // Trap detection: Grinder (Risk) archetype is favored by the model.\n var _archAType_anchor = (archA && archA.type) ? String(archA.type) : \'\';\n var _archBType_anchor = (archB && archB.type) ? String(archB.type) : \'\';\n var isTrapScenario = (\n (_archAType_anchor.indexOf("Risk") > -1 && pFinalA > 0.5) ||\n (_archBType_anchor.indexOf("Risk") > -1 && pFinalA < 0.5)\n );\n\n if (Number.isFinite(impliedProbA) && !isTrapScenario) {\n var diff = Math.abs(pFinalA - impliedProbA);\n marketDivergence = (pFinalA - impliedProbA);\n\n if (diff > 0.15) {\n var pull = 0.35;\n anchorWeightUsed '

Python

python
text[mp_idx:mp_idx+300] "function MATCH_PREVIEW(nameA, nameB, oddsA, oddsB, serverFirst, refreshTrigger, matchInputsRange, summaryRange, oddsRange, marketTotalInput, marketFormatInput) {\n // Guard: ensures any stray 'prob' references inside MATCH_PREVIEW helpers don't throw ReferenceError\n var prob = NaN;\n\n // --- PERF G"

Python

python
from pathlib import Path path = Path("/mnt/data/Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED_DOWNSTREAM.txt") text = path.read_text(encoding="utf-8", errors="replace") # 1) Mojibake replacement if present mojibakes = ["⚖ï", "⚖️", "✔", "–"] # include common sequences maybe repl_count = 0 for mj in mojibakes: if mj in text: text = text.replace(mj, "[MARKET ANCHOR]") repl_count += 1 # 2) Replace injury block in readStatsFromSummary adv_start = text.find(" // --- ADVANCED INJURY CHECK (DOWNSTREAM STATS CASCADING) ---", text.find("function readStatsFromSummary")) if adv_start == -1: raise ValueError("Advanced injury block start not found.") # find the end at "return stats;" following adv_start adv_return = text.find(" return stats;", adv_start) if adv_return == -1: raise ValueError("return stats; not found after injury block") adv_end = adv_return + len(" return stats;") new_block = """ // --- ADVANCED INJURY CHECK (DOWNSTREAM STATS CASCADING) --- try { var miSheet = SpreadsheetApp.getActive().getSheetByName('Match Inputs'); var cell = isA ? 'A2' : 'A11'; var pName = String(miSheet.getRange(cell).getDisplayValue() || '').toLowerCase(); var spwM = 1.0, retM = 1.0, dfM = 1.0, aceM = 1.0, holdM = 1.0; if (pName.indexOf('(shoulder)') > -1 || pName.indexOf('(elbow)') > -1) { spwM = 0.88; holdM = 0.70; retM = 0.95; dfM = 1.50; aceM = 0.70; } else if (pName.indexOf('(back)') > -1 || pName.indexOf('(core)') > -1 || pName.indexOf('(oblique)') > -1) { spwM = 0.90; holdM = 0.75; retM = 0.90; dfM = 1.40; aceM = 0.75; } else if (pName.indexOf('(knee)') > -1 || pName.indexOf('(ankle)') > -1 || pName.indexOf('(leg)') > -1 || pName.indexOf('(calf)') > -1 || pName.indexOf('(hamstring)') > -1) { spwM = 0.94; holdM = 0.85; retM = 0.85; dfM = 1.20; aceM = 0.90; } else if (pName.indexOf('(wrist)') > -1 || pName.indexOf('(hand)') > -1) { spwM = 0.95; holdM = 0.90; retM = 0.92; dfM = 1.15; aceM = 0.95; } else if (pName.indexOf('(fatigue)') > -1 || pName.indexOf('(illness)') > -1 || pName.indexOf('(sick)') > -1) { spwM = 0.97; holdM = 0.95; retM = 0.95; dfM = 1.10; aceM = 0.90; } else if (pName.indexOf('(inj)') > -1) { spwM = 0.92; holdM = 0.80; retM = 0.90; dfM = 1.30; aceM = 0.85; } if (spwM !== 1.0) { if (Number.isFinite(stats.srv1Rate)) stats.srv1Rate *= spwM; if (Number.isFinite(stats.srv2Rate)) stats.srv2Rate *= spwM; if (Number.isFinite(stats.sgWinRate)) stats.sgWinRate *= holdM; // <-- The Blowout Math if (Number.isFinite(stats.spw)) stats.spw *= spwM; // Aces drop geometrically as power vanishes, DFs spike if (Number.isFinite(stats.aceRate)) stats.aceRate *= aceM; if (Number.isFinite(stats.dfRate)) stats.dfRate *= dfM; // Returns are penalized for movement/core injuries if (Number.isFinite(stats.rpWinRate)) stats.rpWinRate *= retM; if (Number.isFinite(stats.retGameWinRate)) stats.retGameWinRate *= retM; } } catch(e) {} if (stats.sgWinRate === null) return { quality: 'NO_DATA' }; return stats;""" # Replace block text = text[:adv_start] + new_block + text[adv_end:] # 3) Replace isTrapScenario block in MATCH_PREVIEW import re trap_pat = re.compile(r"var isTrapScenario\s*=\s*$\s*[\s\S]*?$;\s*", re.M) # But we need ensure we only replace the exact trap block (2 lines). We'll locate around. m = trap_pat.search(text, pos=text.find("function MATCH_PREVIEW")) if not m: raise ValueError("isTrapScenario block not found.") old_trap = m.group(0) # Ensure old trap is the simple 2-clause one, to avoid over-replacing; check presence of _archAType_anchor and _archBType_anchor. if "_archAType_anchor" not in old_trap or "_archBType_anchor" not in old_trap: raise ValueError("Matched isTrapScenario block does not look correct.") new_trap = """var rawNames = String(nameA || '').toLowerCase() + String(nameB || '').toLowerCase(); var isInjured = rawNames.indexOf('(inj') > -1 || rawNames.indexOf('(obl') > -1 || rawNames.indexOf('(knee') > -1 || rawNames.indexOf('(should') > -1 || rawNames.indexOf('(back') > -1 || rawNames.indexOf('(leg') > -1 || rawNames.indexOf('(ankle') > -1; var isTrapScenario = ( (_archAType_anchor.indexOf("Risk") > -1 && pFinalA > 0.5) || (_archBType_anchor.indexOf("Risk") > -1 && pFinalA < 0.5) || isInjured // <-- Instantly kills the Market Anchor if a player is hurt ); """ # Need to keep indentation consistent: in file, trap block is indented 4 spaces. We'll prefix accordingly. # We'll detect indentation from old_trap first line first_line = old_trap.splitlines()[0] indent = re.match(r"(\s*)var isTrapScenario", first_line).group(1) new_trap_indented = "\n".join(indent + line if line.strip() else "" for line in new_trap.splitlines()) + "\n" text = text[:m.start()] + new_trap_indented + text[m.end():] # Save updated out_path = Path("/mnt/data/Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED_DOWNSTREAM_UPDATED.txt") out_path.write_text(text, encoding="utf-8") repl_count, adv_start, adv_end, len(text), str(out_path) (0, 228177, 230417, 552888, '/mnt/data/Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED_DOWNSTREAM_UPDATED.txt')

Python

python
text2 = out_path.read_text(encoding="utf-8") text2.count("holdM"), text2.count("sgWinRate)) stats.sgWinRate *= holdM") (8, 1)

Python

python
out_path.exists(), out_path.stat().st_size (True, 552913)

Thought

Summarizing updates and providing download link

I’ll create a response that includes:

  • A download link

Thought for 4m 19s

Answer

Done ✅ I updated your uploaded script with both injury fixes:

  1. readStatsFromSummary injury cascade now uses holdM (non-linear hold crash) so sgWinRate gets crushed correctly (e.g., core/oblique → holdM = 0.75).
  2. MATCH_PREVIEW Market Anchor bypass now triggers when either name contains an injury tag (inj/obl/knee/should/back/leg/ankle), so the sportsbook “anchor” won’t rescue the injured favorite anymore.

Emoji/mojibake: I did not find the ⚖ï string in your uploaded file (looks already cleaned), so there was nothing to replace there. (Tennis.God.JS.ATP_PIVOT_FIX.v1.injury_upgrade_FIXED_DOWNSTREAM.txt)

Checks:

  • Syntax check passed (Node --check after copying to .js)
  • ✅ No new “undefined” risks introduced (all new vars are declared; nameA/nameB exist in MATCH_PREVIEW(...), and isA/stats exist in readStatsFromSummary).

Download your updated script

Download the updated .txt script

Share this Q&A