Full-text manual diff R1.03 -> R1.05 (catch unlisted R1.04 edits)
tools/manual_diff.py: word-level diff of one manual between two doc folders. version-differences.md: the 20 manuals whose revision tables list only contact information were diffed; E10 and E30 have real content changes, the rest wording only. CLAUDE.md: re-checks use the full diff. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e90ad790a2
commit
af9a1bbe29
3 changed files with 97 additions and 4 deletions
|
|
@ -60,3 +60,7 @@ The R1.05 test server is shared with other projects. Never disturb their work th
|
|||
read-only checks unless the user approves a change, never restart CI Server, never write to other
|
||||
projects' records or to live plant values, and remove anything you put on the server.
|
||||
Fixes that also apply to R1.03 can be cherry-picked onto branch `r1.03`.
|
||||
|
||||
When re-checking a manual for R1.05, compare its full text with the R1.03 edition
|
||||
(`tools/manual_diff.py`), not just the revision table or release notes: R1.04 changes are
|
||||
often not listed there.
|
||||
|
|
|
|||
|
|
@ -67,10 +67,28 @@ the R1.04 and R1.05 revision tables.
|
|||
| IM 36K10G10 Tutorial | R1.04 §9.2, ch. 14 new. R1.05 ch. 14 extended, ch. 15 new | `knowledge/manuals/IM36K10G10-01EN.md` |
|
||||
| IM 36K10L55 Exchange Tool Engineering | R1.04: upgrade scenario for CI Exchange for CENTUM VP. R1.05 §2.7: HIS-TSE connection options | `knowledge/manuals/IM36K10L55-01EN.md` |
|
||||
|
||||
**No content change** (only contact information or wording): C20, C30, E10, E30, J20, J40,
|
||||
L10, L15, L20, L25, L30, L35, L36, L40, P10, P11, P20, P30, R20, R40. Skills based only on
|
||||
these manuals still need a quick re-test on R1.05 (installed files may differ), but their
|
||||
content is expected to hold.
|
||||
**Revision table lists only contact information:** C20, C30, E10, E30, J20, J40, L10, L15,
|
||||
L20, L25, L30, L35, L36, L40, P10, P11, P20, P30, R20, R40. The revision tables do not list
|
||||
every R1.04 edit, so each of these was also compared word by word with its R1.03 edition
|
||||
(`tools/manual_diff.py`, 2026-10-08). Result:
|
||||
|
||||
| Manual | Unlisted changes found by the text diff |
|
||||
|---|---|
|
||||
| E10 Excel Add-in | Item search: new description of the *Item Filter* / *Description Filter* boxes and wildcards (`*` default; both filters combine with AND); a CI Portal SSL note moved |
|
||||
| E30 Alarm System Performance Analysis | KPI grid: the KPI charts now have labels (*In Time*, *Too Late*, *Not Ackn*, *Nuisance*) |
|
||||
| L30 BUS Service | `durwfp` return value reworded (1 = process attached/running, 0 = otherwise); same meaning |
|
||||
| P11 Java class language | §10.2 methods reworded; same meaning |
|
||||
| P20 RQL | Footer `<position>` sentence split in two; same meaning |
|
||||
| L40, R20, R40, C30, L35, J40 | Spacing, punctuation, titles only |
|
||||
| C20, J20, L10, L15, L20, L25, L36, P10, P30 | None |
|
||||
|
||||
Skills based only on these manuals still need a quick re-test on R1.05 (installed files may
|
||||
differ), but their content is expected to hold. The E10 and E30 changes go into the
|
||||
`reporting` and `alarm-management` re-checks.
|
||||
|
||||
**Method for every manual re-check:** do not rely on the revision table or the release notes.
|
||||
Run `python tools/manual_diff.py <R1.03 doc\EN> <R1.05 doc\EN> <IM>` and review every change,
|
||||
including those made in R1.04.
|
||||
|
||||
## 3. Re-check plan
|
||||
|
||||
|
|
|
|||
71
tools/manual_diff.py
Normal file
71
tools/manual_diff.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
"""Word-level diff of one CI Server manual between two releases (e.g. R1.03 and R1.05).
|
||||
|
||||
Catches changes the revision table does not mention (for example R1.04 edits listed only as
|
||||
"Added contact information"). Needs `pdftotext` (Poppler / Git for Windows) on PATH.
|
||||
|
||||
Usage:
|
||||
python tools/manual_diff.py <old_pdf_dir> <new_pdf_dir> IM36K10L40-01EN [more IMs] [-o out_dir]
|
||||
|
||||
Prints one block per change: [replace]/[insert]/[delete] with old (-) and new (+) words.
|
||||
Page footers, edition lines and dates are removed first, so only text changes remain.
|
||||
Writes nothing into the repo; with -o it saves <IM>.diff.txt files in out_dir.
|
||||
"""
|
||||
import difflib
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
NOISE = re.compile(
|
||||
r'IM ?36K\w+-01EN( \d+\w\w Edition)?( : [A-Za-z]+\.? ?\d+, ?\d+-00)?'
|
||||
r'|\d+(st|nd|rd|th) Edition|Toc-\d+|Rev-\d+|App\.\d+-\d+|\b\d+-\d+\b')
|
||||
|
||||
|
||||
def words(pdf):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
out = os.path.join(d, 'm.txt')
|
||||
subprocess.run(['pdftotext', pdf, out], check=True)
|
||||
text = open(out, encoding='utf-8', errors='replace').read()
|
||||
return NOISE.sub(' ', text).split()
|
||||
|
||||
|
||||
def diff(old_pdf, new_pdf):
|
||||
a, b = words(old_pdf), words(new_pdf)
|
||||
sm = difflib.SequenceMatcher(None, a, b, autojunk=False)
|
||||
lines = []
|
||||
for op, a1, a2, b1, b2 in sm.get_opcodes():
|
||||
if op == 'equal':
|
||||
continue
|
||||
lines.append('[%s] at word %d\n - %s\n + %s' % (
|
||||
op, a1, ' '.join(a[a1:a2]), ' '.join(b[b1:b2])))
|
||||
return lines
|
||||
|
||||
|
||||
def main(argv):
|
||||
out_dir = None
|
||||
if '-o' in argv:
|
||||
i = argv.index('-o')
|
||||
out_dir = argv[i + 1]
|
||||
del argv[i:i + 2]
|
||||
if len(argv) < 3:
|
||||
sys.exit(__doc__)
|
||||
old_dir, new_dir, ims = argv[0], argv[1], argv[2:]
|
||||
for im in ims:
|
||||
old_pdf = os.path.join(old_dir, im + '.pdf')
|
||||
new_pdf = os.path.join(new_dir, im + '.pdf')
|
||||
if not os.path.exists(old_pdf):
|
||||
print('=====', im, ': new manual (no old edition)')
|
||||
continue
|
||||
lines = diff(old_pdf, new_pdf)
|
||||
report = '\n'.join(lines)
|
||||
print('=====', im, ':', len(lines), 'changes')
|
||||
if out_dir:
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
open(os.path.join(out_dir, im + '.diff.txt'), 'w', encoding='utf-8').write(report)
|
||||
else:
|
||||
print(report)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
Loading…
Add table
Reference in a new issue