bbdata-cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/bbdata.js +128 -16
- package/dist/bin/bbdata.js.map +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +122 -12
- package/dist/src/index.js.map +1 -1
- package/dist/templates/queries/hitter-batted-ball.ts +66 -0
- package/dist/templates/queries/hitter-hot-cold-zones.ts +81 -0
- package/dist/templates/queries/hitter-vs-pitch-type.ts +78 -0
- package/dist/templates/queries/index.ts +24 -0
- package/dist/templates/queries/leaderboard-comparison.ts +72 -0
- package/dist/templates/queries/leaderboard-custom.ts +90 -0
- package/dist/templates/queries/matchup-pitcher-vs-hitter.ts +81 -0
- package/dist/templates/queries/matchup-situational.ts +68 -0
- package/dist/templates/queries/pitcher-arsenal.ts +89 -0
- package/dist/templates/queries/pitcher-handedness-splits.ts +81 -0
- package/dist/templates/queries/pitcher-velocity-trend.ts +73 -0
- package/dist/templates/queries/registry.ts +73 -0
- package/dist/templates/queries/trend-rolling-average.ts +86 -0
- package/dist/templates/queries/trend-year-over-year.ts +73 -0
- package/dist/templates/reports/advance-lineup.hbs +29 -0
- package/dist/templates/reports/advance-sp.hbs +60 -0
- package/dist/templates/reports/college-hitter-draft.hbs +49 -0
- package/dist/templates/reports/college-pitcher-draft.hbs +48 -0
- package/dist/templates/reports/dev-progress.hbs +29 -0
- package/dist/templates/reports/draft-board-card.hbs +35 -0
- package/dist/templates/reports/hs-prospect.hbs +48 -0
- package/dist/templates/reports/partials/footer.hbs +7 -0
- package/dist/templates/reports/partials/header.hbs +12 -0
- package/dist/templates/reports/post-promotion.hbs +25 -0
- package/dist/templates/reports/pro-hitter-eval.hbs +65 -0
- package/dist/templates/reports/pro-pitcher-eval.hbs +69 -0
- package/dist/templates/reports/registry.ts +215 -0
- package/dist/templates/reports/relief-pitcher-quick.hbs +29 -0
- package/dist/templates/reports/trade-target-onepager.hbs +45 -0
- package/package.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { registerTemplate, type QueryTemplate } from './registry.js';
|
|
2
|
+
import type { PitchData } from '../../adapters/types.js';
|
|
3
|
+
|
|
4
|
+
const template: QueryTemplate = {
|
|
5
|
+
id: 'pitcher-handedness-splits',
|
|
6
|
+
name: 'Pitcher Handedness Splits',
|
|
7
|
+
category: 'pitcher',
|
|
8
|
+
description: 'Performance splits vs LHH and RHH — BA, SLG, K%, BB%, pitch mix, exit velocity',
|
|
9
|
+
preferredSources: ['savant', 'fangraphs'],
|
|
10
|
+
requiredParams: ['player'],
|
|
11
|
+
optionalParams: ['season'],
|
|
12
|
+
examples: [
|
|
13
|
+
'bbdata query pitcher-handedness-splits --player "Blake Snell" --season 2025',
|
|
14
|
+
],
|
|
15
|
+
|
|
16
|
+
buildQuery(params) {
|
|
17
|
+
return {
|
|
18
|
+
player_name: params.player,
|
|
19
|
+
season: params.season ?? new Date().getFullYear(),
|
|
20
|
+
stat_type: 'pitching',
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
columns() {
|
|
25
|
+
return ['vs', 'PA', 'AVG', 'SLG', 'K %', 'BB %', 'Avg EV', 'Whiff %'];
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
transform(data) {
|
|
29
|
+
const pitches = data as PitchData[];
|
|
30
|
+
if (pitches.length === 0) return [];
|
|
31
|
+
|
|
32
|
+
return (['L', 'R'] as const).map((hand) => {
|
|
33
|
+
const group = pitches.filter((p) => p.stand === hand);
|
|
34
|
+
if (group.length === 0) {
|
|
35
|
+
return { vs: `vs ${hand}HH`, PA: 0, AVG: '—', SLG: '—', 'K %': '—', 'BB %': '—', 'Avg EV': '—', 'Whiff %': '—' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Plate appearances (approximate: count events)
|
|
39
|
+
const pas = group.filter((p) => p.events !== null);
|
|
40
|
+
const hits = pas.filter((p) =>
|
|
41
|
+
['single', 'double', 'triple', 'home_run'].includes(p.events ?? ''),
|
|
42
|
+
);
|
|
43
|
+
const strikeouts = pas.filter((p) => p.events === 'strikeout');
|
|
44
|
+
const walks = pas.filter((p) => ['walk', 'hit_by_pitch'].includes(p.events ?? ''));
|
|
45
|
+
|
|
46
|
+
// Exit velocity on contact
|
|
47
|
+
const contacted = group.filter((p) => p.launch_speed !== null && p.launch_speed > 0);
|
|
48
|
+
const avgEv = contacted.length > 0
|
|
49
|
+
? contacted.reduce((s, p) => s + (p.launch_speed ?? 0), 0) / contacted.length
|
|
50
|
+
: null;
|
|
51
|
+
|
|
52
|
+
// Total bases for SLG
|
|
53
|
+
const totalBases = pas.reduce((sum, p) => {
|
|
54
|
+
if (p.events === 'single') return sum + 1;
|
|
55
|
+
if (p.events === 'double') return sum + 2;
|
|
56
|
+
if (p.events === 'triple') return sum + 3;
|
|
57
|
+
if (p.events === 'home_run') return sum + 4;
|
|
58
|
+
return sum;
|
|
59
|
+
}, 0);
|
|
60
|
+
|
|
61
|
+
// Whiff rate
|
|
62
|
+
const swings = group.filter((p) =>
|
|
63
|
+
p.description.includes('swing') || p.description.includes('foul'),
|
|
64
|
+
);
|
|
65
|
+
const whiffs = group.filter((p) => p.description.includes('swinging_strike'));
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
vs: `vs ${hand}HH`,
|
|
69
|
+
PA: pas.length,
|
|
70
|
+
AVG: pas.length > 0 ? (hits.length / pas.length).toFixed(3) : '—',
|
|
71
|
+
SLG: pas.length > 0 ? (totalBases / pas.length).toFixed(3) : '—',
|
|
72
|
+
'K %': pas.length > 0 ? ((strikeouts.length / pas.length) * 100).toFixed(1) + '%' : '—',
|
|
73
|
+
'BB %': pas.length > 0 ? ((walks.length / pas.length) * 100).toFixed(1) + '%' : '—',
|
|
74
|
+
'Avg EV': avgEv !== null ? avgEv.toFixed(1) + ' mph' : '—',
|
|
75
|
+
'Whiff %': swings.length > 0 ? ((whiffs.length / swings.length) * 100).toFixed(1) + '%' : '—',
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
registerTemplate(template);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { registerTemplate, type QueryTemplate } from './registry.js';
|
|
2
|
+
import type { PitchData } from '../../adapters/types.js';
|
|
3
|
+
import { pitchTypeName } from '../../adapters/types.js';
|
|
4
|
+
|
|
5
|
+
const template: QueryTemplate = {
|
|
6
|
+
id: 'pitcher-velocity-trend',
|
|
7
|
+
name: 'Pitcher Velocity Trend',
|
|
8
|
+
category: 'pitcher',
|
|
9
|
+
description: 'Month-by-month fastball velocity tracking — flags drops > 0.5 mph',
|
|
10
|
+
preferredSources: ['savant'],
|
|
11
|
+
requiredParams: ['player'],
|
|
12
|
+
optionalParams: ['season'],
|
|
13
|
+
examples: [
|
|
14
|
+
'bbdata query pitcher-velocity-trend --player "Gerrit Cole" --season 2025',
|
|
15
|
+
],
|
|
16
|
+
|
|
17
|
+
buildQuery(params) {
|
|
18
|
+
return {
|
|
19
|
+
player_name: params.player,
|
|
20
|
+
season: params.season ?? new Date().getFullYear(),
|
|
21
|
+
stat_type: 'pitching',
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
columns() {
|
|
26
|
+
return ['Month', 'Avg Velo', 'Max Velo', 'Min Velo', 'Δ vs Prior', 'Pitches', 'Flag'];
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
transform(data) {
|
|
30
|
+
const pitches = (data as PitchData[]).filter(
|
|
31
|
+
(p) => ['FF', 'SI', 'FC'].includes(p.pitch_type) && p.release_speed > 0,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (pitches.length === 0) return [];
|
|
35
|
+
|
|
36
|
+
// Group by month
|
|
37
|
+
const byMonth = new Map<string, PitchData[]>();
|
|
38
|
+
for (const pitch of pitches) {
|
|
39
|
+
const month = pitch.game_date.slice(0, 7); // YYYY-MM
|
|
40
|
+
const group = byMonth.get(month) ?? [];
|
|
41
|
+
group.push(pitch);
|
|
42
|
+
byMonth.set(month, group);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const months = Array.from(byMonth.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
46
|
+
let prevAvg: number | null = null;
|
|
47
|
+
|
|
48
|
+
return months.map(([month, group]) => {
|
|
49
|
+
const velos = group.map((p) => p.release_speed);
|
|
50
|
+
const avg = velos.reduce((s, v) => s + v, 0) / velos.length;
|
|
51
|
+
const max = Math.max(...velos);
|
|
52
|
+
const min = Math.min(...velos);
|
|
53
|
+
|
|
54
|
+
const delta = prevAvg !== null ? avg - prevAvg : 0;
|
|
55
|
+
const flag = prevAvg !== null && delta < -0.5 ? '⚠ DROP' : '';
|
|
56
|
+
prevAvg = avg;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
Month: month,
|
|
60
|
+
'Avg Velo': avg.toFixed(1) + ' mph',
|
|
61
|
+
'Max Velo': max.toFixed(1) + ' mph',
|
|
62
|
+
'Min Velo': min.toFixed(1) + ' mph',
|
|
63
|
+
'Δ vs Prior': prevAvg !== null && delta !== 0
|
|
64
|
+
? (delta > 0 ? '+' : '') + delta.toFixed(1) + ' mph'
|
|
65
|
+
: '—',
|
|
66
|
+
Pitches: group.length,
|
|
67
|
+
Flag: flag,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
registerTemplate(template);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { DataSource, AdapterQuery, PitchData, PlayerStats } from '../../adapters/types.js';
|
|
2
|
+
|
|
3
|
+
export type QueryCategory = 'pitcher' | 'hitter' | 'matchup' | 'leaderboard' | 'trend';
|
|
4
|
+
|
|
5
|
+
export interface QueryTemplateParams {
|
|
6
|
+
player?: string;
|
|
7
|
+
players?: string[];
|
|
8
|
+
team?: string;
|
|
9
|
+
season?: number;
|
|
10
|
+
stat?: string;
|
|
11
|
+
pitchType?: string;
|
|
12
|
+
minPa?: number;
|
|
13
|
+
minIp?: number;
|
|
14
|
+
top?: number;
|
|
15
|
+
seasons?: string; // "2023-2025"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface QueryResult {
|
|
19
|
+
rows: Record<string, unknown>[];
|
|
20
|
+
columns: string[];
|
|
21
|
+
title: string;
|
|
22
|
+
description: string;
|
|
23
|
+
source: DataSource;
|
|
24
|
+
cached: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface QueryTemplate {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
category: QueryCategory;
|
|
31
|
+
description: string;
|
|
32
|
+
preferredSources: DataSource[];
|
|
33
|
+
requiredParams: (keyof QueryTemplateParams)[];
|
|
34
|
+
optionalParams: (keyof QueryTemplateParams)[];
|
|
35
|
+
examples: string[];
|
|
36
|
+
|
|
37
|
+
/** Build the adapter query from user-provided params */
|
|
38
|
+
buildQuery(params: QueryTemplateParams): AdapterQuery;
|
|
39
|
+
|
|
40
|
+
/** Transform raw adapter data into display rows */
|
|
41
|
+
transform(data: PitchData[] | PlayerStats[], params: QueryTemplateParams): Record<string, unknown>[];
|
|
42
|
+
|
|
43
|
+
/** Column names for display */
|
|
44
|
+
columns(params: QueryTemplateParams): string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Template registry
|
|
48
|
+
const templates = new Map<string, QueryTemplate>();
|
|
49
|
+
|
|
50
|
+
export function registerTemplate(template: QueryTemplate): void {
|
|
51
|
+
templates.set(template.id, template);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getTemplate(id: string): QueryTemplate | undefined {
|
|
55
|
+
return templates.get(id);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getAllTemplates(): QueryTemplate[] {
|
|
59
|
+
return Array.from(templates.values());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getTemplatesByCategory(category: QueryCategory): QueryTemplate[] {
|
|
63
|
+
return getAllTemplates().filter((t) => t.category === category);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function listTemplates(): { id: string; name: string; category: string; description: string }[] {
|
|
67
|
+
return getAllTemplates().map((t) => ({
|
|
68
|
+
id: t.id,
|
|
69
|
+
name: t.name,
|
|
70
|
+
category: t.category,
|
|
71
|
+
description: t.description,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { registerTemplate, type QueryTemplate } from './registry.js';
|
|
2
|
+
import type { PitchData } from '../../adapters/types.js';
|
|
3
|
+
|
|
4
|
+
const template: QueryTemplate = {
|
|
5
|
+
id: 'trend-rolling-average',
|
|
6
|
+
name: 'Season Trend (Rolling Average)',
|
|
7
|
+
category: 'trend',
|
|
8
|
+
description: '15-game (hitters) or 5-start (pitchers) rolling averages — identifies sustained trends',
|
|
9
|
+
preferredSources: ['savant'],
|
|
10
|
+
requiredParams: ['player'],
|
|
11
|
+
optionalParams: ['season'],
|
|
12
|
+
examples: [
|
|
13
|
+
'bbdata query trend-rolling-average --player "Freddie Freeman" --season 2025',
|
|
14
|
+
],
|
|
15
|
+
|
|
16
|
+
buildQuery(params) {
|
|
17
|
+
return {
|
|
18
|
+
player_name: params.player,
|
|
19
|
+
season: params.season ?? new Date().getFullYear(),
|
|
20
|
+
stat_type: 'batting',
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
columns() {
|
|
25
|
+
return ['Window', 'Games', 'AVG', 'SLG', 'K %', 'Avg EV', 'Hard Hit %'];
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
transform(data) {
|
|
29
|
+
const pitches = data as PitchData[];
|
|
30
|
+
if (pitches.length === 0) return [];
|
|
31
|
+
|
|
32
|
+
// Group by game date
|
|
33
|
+
const byDate = new Map<string, PitchData[]>();
|
|
34
|
+
for (const p of pitches) {
|
|
35
|
+
const group = byDate.get(p.game_date) ?? [];
|
|
36
|
+
group.push(p);
|
|
37
|
+
byDate.set(p.game_date, group);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const dates = Array.from(byDate.keys()).sort();
|
|
41
|
+
const windowSize = 15;
|
|
42
|
+
|
|
43
|
+
if (dates.length < windowSize) {
|
|
44
|
+
return [{ Window: 'Insufficient data', Games: dates.length, AVG: '—', SLG: '—', 'K %': '—', 'Avg EV': '—', 'Hard Hit %': '—' }];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Calculate rolling windows
|
|
48
|
+
const results: Record<string, unknown>[] = [];
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i <= dates.length - windowSize; i += Math.max(1, Math.floor(windowSize / 3))) {
|
|
51
|
+
const windowDates = dates.slice(i, i + windowSize);
|
|
52
|
+
const windowPitches = windowDates.flatMap((d) => byDate.get(d) ?? []);
|
|
53
|
+
|
|
54
|
+
const pas = windowPitches.filter((p) => p.events !== null);
|
|
55
|
+
const hits = pas.filter((p) => ['single', 'double', 'triple', 'home_run'].includes(p.events ?? ''));
|
|
56
|
+
const ks = pas.filter((p) => p.events === 'strikeout');
|
|
57
|
+
const totalBases = pas.reduce((sum, p) => {
|
|
58
|
+
if (p.events === 'single') return sum + 1;
|
|
59
|
+
if (p.events === 'double') return sum + 2;
|
|
60
|
+
if (p.events === 'triple') return sum + 3;
|
|
61
|
+
if (p.events === 'home_run') return sum + 4;
|
|
62
|
+
return sum;
|
|
63
|
+
}, 0);
|
|
64
|
+
|
|
65
|
+
const batted = windowPitches.filter((p) => p.launch_speed !== null && p.launch_speed > 0);
|
|
66
|
+
const avgEv = batted.length > 0
|
|
67
|
+
? batted.reduce((s, p) => s + p.launch_speed!, 0) / batted.length
|
|
68
|
+
: null;
|
|
69
|
+
const hardHit = batted.filter((p) => p.launch_speed! >= 95).length;
|
|
70
|
+
|
|
71
|
+
results.push({
|
|
72
|
+
Window: `${windowDates[0]} → ${windowDates[windowDates.length - 1]}`,
|
|
73
|
+
Games: windowDates.length,
|
|
74
|
+
AVG: pas.length > 0 ? (hits.length / pas.length).toFixed(3) : '—',
|
|
75
|
+
SLG: pas.length > 0 ? (totalBases / pas.length).toFixed(3) : '—',
|
|
76
|
+
'K %': pas.length > 0 ? ((ks.length / pas.length) * 100).toFixed(1) + '%' : '—',
|
|
77
|
+
'Avg EV': avgEv !== null ? avgEv.toFixed(1) + ' mph' : '—',
|
|
78
|
+
'Hard Hit %': batted.length > 0 ? ((hardHit / batted.length) * 100).toFixed(1) + '%' : '—',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return results;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
registerTemplate(template);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { registerTemplate, type QueryTemplate } from './registry.js';
|
|
2
|
+
import type { PlayerStats } from '../../adapters/types.js';
|
|
3
|
+
|
|
4
|
+
const template: QueryTemplate = {
|
|
5
|
+
id: 'trend-year-over-year',
|
|
6
|
+
name: 'Year-over-Year Comparison',
|
|
7
|
+
category: 'trend',
|
|
8
|
+
description: 'Compare metric changes year to year — flags changes greater than 10%',
|
|
9
|
+
preferredSources: ['fangraphs', 'mlb-stats-api'],
|
|
10
|
+
requiredParams: ['player'],
|
|
11
|
+
optionalParams: ['seasons'], // e.g. "2023-2025"
|
|
12
|
+
examples: [
|
|
13
|
+
'bbdata query trend-year-over-year --player "Julio Rodriguez" --seasons 2023-2025',
|
|
14
|
+
],
|
|
15
|
+
|
|
16
|
+
buildQuery(params) {
|
|
17
|
+
// We'll need multiple seasons — use the most recent by default
|
|
18
|
+
const season = params.season ?? new Date().getFullYear();
|
|
19
|
+
return {
|
|
20
|
+
player_name: params.player,
|
|
21
|
+
season,
|
|
22
|
+
stat_type: 'batting',
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
columns() {
|
|
27
|
+
return ['Metric', 'Prior', 'Current', 'Change', 'Flag'];
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
transform(data, params) {
|
|
31
|
+
const stats = data as PlayerStats[];
|
|
32
|
+
if (stats.length === 0) return [];
|
|
33
|
+
|
|
34
|
+
// If we have multi-season data, compare last two
|
|
35
|
+
// With single-season data, we show what we have
|
|
36
|
+
const player = stats[0];
|
|
37
|
+
const s = player.stats;
|
|
38
|
+
|
|
39
|
+
// Key metrics to compare
|
|
40
|
+
const metrics = ['AVG', 'OBP', 'SLG', 'HR', 'wRC+', 'K%', 'BB%', 'WAR', 'ISO', 'BABIP'];
|
|
41
|
+
|
|
42
|
+
return metrics.map((metric) => {
|
|
43
|
+
const current = findStat(s, metric);
|
|
44
|
+
// Without multi-season support in a single query, we show current only
|
|
45
|
+
// Full YoY requires querying multiple seasons (future enhancement)
|
|
46
|
+
return {
|
|
47
|
+
Metric: metric,
|
|
48
|
+
Prior: '—',
|
|
49
|
+
Current: current !== null ? formatVal(current) : '—',
|
|
50
|
+
Change: '—',
|
|
51
|
+
Flag: '',
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function findStat(stats: Record<string, unknown>, key: string): number | null {
|
|
58
|
+
const lower = key.toLowerCase().replace(/[+%]/g, '');
|
|
59
|
+
for (const [k, v] of Object.entries(stats)) {
|
|
60
|
+
if (k.toLowerCase().replace(/[+%]/g, '') === lower) {
|
|
61
|
+
const n = Number(v);
|
|
62
|
+
return isNaN(n) ? null : n;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatVal(n: number): string {
|
|
69
|
+
if (n < 1 && n > 0) return n.toFixed(3);
|
|
70
|
+
return n % 1 === 0 ? String(n) : n.toFixed(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
registerTemplate(template);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Advance Report: {{team}} Lineup
|
|
2
|
+
|
|
3
|
+
**Season:** {{season}} | **For:** {{audience}} | **Generated:** {{date}}
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Header
|
|
8
|
+
|
|
9
|
+
| Field | Value |
|
|
10
|
+
|-------|-------|
|
|
11
|
+
| Opponent | {{team}} |
|
|
12
|
+
| Season | {{season}} |
|
|
13
|
+
|
|
14
|
+
## Lineup Overview
|
|
15
|
+
|
|
16
|
+
*Projected lineup, team tendencies, overall approach — to be populated*
|
|
17
|
+
|
|
18
|
+
## Hitter Breakdowns
|
|
19
|
+
|
|
20
|
+
*Individual hitter cards — to be generated per player:*
|
|
21
|
+
*Strengths, weaknesses, pitch approach, hot/cold zones*
|
|
22
|
+
|
|
23
|
+
## Key Matchups
|
|
24
|
+
|
|
25
|
+
*Critical at-bats to plan for — to be filled by coaching staff*
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
*Generated by bbdata CLI · 1-page advance format*
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Advance Report: {{player}} (SP)
|
|
2
|
+
|
|
3
|
+
**Season:** {{season}} | **For:** {{audience}} | **Generated:** {{date}}
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Recent Form
|
|
8
|
+
|
|
9
|
+
*Last 5 starts — to be populated with game log data*
|
|
10
|
+
|
|
11
|
+
## Pitch Mix & Sequencing
|
|
12
|
+
|
|
13
|
+
{{#if data.pitcher-arsenal}}
|
|
14
|
+
### Arsenal Overview
|
|
15
|
+
|
|
16
|
+
| Pitch | Usage | Velo | Spin | Whiff % |
|
|
17
|
+
|-------|-------|------|------|---------|
|
|
18
|
+
{{#each data.pitcher-arsenal}}
|
|
19
|
+
| {{this.[Pitch Type]}} | {{this.[Usage %]}} | {{this.[Avg Velo]}} | {{this.[Avg Spin]}} | {{this.[Whiff %]}} |
|
|
20
|
+
{{/each}}
|
|
21
|
+
|
|
22
|
+
### By Count (to be populated)
|
|
23
|
+
- **Ahead in count:** Primary pitch selection
|
|
24
|
+
- **Behind in count:** Go-to pitch
|
|
25
|
+
- **Two-strike:** Put-away pitch
|
|
26
|
+
{{else}}
|
|
27
|
+
*Arsenal data not available*
|
|
28
|
+
{{/if}}
|
|
29
|
+
|
|
30
|
+
## Times Through the Order
|
|
31
|
+
|
|
32
|
+
*3rd time adjustment, velocity drop patterns — to be analyzed from pitch-level data*
|
|
33
|
+
|
|
34
|
+
## Platoon Vulnerabilities
|
|
35
|
+
|
|
36
|
+
{{#if data.pitcher-handedness-splits}}
|
|
37
|
+
| Split | PA | AVG | SLG | K % | BB % |
|
|
38
|
+
|-------|-----|-----|-----|-----|------|
|
|
39
|
+
{{#each data.pitcher-handedness-splits}}
|
|
40
|
+
| {{this.vs}} | {{this.PA}} | {{this.AVG}} | {{this.SLG}} | {{this.[K %]}} | {{this.[BB %]}} |
|
|
41
|
+
{{/each}}
|
|
42
|
+
{{else}}
|
|
43
|
+
*Splits data not available*
|
|
44
|
+
{{/if}}
|
|
45
|
+
|
|
46
|
+
## How to Attack
|
|
47
|
+
|
|
48
|
+
### Early in Game (1st time through)
|
|
49
|
+
- *To be filled: patient approach? aggressive?*
|
|
50
|
+
|
|
51
|
+
### Late in Game (3rd+ time through)
|
|
52
|
+
- *To be filled: exploit fatigue patterns?*
|
|
53
|
+
|
|
54
|
+
### Key Weaknesses
|
|
55
|
+
- *To be filled by evaluator*
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
*Generated by bbdata CLI · Data sources: {{sources}}*
|
|
60
|
+
*This is a 1-page advance report designed for in-game use.*
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# College Hitter Draft Report: {{player}}
|
|
2
|
+
|
|
3
|
+
**Season:** {{season}} | **Generated:** {{date}}
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Header
|
|
8
|
+
|
|
9
|
+
| Field | Value |
|
|
10
|
+
|-------|-------|
|
|
11
|
+
| Player | {{player}} |
|
|
12
|
+
| School | *To be filled* |
|
|
13
|
+
| Bats/Throws | *To be filled* |
|
|
14
|
+
| Position | *To be filled* |
|
|
15
|
+
| Age | *To be filled* |
|
|
16
|
+
|
|
17
|
+
## Physical
|
|
18
|
+
|
|
19
|
+
*Body type, athleticism, strength projection — to be filled*
|
|
20
|
+
|
|
21
|
+
## Tool Grades (20-80 Scale)
|
|
22
|
+
|
|
23
|
+
| Tool | Present | Future | Notes |
|
|
24
|
+
|------|---------|--------|-------|
|
|
25
|
+
| Hit | — | — | *Bat speed, plate coverage* |
|
|
26
|
+
| Power | — | — | *Raw, game power, pull/oppo* |
|
|
27
|
+
| Speed | — | — | *Run times, baserunning IQ* |
|
|
28
|
+
| Field | — | — | *Actions, arm, range* |
|
|
29
|
+
| Arm | — | — | *Strength, accuracy* |
|
|
30
|
+
|
|
31
|
+
## Performance
|
|
32
|
+
|
|
33
|
+
*College slash line, K%, BB%, wood bat league stats — to be filled*
|
|
34
|
+
|
|
35
|
+
## Projection
|
|
36
|
+
|
|
37
|
+
*Ceiling, floor, likely outcome — to be filled*
|
|
38
|
+
|
|
39
|
+
## Risk
|
|
40
|
+
|
|
41
|
+
*Swing concerns, defensive fit, signability — to be filled*
|
|
42
|
+
|
|
43
|
+
## Recommendation
|
|
44
|
+
|
|
45
|
+
*Draft round range, comparison — to be filled*
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
*Generated by bbdata CLI*
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# College Pitcher Draft Report: {{player}}
|
|
2
|
+
|
|
3
|
+
**Season:** {{season}} | **Generated:** {{date}}
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Header
|
|
8
|
+
|
|
9
|
+
| Field | Value |
|
|
10
|
+
|-------|-------|
|
|
11
|
+
| Player | {{player}} |
|
|
12
|
+
| School | *To be filled* |
|
|
13
|
+
| Throws | *To be filled* |
|
|
14
|
+
| Age | *To be filled* |
|
|
15
|
+
| Height/Weight | *To be filled* |
|
|
16
|
+
|
|
17
|
+
## Physical
|
|
18
|
+
|
|
19
|
+
*Body type, athleticism, projectability — to be filled by evaluator*
|
|
20
|
+
|
|
21
|
+
## Arsenal Grades (20-80 Scale)
|
|
22
|
+
|
|
23
|
+
| Tool | Present | Future | Notes |
|
|
24
|
+
|------|---------|--------|-------|
|
|
25
|
+
| Fastball | — | — | *Velo, life, command* |
|
|
26
|
+
| Breaking Ball | — | — | *Type, spin, depth* |
|
|
27
|
+
| Changeup | — | — | *Fade, deception* |
|
|
28
|
+
| Command | — | — | *Zone control, sequencing* |
|
|
29
|
+
|
|
30
|
+
## Performance
|
|
31
|
+
|
|
32
|
+
*College stats: ERA, K/9, BB/9, WHIP — to be filled*
|
|
33
|
+
|
|
34
|
+
## Projection
|
|
35
|
+
|
|
36
|
+
*Ceiling, floor, likely outcome — to be filled by evaluator*
|
|
37
|
+
|
|
38
|
+
## Risk
|
|
39
|
+
|
|
40
|
+
*Injury history, workload, mechanics — to be filled by evaluator*
|
|
41
|
+
|
|
42
|
+
## Recommendation
|
|
43
|
+
|
|
44
|
+
*Draft round range, comparison, acquire/pass — to be filled*
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
*Generated by bbdata CLI*
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Development Progress Report: {{player}}
|
|
2
|
+
|
|
3
|
+
**Season:** {{season}} | **Generated:** {{date}}
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Current Stats
|
|
8
|
+
|
|
9
|
+
*Current level, slash line, K%, BB%, ISO — to be populated*
|
|
10
|
+
|
|
11
|
+
## Trend Analysis
|
|
12
|
+
|
|
13
|
+
*Month-over-month progression, key metric changes — to be populated*
|
|
14
|
+
|
|
15
|
+
## Mechanical Notes
|
|
16
|
+
|
|
17
|
+
*Changes, adjustments, coach observations — to be filled by evaluator*
|
|
18
|
+
|
|
19
|
+
## Development Goals
|
|
20
|
+
|
|
21
|
+
*Target areas for improvement — to be filled by development staff*
|
|
22
|
+
|
|
23
|
+
## Next Steps
|
|
24
|
+
|
|
25
|
+
*Promotion timeline, assignment recommendation — to be filled*
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
*Generated by bbdata CLI*
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# {{player}} — Draft Board Card
|
|
2
|
+
|
|
3
|
+
**Generated:** {{date}}
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
|-------|-------|
|
|
9
|
+
| **Name** | {{player}} |
|
|
10
|
+
| **Position** | *To be filled* |
|
|
11
|
+
| **School** | *To be filled* |
|
|
12
|
+
| **B/T** | *To be filled* |
|
|
13
|
+
| **Age** | *To be filled* |
|
|
14
|
+
|
|
15
|
+
### Tool Grades
|
|
16
|
+
|
|
17
|
+
| Hit | Power | Speed | Field | Arm | OFP |
|
|
18
|
+
|-----|-------|-------|-------|-----|-----|
|
|
19
|
+
| — | — | — | — | — | — |
|
|
20
|
+
|
|
21
|
+
### Projection
|
|
22
|
+
|
|
23
|
+
*One-line ceiling* — *To be filled*
|
|
24
|
+
|
|
25
|
+
### Comp
|
|
26
|
+
|
|
27
|
+
*Player comparison* — *To be filled*
|
|
28
|
+
|
|
29
|
+
### Round Range
|
|
30
|
+
|
|
31
|
+
*Expected draft range* — *To be filled*
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
*bbdata CLI · Draft room card format*
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# High School Prospect Report: {{player}}
|
|
2
|
+
|
|
3
|
+
**Season:** {{season}} | **Generated:** {{date}}
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Header
|
|
8
|
+
|
|
9
|
+
| Field | Value |
|
|
10
|
+
|-------|-------|
|
|
11
|
+
| Player | {{player}} |
|
|
12
|
+
| School | *To be filled* |
|
|
13
|
+
| Position | *To be filled* |
|
|
14
|
+
| Commitment | *To be filled* |
|
|
15
|
+
|
|
16
|
+
## Physical
|
|
17
|
+
|
|
18
|
+
*Body type, athleticism, projectability — to be filled*
|
|
19
|
+
|
|
20
|
+
## Tool Grades (20-80 Scale)
|
|
21
|
+
|
|
22
|
+
| Tool | Present | Future | Notes |
|
|
23
|
+
|------|---------|--------|-------|
|
|
24
|
+
| Hit | — | — | |
|
|
25
|
+
| Power | — | — | |
|
|
26
|
+
| Speed | — | — | |
|
|
27
|
+
| Field | — | — | |
|
|
28
|
+
| Arm | — | — | |
|
|
29
|
+
|
|
30
|
+
## Makeup
|
|
31
|
+
|
|
32
|
+
*Work ethic, competitiveness, coachability — to be filled*
|
|
33
|
+
|
|
34
|
+
## Projection
|
|
35
|
+
|
|
36
|
+
*Development timeline, ceiling, floor — to be filled*
|
|
37
|
+
|
|
38
|
+
## Signability
|
|
39
|
+
|
|
40
|
+
*Commitment strength, slot expectations — to be filled*
|
|
41
|
+
|
|
42
|
+
## Recommendation
|
|
43
|
+
|
|
44
|
+
*Draft/follow recommendation — to be filled*
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
*Generated by bbdata CLI*
|