Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Fill institutional Word form templates (.doc/.docx) for IRB protocols, ethics applications, grant proposals, and other structured research documents while preserving the original styles, table layouts, fonts, and page geometry. Pairs with write-protocol — write-protocol drafts the scientific content, fill-protocol renders it into the institutional template. Korean-aware (CJK eastAsia font enforcement, table cantSplit) but works for any language template.
.claude/skills/aperivue-fill-protocol/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 19% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 112% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 83% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 172% | 0% |
You are helping a researcher populate an institutional Word form (IRB protocol, ethics application, grant proposal, etc.) without breaking the original document formatting. This skill is the formatting counterpart to write-protocol: where write-protocol drafts content, fill-protocol lays that content into the institutional template.
Recreating institutional forms from scratch with python-docx reliably destroys table layouts, page breaks, and font consistency. The only safe approach is to open the existing template and replace cell/paragraph text in place. This skill enforces that pattern.
Document(template_path), not Document().
pandoc -f doc is not supported; textutil corrupts table structure.
evolve and coordinate matching breaks silently.
cantSplit to every filled row so a row never breaks across pages.eastAsia font attribute, not justrun.font.name. Hangul/Kanji/Hanzi will render in fallback fonts otherwise.
and surface mismatches before saving.
If the template is already .docx, LibreOffice is not required — only the three Python packages below. LibreOffice is needed only when the template is a legacy .doc and must be converted first.
bash# Python libraries (always required) pip install --user docxtpl python-docx pyyaml # LibreOffice (only for legacy .doc input; ~700 MB on macOS) brew install --cask libreoffice # macOS sudo apt-get install -y libreoffice # Debian/Ubuntu sudo dnf install -y libreoffice # Fedora sudo pacman -S --needed libreoffice-fresh # Arch
The skill ships a setup.sh that detects what is missing and installs only those parts, with a confirmation prompt before each step:
bashbash setup.sh check # report what's installed (read-only) bash setup.sh install # install missing pieces (asks before each)
When invoking this skill on behalf of a user:
doc_to_docx.py, run bash setup.sh check. IfLibreOffice is missing, ask the user before installing — the cask is ~700 MB and proceeding silently is unfriendly.
.docx. Onlysurface the install prompt when a .doc is encountered.
--yes to setup.sh install unless the user has explicitlyauthorized unattended installation in this session.
the .doc manually (open in Word/LibreOffice/Pages → Save As → .docx) and then re-run with the converted file.
bashpython scripts/doc_to_docx.py path/to/template.doc path/to/template.docx
bashpython scripts/inspect_template.py path/to/template.docx
This lists every table, every cell (with row/column coordinates and content preview), and every top-level paragraph. Use this output to identify the labels you will match against in your YAML content file.
The YAML supports three fill modes. All keys are optional.
yamlprotections: korean_font: "맑은 고딕" # CJK font (set to "Noto Sans CJK KR", "SimSun", # "MS Mincho", etc. for other locales) cant_split: true # Apply <w:cantSplit/> to every filled row # Readability options (see "Readability" section below for full semantics) blank_between_paragraphs: true # default true — Enter between \n\n chunks blank_around_section_header: true # default true — Enter above/below filled sections blank_around_all_section_headers: false # default false — opt-in; also touches untouched sections # Mode 1 — table key/value (left-label cell → right value cell) table_kv: "Study Title": "Multi-center prospective validation of ..." "Principal Investigator": "Last, First (Department)" "연구 목적": "본 연구는 ..." # Mode 2 — section replacement (find numbered header, replace until next header) section_replace: "1. Background": "Hepatocellular carcinoma is the third leading cause of ..." "4. 연구 배경 및 이론적 근거": "..." # Mode 3 — single paragraph in-place text replacement paragraph_replace: "Title:": "Title: Multi-center prospective validation of ..."
All blank paragraphs inserted by these options use a forced single-line height (<w:spacing w:line="240" w:before="0" w:after="0"/>) so the gap is exactly one body-text line — never inflates the document's apparent line spacing.
| Option | Default | What it does | When to flip | |---|---|---|---| | blank_between_paragraphs | true | Inserts a blank line between every \n\n-split chunk inside section_replace | Disable only for forms where every line must be packed tight | | blank_around_section_header | true | Wraps each header that you section_replace with a blank above and a blank below | Disable when the template style already adds visual gaps via space_before/after | | blank_around_all_section_headers | false | After all fills, scans every numbered header (\d+\.\s+) — including ones you didn't replace — and adds blank lines around them | Enable when uniform readability matters more than form fidelity. Default off because IRB / public-document submissions favor template fidelity over visual consistency (page count stability, boilerplate untouched, reviewer-expected layout) | | normalize_page_breaks | true | On save, converts dangling empty paragraphs whose sole content is <w:br w:type="page"/> into a <w:pageBreakBefore/> attribute on the next content paragraph. Prevents visible blank pages when the preceding content (e.g. an abstract table) grows or shrinks and pushes the empty paragraph onto a page of its own, causing the break to land one page later. | Disable only if your template intentionally relies on the empty-paragraph-as-separator pattern for spacing |
The third option exists because section_replace only touches sections you list in the YAML. If a template has 18 numbered sections and you only fill 12, the other 6 stay tight against their content — visually inconsistent. Turn the opt-in on for documents where you'd rather the consistency than the fidelity.
bashpython scripts/fill_form.py \ --template path/to/template.docx \ --content content.yaml \ --output path/to/filled.docx
The CLI prints [OK] / [MISS] for every fill operation and a summary at the end. Investigate any [MISS] before submitting.
bashsoffice --headless --convert-to pdf path/to/filled.docx
Open the PDF and visually confirm: page count is sensible, no table row was split across pages, no font fell back to Times New Roman, all required fields are populated.
pythonfrom fill_form import FormFiller filler = FormFiller("template.docx", korean_font="맑은 고딕") # Fill table cells filler.fill_table_kv("Study Title", "...") filler.fill_table_kv("연구 목적", "...") # Replace section content (header to next header) filler.replace_paragraphs_after("4. Background", new_content) # Replace a single paragraph filler.replace_paragraph_matching("Title:", "Title: ...") # Validate and save warnings = filler.validate() for w in warnings: print(w) filler.save("filled.docx")
| Anti-pattern | Consequence | |---|---| | Document() then rebuild table | Loss of header logo, custom margins, footer placeholders, and page numbering | | pandoc -f doc -t docx | "Unknown input format doc" — pandoc does not parse .doc | | textutil -convert docx | Table cell merging is dropped or corrupted | | cell.text = "value" (single assignment) | Run-level styles (bold, color, eastAsia font) are erased | | Coordinate-based matching table.cell(2, 1) | Silent breakage when the template adds or reorders rows | | run.font.name alone for Hangul | Hangul characters render in the default Western font |
write-protocol — drafts the scientific content (Background, Study Design,Sample Size, Statistical Plan) that fill-protocol then renders into the form
hwp-pipeline — converts Korean Hangul .hwp / .hwpx files; chain it beforefill-protocol when the institutional form is distributed in HWP format
check-reporting — validates that the filled protocol satisfies CONSORT /STARD / TRIPOD / CLAIM checklists before submission
calc-sample-size — produces the sample size text that fill-protocol slotsinto the corresponding section
scripts/doc_to_docx.py — LibreOffice headless wrapper for .doc → .docxscripts/inspect_template.py — reports tables, cells, and paragraphsscripts/fill_form.py — the FormFiller library and CLI entry pointexamples/ — worked examples for IRB, ethics waiver, and grant templatesreferences/best_practices.md — formatting notes (cantSplit, eastAsia,multi-line cell text)
hwp-pipeline toconvert HWP → HWPX → DOCX first.
may overwrite the merged region's content. Test on a copy first.
paragraph and table cell content only.
/search-lit with confirmed DOI or PMID. Mark unverified references as [UNVERIFIED - NEEDS MANUAL CHECK].[VERIFY] and ask the user.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-06 | fail→fail | 6,272 | 12,855 | +105% | 1 | 1 | 0% | 1,196 | 5,336 | +346% | 0 | 0 | — |
case-01 | fail→fail | 3,607 | 5,645 | +57% | 1 | 1 | 0% | 271 | 3,045 | +1024% | 0 | 0 | — |
case-02 | fail→fail | 18,011 | 4,226 | -77% | 1 | 1 | 0% | 3,832 | 2,993 | -22% | 0 | 0 | — |
case-03 | fail→fail | 6,068 | 4,959 | -18% | 1 | 1 | 0% | 298 | 3,058 | +926% | 0 | 0 | — |
case-04 | pass→pass | 20,228 | 18,990 | -6% | 1 | 1 | 0% | 3,753 | 6,292 | +68% | 0 | 0 | — |
case-05 | fail→pass | 22,077 | 15,969 | -28% | 1 | 1 | 0% | 4,366 | 5,185 | +19% | 0 | 0 | — |
case-07 | pass→pass | 31,582 | 5,506 | -83% | 1 | 1 | 0% | 2,082 | 3,859 | +85% | 0 | 0 | — |
case-08 | pass→pass | 15,726 | 10,906 | -31% | 1 | 1 | 0% | 2,629 | 4,625 | +76% | 0 | 0 | — |
case-09 | pass→pass | 13,112 | 8,203 | -37% | 1 | 1 | 0% | 2,537 | 4,172 | +64% | 0 | 0 | — |
case-10 | pass→pass | 5,124 | 4,595 | -10% | 1 | 1 | 0% | 900 | 3,550 | +294% | 0 | 0 | — |
case-11 | fail→pass | 9,951 | 3,479 | -65% | 1 | 1 | 0% | 1,562 | 3,308 | +112% | 0 | 0 | — |
case-12 | fail→pass | 14,328 | 4,590 | -68% | 1 | 1 | 0% | 2,358 | 3,473 | +47% | 0 | 0 | — |
case-13 | fail→pass | 12,026 | 5,167 | -57% | 1 | 1 | 0% | 1,948 | 3,557 | +83% | 0 | 0 | — |
case-14 | pass→pass | 9,743 | 10,335 | +6% | 1 | 1 | 0% | 1,757 | 4,474 | +155% | 0 | 0 | — |
case-15 | pass→pass | 13,465 | 6,720 | -50% | 1 | 1 | 0% | 2,169 | 3,903 | +80% | 0 | 0 | — |
case-16 | pass→pass | 13,472 | 5,000 | -63% | 1 | 1 | 0% | 2,197 | 3,542 | +61% | 0 | 0 | — |
case-17 | pass→pass | 19,130 | 7,244 | -62% | 1 | 1 | 0% | 3,291 | 3,846 | +17% | 0 | 0 | — |
case-18 | fail→pass | 8,288 | 5,416 | -35% | 1 | 1 | 0% | 1,339 | 3,641 | +172% | 0 | 0 | — |
case-19 | fail→pass | 12,797 | 7,506 | -41% | 1 | 1 | 0% | 2,058 | 3,744 | +82% | 0 | 0 | — |
case-20 | fail→pass | 7,698 | 2,533 | -67% | 1 | 1 | 0% | 1,246 | 3,041 | +144% | 0 | 0 | — |
case-21 | pass→pass | 10,103 | 3,392 | -66% | 1 | 1 | 0% | 1,620 | 3,254 | +101% | 0 | 0 | — |
case-22 | fail→pass | 11,647 | 2,366 | -80% | 1 | 1 | 0% | 1,775 | 3,014 | +70% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +36 percentage points is the difference between those two pass rates over the 19 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The publisher has shipped newer versions since this run, so these numbers describe v1, not the version currently listed.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.