@workingdevshero/olympia-ait-adapter 0.1.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.
Files changed (51) hide show
  1. package/README.md +208 -0
  2. package/dist/index.d.ts +5 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +8 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/reader/parse-day-sheet.d.ts +10 -0
  7. package/dist/reader/parse-day-sheet.d.ts.map +1 -0
  8. package/dist/reader/parse-day-sheet.js +199 -0
  9. package/dist/reader/parse-day-sheet.js.map +1 -0
  10. package/dist/reader/parse-workbook.d.ts +3 -0
  11. package/dist/reader/parse-workbook.d.ts.map +1 -0
  12. package/dist/reader/parse-workbook.js +55 -0
  13. package/dist/reader/parse-workbook.js.map +1 -0
  14. package/dist/schema/program.d.ts +692 -0
  15. package/dist/schema/program.d.ts.map +1 -0
  16. package/dist/schema/program.js +125 -0
  17. package/dist/schema/program.js.map +1 -0
  18. package/dist/server.d.ts +4 -0
  19. package/dist/server.d.ts.map +1 -0
  20. package/dist/server.js +58 -0
  21. package/dist/server.js.map +1 -0
  22. package/dist/tools/json-to-xlsx.d.ts +3 -0
  23. package/dist/tools/json-to-xlsx.d.ts.map +1 -0
  24. package/dist/tools/json-to-xlsx.js +5 -0
  25. package/dist/tools/json-to-xlsx.js.map +1 -0
  26. package/dist/tools/xlsx-to-json.d.ts +3 -0
  27. package/dist/tools/xlsx-to-json.d.ts.map +1 -0
  28. package/dist/tools/xlsx-to-json.js +5 -0
  29. package/dist/tools/xlsx-to-json.js.map +1 -0
  30. package/dist/writer/build-workbook.d.ts +3 -0
  31. package/dist/writer/build-workbook.d.ts.map +1 -0
  32. package/dist/writer/build-workbook.js +166 -0
  33. package/dist/writer/build-workbook.js.map +1 -0
  34. package/dist/writer/layout.d.ts +21 -0
  35. package/dist/writer/layout.d.ts.map +1 -0
  36. package/dist/writer/layout.js +40 -0
  37. package/dist/writer/layout.js.map +1 -0
  38. package/dist/writer/stamp-exercise.d.ts +31 -0
  39. package/dist/writer/stamp-exercise.d.ts.map +1 -0
  40. package/dist/writer/stamp-exercise.js +80 -0
  41. package/dist/writer/stamp-exercise.js.map +1 -0
  42. package/dist/writer/styles.d.ts +59 -0
  43. package/dist/writer/styles.d.ts.map +1 -0
  44. package/dist/writer/styles.js +266 -0
  45. package/dist/writer/styles.js.map +1 -0
  46. package/dist/writer/supplemental-template.d.ts +23 -0
  47. package/dist/writer/supplemental-template.d.ts.map +1 -0
  48. package/dist/writer/supplemental-template.js +112 -0
  49. package/dist/writer/supplemental-template.js.map +1 -0
  50. package/package.json +42 -0
  51. package/src/assets/olympia-logo.png +0 -0
