Editorial Financial Report (.xlsx)
You will generate one .xlsx file via openpyxl by writing a Python
program and running it through the execute_python_code tool. Save to
the workspace, then report the path + a 1-line content summary.
📦 Required runtime packages
The sandboxed execute_python_code ships with pandas, numpy,
matplotlib, and openpyxl>=3.1.0 preinstalled — no extra
installation step is needed. Just import openpyxl at the top of
your script and proceed.
Note: execute_python_code accepts only code and
capture_output arguments; there is no packages parameter.
All required libraries are already available in the sandbox image.
💾 How to save the file
execute_python_code already runs with the task's output directory as
the current working directory. Save the workbook with a plain
filename (no path), e.g.:
wb.save("xagent_metrics_dashboard.xlsx") # ✅ correct
Do NOT use:
wb.save("/workspace/foo.xlsx") — /workspace does not exist on this host
wb.save("output/foo.xlsx") — there is no nested output/ subdir
- BytesIO + base64 round-trips — just save to disk directly.
Verify with os.path.getsize("xagent_metrics_dashboard.xlsx") before
declaring success. A real openpyxl-generated xlsx is always >5 KB —
if your saved file is under 1 KB it's broken or empty.
🔗 Make it clickable in chat — REQUIRED
The Python executor returns a markdown_link field in its response for
every workspace file it generated (or a file_refs[] array with one
entry per file). Read the tool's response and use the returned
markdown_link string verbatim. In your final answer, the first line
MUST be that chip link itself — bare markdown, NOT inside backticks,
NOT presented as "file_id: UUID":
✅ CORRECT (renders as clickable chip — chat UI looks for this exact pattern):
[xagent_metrics_dashboard.xlsx](file:20fae785-3823-4906-b385-d0e8a7807dc8)
The UUID comes from the executor response. Do not fabricate one.
❌ WRONG — common failure that renders as plain text:
已生成报表:
- 文件名: `xagent_metrics_dashboard.xlsx`
- file_id: `20fae785-3823-4906-b385-d0e8a7807dc8`
❌ ALSO WRONG — chip link inside a code fence: ```[name](file:UUID)```
suppresses markdown so the link won't render as a chip.
❌ ALSO WRONG — calling get_file_info(...) to "fetch" the file_id.
Its FileInfo return shape does not include file_id; the chip
reference is already on the executor result.
Plain-text mention of the filename is not clickable — the user must navigate
to File Management to find it. Always lead with the bare [name](file:UUID)
line, then describe the contents.
⚠️ Hard rules — NO exceptions
MATCH THE USER'S LANGUAGE. If the prompt is Chinese (中文), ALL
workbook text (title, subtitle, section banners, KPI labels, table
headers, deltas) must be in Chinese. Translate template phrases like
SUMMARY → 汇总, DETAIL → 明细, As of YYYY-MM-DD → 截至 YYYY-MM-DD. Numbers and date formats stay locale-neutral.
One accent color only. Pick one of the 5 palettes below; use only
its 4 hex values (ink, paper, paper_tint, accent).
paper_tint is the alternating-row band shade — without it you can
only do flat one-color rows. No other colors anywhere.
In code, define a palette = {"ink": "...", "paper": "...", "paper_tint": "...", "accent": "..."} dict ONCE at the top of the
script and reference palette["ink"] / palette["accent"] /
palette["paper_tint"] / palette["paper"] everywhere — do not
copy literal hex values into individual styling calls.
Two fonts only. Headers / titles = Cambria (serif, Office native).
Body / data = Calibri (sans, Office default).
Forbidden:
- 3-D charts, donut/pie charts with > 5 slices, exploded pies
- rainbow chart palettes (color charts only with
ink + accent)
- chart shadows, glow, bevel, gradient fills
- thick borders (use
thin only, sparingly)
- merged-cell abuse (merge only for section banners across columns)
- emoji as decoration
- Comic Sans, Arial Black, MS Sans Serif
- rainbow conditional formatting (only monochrome data bars / 2-stop
min→max scale in
ink shade)
Real data only. No fabricated numbers. If the user didn't provide
data, ask first — don't invent.
Always include: title row, frozen header pane, alternating row
bands using paper-tint, summary KPI block at top.
Failure honesty — NEVER fake the deliverable.
- If
execute_python_code raises after multiple retries, STOP and report
the actual error to the user. Do not write a stub file like
write_file("report.xlsx", "placeholder") to make the chip appear.
- The final answer must reflect what was actually written. Do not
describe KPI values, deltas, or sections that aren't in the saved
.xlsx. If the chart didn't render, say so — don't claim it did.
- It is better to deliver a chart-less but real .xlsx (drop the chart
block and save without it) than to fabricate a success report. If
the chart code keeps failing, ship the workbook without the chart
and tell the user the chart was omitted.
🎨 Palettes — pick ONE
Each: ink (text + borders + chart series), paper (workbook bg / cells),
paper-tint (alternating row band), accent (single highlight color for
KPI values / chart series 2 / positive deltas).
- Bloomberg Mono (default / fintech)
ink
1A1A1A · paper FFFFFF · paper-tint F7F7F5 · accent D97706
- Indigo Sheet (corporate / SaaS)
ink
0F172A · paper FFFFFF · paper-tint F1F5F9 · accent 4F46E5
- Forest Ledger (sustainability / impact)
ink
1A2E1F · paper FAFAF7 · paper-tint F0EDE3 · accent 059669
- Crimson Quarterly (executive / board)
ink
1F1F1F · paper FFFFFF · paper-tint F5F2EE · accent B91C1C
- Slate Audit (compliance / audit)
ink
0F1419 · paper FCFCFC · paper-tint F0F2F4 · accent 0891B2
✒️ Typography rules (openpyxl Font sizes in pt)
- Title (A1, merged across cols): Cambria, 22pt, ink, bold
- Subtitle / date (A2): Calibri, 11pt, ink, italic
- Section header (banner row): Calibri, 12pt, paper bg + ink color, bold,
merged across the section's columns
- Table header: Calibri, 11pt, ink bg + paper color, bold (thin bottom border)
- Data: Calibri, 10pt, ink color
- KPI big number: Cambria, 24pt, ink, bold
- KPI label: Calibri, 9pt, ink-with-50%-opacity (or just slightly lighter), uppercase
🏗️ Standard layout
A1: [merged] Report Title (22pt Cambria bold)
A2: Subtitle / period · As of YYYY-MM-DD (11pt Calibri italic)
A3: (blank, 6pt row height for breathing room)
A4: [section banner] "SUMMARY" (banner row, merged)
A5-D5: KPI cards row — each KPI in a 1×2 cell block
- Row 5: KPI label (small caps)
- Row 6: KPI big number (24pt Cambria)
- Row 7: delta vs previous period (Calibri 10pt, accent if positive, ink-50 if negative)
A9: (blank)
A10: [section banner] "DETAIL"
A11: Table header (frozen at row 12)
A12+: Data rows with alternating paper-tint
📊 Number formatting (Excel format codes)
| Type |
Format string |
| Integer |
#,##0 |
| Currency (USD) |
$#,##0.00;-$#,##0.00 (no parens, no red) |
| Currency (CNY) |
¥#,##0.00;-¥#,##0.00 |
| Percentage |
0.0% |
| Delta percentage |
+0.0%;-0.0%;0.0% (explicit sign) |
| Large numbers |
#,##0,"K";-#,##0,"K" or #,##0,,"M" |
| Date |
yyyy-mm-dd |
💱 Currency default — pick carefully
The user's name / locale is NOT a reliable signal for which currency the
report's subject company uses. Pick currency by the subject domain, not
by who's asking:
- Default → USD (
$) for: SaaS, B2B software, fintech, US/global
companies, crypto, VC metrics (ARR / MRR / NRR / CAC / LTV are
industry-standard USD).
- Use CNY (
¥) only when: the user explicitly says CNY/RMB, the
company is named as Chinese (e.g. Alibaba / Pinduoduo / ByteDance), or
the data source is a Chinese filing.
- Use EUR (
€) for EU-based companies explicitly mentioned.
- Use GBP (
£) for UK-based companies explicitly mentioned.
When in doubt, default to USD and note the assumption in cell A2
subtitle (e.g. Q1 2026 Quarterly Review · As of 2026-03-31 · USD).
📈 Chart styling (openpyxl charts)
- Chart types allowed:
LineChart, BarChart (type="col", never 3-D),
ScatterChart. NEVER import BarChart3D, PieChart3D, etc.
- Series colors: only
ink and accent (max 2 series). For more, use line
variations (solid vs dashed) in ink.
- No legend if only 1 series. If 2 series, legend at right, no border, no fill.
- No axis title unless absolutely necessary. Axis labels font: Calibri 9pt.
- Chart border: none. Chart background: paper.
- Title: 12pt Calibri bold, ink, left-aligned above chart.
⚠️ openpyxl 3.x chart API gotchas — copy this verbatim, don't improvise
The openpyxl chart API is full of footguns. Use ONLY the imports below and
ONLY the kwargs shown. Most common mistakes:
| ❌ Wrong |
✅ Right |
from openpyxl.chart.data_source import StrDataSource |
not needed — Reference covers this |
from openpyxl.drawing.fill import SolidColorFill |
not needed — pass hex string to solidFill="0F172A" |
GraphicalProperties(line=...) |
GraphicalProperties(ln=...) (kwarg is ln, two letters) |
LineProperties(width=25000) |
LineProperties(w=25000) (kwarg is w) |
chart.y_axis.majorGridlines = GraphicalProperties(...) |
wrap in ChartLines(spPr=GraphicalProperties(...)) |
BarChart3D |
BarChart(type="col") |
✅ Tested line-chart snippet (openpyxl 3.x — copy as-is)
Before the snippet: define your chosen palette once. Example for
Indigo Sheet:
palette = {"ink": "0F172A", "paper": "FFFFFF",
"paper_tint": "F1F5F9", "accent": "4F46E5"}
Reference palette["ink"] / palette["accent"] / palette["paper_tint"]
in the styling code below instead of copying hex literals.
from openpyxl.chart import LineChart, Reference
from openpyxl.chart.label import DataLabelList
from openpyxl.chart.marker import Marker
from openpyxl.chart.shapes import GraphicalProperties
from openpyxl.chart.axis import ChartLines
from openpyxl.drawing.line import LineProperties
# Assume helper columns H (categories) and I (values) at rows 11..14
ch = LineChart()
ch.title = "Monthly Agent Runs (Jan–Apr 2026)"
ch.width, ch.height = 20, 10
ch.legend = None # 1-series chart -> no legend
vals = Reference(ws, min_col=9, min_row=11, max_row=14) # column I
cats = Reference(ws, min_col=8, min_row=11, max_row=14) # column H
ch.add_data(vals, titles_from_data=False)
ch.set_categories(cats)
# Series styling — kwarg is `ln=`, not `line=`. `w` is in EMU (12700 EMU = 1pt).
s = ch.series[0]
s.graphicalProperties = GraphicalProperties(
ln=LineProperties(solidFill=palette["ink"], w=25000) # ~2pt
)
s.marker = Marker(symbol="circle", size=7)
s.marker.graphicalProperties = GraphicalProperties(solidFill=palette["accent"])
# Data labels (optional)
s.dLbls = DataLabelList(showVal=True)
s.dLbls.numFmt = '#,##0'
# Value-axis gridlines — must wrap in ChartLines(spPr=...). The
# gridline tint comes from the palette's paper_tint, not a hard-coded
# grey.
ch.y_axis.majorGridlines = ChartLines(
spPr=GraphicalProperties(ln=LineProperties(solidFill=palette["paper_tint"], w=6350))
)
# No category-axis gridlines
ch.x_axis.majorGridlines = None
# Backgrounds (optional — paper is usually already default)
ch.plot_area.graphicalProperties = GraphicalProperties(solidFill=palette["paper"])
ws.add_chart(ch, "A22")
✅ Tested bar-chart snippet
from openpyxl.chart import BarChart, Reference
from openpyxl.chart.shapes import GraphicalProperties
ch = BarChart()
ch.type = "col" # vertical bars; "bar" = horizontal
ch.style = 2 # minimal style; we override colors anyway
ch.title = "Skill Invocations"
ch.width, ch.height = 20, 10
ch.legend = None
ch.add_data(Reference(ws, min_col=3, min_row=11, max_row=20), titles_from_data=False)
ch.set_categories(Reference(ws, min_col=2, min_row=11, max_row=20))
# Fill the bars with `ink` (from your palette dict)
ch.series[0].graphicalProperties = GraphicalProperties(solidFill=palette["ink"])
ws.add_chart(ch, "F22")
🧊 Frozen panes + alternating rows + conditional formatting
ws.freeze_panes = 'A12' # header at row 11 stays visible
# Alternating row bands (apply via conditional formatting MOD)
from openpyxl.formatting.rule import FormulaRule
from openpyxl.styles import PatternFill
band = PatternFill(start_color=palette["paper_tint"],
end_color=palette["paper_tint"], fill_type='solid')
ws.conditional_formatting.add(
f'A12:Z{last_row}',
FormulaRule(formula=['MOD(ROW(),2)=0'], fill=band),
)
# Data bar (monochrome only) for a KPI column
from openpyxl.formatting.rule import DataBarRule
ws.conditional_formatting.add(
'D12:D' + str(last_row),
DataBarRule(start_type='min', end_type='max', color=palette["ink"]),
)
📝 Output checklist
Then write the .xlsx and report path + which palette + which sections.
1---2name: xlsx-financial-report3description: A native .xlsx styled as a financial, KPI, or dashboard report: "Excel report", "financial report", "KPI dashboard", "monthly report". Opens cleanly in Excel / Google Sheets / Numbers.4---56# Editorial Financial Report (.xlsx)78You will generate one `.xlsx` file via openpyxl by writing a Python9program and running it through the `execute_python_code` tool. Save to10the workspace, then report the path + a 1-line content summary.1112## 📦 Required runtime packages1314The sandboxed `execute_python_code` ships with `pandas`, `numpy`,15`matplotlib`, and **`openpyxl>=3.1.0`** preinstalled — no extra16installation step is needed. Just `import openpyxl` at the top of17your script and proceed.1819> **Note:** `execute_python_code` accepts only `code` and20> `capture_output` arguments; there is no `packages` parameter.21> All required libraries are already available in the sandbox image.2223## 💾 How to save the file2425`execute_python_code` already runs with the task's output directory as26the current working directory. Save the workbook with a **plain27filename** (no path), e.g.:2829```python30wb.save("xagent_metrics_dashboard.xlsx") # ✅ correct31```3233Do NOT use:34- `wb.save("/workspace/foo.xlsx")` — `/workspace` does not exist on this host35- `wb.save("output/foo.xlsx")` — there is no nested `output/` subdir36- BytesIO + base64 round-trips — just save to disk directly.3738Verify with `os.path.getsize("xagent_metrics_dashboard.xlsx")` before39declaring success. A real openpyxl-generated xlsx is **always >5 KB** —40if your saved file is under 1 KB it's broken or empty.4142### 🔗 Make it clickable in chat — REQUIRED4344The Python executor returns a `markdown_link` field in its response for45every workspace file it generated (or a `file_refs[]` array with one46entry per file). **Read the tool's response** and use the returned47`markdown_link` string verbatim. In your final answer, the **first line48MUST be that chip link itself** — bare markdown, NOT inside backticks,49NOT presented as "file_id: UUID":5051✅ **CORRECT** (renders as clickable chip — chat UI looks for this exact pattern):5253 [xagent_metrics_dashboard.xlsx](file:20fae785-3823-4906-b385-d0e8a7807dc8)5455The UUID comes from the executor response. Do not fabricate one.5657❌ **WRONG — common failure that renders as plain text**:5859 已生成报表:60 - 文件名: `xagent_metrics_dashboard.xlsx`61 - file_id: `20fae785-3823-4906-b385-d0e8a7807dc8`6263❌ **ALSO WRONG — chip link inside a code fence**: ` ```[name](file:UUID)``` `64 suppresses markdown so the link won't render as a chip.6566❌ **ALSO WRONG — calling `get_file_info(...)` to "fetch" the file_id**.67 Its `FileInfo` return shape does not include `file_id`; the chip68 reference is already on the executor result.6970Plain-text mention of the filename is not clickable — the user must navigate71to File Management to find it. Always lead with the bare `[name](file:UUID)`72line, then describe the contents.7374## ⚠️ Hard rules — NO exceptions75760. **MATCH THE USER'S LANGUAGE.** If the prompt is Chinese (中文), ALL77 workbook text (title, subtitle, section banners, KPI labels, table78 headers, deltas) must be in Chinese. Translate template phrases like79 `SUMMARY` → `汇总`, `DETAIL` → `明细`, `As of YYYY-MM-DD` → `截至80 YYYY-MM-DD`. Numbers and date formats stay locale-neutral.81821. **One accent color only.** Pick one of the 5 palettes below; use only83 its **4 hex values** (`ink`, `paper`, `paper_tint`, `accent`).84 `paper_tint` is the alternating-row band shade — without it you can85 only do flat one-color rows. No other colors anywhere.86 In code, define a ``palette = {"ink": "...", "paper": "...",87 "paper_tint": "...", "accent": "..."}`` dict ONCE at the top of the88 script and reference `palette["ink"]` / `palette["accent"]` /89 `palette["paper_tint"]` / `palette["paper"]` everywhere — do not90 copy literal hex values into individual styling calls.912. **Two fonts only.** Headers / titles = `Cambria` (serif, Office native).92 Body / data = `Calibri` (sans, Office default).933. **Forbidden:**94 - 3-D charts, donut/pie charts with > 5 slices, exploded pies95 - rainbow chart palettes (color charts only with `ink` + `accent`)96 - chart shadows, glow, bevel, gradient fills97 - thick borders (use `thin` only, sparingly)98 - merged-cell abuse (merge only for section banners across columns)99 - emoji as decoration100 - Comic Sans, Arial Black, MS Sans Serif101 - rainbow conditional formatting (only monochrome data bars / 2-stop102 min→max scale in `ink` shade)1034. **Real data only.** No fabricated numbers. If the user didn't provide104 data, ask first — don't invent.1055. **Always include**: title row, frozen header pane, alternating row106 bands using `paper-tint`, summary KPI block at top.1076. **Failure honesty — NEVER fake the deliverable.**108 - If `execute_python_code` raises after multiple retries, STOP and report109 the actual error to the user. Do not write a stub file like110 `write_file("report.xlsx", "placeholder")` to make the chip appear.111 - The final answer must reflect what was actually written. Do not112 describe KPI values, deltas, or sections that aren't in the saved113 `.xlsx`. If the chart didn't render, say so — don't claim it did.114 - It is better to deliver a chart-less but real .xlsx (drop the chart115 block and save without it) than to fabricate a success report. If116 the chart code keeps failing, ship the workbook without the chart117 and tell the user the chart was omitted.118119## 🎨 Palettes — pick ONE120121Each: `ink` (text + borders + chart series), `paper` (workbook bg / cells),122`paper-tint` (alternating row band), `accent` (single highlight color for123KPI values / chart series 2 / positive deltas).124125- **Bloomberg Mono** (default / fintech)126 ink `1A1A1A` · paper `FFFFFF` · paper-tint `F7F7F5` · accent `D97706`127- **Indigo Sheet** (corporate / SaaS)128 ink `0F172A` · paper `FFFFFF` · paper-tint `F1F5F9` · accent `4F46E5`129- **Forest Ledger** (sustainability / impact)130 ink `1A2E1F` · paper `FAFAF7` · paper-tint `F0EDE3` · accent `059669`131- **Crimson Quarterly** (executive / board)132 ink `1F1F1F` · paper `FFFFFF` · paper-tint `F5F2EE` · accent `B91C1C`133- **Slate Audit** (compliance / audit)134 ink `0F1419` · paper `FCFCFC` · paper-tint `F0F2F4` · accent `0891B2`135136## ✒️ Typography rules (openpyxl Font sizes in pt)137138- **Title** (A1, merged across cols): Cambria, 22pt, ink, bold139- **Subtitle / date** (A2): Calibri, 11pt, ink, italic140- **Section header** (banner row): Calibri, 12pt, paper bg + ink color, bold,141 merged across the section's columns142- **Table header**: Calibri, 11pt, ink bg + paper color, bold (thin bottom border)143- **Data**: Calibri, 10pt, ink color144- **KPI big number**: Cambria, 24pt, ink, bold145- **KPI label**: Calibri, 9pt, ink-with-50%-opacity (or just slightly lighter), uppercase146147## 🏗️ Standard layout148149```150A1: [merged] Report Title (22pt Cambria bold)151A2: Subtitle / period · As of YYYY-MM-DD (11pt Calibri italic)152A3: (blank, 6pt row height for breathing room)153154A4: [section banner] "SUMMARY" (banner row, merged)155A5-D5: KPI cards row — each KPI in a 1×2 cell block156 - Row 5: KPI label (small caps)157 - Row 6: KPI big number (24pt Cambria)158 - Row 7: delta vs previous period (Calibri 10pt, accent if positive, ink-50 if negative)159160A9: (blank)161A10: [section banner] "DETAIL"162A11: Table header (frozen at row 12)163A12+: Data rows with alternating paper-tint164```165166## 📊 Number formatting (Excel format codes)167168| Type | Format string |169|---|---|170| Integer | `#,##0` |171| Currency (USD) | `$#,##0.00;-$#,##0.00` (no parens, no red) |172| Currency (CNY) | `¥#,##0.00;-¥#,##0.00` |173| Percentage | `0.0%` |174| Delta percentage | `+0.0%;-0.0%;0.0%` (explicit sign) |175| Large numbers | `#,##0,"K";-#,##0,"K"` or `#,##0,,"M"` |176| Date | `yyyy-mm-dd` |177178### 💱 Currency default — pick carefully179180The user's name / locale is NOT a reliable signal for which currency the181*report's subject company* uses. Pick currency by **the subject domain**, not182by who's asking:183184- **Default → USD (`$`)** for: SaaS, B2B software, fintech, US/global185 companies, crypto, VC metrics (ARR / MRR / NRR / CAC / LTV are186 industry-standard USD).187- **Use CNY (`¥`)** only when: the user explicitly says CNY/RMB, the188 company is named as Chinese (e.g. Alibaba / Pinduoduo / ByteDance), or189 the data source is a Chinese filing.190- **Use EUR (`€`)** for EU-based companies explicitly mentioned.191- **Use GBP (`£`)** for UK-based companies explicitly mentioned.192193When in doubt, **default to USD** and note the assumption in cell A2194subtitle (e.g. `Q1 2026 Quarterly Review · As of 2026-03-31 · USD`).195196## 📈 Chart styling (openpyxl charts)197198- Chart types allowed: `LineChart`, `BarChart` (`type="col"`, **never** 3-D),199 `ScatterChart`. **NEVER** import `BarChart3D`, `PieChart3D`, etc.200- Series colors: only `ink` and `accent` (max 2 series). For more, use line201 variations (solid vs dashed) in `ink`.202- No legend if only 1 series. If 2 series, legend at right, no border, no fill.203- No axis title unless absolutely necessary. Axis labels font: Calibri 9pt.204- Chart border: none. Chart background: paper.205- Title: 12pt Calibri bold, ink, left-aligned above chart.206207### ⚠️ openpyxl 3.x chart API gotchas — copy this verbatim, don't improvise208209The openpyxl chart API is full of footguns. Use ONLY the imports below and210ONLY the kwargs shown. Most common mistakes:211212| ❌ Wrong | ✅ Right |213|---|---|214| `from openpyxl.chart.data_source import StrDataSource` | not needed — `Reference` covers this |215| `from openpyxl.drawing.fill import SolidColorFill` | not needed — pass hex string to `solidFill="0F172A"` |216| `GraphicalProperties(line=...)` | `GraphicalProperties(ln=...)` (kwarg is `ln`, two letters) |217| `LineProperties(width=25000)` | `LineProperties(w=25000)` (kwarg is `w`) |218| `chart.y_axis.majorGridlines = GraphicalProperties(...)` | wrap in `ChartLines(spPr=GraphicalProperties(...))` |219| `BarChart3D` | `BarChart(type="col")` |220221### ✅ Tested line-chart snippet (openpyxl 3.x — copy as-is)222223> **Before the snippet:** define your chosen palette once. Example for224> Indigo Sheet:225>226> ```python227> palette = {"ink": "0F172A", "paper": "FFFFFF",228> "paper_tint": "F1F5F9", "accent": "4F46E5"}229> ```230>231> Reference `palette["ink"]` / `palette["accent"]` / `palette["paper_tint"]`232> in the styling code below instead of copying hex literals.233234235```python236from openpyxl.chart import LineChart, Reference237from openpyxl.chart.label import DataLabelList238from openpyxl.chart.marker import Marker239from openpyxl.chart.shapes import GraphicalProperties240from openpyxl.chart.axis import ChartLines241from openpyxl.drawing.line import LineProperties242243# Assume helper columns H (categories) and I (values) at rows 11..14244ch = LineChart()245ch.title = "Monthly Agent Runs (Jan–Apr 2026)"246ch.width, ch.height = 20, 10247ch.legend = None # 1-series chart -> no legend248249vals = Reference(ws, min_col=9, min_row=11, max_row=14) # column I250cats = Reference(ws, min_col=8, min_row=11, max_row=14) # column H251ch.add_data(vals, titles_from_data=False)252ch.set_categories(cats)253254# Series styling — kwarg is `ln=`, not `line=`. `w` is in EMU (12700 EMU = 1pt).255s = ch.series[0]256s.graphicalProperties = GraphicalProperties(257 ln=LineProperties(solidFill=palette["ink"], w=25000) # ~2pt258)259s.marker = Marker(symbol="circle", size=7)260s.marker.graphicalProperties = GraphicalProperties(solidFill=palette["accent"])261262# Data labels (optional)263s.dLbls = DataLabelList(showVal=True)264s.dLbls.numFmt = '#,##0'265266# Value-axis gridlines — must wrap in ChartLines(spPr=...). The267# gridline tint comes from the palette's paper_tint, not a hard-coded268# grey.269ch.y_axis.majorGridlines = ChartLines(270 spPr=GraphicalProperties(ln=LineProperties(solidFill=palette["paper_tint"], w=6350))271)272# No category-axis gridlines273ch.x_axis.majorGridlines = None274275# Backgrounds (optional — paper is usually already default)276ch.plot_area.graphicalProperties = GraphicalProperties(solidFill=palette["paper"])277278ws.add_chart(ch, "A22")279```280281### ✅ Tested bar-chart snippet282283```python284from openpyxl.chart import BarChart, Reference285from openpyxl.chart.shapes import GraphicalProperties286287ch = BarChart()288ch.type = "col" # vertical bars; "bar" = horizontal289ch.style = 2 # minimal style; we override colors anyway290ch.title = "Skill Invocations"291ch.width, ch.height = 20, 10292ch.legend = None293294ch.add_data(Reference(ws, min_col=3, min_row=11, max_row=20), titles_from_data=False)295ch.set_categories(Reference(ws, min_col=2, min_row=11, max_row=20))296297# Fill the bars with `ink` (from your palette dict)298ch.series[0].graphicalProperties = GraphicalProperties(solidFill=palette["ink"])299300ws.add_chart(ch, "F22")301```302303## 🧊 Frozen panes + alternating rows + conditional formatting304305```python306ws.freeze_panes = 'A12' # header at row 11 stays visible307308# Alternating row bands (apply via conditional formatting MOD)309from openpyxl.formatting.rule import FormulaRule310from openpyxl.styles import PatternFill311band = PatternFill(start_color=palette["paper_tint"],312 end_color=palette["paper_tint"], fill_type='solid')313ws.conditional_formatting.add(314 f'A12:Z{last_row}',315 FormulaRule(formula=['MOD(ROW(),2)=0'], fill=band),316)317318# Data bar (monochrome only) for a KPI column319from openpyxl.formatting.rule import DataBarRule320ws.conditional_formatting.add(321 'D12:D' + str(last_row),322 DataBarRule(start_type='min', end_type='max', color=palette["ink"]),323)324```325326## 📝 Output checklist327328- [ ] One palette, only its 4 hex values appear anywhere329- [ ] Only Cambria + Calibri (verify no Arial/Times slipped in)330- [ ] No 3-D, no shadows, no gradients, no rainbow conditional formatting331- [ ] Title row merged, frozen header at correct row332- [ ] All numbers have explicit Excel format codes333- [ ] Charts: ≤ 2 series, ink + accent only334- [ ] Alternating row bands applied335- [ ] No fabricated data; if KPIs require deltas, deltas come from real prior-period numbers336337Then write the .xlsx and report path + which palette + which sections.