Install
openclaw skills install @robotbird/dairy-market-reportGenerate a comprehensive, magazine-style PDF report summarizing monthly Chinese dairy market data from industry PDFs and policy documents in 12 structured se...
openclaw skills install @robotbird/dairy-market-reportThis skill ingests one or more Chinese-language dairy industry monthly PDFs (e.g. 艾格农业《中国乳业研究月报》) and any related .docx policy briefs in the working directory, extracts the relevant data, and produces a complete, multi-section 乳制品市场行情报告 as a single PDF document (乳制品市场行情报告_YYYYMM.pdf).
The report follows a fixed 12-section structure (see references/report-template.md) and embeds GDT-to-warehouse landed-cost calculations per the user's formulas.
The default PDF aesthetic is a professional business-magazine look — not a default reportlab dump. Concretely:
PageBreak per section. The renderer (reportlab + STSong-Light CJK font) handles natural pagination. Typical output is 8–10 pages.SECTION 0X / 12 ordinal on the left, large dark-navy title, a short gold rule under it.+3.8% red ↑ or -9.4% green ↓).— N — page number on every body page.Color tokens (use these exactly; renderer has them baked in):
| Token | Hex | Use |
|---|---|---|
INK | #1B2733 | body text |
NAVY | #0E2A47 | primary brand, section titles, table header |
ACCENT | #D4A24C | gold accents, callout bar, cover subtitle |
SOFT_BG | #F4F1EA | callout background, zebra row B |
RED | #B0322B | price-up (Chinese stock convention) |
GREEN | #1F7A4D | price-down |
Do not insert a hard PageBreak between sections, and do not wrap every section in KeepInFrame. The renderer already balances density; forcing a page break per section leaves large white gaps.
render_pdf.py consumes)render_pdf.py --data <data.json> expects a single JSON object. The schema lives at the top of scripts/render_pdf.py and is mirrored in references/data-schema.md. The minimum fields are:
{
"meta": {
"title": "乳制品市场行情报告",
"period": "2023年10月",
"generated_at": "2026-06-26",
"sources": ["艾格农业《中国乳业研究月报》202310", "农业农村部"]
},
"key_takeaways": ["国内奶价 ...", "GDT 反弹 ...", "..."],
"sections": [
{ "title": "乳业整体形势概览", "kpis": [...], "tables": [...], "callout": "...", "source": "..." },
{ "title": "生鲜乳", "kpis": [...], "tables": [...], "callout": "...", "source": "..." },
...
{ "title": "乳业重要事件及乳品企业动态资讯", "events": [...], "source": "..." }
]
}
Each kpis entry is {label, value, change} (change string is parsed for +/- or ↑/↓ to pick the pill color). Each tables entry is {header: [str], rows: [[str, ...]]}. callout is a single string.
Trigger this skill when the user asks for any of the following, especially when Chinese-language dairy PDFs are present in the working directory:
Do NOT use this skill for: ad-hoc single-data-point questions, English-language dairy market reports (use the English-language equivalent skill), or topics unrelated to dairy commodities.
Read these on demand — they are not all needed up front.
| File | Read it when |
|---|---|
references/report-template.md | You are about to draft the report — it defines each of the 12 sections, expected data points, narrative guidance, and a "deep takeaway" expectation per section |
references/data-schema.md | You are about to assemble the data JSON for the renderer — it documents every key (meta, key_takeaways, sections[*].kpis/tables/callout/source/events) and the change-pill sign convention |
references/data-sources.md | You need to look up a number but the PDF didn't spell it out — covers 农业农村部, 国家统计局, 海关总署, GDT, USDA, Dairy Australia, Eurostat, DCANZ, AHDB, etc. |
references/gdt-formulas.md | You are filling section 8 (GDT → 入仓成本) — restates the user's formulas and shows the example worked-out numbers |
scripts/extract_pdf_text.py | You need raw text from a .pdf (e.g. 艾格农业月报). Usage: python extract_pdf_text.py <pdf> [out.txt] |
scripts/extract_docx_text.py | You need raw text from a .docx (e.g. 政策文件). Usage: python extract_docx_text.py <docx> [out.txt] |
scripts/gdt_calculator.py | You are computing section 8 numbers. Usage: python gdt_calculator.py --contract2-amf <price> --contract2-wmp <price> --contract2-smp <price> --fx <CNY/USD> --out <csv> |
scripts/render_pdf.py | You have the filled data dict and need to emit the magazine-style PDF. Usage: python render_pdf.py --data <data.json> --out <report.pdf>. Uses reportlab + built-in CJK font (STSong-Light); no Chromium/Playwright required. Do not modify the layout logic in this file — it is the canonical visual spec for the report. |
assets/styles.css | (DEPRECATED) — kept for reference only; the PDF is generated natively and does not need CSS |
assets/report-template.html | (DEPRECATED) — kept for reference only; the PDF is generated natively and does not need an HTML template |
Follow these steps in order. Stop and ask the user only if a required input is genuinely missing — most inputs are recoverable from the PDFs themselves.
List the working directory (ls / Glob for *.pdf, *.docx, *.txt, *.md, *.xlsx, *.csv). For each:
Ask the user only if the report target month / year is not obvious from the filenames.
Run the bundled extractors on every relevant file:
python scripts/extract_pdf_text.py "艾格农业-中国乳业研究月报202310.pdf" work/202310.txt
python scripts/extract_docx_text.py "农业农村部召开科技创新....docx" work/policy.txt
Place the text files in a work/ subfolder (scratch space). Read them fully (or in slices) to identify which sections of references/report-template.md each PDF covers. One monthly typically covers raw milk, feed, production, imports, exports, GDT, and policy; a single PDF rarely has everything, so cross-check.
Open references/report-template.md and references/data-sources.md. For each section:
data-sources.md for the official source URL — but do not fabricate. Mark it as "数据待补充" (data TBD) if truly unavailable.Special handling for section 8 (GDT → 入仓成本):
data-sources.md). Mark the FX source.scripts/gdt_calculator.py with those three contract-2 prices + FX to produce the landed-cost table.Special handling for section 11 (AI 深度解读 key takeaways):
Assemble the filled content into a single JSON object matching the schema in references/data-schema.md (mirrored in the docstring of scripts/render_pdf.py). Top-level keys:
meta — {title, period, generated_at, sources} (cover band + footer)key_takeaways — list of 3–6 short strings, bulleted on the cover cardsections — ordered list of 12 entries, each with:
title (Chinese)kpis — list of {label, value, change}; change is a free-form string like +3.8% or -9.4% YoY; the renderer auto-colors it (red for +/↑, green for -/↓, grey otherwise)tables — list of {header: [str, ...], rows: [[str, ...], ...]}callout — a single string, becomes the gold-bar 解读 boxsource — a single string, becomes the grey footer lineevents — a list of {date, title, body} cards)Rules:
change string and the renderer handles the rest.(date, value) tuples under a sparkline key and the renderer will draw an inline SVG-like bar.PageBreak markers between sections. The renderer takes care of pagination.KeepInFrame mode=shrink — that was an old attempt and produces cramped sections.python scripts/render_pdf.py --data work/report_data.json --out "乳制品市场行情报告_202310.pdf"
The renderer uses reportlab + the built-in STSong-Light CJK font, so no Chromium / Playwright / wkhtmltopdf installation is required. If reportlab is missing, install it with pip install reportlab.
Expected page count for a full 12-section report: 8–10 pages (cover + body). If you see 13+ pages, you have added a per-section page break somewhere — remove it.
Use the present_files tool to show the PDF (it previews as an artifact card). In the chat reply, give a 3–4 line summary of which sections came from which input PDFs and one highlighted takeaway from section 11.
data-sources.md, or the GDT calculator. If a number is genuinely missing, write "数据待补充" — do not invent.(contract2_price + 165) * FX * 1.13 + 300(contract2_price + 115) * FX * 1.13 + 300PageBreak. No KeepInFrame shrink-mode per section. The renderer already produces a balanced 8–10 page magazine layout. If your output is 13+ pages, you have re-introduced one of those — fix the data builder, not the renderer.NAVY / ACCENT / SOFT_BG / RED / GREEN) — do not invent new colors. Pill color is auto-driven by the +/- sign in the change string, so you do not pick the color manually.User: "帮我跑一下 10 月的乳业月报"
→ Skill: detects 艾格农业-中国乳业研究月报202310.pdf
→ Extracts text → fills 12 sections → assembles data dict (per references/data-schema.md)
→ Calls scripts/render_pdf.py --data work/report_data.json --out 乳制品市场行情报告_202310.pdf
→ Presents: 乳制品市场行情报告_202310.pdf (8–10 pages, magazine style)
See references/report-template.md for the authoritative section spec, and references/data-schema.md for the data JSON schema the renderer expects.