Session 47: Name normalization + grade intel + ticker polish (2149 tests)
- Name normalization completed: NICKNAMES table (Matt↔Matthew, Mike↔Michael...)
resolved in nameKey, parenthetical team-tag strip "(STL)", verified accent-fold
(Iván/Ivan, José/Jose). Slate strip now DISPLAYS the normalized de-dotted name
("AJ Ewing" not "A.J. Ewing") via buildPlayerStripsFromProps.
- Complete MLB VYNDR INTELLIGENCE: mlbGameLogFeatures derives rest_days (days off
between latest games; 0=B2B) + ab_per_game (usage). buildIntelFields renders
usage as "X AB/G", rest as B2B/Xd, matchup from bvp_advantage fallback.
- Ticker SCAN dedup: pushTickerItems keeps one SCAN per sport (sport field or
text-prefix parse for legacy); MOVE/GRADE preserved; cap 50.
- BOMBER threshold prorated for mid-season (hr>=15 strong / >=10 mod) so June
sluggers classify BOMBER not FLEX/DRIVER.
Backend 2122 -> 2149 tests (+27), 179 suites. Web build clean (exit 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+41
-14
@@ -1,18 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Player-name normalization (Session 46) — the ONE source of truth for comparing
|
||||
* and de-duplicating player names. PropLine sends variants ("A.J. Ewing" vs
|
||||
* "AJ Ewing", "Jazz Chisholm" vs "Jazz Chisholm Jr.") as different players; this
|
||||
* collapses them.
|
||||
* Player-name normalization (Session 46, completed Session 47) — the ONE source
|
||||
* of truth for comparing/de-duplicating player names. PropLine sends the same
|
||||
* player many ways: "A.J. Ewing"/"AJ Ewing", "Jazz Chisholm"/"Jazz Chisholm Jr.",
|
||||
* "Iván Herrera"/"Ivan Herrera", "Matt Liberatore"/"Matthew Liberatore",
|
||||
* "Jose Fermin (STL)". This collapses them all.
|
||||
*
|
||||
* normalizeName("A.J. Ewing") → { display: "AJ Ewing", key: "aj ewing" }
|
||||
* normalizeName("Jazz Chisholm Jr.")→ { display: "Jazz Chisholm Jr", key: "jazz chisholm" }
|
||||
* normalizeName("Jazz Chisholm") → { display: "Jazz Chisholm", key: "jazz chisholm" }
|
||||
* normalizeName("Ronald Acuña Jr.") → { display: "Ronald Acuña Jr", key: "ronald acuna" }
|
||||
* normalizeName("A.J. Ewing") → { display: "AJ Ewing", key: "aj ewing" }
|
||||
* normalizeName("Jose Fermin (STL)") → { display: "Jose Fermin", key: "jose fermin" }
|
||||
* normalizeName("Ronald Acuña Jr.") → { display: "Ronald Acuña Jr", key: "ronald acuna" }
|
||||
* nameKey("Matt Liberatore") === nameKey("Matthew Liberatore") === "matthew liberatore"
|
||||
* nameKey("Iván Herrera") === nameKey("Ivan Herrera") === "ivan herrera"
|
||||
*
|
||||
* `display` keeps proper casing + accents (periods stripped, suffix de-dotted).
|
||||
* `key` is accent-folded, lowercased, suffix-stripped for comparison.
|
||||
* `display` keeps proper casing + accents (periods, suffix dots, and parenthetical
|
||||
* team tags stripped). `key` is accent-folded, lowercased, suffix-stripped, and
|
||||
* first-name-nickname-resolved for comparison.
|
||||
*
|
||||
* NOTE: an identical copy lives at web/src/lib/playerName.js for the frontend
|
||||
* (the Next bundle can't import from src/). A test cross-checks they agree.
|
||||
@@ -20,9 +23,27 @@
|
||||
|
||||
const SUFFIXES = new Set(['jr', 'sr', 'ii', 'iii', 'iv', 'v']);
|
||||
|
||||
// Common first-name nicknames in sports. KEY = short form, VALUE = canonical
|
||||
// long form. Both sides reduce to the long form so "Matt"/"Matthew" merge.
|
||||
const NICKNAMES = {
|
||||
matt: 'matthew', mike: 'michael', chris: 'christopher', jake: 'jacob',
|
||||
josh: 'joshua', nick: 'nicholas', nate: 'nathaniel', dan: 'daniel',
|
||||
danny: 'daniel', dave: 'david', rob: 'robert', bob: 'robert', joe: 'joseph',
|
||||
joey: 'joseph', jon: 'jonathan', johnny: 'john', tony: 'anthony',
|
||||
alex: 'alexander', andy: 'andrew', drew: 'andrew', ben: 'benjamin',
|
||||
will: 'william', bill: 'william', billy: 'william', willy: 'william',
|
||||
zach: 'zachary', zack: 'zachary', tom: 'thomas', tommy: 'thomas',
|
||||
ty: 'tyler', ed: 'edward', eddie: 'edward', teddy: 'theodore',
|
||||
charlie: 'charles', chuck: 'charles', rick: 'richard', dick: 'richard',
|
||||
jim: 'james', jimmy: 'james', ray: 'raymond', fred: 'frederick',
|
||||
kenny: 'kenneth', sam: 'samuel', pat: 'patrick', greg: 'gregory',
|
||||
steve: 'steven', tim: 'timothy', frank: 'francis',
|
||||
};
|
||||
|
||||
function normalizeName(raw) {
|
||||
const display = String(raw == null ? '' : raw)
|
||||
.replace(/\./g, '') // "A.J." → "AJ", "Jr." → "Jr"
|
||||
.replace(/\s*\([^)]*\)\s*/g, ' ') // strip parenthetical team tags "(STL)"
|
||||
.replace(/\./g, '') // strip dots: "A.J." → "AJ", "Jr." → "Jr"
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
const folded = display.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
|
||||
@@ -30,9 +51,15 @@ function normalizeName(raw) {
|
||||
return { display, key };
|
||||
}
|
||||
|
||||
/** Comparison key only (the common case). */
|
||||
/**
|
||||
* Comparison key: normalized + first-name nickname resolved to the canonical
|
||||
* form ("matt" → "matthew") so nicknames collapse.
|
||||
*/
|
||||
function nameKey(raw) {
|
||||
return normalizeName(raw).key;
|
||||
const { key } = normalizeName(raw);
|
||||
const parts = key.split(/\s+/).filter(Boolean);
|
||||
if (parts.length >= 2 && NICKNAMES[parts[0]]) parts[0] = NICKNAMES[parts[0]];
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
module.exports = { normalizeName, nameKey, SUFFIXES };
|
||||
module.exports = { normalizeName, nameKey, SUFFIXES, NICKNAMES };
|
||||
|
||||
Reference in New Issue
Block a user