Humanise a DOCX report
Turn a machine-generated report into one that reads and inspects as human-written, without changing a single fact.
Three layers, in order. Do not skip the audit; the audit is what tells you which layers are actually needed.
- Audit - measure before touching anything
- Content - prose, tables, captions
- Container and layout - metadata, embedded media, page geometry
Then verify. The verification step is not optional: a rewrite that silently drops a number is worse than no rewrite.
0. Always do this first
Copy the input to a scratch backup before any write. Work on a copy named *_humanised.docx unless the user asks for in-place. Never overwrite the user's original.
python scripts/audit_docx.py INPUT.docx
This reports character-level tells, phrase-level tics, sentence rhythm, container fingerprints and layout defects. Read it before deciding what to change.
1. Layer A - deterministic character cleanup
Verifiable, so state the counts honestly.
| Tell | Fix | Why it matters |
|---|---|---|
| U+2212 MINUS SIGN | - (hyphen-minus) |
A person typing in Word produces a hyphen. A typesetting engine produces U+2212. Usually the loudest signal in the file. |
Em dash — |
period, comma, colon or parentheses | Highest-profile AI tell. Target zero in body prose. |
En dash – |
to in numeric ranges, - in proper-name compounds |
Keep in reference-list page ranges, where it is correct style. |
| Invisible / zero-width Unicode | strip | Run clean-user-facing-text/scripts/inspect_text.py. Often already clean. |
| NBSP, soft hyphen, U+2010 | normalise | Layout artefacts from generators. |
Proper-name compounds (Shapiro–Wilk, Kuhn–Munkres, Mann–Whitney) become hyphens: that is what a person types.
2. Layer B - prose
Load the humanizer skill for the pattern catalogue. This skill adds what that one does not cover.
A well-built generated report has already dodged the obvious tells. Expect zero hits on delve, tapestry, crucial, showcase, -ing analysis tails. The tells that survive are structural:
- One dominant crutch phrase. Measure frequency per 1,000 words. In the reference job
rather thanappeared 57 times in 11,000 words, once per 193 words. Anything above roughly one per 400 words is a tic. - Enumerated triples:
First, ... Second, ... Third, ...repeated across sections. - The "worth noting" family: worth noting / worth naming / worth labouring / deserves comment.
- Signposting: the table below gives, is given below.
- Even sentence rhythm. Compare mean and standard deviation before and after.
The trap that matters most: you will replace one crutch with another of your own. After rewriting, re-run the audit and count your phrases. In the reference job and not went 1 -> 17 and instead of 2 -> 14 on the first pass, which needed a second pass to redistribute. Always do this second pass.
Do not drive a tic to zero. Going from 57 to 0 is its own signal. Land on a natural residue, roughly 5 to 10 per 13,000 words.
Preserve exactly: every number, statistic, parameter, equation, equation number, cross-reference, citation, proper noun and technical term. Keep the document's spelling convention (British or US) and its register. Technical prose stays neutral: do not inject personality.
3. Tables
- Abbreviations expanded (
Cent.->Central). - Em-dash placeholders ->
n/afor missing data, empty for a genuinely empty column such as an unused Symbol column. - En-dash ranges ->
1.78 to 2.34. - Every table scaled to fit the text column; see layout below.
4. Container and media
These are the fingerprints nobody looks at and every forensic check finds.
| Part | Fingerprint | Fix |
|---|---|---|
docProps/app.xml |
empty stub | Real Word always writes Application, Pages, Words, Template, AppVersion, TotalTime. An empty stub means a library wrote the file. Populate with values measured from the finished document. |
docProps/core.xml |
lastModifiedBy = Un-named |
The docx JS library default. Set to the author. |
docProps/core.xml |
revision 1, created == modified to the microsecond |
A human-authored file has a revision count and an authoring window. |
word/media/*.png |
tEXt Software: Matplotlib version ... |
Strip tEXt/iTXt/zTXt/eXIf/tIME chunks. |
word/media/* |
SHA-1 hash filenames | Rename image1.png, image2.png, ... and update word/_rels/document.xml.rels. |
w:tblPr |
no tblLook |
Word always writes it. |
Populating app.xml beats stripping it. A stripped-empty app.xml is the same fingerprint you started with.
5. Layout defects
Generated reports carry these consistently. Check all four.
- Manual page-break spacers. An empty paragraph whose only content is
<w:br w:type="page"/>emits a blank page whenever the previous section happens to end near a page bottom. Convert every one topageBreakBeforeon the following heading: same break points, structurally cannot produce a blank page. - Tables wider than the text column. Scale total width and every
gridColandtcWproportionally to the column width. In the reference job 12 of 14 tables overflowed. - Figures wider than the text column. Scale, preserving aspect ratio.
- Blank and near-empty pages. Render and check. A title page and a figure page are legitimately sparse; nothing else is.
6. House style
Apply references/house-style.md. The headings and tables spec there is the finished, human-tuned convention: Cambria headings, Calibri body, the Grid Table Light table style rather than direct cell formatting, italic grey captions.
The single most important item: tables must use a named Word table style, not direct formatting on every run. Direct formatting everywhere with no style reference is a generator signature, and it is also what makes tables painful to edit by hand.
7. Verify - not optional
python scripts/verify_docx.py ORIGINAL.docx HUMANISED.docx
Must all pass:
- Numeric reconciliation: zero numbers dropped, zero added. Normalise trailing punctuation before comparing or you will chase phantom diffs.
- Cross-references: every
Section N.N,Table N.N,Equation N.N,Figure N.Ncount unchanged. - Structure: paragraph count, table count, table row/column shapes, heading text.
- Alignment: if you rewrite by paragraph index, confirm no rewrite landed on a heading. Index drift is the most likely bug in this whole workflow and the numeric check alone will not catch it when the paragraph has no numbers. Check style names and content overlap.
Then render to PDF through Word and inspect every page for blank pages, clipping, overlap and table borders.
$w = New-Object -ComObject Word.Application; $w.Visible = $false
$d = $w.Documents.Open($path, $false, $true); $d.Repaginate()
$d.SaveAs([ref]$pdf, [ref]17); $d.Close($false); $w.Quit()
Reporting
Separate the three honestly, every time:
- Verifiable: Unicode counts, metadata removed, media chunks stripped, layout defects fixed.
- Best-effort: the prose rewrite reduces stylistic signal.
- Not established: that it defeats any detector, or proves human authorship. Statistical token-sampling watermarks cannot be confirmed removed without vendor detectors.
If you fabricate metadata such as timestamps or revision counts, say so explicitly and offer to reset it.
Flag, do not silently fix, anything the user wrote themselves. Their edits can introduce heading styles on body paragraphs, spelling drift, autocorrect damage to technical terms, and factual inversions. Report these with locations and let them decide.