@@ -0,0 +1,125 @@
1
+ import { z } from "zod";
2
+ const WEEK_COUNT = 6;
3
+ /**
4
+ * A single prescription cell for one set in one week.
5
+ *
6
+ * Olympia does not prescribe weights — the unit is reps, time, or breaths,
7
+ * encoded by notation in the cell text:
8
+ *
9
+ * "12" → 12 reps
10
+ * "10E" → 10 reps each side
11
+ * ":20" → 20-second hold
12
+ * ":20E" → 20-second hold each side
13
+ * ":60" → 60 seconds
14
+ * "5 breaths" → 5 breaths
15
+ * null → empty cell
16
+ *
17
+ * Free-form strings like "x3 Rest :60" are also valid (Giulianna's file
18
+ * has these for circuit instructions), so the grammar is intentionally
19
+ * not validated at the schema level.
20
+ */
21
+ export const Cell = z
22
+ .string()
23
+ .nullable()
24
+ .describe('A single prescription. Common formats: "12" (12 reps), "10E" (10 reps each side), ":20" (20-sec hold), ":20E" (20-sec hold each side), ":60" (60 seconds), "5 breaths" (5 breaths). Free-form circuit instructions like "x3 Rest :60" are allowed. Use null for empty cells. Weights are NOT prescribed — units are reps, time, or breaths only.');
25
+ export const SetRow = z
26
+ .array(Cell)
27
+ .length(WEEK_COUNT)
28
+ .describe("One set's prescription across all 6 weeks of the program, in week order.");
29
+ export const Exercise = z
30
+ .object({
31
+ name: z
32
+ .string()
33
+ .min(1)
34
+ .describe('Exercise name, e.g. "2DB Alternating Bench Press".'),
35
+ groupLabel: z
36
+ .string()
37
+ .regex(/^\d+[A-Z]$/, "Group label must be a digit-letter pair like 1A, 1B, 2A")
38
+ .optional()
39
+ .describe('Superset/circuit grouping. Exercises sharing the same number form a group (e.g. 1A/1B run back-to-back). Omit for ungrouped exercises.'),
40
+ notes: z
41
+ .string()
42
+ .optional()
43
+ .describe('Coaching cue / instruction shown in the notes column.'),
44
+ sets: z
45
+ .array(SetRow)
46
+ .min(1)
47
+ .describe("2-D matrix of prescriptions: sets[setIdx][weekIdx]. Outer length = number of sets for this exercise (typically 3); each inner array is length 6 (weeks 1-6). Set positions are coherent across weeks — sets[0] is set 1's progression across the 6 weeks."),
48
+ })
49
+ .describe("One exercise in a training day, with its 6-week set-by-set prescription.");
50
+ export const SectionKind = z
51
+ .enum(["warmup", "main", "sportSpecific", "supplemental", "conditioning"])
52
+ .describe("Section type within a day. Olympia day-sheets are organized into ordered sections delimited by column-C labels.");
53
+ export const Section = z
54
+ .object({
55
+ kind: SectionKind,
56
+ exercises: z.array(Exercise),
57
+ })
58
+ .describe("An ordered group of exercises within a day.");
59
+ export const SupplementalNote = z
60
+ .object({
61
+ row: z
62
+ .number()
63
+ .int()
64
+ .nonnegative()
65
+ .describe('Row offset from the top of the supplemental area (0 = the "Sport Specific/" label row).'),
66
+ col: z
67
+ .string()
68
+ .regex(/^[A-Z]+$/)
69
+ .describe("Column letter where the cell sits. Typical values: C (label column), D/F/H/J/L/N (one per week, master cells of the single-row merges)."),
70
+ value: z.string().describe("The raw cell text the trainer wrote."),
71
+ })
72
+ .describe("One freeform cell captured from the trainer-managed Sport Specific / Supplemental area at the bottom of a day sheet.");
73
+ export const Day = z
74
+ .object({
75
+ name: z
76
+ .string()
77
+ .min(1)
78
+ .describe('Day name (becomes the sheet name), e.g. "Day 1".'),
79
+ sections: z.array(Section).min(1),
80
+ supplementalNotes: z
81
+ .array(SupplementalNote)
82
+ .optional()
83
+ .describe("Freeform notes the trainer wrote in the Sport Specific / Supplemental area at the bottom of the day. Sparse — only populated cells appear. The schema is intentionally loose because trainers use this area for athlete-specific instructions that don't fit the structured exercise table (e.g. conditioning circuits, sport-specific drills, recovery breath work)."),
84
+ })
85
+ .describe("One training day = one sheet in the output workbook.");
86
+ export const Program = z
87
+ .object({
88
+ client: z.string().min(1).describe('Client name, e.g. "Bobby Galli".'),
89
+ programDate: z
90
+ .string()
91
+ .describe('Program identifier date as it appears in the filename, e.g. "4.26" for April 2026. Free-form to match Olympia conventions.'),
92
+ weekDates: z
93
+ .array(z.string().nullable())
94
+ .length(WEEK_COUNT)
95
+ .optional()
96
+ .describe('Per-week date labels (the "Date:" header row in each sheet). 6 entries, one per week, or null where empty.'),
97
+ days: z.array(Day).min(1),
98
+ })
99
+ .describe("A complete Olympia training program. Maps 1:1 to one client XLSX workbook.");
100
+ // --- Tool input/output schemas ---
101
+ export const XlsxToJsonInput = z.object({
102
+ xlsxPath: z
103
+ .string()
104
+ .min(1)
105
+ .describe("Absolute path to an Olympia .xlsx file on the local filesystem."),
106
+ });
107
+ export const XlsxToJsonOutput = z.object({
108
+ program: Program,
109
+ warnings: z
110
+ .array(z.string())
111
+ .describe("Structural surprises encountered while parsing (e.g. unexpected section labels, exercises without group labels where one was expected). Empty if the file parsed cleanly."),
112
+ });
113
+ export const JsonToXlsxInput = z.object({
114
+ program: Program,
115
+ outputPath: z
116
+ .string()
117
+ .min(1)
118
+ .describe("Absolute path where the generated .xlsx file should be written. Parent directory must exist."),
119
+ });
120
+ export const JsonToXlsxOutput = z.object({
121
+ outputPath: z.string(),
122
+ bytesWritten: z.number().int().nonnegative(),
123
+ });
124
+ export const WEEK_COUNT_CONST = WEEK_COUNT;
125
+ //# sourceMappingURL=program.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program.js","sourceRoot":"","sources":["../../src/schema/program.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ export declare function createServer(): McpServer;
4
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAWpE,wBAAgB,YAAY,IAAI,SAAS,CAiDxC"}
package/dist/server.js ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { JsonToXlsxInput, JsonToXlsxOutput, XlsxToJsonInput, XlsxToJsonOutput, } from "./schema/program.js";
5
+ import { xlsxToJson } from "./tools/xlsx-to-json.js";
6
+ import { jsonToXlsx } from "./tools/json-to-xlsx.js";
7
+ export function createServer() {
8
+ const server = new McpServer({
9
+ name: "olympia-ait-adapter",
10
+ version: "0.1.0",
11
+ });
12
+ server.registerTool("olympia_xlsx_to_json", {
13
+ title: "Olympia XLSX → JSON",
14
+ description: "Parse an Olympia Fitness program XLSX workbook into a strict JSON document. The output `program` is a structured representation of all training days, sections, exercises, and per-set/per-week prescriptions. `warnings` lists any structural surprises the parser encountered.",
15
+ inputSchema: XlsxToJsonInput.shape,
16
+ outputSchema: XlsxToJsonOutput.shape,
17
+ }, async (raw) => {
18
+ const input = XlsxToJsonInput.parse(raw);
19
+ const result = await xlsxToJson(input);
20
+ return {
21
+ content: [
22
+ { type: "text", text: JSON.stringify(result, null, 2) },
23
+ ],
24
+ structuredContent: result,
25
+ };
26
+ });
27
+ server.registerTool("olympia_json_to_xlsx", {
28
+ title: "Olympia JSON → XLSX",
29
+ description: "Render an Olympia program JSON document to an .xlsx workbook on disk. The input `program` MUST conform to the strict Olympia schema (one sheet per day; 6 weeks per set; group labels match `<digit><uppercase letter>`; units are reps/time/breaths only — weights are never prescribed). Returns the output path and bytes written.",
30
+ inputSchema: JsonToXlsxInput.shape,
31
+ outputSchema: JsonToXlsxOutput.shape,
32
+ }, async (raw) => {
33
+ const input = JsonToXlsxInput.parse(raw);
34
+ const result = await jsonToXlsx(input);
35
+ return {
36
+ content: [
37
+ { type: "text", text: JSON.stringify(result, null, 2) },
38
+ ],
39
+ structuredContent: result,
40
+ };
41
+ });
42
+ return server;
43
+ }
44
+ async function main() {
45
+ const server = createServer();
46
+ const transport = new StdioServerTransport();
47
+ await server.connect(transport);
48
+ }
49
+ const isMain = process.argv[1] !== undefined &&
50
+ (import.meta.url === `file://${process.argv[1]}` ||
51
+ import.meta.url.endsWith(process.argv[1]));
52
+ if (isMain) {
53
+ main().catch((err) => {
54
+ console.error(err);
55
+ process.exit(1);
56
+ });
57
+ }
58
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EACT,kRAAkR;QACpR,WAAW,EAAE,eAAe,CAAC,KAAK;QAClC,YAAY,EAAE,gBAAgB,CAAC,KAAK;KACrC,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aACxD;YACD,iBAAiB,EAAE,MAAM;SAC1B,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EACT,uUAAuU;QACzU,WAAW,EAAE,eAAe,CAAC,KAAK;QAClC,YAAY,EAAE,gBAAgB,CAAC,KAAK;KACrC,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aACxD;YACD,iBAAiB,EAAE,MAAM;SAC1B,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS;IAC7B,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;QAC9C,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE/C,IAAI,MAAM,EAAE,CAAC;IACX,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { JsonToXlsxInput, JsonToXlsxOutput } from "../schema/program.js";
2
+ export declare function jsonToXlsx(input: JsonToXlsxInput): Promise<JsonToXlsxOutput>;
3
+ //# sourceMappingURL=json-to-xlsx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-to-xlsx.d.ts","sourceRoot":"","sources":["../../src/tools/json-to-xlsx.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE9E,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,gBAAgB,CAAC,CAE3B"}
@@ -0,0 +1,5 @@
1
+ import { buildWorkbook } from "../writer/build-workbook.js";
2
+ export async function jsonToXlsx(input) {
3
+ return buildWorkbook(input.program, input.outputPath);
4
+ }
5
+ //# sourceMappingURL=json-to-xlsx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-to-xlsx.js","sourceRoot":"","sources":["../../src/tools/json-to-xlsx.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG5D,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAsB;IAEtB,OAAO,aAAa,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { XlsxToJsonInput, XlsxToJsonOutput } from "../schema/program.js";
2
+ export declare function xlsxToJson(input: XlsxToJsonInput): Promise<XlsxToJsonOutput>;
3
+ //# sourceMappingURL=xlsx-to-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xlsx-to-json.d.ts","sourceRoot":"","sources":["../../src/tools/xlsx-to-json.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE9E,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,gBAAgB,CAAC,CAE3B"}
@@ -0,0 +1,5 @@
1
+ import { parseWorkbook } from "../reader/parse-workbook.js";
2
+ export async function xlsxToJson(input) {
3
+ return parseWorkbook(input.xlsxPath);
4
+ }
5
+ //# sourceMappingURL=xlsx-to-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xlsx-to-json.js","sourceRoot":"","sources":["../../src/tools/xlsx-to-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG5D,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAsB;IAEtB,OAAO,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { JsonToXlsxOutput, Program } from "../schema/program.js";
2
+ export declare function buildWorkbook(program: Program, outputPath: string): Promise<JsonToXlsxOutput>;
3
+ //# sourceMappingURL=build-workbook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-workbook.d.ts","sourceRoot":"","sources":["../../src/writer/build-workbook.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,OAAO,EAER,MAAM,sBAAsB,CAAC;AA+B9B,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,gBAAgB,CAAC,CA4J3B"}
@@ -0,0 +1,166 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { promises as fs } from "node:fs";
4
+ import ExcelJS from "exceljs";
5
+ import { COLUMN_WIDTHS, DATE_ROW, FIRST_EXERCISE_ROW, HEADER_ROW, LOGO_ANCHOR_TL, LOGO_PIXEL_SIZE, NOTES_COL, WARMUP_ROW, WEEK_REPS_COLS, WEEK_WEIGHT_COLS, } from "./layout.js";
6
+ import { DATE_LABEL, EXERCISE_HEADER, headerPadAboveStyle, headerPadBelowStyle, NOTES_HEADER_CELL, REPS_HEADER, SECTION_LABEL, WARMUP_DAY, WARMUP_DAY_LAST, WEIGHT_HEADER, } from "./styles.js";
7
+ import { groupNumberOf, stampExercise } from "./stamp-exercise.js";
8
+ import { stampSupplementalTemplate } from "./supplemental-template.js";
9
+ const here = path.dirname(fileURLToPath(import.meta.url));
10
+ const LOGO_PATH = path.resolve(here, "../../src/assets/olympia-logo.png");
11
+ export async function buildWorkbook(program, outputPath) {
12
+ const workbook = new ExcelJS.Workbook();
13
+ workbook.creator = "olympia-ait-adapter";
14
+ workbook.created = new Date(0);
15
+ workbook.modified = new Date(0);
16
+ workbook.title = `${program.client} Programs ${program.programDate}`;
17
+ workbook.subject = program.client;
18
+ workbook.keywords = program.programDate;
19
+ let logoBytes;
20
+ try {
21
+ const raw = await fs.readFile(LOGO_PATH);
22
+ logoBytes = new Uint8Array(raw);
23
+ }
24
+ catch {
25
+ /* logo optional in dev */
26
+ }
27
+ const imageId = logoBytes !== undefined
28
+ ? workbook.addImage({
29
+ buffer: logoBytes,
30
+ extension: "png",
31
+ })
32
+ : undefined;
33
+ for (const day of program.days) {
34
+ const ws = workbook.addWorksheet(day.name);
35
+ // Column widths
36
+ for (const { col, width } of COLUMN_WIDTHS) {
37
+ ws.getColumn(col).width = width;
38
+ }
39
+ // --- Row 2: "Date:" cells + "Notes" header ---
40
+ for (const col of WEEK_REPS_COLS) {
41
+ const c = ws.getCell(`${col}${DATE_ROW}`);
42
+ c.value = "Date:";
43
+ Object.assign(c, DATE_LABEL);
44
+ }
45
+ for (const col of WEEK_WEIGHT_COLS) {
46
+ const c = ws.getCell(`${col}${DATE_ROW}`);
47
+ c.value = "Date:";
48
+ Object.assign(c, DATE_LABEL);
49
+ }
50
+ {
51
+ const cell = ws.getCell(`${NOTES_COL}${DATE_ROW}`);
52
+ cell.value = "Notes";
53
+ Object.assign(cell, NOTES_HEADER_CELL);
54
+ }
55
+ for (let i = 0; i < 6; i++) {
56
+ ws.mergeCells(`${WEEK_REPS_COLS[i]}${DATE_ROW}:${WEEK_WEIGHT_COLS[i]}${DATE_ROW}`);
57
+ }
58
+ // --- Row 3: "Warmup" + "Warm Up #1" labels ---
59
+ {
60
+ const c = ws.getCell(`C${WARMUP_ROW}`);
61
+ c.value = "Warmup";
62
+ Object.assign(c, SECTION_LABEL);
63
+ }
64
+ for (let i = 0; i < 6; i++) {
65
+ const a = WEEK_REPS_COLS[i];
66
+ const b = WEEK_WEIGHT_COLS[i];
67
+ ws.mergeCells(`${a}${WARMUP_ROW}:${b}${WARMUP_ROW}`);
68
+ const c = ws.getCell(`${a}${WARMUP_ROW}`);
69
+ c.value = "Warm Up #1";
70
+ Object.assign(c, i === 5 ? WARMUP_DAY_LAST : WARMUP_DAY);
71
+ }
72
+ // --- Row 4: gray padding above the "Exercise" header ---
73
+ {
74
+ Object.assign(ws.getCell(`C4`), headerPadAboveStyle("C"));
75
+ for (let i = 0; i < 6; i++) {
76
+ Object.assign(ws.getCell(`${WEEK_REPS_COLS[i]}4`), headerPadAboveStyle("reps"));
77
+ Object.assign(ws.getCell(`${WEEK_WEIGHT_COLS[i]}4`), headerPadAboveStyle("weight"));
78
+ }
79
+ }
80
+ // --- Row 5: column headers ---
81
+ {
82
+ const c = ws.getCell(`C${HEADER_ROW}`);
83
+ c.value = "Exercise";
84
+ Object.assign(c, EXERCISE_HEADER);
85
+ }
86
+ for (let i = 0; i < 6; i++) {
87
+ const reps = ws.getCell(`${WEEK_REPS_COLS[i]}${HEADER_ROW}`);
88
+ reps.value = "Reps";
89
+ Object.assign(reps, REPS_HEADER);
90
+ const weight = ws.getCell(`${WEEK_WEIGHT_COLS[i]}${HEADER_ROW}`);
91
+ weight.value = "Weight";
92
+ Object.assign(weight, WEIGHT_HEADER);
93
+ }
94
+ // --- Row 6: gray padding below the "Exercise" header ---
95
+ {
96
+ Object.assign(ws.getCell(`C6`), headerPadBelowStyle("C"));
97
+ for (let i = 0; i < 6; i++) {
98
+ Object.assign(ws.getCell(`${WEEK_REPS_COLS[i]}6`), headerPadBelowStyle("reps"));
99
+ Object.assign(ws.getCell(`${WEEK_WEIGHT_COLS[i]}6`), headerPadBelowStyle("weight"));
100
+ }
101
+ }
102
+ // Week dates (row 2) — override "Date:" labels if program has them.
103
+ if (program.weekDates) {
104
+ for (let i = 0; i < 6; i++) {
105
+ const v = program.weekDates[i];
106
+ if (v) {
107
+ const c = ws.getCell(`${WEEK_REPS_COLS[i]}${DATE_ROW}`);
108
+ c.value = v;
109
+ }
110
+ }
111
+ }
112
+ // --- Body: walk sections, stamp exercises with neighbor-aware borders. ---
113
+ let row = FIRST_EXERCISE_ROW;
114
+ for (const section of day.sections) {
115
+ if (section.kind === "sportSpecific" || section.kind === "supplemental") {
116
+ // These section kinds are visualized by the fixed bottom template
117
+ // appended later; skip any user-supplied content (the agent should
118
+ // not be filling these in for now).
119
+ continue;
120
+ }
121
+ row = stampSection(ws, row, section);
122
+ }
123
+ // --- Fixed Sport Specific / Supplemental template at the bottom of
124
+ // the sheet, with any freeform notes the trainer/agent supplied. ---
125
+ row = stampSupplementalTemplate(ws, row, day.supplementalNotes);
126
+ // Olympia logo: one-cell anchor with explicit pixel dimensions so the
127
+ // image keeps its native aspect ratio (a two-cell anchor stretches the
128
+ // image to fit the cell range, which distorts the logo vertically).
129
+ if (imageId !== undefined) {
130
+ ws.addImage(imageId, {
131
+ tl: { col: LOGO_ANCHOR_TL.col, row: LOGO_ANCHOR_TL.row },
132
+ ext: { width: LOGO_PIXEL_SIZE.width, height: LOGO_PIXEL_SIZE.height },
133
+ editAs: "oneCell",
134
+ });
135
+ }
136
+ }
137
+ await workbook.xlsx.writeFile(outputPath);
138
+ const stat = await fs.stat(outputPath);
139
+ return { outputPath, bytesWritten: stat.size };
140
+ }
141
+ /**
142
+ * Walk a section's exercises, computing each block's top/bottom border
143
+ * based on neighbor group numbers:
144
+ * - first block in section: top MEDIUM
145
+ * - block whose previous neighbor shares the group number: top HAIR
146
+ * - block whose previous neighbor has a different group number: top MEDIUM
147
+ * - last block in section: group-label bottom MEDIUM
148
+ * - non-last block: group-label bottom NONE (the next block's top provides
149
+ * the visual separator)
150
+ */
151
+ function stampSection(ws, startRow, section) {
152
+ let row = startRow;
153
+ for (let i = 0; i < section.exercises.length; i++) {
154
+ const ex = section.exercises[i];
155
+ const prev = i > 0 ? section.exercises[i - 1] : undefined;
156
+ const prevGroup = groupNumberOf(prev?.groupLabel);
157
+ const myGroup = groupNumberOf(ex.groupLabel);
158
+ const topBorder = prev !== undefined && prevGroup !== undefined && prevGroup === myGroup
159
+ ? "hair"
160
+ : "medium";
161
+ const bottomBorder = i === section.exercises.length - 1 ? "medium" : "none";
162
+ row = stampExercise(ws, row, ex, { topBorder, bottomBorder });
163
+ }
164
+ return row;
165
+ }
166
+ //# sourceMappingURL=build-workbook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-workbook.js","sourceRoot":"","sources":["../../src/writer/build-workbook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export declare const COLUMN_WIDTHS: {
2
+ col: string;
3
+ width: number;
4
+ }[];
5
+ export declare const DATE_ROW = 2;
6
+ export declare const WARMUP_ROW = 3;
7
+ export declare const HEADER_ROW = 5;
8
+ export declare const FIRST_EXERCISE_ROW = 7;
9
+ export declare const WEEK_REPS_COLS: readonly ["D", "F", "H", "J", "L", "N"];
10
+ export declare const WEEK_WEIGHT_COLS: readonly ["E", "G", "I", "K", "M", "O"];
11
+ export declare const NOTES_COL = "P";
12
+ export declare const EXERCISE_NAME_COL = "C";
13
+ export declare const LOGO_ANCHOR_TL: {
14
+ readonly col: 5;
15
+ readonly row: 32;
16
+ };
17
+ export declare const LOGO_PIXEL_SIZE: {
18
+ readonly width: 301;
19
+ readonly height: 70;
20
+ };
21
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/writer/layout.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAoBzD,CAAC;AAEF,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAGpC,eAAO,MAAM,cAAc,yCAA0C,CAAC;AACtE,eAAO,MAAM,gBAAgB,yCAA0C,CAAC;AACxE,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAOrC,eAAO,MAAM,cAAc;;;CAA+B,CAAC;AAC3D,eAAO,MAAM,eAAe;;;CAAsC,CAAC"}
@@ -0,0 +1,40 @@
1
+ // Column widths and row positions extracted from the sample Olympia workbooks.
2
+ // These are constants so output is byte-stable.
3
+ export const COLUMN_WIDTHS = [
4
+ { col: "A", width: 0.66 },
5
+ { col: "B", width: 0.66 },
6
+ { col: "C", width: 25.83 },
7
+ { col: "D", width: 7.66 },
8
+ { col: "E", width: 8.83 },
9
+ { col: "F", width: 7.66 },
10
+ { col: "G", width: 7.83 },
11
+ { col: "H", width: 7.66 },
12
+ { col: "I", width: 8.0 },
13
+ { col: "J", width: 7.66 },
14
+ { col: "K", width: 8.66 },
15
+ { col: "L", width: 7.66 },
16
+ { col: "M", width: 9.33 },
17
+ { col: "N", width: 7.66 },
18
+ { col: "O", width: 8.33 },
19
+ // Notes column — widened from the source's 10.83 so coaching cues fit
20
+ // without wrapping aggressively.
21
+ { col: "P", width: 18 },
22
+ { col: "Q", width: 10.5 },
23
+ ];
24
+ export const DATE_ROW = 2;
25
+ export const WARMUP_ROW = 3;
26
+ export const HEADER_ROW = 5;
27
+ export const FIRST_EXERCISE_ROW = 7;
28
+ // Per-week column pairs: D/E = week 1 reps/weight, F/G = week 2, etc.
29
+ export const WEEK_REPS_COLS = ["D", "F", "H", "J", "L", "N"];
30
+ export const WEEK_WEIGHT_COLS = ["E", "G", "I", "K", "M", "O"];
31
+ export const NOTES_COL = "P";
32
+ export const EXERCISE_NAME_COL = "C";
33
+ // Olympia logo anchor. We use a one-cell anchor with explicit pixel
34
+ // dimensions (rather than a two-cell anchor) so the image keeps its native
35
+ // 693:160 aspect ratio regardless of column/row sizes. The native PNG is
36
+ // 693 × 160 (4.33:1); we render at a smaller pixel size so the logo fits
37
+ // under the supplemental template without overlapping it.
38
+ export const LOGO_ANCHOR_TL = { col: 5, row: 32 };
39
+ export const LOGO_PIXEL_SIZE = { width: 301, height: 70 };
40
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/writer/layout.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,gDAAgD;AAEhD,MAAM,CAAC,MAAM,aAAa,GAAqC;IAC7D,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE;IAC1B,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE;IACxB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;IACzB,sEAAsE;IACtE,iCAAiC;IACjC,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE;IACvB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE;CAC1B,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC;AAC1B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AACtE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AACxE,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAC7B,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,oEAAoE;AACpE,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AACzE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAW,CAAC;AAC3D,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAW,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type ExcelJS from "exceljs";
2
+ import type { Exercise } from "../schema/program.js";
3
+ import { type CTopBorder, type CBottomBorder } from "./styles.js";
4
+ export interface StampOptions {
5
+ /** Border style for the top of the exercise-name (column C) cell. */
6
+ topBorder: CTopBorder;
7
+ /**
8
+ * Border style for the bottom of the group-label (column C) cell. Use
9
+ * "medium" only on the last exercise of a section; otherwise "none".
10
+ */
11
+ bottomBorder: CBottomBorder;
12
+ }
13
+ /**
14
+ * Stamps one exercise block onto the worksheet starting at `startRow`.
15
+ *
16
+ * Layout rules (N = exercise.sets.length):
17
+ * - With groupLabel: name occupies the first N−1 rows of col C (merged
18
+ * when N > 2); group label sits in col C of the last row.
19
+ * - Without groupLabel: name occupies all N rows of col C (merged when
20
+ * N > 1); no group-label row.
21
+ *
22
+ * Borders on column C (the gray bar) are driven by `topBorder` and
23
+ * `bottomBorder` so the caller can express "this block is the first/last
24
+ * of its section" and "this block starts a new group vs. continues one".
25
+ *
26
+ * @returns the next free row after the block.
27
+ */
28
+ export declare function stampExercise(ws: ExcelJS.Worksheet, startRow: number, exercise: Exercise, opts: StampOptions): number;
29
+ /** Extracts the leading numeric portion of a group label ("1A" → "1"). */
30
+ export declare function groupNumberOf(label: string | undefined): string | undefined;
31
+ //# sourceMappingURL=stamp-exercise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamp-exercise.d.ts","sourceRoot":"","sources":["../../src/writer/stamp-exercise.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAOrD,OAAO,EAML,KAAK,UAAU,EACf,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,SAAS,EAAE,UAAU,CAAC;IACtB;;;OAGG;IACH,YAAY,EAAE,aAAa,CAAC;CAC7B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,OAAO,CAAC,SAAS,EACrB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,YAAY,GACjB,MAAM,CAiER;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAI3E"}
@@ -0,0 +1,80 @@
1
+ import { EXERCISE_NAME_COL, NOTES_COL, WEEK_REPS_COLS, WEEK_WEIGHT_COLS, } from "./layout.js";
2
+ import { exerciseNameStyle, groupLabelStyle, NOTES_CELL, weekRepsCellStyle, weekWeightCellStyle, } from "./styles.js";
3
+ /**
4
+ * Stamps one exercise block onto the worksheet starting at `startRow`.
5
+ *
6
+ * Layout rules (N = exercise.sets.length):
7
+ * - With groupLabel: name occupies the first N−1 rows of col C (merged
8
+ * when N > 2); group label sits in col C of the last row.
9
+ * - Without groupLabel: name occupies all N rows of col C (merged when
10
+ * N > 1); no group-label row.
11
+ *
12
+ * Borders on column C (the gray bar) are driven by `topBorder` and
13
+ * `bottomBorder` so the caller can express "this block is the first/last
14
+ * of its section" and "this block starts a new group vs. continues one".
15
+ *
16
+ * @returns the next free row after the block.
17
+ */
18
+ export function stampExercise(ws, startRow, exercise, opts) {
19
+ const setCount = exercise.sets.length;
20
+ const hasGroupLabel = exercise.groupLabel !== undefined;
21
+ const lastBlockRow = startRow + setCount - 1;
22
+ // Where the exercise-name region ends.
23
+ const nameLastRow = hasGroupLabel && setCount > 1 ? lastBlockRow - 1 : lastBlockRow;
24
+ if (nameLastRow > startRow) {
25
+ ws.mergeCells(`${EXERCISE_NAME_COL}${startRow}:${EXERCISE_NAME_COL}${nameLastRow}`);
26
+ }
27
+ const nameCell = ws.getCell(`${EXERCISE_NAME_COL}${startRow}`);
28
+ nameCell.value = exercise.name;
29
+ Object.assign(nameCell, exerciseNameStyle(opts.topBorder));
30
+ if (hasGroupLabel && setCount > 1) {
31
+ const groupCell = ws.getCell(`${EXERCISE_NAME_COL}${lastBlockRow}`);
32
+ groupCell.value = exercise.groupLabel;
33
+ Object.assign(groupCell, groupLabelStyle(opts.bottomBorder));
34
+ }
35
+ else if (!hasGroupLabel && opts.bottomBorder === "medium") {
36
+ // No group label but we still need a bottom border on the merged name
37
+ // cell. Apply both the top and bottom variants by composing.
38
+ const composed = exerciseNameStyle(opts.topBorder);
39
+ // The merged name cell occupies all N rows; its border 'bottom' is what
40
+ // closes the section. Re-apply with bottom merged in.
41
+ composed.border = {
42
+ ...composed.border,
43
+ bottom: { style: "medium", color: { indexed: 64 } },
44
+ };
45
+ Object.assign(nameCell, composed);
46
+ }
47
+ // Notes column P: merge across the whole block when there's more than one row.
48
+ if (setCount > 1) {
49
+ ws.mergeCells(`${NOTES_COL}${startRow}:${NOTES_COL}${lastBlockRow}`);
50
+ }
51
+ const noteCell = ws.getCell(`${NOTES_COL}${startRow}`);
52
+ noteCell.value = exercise.notes ?? "";
53
+ Object.assign(noteCell, NOTES_CELL);
54
+ // Per-set per-week cells
55
+ for (let setIdx = 0; setIdx < setCount; setIdx++) {
56
+ const r = startRow + setIdx;
57
+ const isTop = setIdx === 0;
58
+ const isBottom = setIdx === setCount - 1;
59
+ const setRow = exercise.sets[setIdx];
60
+ for (let weekIdx = 0; weekIdx < 6; weekIdx++) {
61
+ const repsCol = WEEK_REPS_COLS[weekIdx];
62
+ const weightCol = WEEK_WEIGHT_COLS[weekIdx];
63
+ const repsCell = ws.getCell(`${repsCol}${r}`);
64
+ repsCell.value = setRow[weekIdx] ?? null;
65
+ Object.assign(repsCell, weekRepsCellStyle({ isTop, isBottom }));
66
+ const weightCell = ws.getCell(`${weightCol}${r}`);
67
+ weightCell.value = null;
68
+ Object.assign(weightCell, weekWeightCellStyle({ isTop, isBottom }));
69
+ }
70
+ }
71
+ return lastBlockRow + 1;
72
+ }
73
+ /** Extracts the leading numeric portion of a group label ("1A" → "1"). */
74
+ export function groupNumberOf(label) {
75
+ if (!label)
76
+ return undefined;
77
+ const m = label.match(/^(\d+)/);
78
+ return m ? m[1] : undefined;
79
+ }
80
+ //# sourceMappingURL=stamp-exercise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamp-exercise.js","sourceRoot":"","sources":["../../src/writer/stamp-exercise.ts"],"names":[],"mappings":"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"}