Fix three matrix generation bugs found in a real generated sheet
Diagnosed directly against a real generated spreadsheet the user
shared, rather than guessing:
- Player/opponent names came out blank ("- Necrons" instead of "John
Smith - Necrons"): fullName() takes the nested BCP user object
({firstName, lastName}), not the roster/teamplayer entry itself —
same convention main.js's own playerRowHTML() already follows, now
matched here via a shared nameOf() helper (with the same nickname
fallback playerRowHTML uses too).
- That cascaded into a worse, harder-to-spot bug: with the name blank,
a win-rate cell became literally " (62.5%)". Writes used
valueInputOption: USER_ENTERED, which smart-parses values — Sheets
read "(62.5%)" as accounting notation for a negative number and
silently replaced the cell with e.g. "-62.50%" instead of the
intended text. Same setting explains the "#ERROR!" cells: some raw
list text got parsed as a formula. Switched to RAW, which stores
exactly what's sent — correct for the plain descriptive labels this
writes, never intended to be interpreted.
- Lists with a New Recruit "+++...+++" header came through raw,
uncleaned: parseNRGW/parseNRTournament were vendored (needed for
NR_GW/NR_TOURMENT detectFormat() results) but never actually wired
into the format dispatch table, and aren't re-exported from the
package's own index.js (only from the internal modules/parsers.js,
which is now imported directly). Verified against the tool's own
NR-GW/NR-Tournament sample fixtures with real Node before committing
— both now correctly detected and cleaned.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
This commit is contained in:
@@ -13,7 +13,10 @@
|
|||||||
Drive's API returns 404, not 403, for a file outside the token's grant,
|
Drive's API returns 404, not 403, for a file outside the token's grant,
|
||||||
so it looks identical to a bad file ID) and spreadsheets. */
|
so it looks identical to a bad file ID) and spreadsheets. */
|
||||||
|
|
||||||
import { detectFormat, parseGwAppV11, parseWarOrganV11, parseV11List, generateDiscordText, buildAbbreviationIndex } from "./vendor/40k-compactor/index.js";
|
import { generateDiscordText, buildAbbreviationIndex } from "./vendor/40k-compactor/index.js";
|
||||||
|
// parseNRGW/parseNRTournament aren't re-exported from the package's own
|
||||||
|
// index.js (only from this internal module) — pulled from here directly.
|
||||||
|
import { detectFormat, parseGwAppV11, parseWarOrganV11, parseV11List, parseNRGW, parseNRTournament } from "./vendor/40k-compactor/modules/parsers.js";
|
||||||
import skippableWargear from "./vendor/40k-compactor/skippable_wargear.json";
|
import skippableWargear from "./vendor/40k-compactor/skippable_wargear.json";
|
||||||
|
|
||||||
const GOOGLE_CLIENT_ID = "698705216189-rqbg3f7vlosi3077c3n410a7lpmsn16r.apps.googleusercontent.com";
|
const GOOGLE_CLIENT_ID = "698705216189-rqbg3f7vlosi3077c3n410a7lpmsn16r.apps.googleusercontent.com";
|
||||||
@@ -25,14 +28,12 @@ const SHEETS_API = "https://sheets.googleapis.com/v4/spreadsheets";
|
|||||||
|
|
||||||
/* ---- List cleaning (vendored 40k-compactor — see vendor/40k-compactor/NOTICE.md) ---- */
|
/* ---- List cleaning (vendored 40k-compactor — see vendor/40k-compactor/NOTICE.md) ---- */
|
||||||
|
|
||||||
// detectFormat() only distinguishes the 11th-edition dialects this parser
|
|
||||||
// set understands; NR_TOURNAMENT isn't separately parseable from the
|
|
||||||
// functions this tool exports (no dedicated parser in the vendored code),
|
|
||||||
// so it falls back to the generic V11 parser same as an unrecognized format.
|
|
||||||
const PARSERS = {
|
const PARSERS = {
|
||||||
GW_APP_V11: parseGwAppV11,
|
GW_APP_V11: parseGwAppV11,
|
||||||
WAR_ORGAN_V11: parseWarOrganV11,
|
WAR_ORGAN_V11: parseWarOrganV11,
|
||||||
V11_GENERIC: parseV11List,
|
V11_GENERIC: parseV11List,
|
||||||
|
NR_GW: parseNRGW,
|
||||||
|
NR_TOURNAMENT: parseNRTournament,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns cleaned, sheet-friendly (plain, not Discord-markdown) list text,
|
// Returns cleaned, sheet-friendly (plain, not Discord-markdown) list text,
|
||||||
@@ -125,10 +126,17 @@ const batchUpdate = (token, fileId, requests) =>
|
|||||||
body: JSON.stringify({ requests }),
|
body: JSON.stringify({ requests }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// RAW, not USER_ENTERED: everything written here (names, factions, win
|
||||||
|
// rates, list text) is a literal label, never meant to be interpreted.
|
||||||
|
// USER_ENTERED's smart-parsing was actively harmful — e.g. a blank name
|
||||||
|
// next to a win rate produced " (62.5%)", which Sheets read as accounting
|
||||||
|
// notation for a *negative* number and silently replaced the whole cell
|
||||||
|
// with e.g. "-62.50%"; list text starting with certain characters came
|
||||||
|
// back as "#ERROR!" for the same reason.
|
||||||
const batchUpdateValues = (token, fileId, data) =>
|
const batchUpdateValues = (token, fileId, data) =>
|
||||||
googleFetch(`${SHEETS_API}/${fileId}/values:batchUpdate`, token, {
|
googleFetch(`${SHEETS_API}/${fileId}/values:batchUpdate`, token, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ valueInputOption: "USER_ENTERED", data }),
|
body: JSON.stringify({ valueInputOption: "RAW", data }),
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ---- Template layout ----
|
/* ---- Template layout ----
|
||||||
@@ -243,10 +251,14 @@ const a1 = (sheetTitle, col, row) => `'${sheetTitle.replace(/'/g, "''")}'!${colL
|
|||||||
|
|
||||||
/* ---- Row/column value builders ---- */
|
/* ---- Row/column value builders ---- */
|
||||||
|
|
||||||
|
// fullName() takes the nested BCP user object ({firstName, lastName}), not
|
||||||
|
// the roster/teamplayer entry itself — same convention main.js's own
|
||||||
|
// playerRowHTML() uses, matched here.
|
||||||
|
const nameOf = member => fullNameOf(member.user || {}) || member.user?.nickname || "Unknown";
|
||||||
|
|
||||||
function playerLabel(member) {
|
function playerLabel(member) {
|
||||||
const name = fullNameOf(member);
|
|
||||||
const faction = member.faction?.name || "Unknown";
|
const faction = member.faction?.name || "Unknown";
|
||||||
return `${name} - ${faction}`;
|
return `${nameOf(member)} - ${faction}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fullName/fmtWpct live in main.js today; passed in rather than duplicated.
|
// fullName/fmtWpct live in main.js today; passed in rather than duplicated.
|
||||||
@@ -258,11 +270,10 @@ export function configureMatrixHelpers({ fullName, fmtWpct }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function opponentNameAndWinRate(member, careerRecords) {
|
function opponentNameAndWinRate(member, careerRecords) {
|
||||||
const name = fullNameOf(member);
|
|
||||||
const uid = member.userId || member.user?.id;
|
const uid = member.userId || member.user?.id;
|
||||||
const rec = careerRecords.get(uid);
|
const rec = careerRecords.get(uid);
|
||||||
const wpct = rec ? fmtWpctOf(rec.w, rec.l, rec.t) : "—";
|
const wpct = rec ? fmtWpctOf(rec.w, rec.l, rec.t) : "—";
|
||||||
return `${name} (${wpct})`;
|
return `${nameOf(member)} (${wpct})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Orchestration ---- */
|
/* ---- Orchestration ---- */
|
||||||
|
|||||||
Reference in New Issue
Block a user