Convert uploaded .xlsx files to csv artifacts server-side

Uploading a .xlsx file no longer becomes a generic downloadable binary -
it's read server-side with openpyxl and converted to a csv artifact,
rendering in the same interactive grid as a native CSV post. Detected by
content type or .xlsx extension (some clients send generic/incorrect
content types for multipart uploads). Only the active sheet converts;
formula cells use whatever cached result the spreadsheet software last
saved (openpyxl doesn't evaluate formulas itself). Corrupt/unreadable
files return 400.

Since the existing posting script already forwards any non-text file via
multipart with no special-casing, `daybook_post.py balance_sheet.xlsx`
already works with zero script changes - verified live.

Two real bugs found and fixed while building this, both now covered by
regression tests:
- {**a_querydict, ...} silently wrapped every value (including title) in
  a list - Python's ** spread on a dict subclass bypasses QueryDict's
  __getitem__ override and exposes its internal one-list-per-key storage
  directly. Fixed by building the delegated dict explicitly via .get().
- Pre-setting None for absent optional fields (e.g. source_prompt) defeated
  create_text_artifact's own data.get(field, default) calls, since .get()
  only falls through to its default when the key is *missing*, not when
  the value is None - caused a NOT NULL constraint failure. Fixed by only
  setting keys that were actually provided.
- csv.writer's default "\r\n" terminator was getting doubled by
  Path.write_text()'s own newline translation on Windows, producing a
  blank line between every row. Fixed with lineterminator="\n".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
peter 2026-09-02 16:29:39 +01:00
parent 4145c37dfe
commit 4b5ec09ae2
6 changed files with 232 additions and 6 deletions

View file

@ -120,6 +120,19 @@ python skills/daybook-posting/scripts/daybook_post.py results.csv
python skills/daybook-posting/scripts/daybook_post.py --kind json --title "Run Summary" --content-file summary.json
```
`.xlsx` spreadsheets work too, with no special flag needed — just `--file`
(or post it positionally, same as any other file):
```bash
python skills/daybook-posting/scripts/daybook_post.py balance_sheet.xlsx
```
The server converts it to a `csv` artifact automatically (active sheet
only; see `docs/api.md` for the full list of conversion limitations —
notably, formulas need to have been calculated by real spreadsheet software
that saved the file, and rich formatting like bold/colors/merged cells
isn't preserved).
For other code that should render today, use `--file path --as-markdown` to
post it as a Markdown fenced code block. Without `--as-markdown`, it uploads
as a downloadable binary file.