@workingdevshero/olympia-ait-adapter 0.1.5 → 1.0.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/README.md +104 -28
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/reader/cell-utils.d.ts +10 -0
- package/dist/reader/cell-utils.d.ts.map +1 -0
- package/dist/reader/cell-utils.js +40 -0
- package/dist/reader/cell-utils.js.map +1 -0
- package/dist/reader/parse-day-sheet.d.ts +0 -1
- package/dist/reader/parse-day-sheet.d.ts.map +1 -1
- package/dist/reader/parse-day-sheet.js +1 -50
- package/dist/reader/parse-day-sheet.js.map +1 -1
- package/dist/reader/parse-warmup-workbook.d.ts +13 -0
- package/dist/reader/parse-warmup-workbook.d.ts.map +1 -0
- package/dist/reader/parse-warmup-workbook.js +154 -0
- package/dist/reader/parse-warmup-workbook.js.map +1 -0
- package/dist/reader/parse-workbook.d.ts +2 -2
- package/dist/reader/parse-workbook.d.ts.map +1 -1
- package/dist/reader/parse-workbook.js +1 -6
- package/dist/reader/parse-workbook.js.map +1 -1
- package/dist/schema/program.d.ts +35 -48
- package/dist/schema/program.d.ts.map +1 -1
- package/dist/schema/program.js +26 -15
- package/dist/schema/program.js.map +1 -1
- package/dist/schema/warmup.d.ts +426 -0
- package/dist/schema/warmup.d.ts.map +1 -0
- package/dist/schema/warmup.js +100 -0
- package/dist/schema/warmup.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +51 -18
- package/dist/server.js.map +1 -1
- package/dist/tools/program-json-to-xlsx.d.ts +3 -0
- package/dist/tools/program-json-to-xlsx.d.ts.map +1 -0
- package/dist/tools/program-json-to-xlsx.js +5 -0
- package/dist/tools/program-json-to-xlsx.js.map +1 -0
- package/dist/tools/program-xlsx-to-json.d.ts +3 -0
- package/dist/tools/program-xlsx-to-json.d.ts.map +1 -0
- package/dist/tools/program-xlsx-to-json.js +5 -0
- package/dist/tools/program-xlsx-to-json.js.map +1 -0
- package/dist/tools/warmup-json-to-xlsx.d.ts +3 -0
- package/dist/tools/warmup-json-to-xlsx.d.ts.map +1 -0
- package/dist/tools/warmup-json-to-xlsx.js +5 -0
- package/dist/tools/warmup-json-to-xlsx.js.map +1 -0
- package/dist/tools/warmup-xlsx-to-json.d.ts +3 -0
- package/dist/tools/warmup-xlsx-to-json.d.ts.map +1 -0
- package/dist/tools/warmup-xlsx-to-json.js +5 -0
- package/dist/tools/warmup-xlsx-to-json.js.map +1 -0
- package/dist/writer/build-warmup-workbook.d.ts +3 -0
- package/dist/writer/build-warmup-workbook.d.ts.map +1 -0
- package/dist/writer/build-warmup-workbook.js +101 -0
- package/dist/writer/build-warmup-workbook.js.map +1 -0
- package/dist/writer/build-workbook.d.ts +2 -2
- package/dist/writer/build-workbook.d.ts.map +1 -1
- package/dist/writer/build-workbook.js +1 -11
- package/dist/writer/build-workbook.js.map +1 -1
- package/package.json +5 -4
- package/skills/olympia-authoring/SKILL.md +124 -0
package/README.md
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
# olympia-ait-adapter
|
|
6
6
|
|
|
7
|
-
MCP adapter that converts Olympia Fitness & Performance
|
|
8
|
-
|
|
9
|
-
[Automate It](https://automate.it.com) framework
|
|
10
|
-
strength-and-conditioning
|
|
11
|
-
touching XLSX directly.
|
|
7
|
+
MCP adapter that converts Olympia Fitness & Performance XLSX workbooks —
|
|
8
|
+
training programs and their companion warm-up documents — to and from strict
|
|
9
|
+
JSON schemas. Built for the [Automate It](https://automate.it.com) framework
|
|
10
|
+
so AI agents can author new strength-and-conditioning paperwork from prior
|
|
11
|
+
client data without ever touching XLSX directly.
|
|
12
12
|
|
|
13
13
|
## Why this exists
|
|
14
14
|
|
|
@@ -19,35 +19,46 @@ embedded logo, and a domain-specific notation (`12E`, `:20E`, `12EL`,
|
|
|
19
19
|
corrupts merges, loses the logo, and can't reason about the implicit
|
|
20
20
|
6-week / 1A-1B / set-by-set structure encoded in the visual layout.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Each program also ships with a **companion warm-up workbook** — same client,
|
|
23
|
+
same date, paired day-for-day (the "Day 4" warm-up column is performed
|
|
24
|
+
before the "Day 4" program sheet). It's a completely different layout: one
|
|
25
|
+
sheet where each day is a column of titled sections of freeform exercise
|
|
26
|
+
lines.
|
|
27
|
+
|
|
28
|
+
This package gives the agent a clean structured view of both document types
|
|
23
29
|
and guarantees well-formed output: the LLM reads JSON, reasons about it,
|
|
24
30
|
writes JSON, and the adapter handles all the visual fidelity on the way
|
|
25
31
|
back to XLSX.
|
|
26
32
|
|
|
27
33
|
## What it does
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
Four MCP tools, exposed over stdio:
|
|
30
36
|
|
|
31
|
-
| Tool
|
|
32
|
-
|
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
37
|
+
| Tool | Direction | Purpose |
|
|
38
|
+
| ------------------------------ | ----------- | ---------------------------------------------------------------- |
|
|
39
|
+
| `olympia_program_xlsx_to_json` | XLSX → JSON | Parse an Olympia program workbook into a typed `Program`. |
|
|
40
|
+
| `olympia_program_json_to_xlsx` | JSON → XLSX | Render a `Program` back to a styled .xlsx on disk. |
|
|
41
|
+
| `olympia_warmup_xlsx_to_json` | XLSX → JSON | Parse a companion warm-up workbook into a typed `WarmUpDocument`. |
|
|
42
|
+
| `olympia_warmup_json_to_xlsx` | JSON → XLSX | Render a `WarmUpDocument` back to a styled .xlsx on disk. |
|
|
35
43
|
|
|
36
|
-
The JSON
|
|
37
|
-
|
|
44
|
+
The JSON schemas are defined once in `src/schema/program.ts` and
|
|
45
|
+
`src/schema/warmup.ts` using Zod and surface as:
|
|
38
46
|
|
|
39
47
|
1. The TypeScript type used internally
|
|
40
48
|
2. The runtime validator at each tool boundary
|
|
41
49
|
3. The JSON Schema published as each tool's `inputSchema` so the LLM sees
|
|
42
50
|
the full typed contract at discovery time
|
|
43
51
|
|
|
44
|
-
|
|
52
|
+
All object schemas are strict: unknown fields are rejected with an error
|
|
53
|
+
rather than silently stripped, so callers authoring against a stale
|
|
54
|
+
contract fail loudly.
|
|
55
|
+
|
|
56
|
+
## JSON shape — programs
|
|
45
57
|
|
|
46
58
|
```ts
|
|
47
59
|
type Program = {
|
|
48
60
|
client: string; // "Bobby Galli"
|
|
49
61
|
programDate: string; // "4.26"
|
|
50
|
-
weekDates?: (string | null)[]; // optional, length 6
|
|
51
62
|
days: Day[];
|
|
52
63
|
};
|
|
53
64
|
|
|
@@ -94,6 +105,10 @@ The schema enforces shape (6 weeks per set, group label regex, allowed
|
|
|
94
105
|
section kinds) but does **not** validate the grammar of each prescription
|
|
95
106
|
string — Olympia's own files include free-form entries in some spots.
|
|
96
107
|
|
|
108
|
+
The `Date:` cells in each sheet's header row are **intentionally left
|
|
109
|
+
blank** — clients hand-write the week dates on the printed sheet, so the
|
|
110
|
+
schema has no field for them and the writer never fills them.
|
|
111
|
+
|
|
97
112
|
### Supplemental area
|
|
98
113
|
|
|
99
114
|
The bottom of every day sheet is a trainer-managed "Sport Specific /
|
|
@@ -107,6 +122,46 @@ drills, recovery work). The adapter:
|
|
|
107
122
|
"Supplemental" labels) at the bottom of every day, then layers any
|
|
108
123
|
supplied `supplementalNotes` on top.
|
|
109
124
|
|
|
125
|
+
## JSON shape — warm-ups
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
type WarmUpDocument = {
|
|
129
|
+
client: string; // "Bobby Galli" — same as companion program
|
|
130
|
+
warmUpDate: string; // "4.26" — matches the companion programDate
|
|
131
|
+
days: WarmUpDay[];
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
type WarmUpDay = {
|
|
135
|
+
name: string; // "Day 4" — matches a program day sheet name
|
|
136
|
+
sections: WarmUpSection[];
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
type WarmUpSection = {
|
|
140
|
+
title: string; // "Foam Roll", "Prep \ Activation X2 Rounds"
|
|
141
|
+
items: WarmUpItem[];
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
type WarmUpItem = {
|
|
145
|
+
text: string; // "Pecs :45E" — prescription inline, never parsed
|
|
146
|
+
note?: string; // "(^R.F.E on SB, F-Roller for Balance^)"
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The warm-up layout is days-as-columns on a single sheet (A, C, E, … with
|
|
151
|
+
empty gutter columns between), sections row-aligned across days. Section
|
|
152
|
+
headers are bold; exercise lines are plain with the prescription shorthand
|
|
153
|
+
embedded in the text (`:45E`, `X8br`, `X4ES` — trainer conventions, kept
|
|
154
|
+
opaque); note lines render in italics under their exercise. When decoding,
|
|
155
|
+
italic or fully-parenthesized lines are treated as notes attached to the
|
|
156
|
+
item above — so never author an item whose entire text is parenthesized.
|
|
157
|
+
|
|
158
|
+
## Skill
|
|
159
|
+
|
|
160
|
+
`skills/olympia-authoring/SKILL.md` (shipped in the npm package) is an
|
|
161
|
+
agent-facing technical reference covering both formats: the schemas, the
|
|
162
|
+
prescription notation, the program/warm-up pairing rules, and the
|
|
163
|
+
decode → author → encode workflow.
|
|
164
|
+
|
|
110
165
|
## Installation
|
|
111
166
|
|
|
112
167
|
```bash
|
|
@@ -143,29 +198,38 @@ Claude or another MCP client, add an entry like:
|
|
|
143
198
|
}
|
|
144
199
|
```
|
|
145
200
|
|
|
146
|
-
|
|
201
|
+
All tools take absolute paths to .xlsx files on the local filesystem.
|
|
147
202
|
|
|
148
203
|
## Repo layout
|
|
149
204
|
|
|
150
205
|
```
|
|
151
206
|
src/
|
|
152
|
-
schema/
|
|
207
|
+
schema/
|
|
208
|
+
program.ts # Zod schema for programs — single source of truth
|
|
209
|
+
warmup.ts # Zod schema for warm-up documents
|
|
153
210
|
reader/
|
|
211
|
+
cell-utils.ts # Shared ExcelJS cell-value helpers
|
|
154
212
|
parse-workbook.ts # Workbook → Program JSON
|
|
155
213
|
parse-day-sheet.ts # One sheet, section detection, supplemental capture
|
|
214
|
+
parse-warmup-workbook.ts # Warm-up workbook → WarmUpDocument JSON
|
|
156
215
|
writer/
|
|
157
216
|
build-workbook.ts # Program → ExcelJS workbook
|
|
217
|
+
build-warmup-workbook.ts # WarmUpDocument → ExcelJS workbook
|
|
158
218
|
layout.ts # Column widths, row positions, logo anchor
|
|
159
219
|
styles.ts # All borders/fonts/fills as constants
|
|
160
220
|
stamp-exercise.ts # One exercise block (variable sets, group borders)
|
|
161
221
|
supplemental-template.ts # Fixed bottom template + supplementalNotes overlay
|
|
162
222
|
tools/
|
|
163
|
-
xlsx-to-json.ts
|
|
164
|
-
json-to-xlsx.ts
|
|
165
|
-
|
|
223
|
+
program-xlsx-to-json.ts # MCP handler
|
|
224
|
+
program-json-to-xlsx.ts # MCP handler
|
|
225
|
+
warmup-xlsx-to-json.ts # MCP handler
|
|
226
|
+
warmup-json-to-xlsx.ts # MCP handler
|
|
227
|
+
server.ts # Bootstrap, registers all four tools
|
|
166
228
|
assets/
|
|
167
229
|
olympia-logo.png # Extracted from sample workbooks; embedded in output
|
|
168
230
|
|
|
231
|
+
skills/
|
|
232
|
+
olympia-authoring/SKILL.md # Agent-facing technical reference for both formats
|
|
169
233
|
samples/ # Real Olympia .xlsx files used as round-trip fixtures
|
|
170
234
|
scripts/ # Inspection helpers used during development
|
|
171
235
|
tests/ # vitest — schema, reader, round-trip, MCP smoke
|
|
@@ -181,16 +245,28 @@ npm run typecheck # tsc --noEmit
|
|
|
181
245
|
|
|
182
246
|
Coverage:
|
|
183
247
|
|
|
184
|
-
- **`schema.test.ts`** — Zod rejects malformed input (week count,
|
|
185
|
-
label regex, section kinds, missing fields).
|
|
186
|
-
- **`reader.test.ts`** — All three
|
|
187
|
-
|
|
188
|
-
|
|
248
|
+
- **`schema.test.ts`** — Zod rejects malformed program input (week count,
|
|
249
|
+
group label regex, section kinds, missing fields).
|
|
250
|
+
- **`reader.test.ts`** — All three program samples parse into schema-valid
|
|
251
|
+
`Program` documents; spot checks on Bobby's first exercise, Giulianna's
|
|
252
|
+
variable set counts, exercise notes, supplemental notes.
|
|
189
253
|
- **`round-trip.test.ts`** — The primary determinism guarantee: for each
|
|
190
|
-
sample, `parse → write → parse` produces JSON equal to the
|
|
254
|
+
program sample, `parse → write → parse` produces JSON equal to the
|
|
255
|
+
original.
|
|
256
|
+
- **`warmup-doc-schema.test.ts`** — Zod accept/reject tables for the
|
|
257
|
+
warm-up document schema.
|
|
258
|
+
- **`warmup-doc-reader.test.ts`** — The warm-up sample parses into a
|
|
259
|
+
schema-valid `WarmUpDocument` with verbatim spot checks (including the
|
|
260
|
+
note-attachment rule); warning paths for irregular layouts.
|
|
261
|
+
- **`warmup-doc-writer.test.ts`** — Day/section/item/note styling and
|
|
262
|
+
cross-day section alignment, verified by re-reading the written file.
|
|
263
|
+
- **`warmup-doc-round-trip.test.ts`** — Warm-up determinism: the sample
|
|
264
|
+
and an authored document both survive `parse → write → parse` /
|
|
265
|
+
`write → parse` unchanged.
|
|
191
266
|
- **`mcp-smoke.test.ts`** — End-to-end MCP server check via the in-memory
|
|
192
|
-
transport: tool discovery, round-
|
|
193
|
-
|
|
267
|
+
transport: tool discovery (all four tools), round-trips via
|
|
268
|
+
`client.callTool` for both document types, schema rejection of malformed
|
|
269
|
+
input at the SDK boundary.
|
|
194
270
|
|
|
195
271
|
## Determinism
|
|
196
272
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export { Program, Day, Section, SectionKind, Exercise, SetRow, Cell, SupplementalNote,
|
|
1
|
+
export { Program, Day, Section, SectionKind, Exercise, SetRow, Cell, SupplementalNote, ProgramXlsxToJsonInput, ProgramXlsxToJsonOutput, ProgramJsonToXlsxInput, ProgramJsonToXlsxOutput, } from "./schema/program.js";
|
|
2
|
+
export { WarmUpDocument, WarmUpDay, WarmUpSection, WarmUpItem, WarmUpXlsxToJsonInput, WarmUpXlsxToJsonOutput, WarmUpJsonToXlsxInput, WarmUpJsonToXlsxOutput, } from "./schema/warmup.js";
|
|
2
3
|
export { createServer } from "./server.js";
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
4
|
+
export { parseProgramWorkbook } from "./reader/parse-workbook.js";
|
|
5
|
+
export { buildProgramWorkbook } from "./writer/build-workbook.js";
|
|
6
|
+
export { parseWarmUpWorkbook } from "./reader/parse-warmup-workbook.js";
|
|
7
|
+
export { buildWarmUpWorkbook } from "./writer/build-warmup-workbook.js";
|
|
5
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,OAAO,EACP,GAAG,EACH,OAAO,EACP,WAAW,EACX,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,gBAAgB,EAChB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,OAAO,EACP,GAAG,EACH,OAAO,EACP,WAAW,EACX,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,cAAc,EACd,SAAS,EACT,aAAa,EACb,UAAU,EACV,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// Programmatic entry point. The package's primary use is the MCP CLI
|
|
2
2
|
// (registered under the `bin` field), but the schema and server factory
|
|
3
3
|
// are also exported for callers that want to embed or compose the adapter.
|
|
4
|
-
export { Program, Day, Section, SectionKind, Exercise, SetRow, Cell, SupplementalNote,
|
|
4
|
+
export { Program, Day, Section, SectionKind, Exercise, SetRow, Cell, SupplementalNote, ProgramXlsxToJsonInput, ProgramXlsxToJsonOutput, ProgramJsonToXlsxInput, ProgramJsonToXlsxOutput, } from "./schema/program.js";
|
|
5
|
+
export { WarmUpDocument, WarmUpDay, WarmUpSection, WarmUpItem, WarmUpXlsxToJsonInput, WarmUpXlsxToJsonOutput, WarmUpJsonToXlsxInput, WarmUpJsonToXlsxOutput, } from "./schema/warmup.js";
|
|
5
6
|
export { createServer } from "./server.js";
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
7
|
+
export { parseProgramWorkbook } from "./reader/parse-workbook.js";
|
|
8
|
+
export { buildProgramWorkbook } from "./writer/build-workbook.js";
|
|
9
|
+
export { parseWarmUpWorkbook } from "./reader/parse-warmup-workbook.js";
|
|
10
|
+
export { buildWarmUpWorkbook } from "./writer/build-warmup-workbook.js";
|
|
8
11
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,wEAAwE;AACxE,2EAA2E;AAE3E,OAAO,EACL,OAAO,EACP,GAAG,EACH,OAAO,EACP,WAAW,EACX,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,gBAAgB,EAChB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,wEAAwE;AACxE,2EAA2E;AAE3E,OAAO,EACL,OAAO,EACP,GAAG,EACH,OAAO,EACP,WAAW,EACX,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,cAAc,EACd,SAAS,EACT,aAAa,EACb,UAAU,EACV,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type ExcelJS from "exceljs";
|
|
2
|
+
/**
|
|
3
|
+
* Best-effort plain-text extraction from an ExcelJS cell value: handles
|
|
4
|
+
* strings, numbers, booleans, dates, rich text, hyperlink-style `{ text }`
|
|
5
|
+
* objects, and formula results. Anything else becomes "".
|
|
6
|
+
*/
|
|
7
|
+
export declare function cellText(value: ExcelJS.CellValue | undefined | null): string;
|
|
8
|
+
export declare function cellOrNull(value: ExcelJS.CellValue | undefined | null): string | null;
|
|
9
|
+
export declare function isMergeContinuation(cell: ExcelJS.Cell): boolean;
|
|
10
|
+
//# sourceMappingURL=cell-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cell-utils.d.ts","sourceRoot":"","sources":["../../src/reader/cell-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAEnC;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAmB5E;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,OAAO,CAAC,SAAS,GAAG,SAAS,GAAG,IAAI,GAC1C,MAAM,GAAG,IAAI,CAGf;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,GAAG,OAAO,CAI/D"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort plain-text extraction from an ExcelJS cell value: handles
|
|
3
|
+
* strings, numbers, booleans, dates, rich text, hyperlink-style `{ text }`
|
|
4
|
+
* objects, and formula results. Anything else becomes "".
|
|
5
|
+
*/
|
|
6
|
+
export function cellText(value) {
|
|
7
|
+
if (value === null || value === undefined)
|
|
8
|
+
return "";
|
|
9
|
+
if (typeof value === "string")
|
|
10
|
+
return value;
|
|
11
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
12
|
+
return String(value);
|
|
13
|
+
if (value instanceof Date)
|
|
14
|
+
return value.toISOString();
|
|
15
|
+
if (typeof value === "object") {
|
|
16
|
+
if ("richText" in value && Array.isArray(value.richText)) {
|
|
17
|
+
return value.richText
|
|
18
|
+
.map((r) => r.text)
|
|
19
|
+
.join("");
|
|
20
|
+
}
|
|
21
|
+
if ("text" in value && typeof value.text === "string") {
|
|
22
|
+
return value.text;
|
|
23
|
+
}
|
|
24
|
+
if ("result" in value) {
|
|
25
|
+
return cellText(value.result);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
export function cellOrNull(value) {
|
|
31
|
+
const t = cellText(value).trim();
|
|
32
|
+
return t === "" ? null : t;
|
|
33
|
+
}
|
|
34
|
+
export function isMergeContinuation(cell) {
|
|
35
|
+
if (!cell.isMerged)
|
|
36
|
+
return false;
|
|
37
|
+
const master = cell.master;
|
|
38
|
+
return master !== undefined && master.address !== cell.address;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=cell-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cell-utils.js","sourceRoot":"","sources":["../../src/reader/cell-utils.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,KAA2C;IAClE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAClF,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;IACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,UAAU,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,CAAE,KAA+B,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpF,OAAQ,KAA0C,CAAC,QAAQ;iBACxD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;iBAClB,IAAI,CAAC,EAAE,CAAC,CAAC;QACd,CAAC;QACD,IAAI,MAAM,IAAI,KAAK,IAAI,OAAQ,KAA2B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7E,OAAQ,KAA0B,CAAC,IAAI,CAAC;QAC1C,CAAC;QACD,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;YACtB,OAAO,QAAQ,CAAE,KAAuC,CAAC,MAAM,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,KAA2C;IAE3C,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAkB;IACpD,IAAI,CAAC,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC;AACjE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-day-sheet.d.ts","sourceRoot":"","sources":["../../src/reader/parse-day-sheet.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EACV,gBAAgB,EAEhB,GAAG,EAIJ,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"parse-day-sheet.d.ts","sourceRoot":"","sources":["../../src/reader/parse-day-sheet.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EACV,gBAAgB,EAEhB,GAAG,EAIJ,MAAM,sBAAsB,CAAC;AAG9B,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAC1B,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA2CD,wBAAgB,aAAa,CAAC,EAAE,EAAE,OAAO,CAAC,SAAS,GAAG,SAAS,CA8F9D"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cellText, cellOrNull, isMergeContinuation } from "./cell-utils.js";
|
|
1
2
|
const FIRST_BODY_ROW = 7;
|
|
2
3
|
const WEEK_COLS = ["D", "F", "H", "J", "L", "N"];
|
|
3
4
|
const SECTION_LABELS = new Map([
|
|
@@ -20,63 +21,14 @@ const SUPPLEMENTAL_COLS = ["C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"
|
|
|
20
21
|
function isGroupLabel(s) {
|
|
21
22
|
return /^\d+[A-Z]$/.test(s);
|
|
22
23
|
}
|
|
23
|
-
function cellText(value) {
|
|
24
|
-
if (value === null || value === undefined)
|
|
25
|
-
return "";
|
|
26
|
-
if (typeof value === "string")
|
|
27
|
-
return value;
|
|
28
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
29
|
-
return String(value);
|
|
30
|
-
if (value instanceof Date)
|
|
31
|
-
return value.toISOString();
|
|
32
|
-
if (typeof value === "object") {
|
|
33
|
-
if ("richText" in value && Array.isArray(value.richText)) {
|
|
34
|
-
return value.richText
|
|
35
|
-
.map((r) => r.text)
|
|
36
|
-
.join("");
|
|
37
|
-
}
|
|
38
|
-
if ("text" in value && typeof value.text === "string") {
|
|
39
|
-
return value.text;
|
|
40
|
-
}
|
|
41
|
-
if ("result" in value) {
|
|
42
|
-
return cellText(value.result);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return "";
|
|
46
|
-
}
|
|
47
|
-
function cellOrNull(value) {
|
|
48
|
-
const t = cellText(value).trim();
|
|
49
|
-
return t === "" ? null : t;
|
|
50
|
-
}
|
|
51
|
-
function isMergeContinuation(cell) {
|
|
52
|
-
if (!cell.isMerged)
|
|
53
|
-
return false;
|
|
54
|
-
const master = cell.master;
|
|
55
|
-
return master !== undefined && master.address !== cell.address;
|
|
56
|
-
}
|
|
57
24
|
function readWeekRow(row) {
|
|
58
25
|
return WEEK_COLS.map((col) => cellOrNull(row.getCell(col).value));
|
|
59
26
|
}
|
|
60
|
-
function readWeekDates(row) {
|
|
61
|
-
return WEEK_COLS.map((col) => {
|
|
62
|
-
const t = cellText(row.getCell(col).value).trim();
|
|
63
|
-
if (t === "" || t.toLowerCase() === "date:")
|
|
64
|
-
return null;
|
|
65
|
-
return t;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
27
|
export function parseDaySheet(ws) {
|
|
69
28
|
const warnings = [];
|
|
70
29
|
const sections = [];
|
|
71
30
|
let current = { kind: "main", exercises: [] };
|
|
72
31
|
let exercise = null;
|
|
73
|
-
let weekDates;
|
|
74
|
-
const dateRow = ws.getRow(2);
|
|
75
|
-
if (dateRow) {
|
|
76
|
-
const dates = readWeekDates(dateRow);
|
|
77
|
-
if (dates.some((d) => d !== null))
|
|
78
|
-
weekDates = dates;
|
|
79
|
-
}
|
|
80
32
|
const flushExercise = (groupLabel) => {
|
|
81
33
|
if (!exercise)
|
|
82
34
|
return;
|
|
@@ -153,7 +105,6 @@ export function parseDaySheet(ws) {
|
|
|
153
105
|
}
|
|
154
106
|
return {
|
|
155
107
|
sections: nonEmpty,
|
|
156
|
-
...(weekDates ? { weekDates } : {}),
|
|
157
108
|
...(supplementalNotes && supplementalNotes.length > 0
|
|
158
109
|
? { supplementalNotes }
|
|
159
110
|
: {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-day-sheet.js","sourceRoot":"","sources":["../../src/reader/parse-day-sheet.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"parse-day-sheet.js","sourceRoot":"","sources":["../../src/reader/parse-day-sheet.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WarmUpXlsxToJsonOutput } from "../schema/warmup.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse an Olympia warm-up workbook (the companion document to a program
|
|
4
|
+
* workbook) into a WarmUpDocument.
|
|
5
|
+
*
|
|
6
|
+
* Layout: a single sheet where each day is a column (with empty gutter
|
|
7
|
+
* columns between), starting with a day label in the first non-empty row.
|
|
8
|
+
* Below it, each day column is a vertical run of bold section headers,
|
|
9
|
+
* plain exercise lines, and note lines. Blank cells are structural padding
|
|
10
|
+
* and carry no information.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseWarmUpWorkbook(xlsxPath: string): Promise<WarmUpXlsxToJsonOutput>;
|
|
13
|
+
//# sourceMappingURL=parse-warmup-workbook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-warmup-workbook.d.ts","sourceRoot":"","sources":["../../src/reader/parse-warmup-workbook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,sBAAsB,EACvB,MAAM,qBAAqB,CAAC;AAG7B;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,CAAC,CAiDjC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import ExcelJS from "exceljs";
|
|
2
|
+
import { cellText } from "./cell-utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Parse an Olympia warm-up workbook (the companion document to a program
|
|
5
|
+
* workbook) into a WarmUpDocument.
|
|
6
|
+
*
|
|
7
|
+
* Layout: a single sheet where each day is a column (with empty gutter
|
|
8
|
+
* columns between), starting with a day label in the first non-empty row.
|
|
9
|
+
* Below it, each day column is a vertical run of bold section headers,
|
|
10
|
+
* plain exercise lines, and note lines. Blank cells are structural padding
|
|
11
|
+
* and carry no information.
|
|
12
|
+
*/
|
|
13
|
+
export async function parseWarmUpWorkbook(xlsxPath) {
|
|
14
|
+
const workbook = new ExcelJS.Workbook();
|
|
15
|
+
await workbook.xlsx.readFile(xlsxPath);
|
|
16
|
+
const warnings = [];
|
|
17
|
+
const days = [];
|
|
18
|
+
for (const ws of workbook.worksheets) {
|
|
19
|
+
days.push(...parseWarmUpSheet(ws, warnings));
|
|
20
|
+
}
|
|
21
|
+
if (days.length === 0) {
|
|
22
|
+
warnings.push("No day columns found in any sheet.");
|
|
23
|
+
days.push({ name: "Day 1", sections: [] });
|
|
24
|
+
}
|
|
25
|
+
// Prefer workbook custom properties (set by our own writer); fall back to
|
|
26
|
+
// filename parsing for source files coming straight from Olympia.
|
|
27
|
+
let clientName;
|
|
28
|
+
let warmUpDate;
|
|
29
|
+
const subject = typeof workbook.subject === "string" ? workbook.subject.trim() : "";
|
|
30
|
+
const keywords = typeof workbook.keywords === "string" ? workbook.keywords.trim() : "";
|
|
31
|
+
if (subject && keywords) {
|
|
32
|
+
clientName = subject;
|
|
33
|
+
warmUpDate = keywords;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const match = xlsxPath.match(/([^/\\]+?)\s+Warm\s?Ups?\s+([\d.]+)\.xlsx$/i);
|
|
37
|
+
if (match && match[1] !== undefined && match[2] !== undefined) {
|
|
38
|
+
clientName = match[1];
|
|
39
|
+
warmUpDate = match[2];
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
clientName = "Unknown";
|
|
43
|
+
warmUpDate = "";
|
|
44
|
+
warnings.push(`Could not derive client name or warm-up date from filename or workbook properties: ${xlsxPath}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
warmUp: {
|
|
49
|
+
client: clientName,
|
|
50
|
+
warmUpDate,
|
|
51
|
+
days,
|
|
52
|
+
},
|
|
53
|
+
warnings,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function parseWarmUpSheet(ws, warnings) {
|
|
57
|
+
// The first row containing any non-empty cell holds the day labels.
|
|
58
|
+
let headerRow;
|
|
59
|
+
for (let r = 1; r <= ws.rowCount; r++) {
|
|
60
|
+
const row = ws.getRow(r);
|
|
61
|
+
let found = false;
|
|
62
|
+
row.eachCell({ includeEmpty: false }, (cell) => {
|
|
63
|
+
if (cellText(cell.value).trim() !== "")
|
|
64
|
+
found = true;
|
|
65
|
+
});
|
|
66
|
+
if (found) {
|
|
67
|
+
headerRow = r;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (headerRow === undefined)
|
|
72
|
+
return []; // empty sheet
|
|
73
|
+
const days = [];
|
|
74
|
+
const header = ws.getRow(headerRow);
|
|
75
|
+
header.eachCell({ includeEmpty: false }, (cell, colNumber) => {
|
|
76
|
+
const name = cellText(cell.value).trim();
|
|
77
|
+
if (name === "")
|
|
78
|
+
return;
|
|
79
|
+
days.push(parseDayColumn(ws, colNumber, headerRow, name, warnings));
|
|
80
|
+
});
|
|
81
|
+
return days;
|
|
82
|
+
}
|
|
83
|
+
function parseDayColumn(ws, col, headerRow, name, warnings) {
|
|
84
|
+
const prefix = `[${ws.name}] "${name}"`;
|
|
85
|
+
const sections = [];
|
|
86
|
+
let current;
|
|
87
|
+
for (let r = headerRow + 1; r <= ws.rowCount; r++) {
|
|
88
|
+
const cell = ws.getRow(r).getCell(col);
|
|
89
|
+
const text = cellText(cell.value).trim();
|
|
90
|
+
if (text === "")
|
|
91
|
+
continue; // blank = structural padding
|
|
92
|
+
const { bold, italic } = fontFlags(cell);
|
|
93
|
+
const isNote = italic || (!bold && /^\(.*\)$/.test(text));
|
|
94
|
+
if (isNote) {
|
|
95
|
+
const lastItem = current?.items[current.items.length - 1];
|
|
96
|
+
if (lastItem) {
|
|
97
|
+
lastItem.note = lastItem.note ? `${lastItem.note}\n${text}` : text;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// A note with nothing to attach to — keep the text as an item so it
|
|
101
|
+
// isn't lost. Re-parsing the re-rendered file demotes it identically,
|
|
102
|
+
// so round-trips stay stable.
|
|
103
|
+
warnings.push(`${prefix} row ${r}: note "${text}" has no preceding exercise; kept as an item`);
|
|
104
|
+
current = ensureSection(sections, current, warnings, prefix, r);
|
|
105
|
+
current.items.push({ text });
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (bold) {
|
|
110
|
+
current = { title: text, items: [] };
|
|
111
|
+
sections.push(current);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
current = ensureSection(sections, current, warnings, prefix, r);
|
|
115
|
+
current.items.push({ text });
|
|
116
|
+
}
|
|
117
|
+
return { name, sections };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Bold/italic flags for note/header classification. `cell.font` is the
|
|
121
|
+
* primary source, but Excel can carry the formatting on rich-text runs
|
|
122
|
+
* while leaving the cell-level font unset — when a flag is undefined at
|
|
123
|
+
* the cell level, fall back to the runs (every non-blank run must carry
|
|
124
|
+
* the flag). The parenthesized-text rule in the caller remains the
|
|
125
|
+
* style-independent safety net.
|
|
126
|
+
*/
|
|
127
|
+
function fontFlags(cell) {
|
|
128
|
+
const font = cell.font ?? {};
|
|
129
|
+
let bold = font.bold;
|
|
130
|
+
let italic = font.italic;
|
|
131
|
+
if (bold === undefined || italic === undefined) {
|
|
132
|
+
const value = cell.value;
|
|
133
|
+
if (value !== null &&
|
|
134
|
+
typeof value === "object" &&
|
|
135
|
+
"richText" in value &&
|
|
136
|
+
Array.isArray(value.richText)) {
|
|
137
|
+
const runs = value.richText.filter((run) => (run.text ?? "").trim() !== "");
|
|
138
|
+
if (runs.length > 0) {
|
|
139
|
+
bold ??= runs.every((run) => run.font?.bold === true);
|
|
140
|
+
italic ??= runs.every((run) => run.font?.italic === true);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { bold: bold === true, italic: italic === true };
|
|
145
|
+
}
|
|
146
|
+
function ensureSection(sections, current, warnings, prefix, row) {
|
|
147
|
+
if (current)
|
|
148
|
+
return current;
|
|
149
|
+
warnings.push(`${prefix} row ${row}: content before any section header; opened an untitled section`);
|
|
150
|
+
const section = { title: "", items: [] };
|
|
151
|
+
sections.push(section);
|
|
152
|
+
return section;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=parse-warmup-workbook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-warmup-workbook.js","sourceRoot":"","sources":["../../src/reader/parse-warmup-workbook.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function
|
|
1
|
+
import type { ProgramXlsxToJsonOutput } from "../schema/program.js";
|
|
2
|
+
export declare function parseProgramWorkbook(xlsxPath: string): Promise<ProgramXlsxToJsonOutput>;
|
|
3
3
|
//# sourceMappingURL=parse-workbook.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-workbook.d.ts","sourceRoot":"","sources":["../../src/reader/parse-workbook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"parse-workbook.d.ts","sourceRoot":"","sources":["../../src/reader/parse-workbook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAGpE,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,uBAAuB,CAAC,CAoDlC"}
|