@kortix/sandbox 0.4.1
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/config/customize.sh +143 -0
- package/config/kortix-env-setup.sh +25 -0
- package/kortix-master/package.json +22 -0
- package/kortix-master/src/config.ts +22 -0
- package/kortix-master/src/index.ts +44 -0
- package/kortix-master/src/routes/env.ts +65 -0
- package/kortix-master/src/routes/proxy.ts +108 -0
- package/kortix-master/src/routes/update.ts +185 -0
- package/kortix-master/src/services/proxy.ts +43 -0
- package/kortix-master/src/services/secret-store.ts +156 -0
- package/kortix-master/tsconfig.json +14 -0
- package/opencode/agents/kortix-browser.md +142 -0
- package/opencode/agents/kortix-build.md +62 -0
- package/opencode/agents/kortix-explore.md +66 -0
- package/opencode/agents/kortix-image-gen.md +33 -0
- package/opencode/agents/kortix-main.md +450 -0
- package/opencode/agents/kortix-plan.md +100 -0
- package/opencode/agents/kortix-research.md +84 -0
- package/opencode/agents/kortix-sheets.md +61 -0
- package/opencode/agents/kortix-slides.md +64 -0
- package/opencode/agents/kortix-web-dev.md +572 -0
- package/opencode/commands/email.md +36 -0
- package/opencode/commands/init.md +43 -0
- package/opencode/commands/journal.md +44 -0
- package/opencode/commands/memory-init.md +81 -0
- package/opencode/commands/memory-search.md +50 -0
- package/opencode/commands/memory-status.md +56 -0
- package/opencode/commands/research.md +36 -0
- package/opencode/commands/search.md +38 -0
- package/opencode/commands/slides.md +32 -0
- package/opencode/commands/spreadsheet.md +30 -0
- package/opencode/memory.json +37 -0
- package/opencode/ocx.jsonc +10 -0
- package/opencode/opencode.jsonc +103 -0
- package/opencode/package.json +25 -0
- package/opencode/patches/apply.sh +19 -0
- package/opencode/patches/opencode-pty-spawn.txt +49 -0
- package/opencode/plugin/background-agents.ts.disabled +483 -0
- package/opencode/plugin/kdco-primitives/get-project-id.ts +172 -0
- package/opencode/plugin/kdco-primitives/index.ts +26 -0
- package/opencode/plugin/kdco-primitives/log-warn.ts +51 -0
- package/opencode/plugin/kdco-primitives/mutex.ts +122 -0
- package/opencode/plugin/kdco-primitives/shell.ts +138 -0
- package/opencode/plugin/kdco-primitives/temp.ts +36 -0
- package/opencode/plugin/kdco-primitives/terminal-detect.ts +34 -0
- package/opencode/plugin/kdco-primitives/types.ts +13 -0
- package/opencode/plugin/kdco-primitives/with-timeout.ts +84 -0
- package/opencode/plugin/memory.ts +306 -0
- package/opencode/plugin/worktree/state.ts +412 -0
- package/opencode/plugin/worktree/terminal.ts +1002 -0
- package/opencode/plugin/worktree.ts +861 -0
- package/opencode/skills/KORTIX-browser/SKILL.md +478 -0
- package/opencode/skills/KORTIX-cron-triggers/SKILL.md +173 -0
- package/opencode/skills/KORTIX-deep-research/SKILL.md +278 -0
- package/opencode/skills/KORTIX-docx/SKILL.md +398 -0
- package/opencode/skills/KORTIX-docx/scripts/__init__.py +1 -0
- package/opencode/skills/KORTIX-docx/scripts/accept_changes.py +104 -0
- package/opencode/skills/KORTIX-docx/scripts/comment.py +244 -0
- package/opencode/skills/KORTIX-docx/scripts/office/helpers/__init__.py +0 -0
- package/opencode/skills/KORTIX-docx/scripts/office/helpers/merge_runs.py +199 -0
- package/opencode/skills/KORTIX-docx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/opencode/skills/KORTIX-docx/scripts/office/pack.py +159 -0
- package/opencode/skills/KORTIX-docx/scripts/office/soffice.py +183 -0
- package/opencode/skills/KORTIX-docx/scripts/office/unpack.py +132 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validate.py +111 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validators/__init__.py +15 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validators/base.py +847 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validators/docx.py +446 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validators/pptx.py +275 -0
- package/opencode/skills/KORTIX-docx/scripts/office/validators/redlining.py +247 -0
- package/opencode/skills/KORTIX-docx/scripts/render_docx.py +179 -0
- package/opencode/skills/KORTIX-docx/scripts/templates/comments.xml +3 -0
- package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtended.xml +3 -0
- package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtensible.xml +3 -0
- package/opencode/skills/KORTIX-docx/scripts/templates/commentsIds.xml +3 -0
- package/opencode/skills/KORTIX-docx/scripts/templates/people.xml +3 -0
- package/opencode/skills/KORTIX-domain-research/SKILL.md +96 -0
- package/opencode/skills/KORTIX-domain-research/scripts/domain-lookup.py +810 -0
- package/opencode/skills/KORTIX-elevenlabs/SKILL.md +230 -0
- package/opencode/skills/KORTIX-elevenlabs/scripts/tts.py +389 -0
- package/opencode/skills/KORTIX-email/SKILL.md +145 -0
- package/opencode/skills/KORTIX-legal-writer/SKILL.md +409 -0
- package/opencode/skills/KORTIX-legal-writer/references/bluebook.md +152 -0
- package/opencode/skills/KORTIX-legal-writer/references/document-types.md +416 -0
- package/opencode/skills/KORTIX-legal-writer/scripts/courtlistener.py +291 -0
- package/opencode/skills/KORTIX-legal-writer/scripts/ecfr_lookup.py +299 -0
- package/opencode/skills/KORTIX-legal-writer/scripts/verify-legal.py +507 -0
- package/opencode/skills/KORTIX-logo-creator/SKILL.md +293 -0
- package/opencode/skills/KORTIX-logo-creator/references/prompt-patterns.md +134 -0
- package/opencode/skills/KORTIX-logo-creator/scripts/compose_logo.py +406 -0
- package/opencode/skills/KORTIX-logo-creator/scripts/create_logo_sheet.py +258 -0
- package/opencode/skills/KORTIX-logo-creator/scripts/remove_bg.py +96 -0
- package/opencode/skills/KORTIX-memory/SKILL.md +261 -0
- package/opencode/skills/KORTIX-memory/scripts/export-sessions.py +409 -0
- package/opencode/skills/KORTIX-paper-creator/SKILL.md +549 -0
- package/opencode/skills/KORTIX-paper-creator/assets/template.tex +101 -0
- package/opencode/skills/KORTIX-paper-creator/scripts/compile.sh +177 -0
- package/opencode/skills/KORTIX-paper-creator/scripts/openalex_to_bibtex.py +220 -0
- package/opencode/skills/KORTIX-paper-creator/scripts/verify.sh +354 -0
- package/opencode/skills/KORTIX-paper-search/SKILL.md +418 -0
- package/opencode/skills/KORTIX-pdf/SKILL.md +232 -0
- package/opencode/skills/KORTIX-pdf/forms.md +36 -0
- package/opencode/skills/KORTIX-pdf/reference.md +105 -0
- package/opencode/skills/KORTIX-pdf/scripts/check_bounding_boxes.py +65 -0
- package/opencode/skills/KORTIX-pdf/scripts/check_fillable_fields.py +11 -0
- package/opencode/skills/KORTIX-pdf/scripts/convert_pdf_to_images.py +33 -0
- package/opencode/skills/KORTIX-pdf/scripts/create_validation_image.py +37 -0
- package/opencode/skills/KORTIX-pdf/scripts/extract_form_field_info.py +122 -0
- package/opencode/skills/KORTIX-pdf/scripts/extract_form_structure.py +115 -0
- package/opencode/skills/KORTIX-pdf/scripts/fill_fillable_fields.py +98 -0
- package/opencode/skills/KORTIX-pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/opencode/skills/KORTIX-plan/SKILL.md +228 -0
- package/opencode/skills/KORTIX-presentation-viewer/SKILL.md +87 -0
- package/opencode/skills/KORTIX-presentation-viewer/serve.ts +136 -0
- package/opencode/skills/KORTIX-presentation-viewer/viewer.html +559 -0
- package/opencode/skills/KORTIX-presentations/SKILL.md +344 -0
- package/opencode/skills/KORTIX-remotion/SKILL.md +56 -0
- package/opencode/skills/KORTIX-remotion/rules/3d.md +86 -0
- package/opencode/skills/KORTIX-remotion/rules/animations.md +29 -0
- package/opencode/skills/KORTIX-remotion/rules/assets.md +78 -0
- package/opencode/skills/KORTIX-remotion/rules/audio-visualization.md +198 -0
- package/opencode/skills/KORTIX-remotion/rules/audio.md +169 -0
- package/opencode/skills/KORTIX-remotion/rules/calculate-metadata.md +104 -0
- package/opencode/skills/KORTIX-remotion/rules/can-decode.md +75 -0
- package/opencode/skills/KORTIX-remotion/rules/charts.md +120 -0
- package/opencode/skills/KORTIX-remotion/rules/compositions.md +141 -0
- package/opencode/skills/KORTIX-remotion/rules/display-captions.md +184 -0
- package/opencode/skills/KORTIX-remotion/rules/extract-frames.md +229 -0
- package/opencode/skills/KORTIX-remotion/rules/ffmpeg.md +38 -0
- package/opencode/skills/KORTIX-remotion/rules/fonts.md +152 -0
- package/opencode/skills/KORTIX-remotion/rules/get-audio-duration.md +58 -0
- package/opencode/skills/KORTIX-remotion/rules/get-video-dimensions.md +68 -0
- package/opencode/skills/KORTIX-remotion/rules/get-video-duration.md +58 -0
- package/opencode/skills/KORTIX-remotion/rules/gifs.md +141 -0
- package/opencode/skills/KORTIX-remotion/rules/images.md +130 -0
- package/opencode/skills/KORTIX-remotion/rules/import-srt-captions.md +69 -0
- package/opencode/skills/KORTIX-remotion/rules/light-leaks.md +73 -0
- package/opencode/skills/KORTIX-remotion/rules/lottie.md +68 -0
- package/opencode/skills/KORTIX-remotion/rules/maps.md +401 -0
- package/opencode/skills/KORTIX-remotion/rules/measuring-dom-nodes.md +35 -0
- package/opencode/skills/KORTIX-remotion/rules/measuring-text.md +143 -0
- package/opencode/skills/KORTIX-remotion/rules/parameters.md +98 -0
- package/opencode/skills/KORTIX-remotion/rules/sequencing.md +118 -0
- package/opencode/skills/KORTIX-remotion/rules/subtitles.md +36 -0
- package/opencode/skills/KORTIX-remotion/rules/tailwind.md +11 -0
- package/opencode/skills/KORTIX-remotion/rules/text-animations.md +20 -0
- package/opencode/skills/KORTIX-remotion/rules/timing.md +179 -0
- package/opencode/skills/KORTIX-remotion/rules/transcribe-captions.md +70 -0
- package/opencode/skills/KORTIX-remotion/rules/transitions.md +197 -0
- package/opencode/skills/KORTIX-remotion/rules/transparent-videos.md +106 -0
- package/opencode/skills/KORTIX-remotion/rules/trimming.md +53 -0
- package/opencode/skills/KORTIX-remotion/rules/videos.md +171 -0
- package/opencode/skills/KORTIX-secrets/SKILL.md +280 -0
- package/opencode/skills/KORTIX-semantic-search/SKILL.md +213 -0
- package/opencode/skills/KORTIX-session-search/SKILL.md +807 -0
- package/opencode/skills/KORTIX-session-search/Untitled +1 -0
- package/opencode/skills/KORTIX-skill-creator/SKILL.md +163 -0
- package/opencode/skills/KORTIX-web-research/SKILL.md +69 -0
- package/opencode/skills/KORTIX-xlsx/LICENSE.txt +30 -0
- package/opencode/skills/KORTIX-xlsx/SKILL.md +549 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/__init__.py +0 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/merge_runs.py +199 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/pack.py +159 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/soffice.py +183 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/unpack.py +132 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validate.py +111 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validators/__init__.py +15 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validators/base.py +847 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validators/docx.py +446 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validators/pptx.py +275 -0
- package/opencode/skills/KORTIX-xlsx/scripts/office/validators/redlining.py +247 -0
- package/opencode/skills/KORTIX-xlsx/scripts/recalc.py +184 -0
- package/opencode/tools/image-gen.ts +342 -0
- package/opencode/tools/image-search.ts +190 -0
- package/opencode/tools/memory-get.ts +168 -0
- package/opencode/tools/memory-search.ts +247 -0
- package/opencode/tools/presentation-gen.ts +723 -0
- package/opencode/tools/scrape-webpage.ts +115 -0
- package/opencode/tools/scripts/.python-version +1 -0
- package/opencode/tools/scripts/convert_pdf.py +184 -0
- package/opencode/tools/scripts/convert_pptx.py +562 -0
- package/opencode/tools/scripts/pyproject.toml +11 -0
- package/opencode/tools/scripts/uv.lock +287 -0
- package/opencode/tools/scripts/validate_slide.py +74 -0
- package/opencode/tools/show-user.ts +217 -0
- package/opencode/tools/tests/e2e-presentation-fix.ts +277 -0
- package/opencode/tools/tests/image-gen.test.ts +215 -0
- package/opencode/tools/tests/image-search.test.ts +125 -0
- package/opencode/tools/tests/memory-system-benchmark.ts +1076 -0
- package/opencode/tools/tests/presentation-gen.test.ts +389 -0
- package/opencode/tools/tests/scrape-webpage.test.ts +74 -0
- package/opencode/tools/tests/show-user.test.ts +241 -0
- package/opencode/tools/tests/video-gen.test.ts +110 -0
- package/opencode/tools/tests/web-search.test.ts +106 -0
- package/opencode/tools/video-gen.ts +200 -0
- package/opencode/tools/web-search.ts +153 -0
- package/opencode/tsconfig.json +29 -0
- package/package.json +36 -0
- package/patch-agent-browser.js +100 -0
- package/postinstall.sh +88 -0
- package/services/KORTIX-presentation-viewer/run +37 -0
- package/services/agent-browser-viewer/run +48 -0
- package/services/kortix-master/run +16 -0
- package/services/lss-sync/run +22 -0
- package/services/opencode-serve/run +25 -0
- package/services/opencode-web/run +21 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kortix-xlsx
|
|
3
|
+
description: "Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like 'the xlsx in my downloads') — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Kortix XLSX — Spreadsheet Skill
|
|
7
|
+
|
|
8
|
+
You are loading the spreadsheet skill. Follow these instructions for ALL spreadsheet work.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Autonomy Doctrine
|
|
13
|
+
|
|
14
|
+
**Act, don't ask.** Receive the task, build the spreadsheet, verify it, deliver it. No permission requests. No presenting options. Pick the best approach and execute.
|
|
15
|
+
|
|
16
|
+
- Write the Python script, run it, verify the output, clean up.
|
|
17
|
+
- If it fails, debug and retry. Only surface blockers after exhausting options.
|
|
18
|
+
- Every spreadsheet gets professional formatting by default — headers, borders, number formats, frozen panes, auto-width columns.
|
|
19
|
+
- Verify your own work: read the file back, check structure, run `recalc.py`, confirm zero errors.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Communication Rules
|
|
24
|
+
|
|
25
|
+
**The user is non-technical. NEVER expose implementation details.**
|
|
26
|
+
|
|
27
|
+
**DO say:**
|
|
28
|
+
- "I'll create that spreadsheet for you"
|
|
29
|
+
- "Here's your budget spreadsheet with the calculations"
|
|
30
|
+
- "I've organized the data and the totals calculate automatically"
|
|
31
|
+
- "I've added a new sheet for Q2 data"
|
|
32
|
+
|
|
33
|
+
**NEVER say:**
|
|
34
|
+
- "I'll use openpyxl to create an .xlsx file"
|
|
35
|
+
- "I'm executing a Python script"
|
|
36
|
+
- "I'll load_workbook and update cells"
|
|
37
|
+
- "I'll use PatternFill and Font classes"
|
|
38
|
+
- "Running recalc.py to evaluate formulas"
|
|
39
|
+
|
|
40
|
+
**Tone:** Friendly, conversational. Describe WHAT the spreadsheet does, not HOW you built it. Make it feel effortless.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
# Requirements for Outputs
|
|
45
|
+
|
|
46
|
+
## All Excel Files
|
|
47
|
+
|
|
48
|
+
### Professional Font
|
|
49
|
+
- Use a consistent, professional font (e.g., Arial, Calibri) for all deliverables unless otherwise instructed
|
|
50
|
+
|
|
51
|
+
### Zero Formula Errors
|
|
52
|
+
- Every Excel file MUST be delivered with ZERO formula errors (#REF!, #DIV/0!, #VALUE!, #N/A, #NAME?)
|
|
53
|
+
- Run `scripts/recalc.py` on every file that contains formulas before delivering
|
|
54
|
+
- If errors are found, fix them and recalculate until clean
|
|
55
|
+
|
|
56
|
+
### Preserve Existing Templates (when updating)
|
|
57
|
+
- Study and EXACTLY match existing format, style, and conventions when modifying files
|
|
58
|
+
- Never impose standardized formatting on files with established patterns
|
|
59
|
+
- Existing template conventions ALWAYS override these guidelines
|
|
60
|
+
|
|
61
|
+
### Professional Styling (new files)
|
|
62
|
+
- Styled headers (dark fill, white bold text)
|
|
63
|
+
- Borders on all data cells
|
|
64
|
+
- Number formatting (currency, percentages, dates)
|
|
65
|
+
- Frozen header row (`ws.freeze_panes = "A2"`)
|
|
66
|
+
- Auto-fit column widths
|
|
67
|
+
- Alternating row fills for large datasets
|
|
68
|
+
|
|
69
|
+
## Financial Models
|
|
70
|
+
|
|
71
|
+
### Color Coding Standards
|
|
72
|
+
Unless otherwise stated by the user or existing template:
|
|
73
|
+
|
|
74
|
+
| Color | RGB | Use |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| Blue text | 0,0,255 | Hardcoded inputs, scenario-changeable numbers |
|
|
77
|
+
| Black text | 0,0,0 | ALL formulas and calculations |
|
|
78
|
+
| Green text | 0,128,0 | Links pulling from other worksheets |
|
|
79
|
+
| Red text | 255,0,0 | External links to other files |
|
|
80
|
+
| Yellow background | 255,255,0 | Key assumptions needing attention |
|
|
81
|
+
|
|
82
|
+
### Number Formatting Standards
|
|
83
|
+
|
|
84
|
+
| Type | Format | Example |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Years | Text string | "2024" not "2,024" |
|
|
87
|
+
| Currency | `$#,##0` | Specify units in headers: "Revenue ($mm)" |
|
|
88
|
+
| Zeros | Dash format | `$#,##0;($#,##0);-` |
|
|
89
|
+
| Percentages | `0.0%` | One decimal default |
|
|
90
|
+
| Multiples | `0.0x` | EV/EBITDA, P/E ratios |
|
|
91
|
+
| Negative numbers | Parentheses | (123) not -123 |
|
|
92
|
+
|
|
93
|
+
### Formula Construction Rules
|
|
94
|
+
|
|
95
|
+
**Assumptions Placement:**
|
|
96
|
+
- Place ALL assumptions (growth rates, margins, multiples) in separate assumption cells
|
|
97
|
+
- Use cell references, not hardcoded values: `=B5*(1+$B$6)` not `=B5*1.05`
|
|
98
|
+
|
|
99
|
+
**Formula Error Prevention:**
|
|
100
|
+
- Verify all cell references are correct
|
|
101
|
+
- Check for off-by-one errors in ranges
|
|
102
|
+
- Ensure consistent formulas across all projection periods
|
|
103
|
+
- Test with edge cases (zero values, negative numbers)
|
|
104
|
+
- Verify no circular references
|
|
105
|
+
|
|
106
|
+
**Documentation Requirements for Hardcodes:**
|
|
107
|
+
- Add cell comments with source info: `"Source: [System/Document], [Date], [Reference], [URL]"`
|
|
108
|
+
- Examples:
|
|
109
|
+
- "Source: Company 10-K, FY2024, Page 45, Revenue Note"
|
|
110
|
+
- "Source: Bloomberg Terminal, 8/15/2025, AAPL US Equity"
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
# XLSX Creation, Editing, and Analysis
|
|
115
|
+
|
|
116
|
+
## CRITICAL: Use Formulas, Not Hardcoded Values
|
|
117
|
+
|
|
118
|
+
**Always use Excel formulas instead of calculating values in Python and hardcoding them.** The spreadsheet must remain dynamic and updateable.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
# WRONG — hardcoding calculated values
|
|
122
|
+
total = df['Sales'].sum()
|
|
123
|
+
sheet['B10'] = total # Hardcodes 5000
|
|
124
|
+
|
|
125
|
+
# CORRECT — Excel formulas
|
|
126
|
+
sheet['B10'] = '=SUM(B2:B9)'
|
|
127
|
+
sheet['C5'] = '=(C4-C2)/C2'
|
|
128
|
+
sheet['D20'] = '=AVERAGE(D2:D19)'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This applies to ALL calculations — totals, percentages, ratios, differences. The spreadsheet should recalculate when source data changes.
|
|
132
|
+
|
|
133
|
+
## Execution Workflow
|
|
134
|
+
|
|
135
|
+
1. **Choose tool**: pandas for data analysis/bulk ops, openpyxl for formulas/formatting
|
|
136
|
+
2. **Create/Load**: New workbook or load existing
|
|
137
|
+
3. **Modify**: Add data, formulas, formatting
|
|
138
|
+
4. **Save**: Write to file
|
|
139
|
+
5. **Recalculate (MANDATORY for formulas)**: `python scripts/recalc.py output.xlsx`
|
|
140
|
+
6. **Verify**: Check recalc output JSON — if `errors_found`, fix and recalculate again
|
|
141
|
+
7. **Clean up**: Remove temp Python scripts
|
|
142
|
+
8. **Report**: Describe result in user-friendly language with file path
|
|
143
|
+
|
|
144
|
+
### Script Path Resolution
|
|
145
|
+
|
|
146
|
+
The `scripts/` directory lives alongside this SKILL.md file. When running recalc:
|
|
147
|
+
```bash
|
|
148
|
+
python <skill_dir>/scripts/recalc.py output.xlsx
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Where `<skill_dir>` is the directory containing this SKILL.md (e.g., `skills/KORTIX-xlsx/` or `.opencode/skills/kortix-xlsx/`).
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Reading and Analyzing Data
|
|
156
|
+
|
|
157
|
+
### pandas (data analysis)
|
|
158
|
+
```python
|
|
159
|
+
import pandas as pd
|
|
160
|
+
|
|
161
|
+
df = pd.read_excel('file.xlsx') # First sheet
|
|
162
|
+
all_sheets = pd.read_excel('file.xlsx', sheet_name=None) # All sheets as dict
|
|
163
|
+
|
|
164
|
+
df.head() # Preview
|
|
165
|
+
df.info() # Column types
|
|
166
|
+
df.describe() # Statistics
|
|
167
|
+
|
|
168
|
+
df.to_excel('output.xlsx', index=False)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### openpyxl (read with formulas preserved)
|
|
172
|
+
```python
|
|
173
|
+
from openpyxl import load_workbook
|
|
174
|
+
|
|
175
|
+
wb = load_workbook('file.xlsx') # Preserves formulas
|
|
176
|
+
wb_values = load_workbook('file.xlsx', data_only=True) # Reads calculated values (WARNING: saving loses formulas)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Creating New Excel Files
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from openpyxl import Workbook
|
|
185
|
+
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
|
186
|
+
from openpyxl.utils import get_column_letter
|
|
187
|
+
|
|
188
|
+
wb = Workbook()
|
|
189
|
+
ws = wb.active
|
|
190
|
+
ws.title = "Sheet Name"
|
|
191
|
+
|
|
192
|
+
# Headers
|
|
193
|
+
headers = ["Product", "Revenue", "Cost", "Profit", "Margin %"]
|
|
194
|
+
header_fill = PatternFill('solid', start_color='1F4E79')
|
|
195
|
+
header_font = Font(bold=True, color='FFFFFF', name='Calibri', size=11)
|
|
196
|
+
header_align = Alignment(horizontal='center', vertical='center')
|
|
197
|
+
|
|
198
|
+
for col, header in enumerate(headers, 1):
|
|
199
|
+
cell = ws.cell(row=1, column=col, value=header)
|
|
200
|
+
cell.fill = header_fill
|
|
201
|
+
cell.font = header_font
|
|
202
|
+
cell.alignment = header_align
|
|
203
|
+
|
|
204
|
+
# Data with formulas
|
|
205
|
+
data = [
|
|
206
|
+
["Product A", 50000, 35000, "=B2-C2", "=IFERROR(D2/B2*100,0)"],
|
|
207
|
+
["Product B", 75000, 45000, "=B3-C3", "=IFERROR(D3/B3*100,0)"],
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
for row_idx, row_data in enumerate(data, 2):
|
|
211
|
+
for col_idx, value in enumerate(row_data, 1):
|
|
212
|
+
ws.cell(row=row_idx, column=col_idx, value=value)
|
|
213
|
+
|
|
214
|
+
# Summary row (dynamic — never hardcode row numbers)
|
|
215
|
+
summary_row = len(data) + 2
|
|
216
|
+
last_data_row = summary_row - 1
|
|
217
|
+
ws.cell(row=summary_row, column=1, value="Total").font = Font(bold=True)
|
|
218
|
+
ws.cell(row=summary_row, column=2, value=f"=SUM(B2:B{last_data_row})")
|
|
219
|
+
ws.cell(row=summary_row, column=3, value=f"=SUM(C2:C{last_data_row})")
|
|
220
|
+
ws.cell(row=summary_row, column=4, value=f"=SUM(D2:D{last_data_row})")
|
|
221
|
+
ws.cell(row=summary_row, column=5, value=f"=IFERROR(D{summary_row}/B{summary_row}*100,0)")
|
|
222
|
+
|
|
223
|
+
# Borders
|
|
224
|
+
thin_border = Border(
|
|
225
|
+
left=Side(style='thin'), right=Side(style='thin'),
|
|
226
|
+
top=Side(style='thin'), bottom=Side(style='thin')
|
|
227
|
+
)
|
|
228
|
+
for row in ws.iter_rows(min_row=1, max_row=summary_row, max_col=len(headers)):
|
|
229
|
+
for cell in row:
|
|
230
|
+
cell.border = thin_border
|
|
231
|
+
|
|
232
|
+
# Number formatting
|
|
233
|
+
for row in range(2, summary_row + 1):
|
|
234
|
+
for col in [2, 3, 4]:
|
|
235
|
+
ws.cell(row=row, column=col).number_format = '#,##0'
|
|
236
|
+
ws.cell(row=row, column=5).number_format = '0.0'
|
|
237
|
+
|
|
238
|
+
# Auto-width columns
|
|
239
|
+
for col in range(1, len(headers) + 1):
|
|
240
|
+
max_len = max(len(str(ws.cell(row=r, column=col).value or "")) for r in range(1, summary_row + 1))
|
|
241
|
+
ws.column_dimensions[get_column_letter(col)].width = min(max_len + 4, 50)
|
|
242
|
+
|
|
243
|
+
# Freeze header row
|
|
244
|
+
ws.freeze_panes = "A2"
|
|
245
|
+
|
|
246
|
+
wb.save('output.xlsx')
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Editing Existing Files
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from openpyxl import load_workbook
|
|
255
|
+
|
|
256
|
+
wb = load_workbook('existing.xlsx')
|
|
257
|
+
ws = wb.active # or wb['SheetName']
|
|
258
|
+
|
|
259
|
+
# Modify cells
|
|
260
|
+
ws['A1'] = 'New Value'
|
|
261
|
+
ws.insert_rows(2)
|
|
262
|
+
ws.delete_cols(3)
|
|
263
|
+
|
|
264
|
+
# Add new sheet (preserves existing sheets)
|
|
265
|
+
new_sheet = wb.create_sheet('NewSheet')
|
|
266
|
+
new_sheet['A1'] = 'Data'
|
|
267
|
+
|
|
268
|
+
wb.save('modified.xlsx')
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Use `wb.create_sheet()` to add sheets — NEVER recreate the workbook.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Cross-Sheet References
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
ws = wb.create_sheet(title="Summary")
|
|
279
|
+
|
|
280
|
+
data = [
|
|
281
|
+
["Q1 Total Revenue", "=SUM('Q1 Sales'!B2:B100)"],
|
|
282
|
+
["Q2 Total Revenue", "=SUM('Q2 Sales'!B2:B100)"],
|
|
283
|
+
["Combined Total", "=B2+B3"],
|
|
284
|
+
]
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## CSV Import and Transform
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
import csv
|
|
293
|
+
from openpyxl import Workbook
|
|
294
|
+
from openpyxl.styles import Font, PatternFill
|
|
295
|
+
|
|
296
|
+
with open('input.csv', 'r') as f:
|
|
297
|
+
rows = list(csv.reader(f))
|
|
298
|
+
|
|
299
|
+
wb = Workbook()
|
|
300
|
+
ws = wb.active
|
|
301
|
+
ws.title = "Imported Data"
|
|
302
|
+
|
|
303
|
+
header_fill = PatternFill('solid', start_color='1F4E79')
|
|
304
|
+
header_font = Font(bold=True, color='FFFFFF')
|
|
305
|
+
|
|
306
|
+
for row_idx, row_data in enumerate(rows, 1):
|
|
307
|
+
for col_idx, value in enumerate(row_data, 1):
|
|
308
|
+
cell = ws.cell(row=row_idx, column=col_idx, value=value)
|
|
309
|
+
if row_idx == 1:
|
|
310
|
+
cell.fill = header_fill
|
|
311
|
+
cell.font = header_font
|
|
312
|
+
|
|
313
|
+
ws.freeze_panes = "A2"
|
|
314
|
+
wb.save('output.xlsx')
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Pandas + openpyxl (Analysis to Formatted Output)
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
import pandas as pd
|
|
323
|
+
from openpyxl import Workbook
|
|
324
|
+
from openpyxl.styles import Font, PatternFill
|
|
325
|
+
from openpyxl.utils.dataframe import dataframe_to_rows
|
|
326
|
+
|
|
327
|
+
df = pd.read_csv('data.csv')
|
|
328
|
+
summary = df.groupby('category').agg(
|
|
329
|
+
total_revenue=('revenue', 'sum'),
|
|
330
|
+
avg_price=('price', 'mean'),
|
|
331
|
+
count=('id', 'count')
|
|
332
|
+
).reset_index()
|
|
333
|
+
|
|
334
|
+
wb = Workbook()
|
|
335
|
+
ws = wb.active
|
|
336
|
+
ws.title = "Analysis"
|
|
337
|
+
|
|
338
|
+
for r_idx, row in enumerate(dataframe_to_rows(summary, index=False, header=True), 1):
|
|
339
|
+
for c_idx, value in enumerate(row, 1):
|
|
340
|
+
ws.cell(row=r_idx, column=c_idx, value=value)
|
|
341
|
+
|
|
342
|
+
header_fill = PatternFill('solid', start_color='1F4E79')
|
|
343
|
+
header_font = Font(bold=True, color='FFFFFF')
|
|
344
|
+
for cell in ws[1]:
|
|
345
|
+
cell.fill = header_fill
|
|
346
|
+
cell.font = header_font
|
|
347
|
+
|
|
348
|
+
ws.freeze_panes = "A2"
|
|
349
|
+
wb.save('analysis.xlsx')
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Recalculating Formulas
|
|
355
|
+
|
|
356
|
+
openpyxl writes formulas as strings but does NOT evaluate them. Use LibreOffice via the bundled `recalc.py`:
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
python scripts/recalc.py <excel_file> [timeout_seconds]
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
The script:
|
|
363
|
+
- Sets up a LibreOffice macro on first run
|
|
364
|
+
- Recalculates ALL formulas in ALL sheets
|
|
365
|
+
- Scans every cell for Excel errors (#REF!, #DIV/0!, #VALUE!, #NAME?, #NULL!, #NUM!, #N/A)
|
|
366
|
+
- Returns JSON with error locations and counts
|
|
367
|
+
- Works on Linux and macOS (handles sandboxed environments via `soffice.py` shim)
|
|
368
|
+
|
|
369
|
+
### Interpreting Output
|
|
370
|
+
|
|
371
|
+
```json
|
|
372
|
+
{
|
|
373
|
+
"status": "success",
|
|
374
|
+
"total_errors": 0,
|
|
375
|
+
"total_formulas": 42,
|
|
376
|
+
"error_summary": {}
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
If `status` is `errors_found`:
|
|
381
|
+
1. Check `error_summary` for error types and cell locations
|
|
382
|
+
2. Fix the formulas in Python
|
|
383
|
+
3. Save and recalculate again
|
|
384
|
+
4. Repeat until `total_errors: 0`
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Formula Safety Rules
|
|
389
|
+
|
|
390
|
+
### Preventing Circular References
|
|
391
|
+
|
|
392
|
+
Headers = ROW 1. Data starts ROW 2. Summary/total row = LAST row.
|
|
393
|
+
|
|
394
|
+
**CORRECT** — total row references only data rows above it:
|
|
395
|
+
```python
|
|
396
|
+
# 3 data rows (rows 2-4), total in row 5
|
|
397
|
+
summary_row = len(data) + 2
|
|
398
|
+
last_data_row = summary_row - 1
|
|
399
|
+
ws.cell(row=summary_row, column=2, value=f"=SUM(B2:B{last_data_row})")
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**WRONG** — total row includes itself:
|
|
403
|
+
```python
|
|
404
|
+
# BAD: row 5 formula references B2:B5 which includes itself
|
|
405
|
+
["Total", "=SUM(B2:B5)", "=SUM(C2:C5)"]
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Preventing #DIV/0! Errors
|
|
409
|
+
|
|
410
|
+
ALWAYS wrap division with IFERROR:
|
|
411
|
+
```python
|
|
412
|
+
"=IFERROR(C2/B2*100,0)" # Returns 0 if division fails
|
|
413
|
+
"=IFERROR(A1/B1,\"N/A\")" # Returns "N/A" if division fails
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Formula Verification Checklist
|
|
419
|
+
|
|
420
|
+
### Essential
|
|
421
|
+
- [ ] Test 2-3 sample references before building full model
|
|
422
|
+
- [ ] Column mapping correct (column 64 = BL, not BK)
|
|
423
|
+
- [ ] Row offset correct (Excel is 1-indexed; DataFrame row 5 = Excel row 6)
|
|
424
|
+
|
|
425
|
+
### Common Pitfalls
|
|
426
|
+
- [ ] NaN handling: use `pd.notna()` before writing
|
|
427
|
+
- [ ] Division by zero: wrap all division in IFERROR
|
|
428
|
+
- [ ] Wrong references: verify cell refs point to intended cells
|
|
429
|
+
- [ ] Cross-sheet refs: use `'Sheet Name'!A1` format (quotes around names with spaces)
|
|
430
|
+
- [ ] Off-by-one: summary row formulas end at `last_data_row`, not `summary_row`
|
|
431
|
+
|
|
432
|
+
### Testing Strategy
|
|
433
|
+
- [ ] Start small: test on 2-3 cells before applying broadly
|
|
434
|
+
- [ ] Verify all referenced cells exist
|
|
435
|
+
- [ ] Test edge cases: zero, negative, very large values
|
|
436
|
+
- [ ] Run `recalc.py` and confirm `total_errors: 0`
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Formatting Reference
|
|
441
|
+
|
|
442
|
+
### Standard Style Objects
|
|
443
|
+
|
|
444
|
+
```python
|
|
445
|
+
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
|
446
|
+
from openpyxl.formatting.rule import CellIsRule
|
|
447
|
+
|
|
448
|
+
# Fills
|
|
449
|
+
header_fill = PatternFill('solid', start_color='1F4E79')
|
|
450
|
+
alt_row_fill = PatternFill('solid', start_color='F2F2F2')
|
|
451
|
+
green_fill = PatternFill('solid', start_color='E8F5E9')
|
|
452
|
+
red_fill = PatternFill('solid', start_color='FFEBEE')
|
|
453
|
+
yellow_fill = PatternFill('solid', start_color='FFFF00')
|
|
454
|
+
|
|
455
|
+
# Fonts
|
|
456
|
+
header_font = Font(bold=True, color='FFFFFF', name='Calibri', size=11)
|
|
457
|
+
title_font = Font(bold=True, name='Calibri', size=14)
|
|
458
|
+
input_font = Font(color='0000FF') # Blue — hardcoded inputs
|
|
459
|
+
formula_font = Font(color='000000') # Black — formulas
|
|
460
|
+
link_font = Font(color='008000') # Green — cross-sheet links
|
|
461
|
+
|
|
462
|
+
# Alignment
|
|
463
|
+
center = Alignment(horizontal='center', vertical='center')
|
|
464
|
+
wrap = Alignment(horizontal='left', vertical='top', wrap_text=True)
|
|
465
|
+
|
|
466
|
+
# Borders
|
|
467
|
+
thin_border = Border(
|
|
468
|
+
left=Side(style='thin'), right=Side(style='thin'),
|
|
469
|
+
top=Side(style='thin'), bottom=Side(style='thin')
|
|
470
|
+
)
|
|
471
|
+
thick_bottom = Border(bottom=Side(style='medium'))
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Alternating Row Colors
|
|
475
|
+
```python
|
|
476
|
+
for row_idx in range(2, ws.max_row + 1):
|
|
477
|
+
if row_idx % 2 == 0:
|
|
478
|
+
for col_idx in range(1, ws.max_column + 1):
|
|
479
|
+
ws.cell(row=row_idx, column=col_idx).fill = alt_row_fill
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Conditional Formatting (positive/negative)
|
|
483
|
+
```python
|
|
484
|
+
ws.conditional_formatting.add(
|
|
485
|
+
f'D2:D{ws.max_row}',
|
|
486
|
+
CellIsRule(operator='greaterThan', formula=['0'],
|
|
487
|
+
fill=PatternFill('solid', start_color='E8F5E9'))
|
|
488
|
+
)
|
|
489
|
+
ws.conditional_formatting.add(
|
|
490
|
+
f'D2:D{ws.max_row}',
|
|
491
|
+
CellIsRule(operator='lessThan', formula=['0'],
|
|
492
|
+
fill=PatternFill('solid', start_color='FFEBEE'))
|
|
493
|
+
)
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Auto-Fit Column Widths
|
|
497
|
+
```python
|
|
498
|
+
from openpyxl.utils import get_column_letter
|
|
499
|
+
|
|
500
|
+
for col in range(1, ws.max_column + 1):
|
|
501
|
+
max_len = max(len(str(ws.cell(row=r, column=col).value or "")) for r in range(1, ws.max_row + 1))
|
|
502
|
+
ws.column_dimensions[get_column_letter(col)].width = min(max_len + 4, 50)
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Best Practices
|
|
508
|
+
|
|
509
|
+
### Library Selection
|
|
510
|
+
- **pandas**: Data analysis, bulk operations, simple data export
|
|
511
|
+
- **openpyxl**: Formatting, formulas, Excel-specific features
|
|
512
|
+
- **Both**: pandas for analysis, openpyxl for final formatted output
|
|
513
|
+
|
|
514
|
+
### openpyxl
|
|
515
|
+
- Cell indices are 1-based (row=1, column=1 = A1)
|
|
516
|
+
- `data_only=True` reads calculated values — WARNING: saving LOSES formulas permanently
|
|
517
|
+
- `read_only=True` / `write_only=True` for large files
|
|
518
|
+
- Formulas are strings, not evaluated — always run `recalc.py`
|
|
519
|
+
|
|
520
|
+
### pandas
|
|
521
|
+
- Specify dtypes: `pd.read_excel('f.xlsx', dtype={'id': str})`
|
|
522
|
+
- Read specific columns: `usecols=['A', 'C', 'E']`
|
|
523
|
+
- Handle dates: `parse_dates=['date_column']`
|
|
524
|
+
|
|
525
|
+
### Code Style
|
|
526
|
+
- Minimal, concise Python — no unnecessary comments or verbose variable names
|
|
527
|
+
- No unnecessary print statements
|
|
528
|
+
- Add cell comments for complex formulas and assumptions
|
|
529
|
+
- Document data sources for all hardcoded values
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Common Formulas Quick Reference
|
|
534
|
+
|
|
535
|
+
| Formula | Example | Use |
|
|
536
|
+
|---|---|---|
|
|
537
|
+
| SUM | `=SUM(B2:B10)` | Total a range |
|
|
538
|
+
| AVERAGE | `=AVERAGE(B2:B10)` | Mean |
|
|
539
|
+
| COUNT | `=COUNT(A1:A100)` | Count numbers |
|
|
540
|
+
| COUNTA | `=COUNTA(A1:A100)` | Count non-empty |
|
|
541
|
+
| IF | `=IF(A1>100,"High","Low")` | Conditional |
|
|
542
|
+
| VLOOKUP | `=VLOOKUP(A1,Sheet2!A:B,2,FALSE)` | Cross-sheet lookup |
|
|
543
|
+
| SUMIF | `=SUMIF(A:A,"Product A",B:B)` | Conditional sum |
|
|
544
|
+
| COUNTIF | `=COUNTIF(A:A,"Product A")` | Conditional count |
|
|
545
|
+
| IFERROR | `=IFERROR(C2/B2*100,0)` | Safe division |
|
|
546
|
+
| Cross-sheet | `=SUM('Sheet Name'!B2:B10)` | Reference another sheet |
|
|
547
|
+
| INDEX/MATCH | `=INDEX(B:B,MATCH(D1,A:A,0))` | Flexible lookup |
|
|
548
|
+
| MIN/MAX | `=MIN(B2:B10)` | Range extremes |
|
|
549
|
+
| CONCATENATE | `=A1&" "&B1` | Join text |
|
|
File without changes
|