Mark all correct answers
cd /home/daytona/workspace && pandoc --track-changes=all "/home/daytona/workspac…
ls /home/daytona/workspace/attachments/be8e8ecc-c01e-472d-9ffb-7ddcf45c14df/
ls /home/daytona/skills/docx/scripts/office/ 2>/dev/null && ls /home/daytona/ski…
mkdir -p /home/daytona/workspace/surgery-output && python /home/daytona/skills/d…
~/surgery-output/mark_answers.py
#!/usr/bin/env python3
"""
Mark correct answers in the Surgery.docx by adding green highlight to the correct answer paragraphs.
Strategy: parse document.xml, identify question blocks, find the correct answer paragraph,
add w:highlight w:val="green" to each run in that paragraph.
"""
import re
import shutil
import subprocess
import os
# Correct answers (1-indexed, matching the answer text fragments)
# Each entry: question number -> substring to match in the correct answer
correct_answers = {
1: "penetration",
2: "pyloroduodenal stenosis",
3: "deep ulcerative niche",
4: "pneumoperitoneum",
5: "deep ulcerative niche",
6: "malignancy of the ulcer",
7: "hemorrhagic shock",
8: "pneumoperitoneum",
9: "perforated ulcer",
10: "Kloiber",
11: "atheroma",
12: "acute pancreatitis",
13: "pancreatogenic shock",
14: "omental bursa abscess",
15: "acute appendicitis",
16: "appendicular infiltrate",
17: "abscess formation",
18: "subungual panaritium",
19: "paronychia",
20: "postoperative ventral hernia",
21: "without ulcers and inflammation",
22: "Ischemia 2B",
23: "acute thrombophlebitis of superficial veins",
24: "chronic external hemorrhoids",
25: "acute subcutaneous paraproctitis",
26: "crack of the anus",
27: "rectal prolapse",
28: "chronic paraproctitis",
29: "rectal prolapse, stage III",
30: "pinched inguinal hernia on the right",
31: "pinched femoral hernia on the left",
32: "postoperative ventral hernia",
33: "unrecoverable umbilical hernia",
34: "dropsy of the testicle",
35: "direct inguinal hernia on the right",
36: "fracture of the radius in a typical location",
37: "phlegmon of the foot",
38: "furuncle",
39: "late adhesive intestinal obstruction",
40: "retinal hemorrhage",
41: "FGDS with biopsy",
42: "FGDS",
43: "overview radiography of the abdominal cavity",
44: "fibrogastroduodenoscopy",
45: "FGDS",
46: "fibrogastroduodenoscopy",
47: "call of the JV team, referral to a surgical hospital in an emergency",
48: "referral to a planned operation in a hospital",
49: "transfer to the surgical department",
50: "call of the JV team, referral to the emergency surgery department",
51: "JV call and referral to the hospital in an emergency",
52: "Ultrasound of the abdominal cavity",
53: "Ultrasound of the abdominal cavity",
54: "JV call and referral to the hospital in an emergency",
55: "referral for surgical treatment to a hospital as planned",
56: "referral to emergency surgery",
57: "overview radiography of the abdominal cavity",
58: "overview radiography of the abdominal cavity",
59: "immediate referral to a surgical hospital by ambulance",
60: "immediate referral to a surgical hospital by ambulance",
61: "remove the foreign body after 2 times local drip anesthesia with a spear",
62: "removal of a foreign body after 2-fold local drip anesthesia with a wet swab",
63: "instillation of an anesthetic solution, removal of a foreign body, washing with water",
64: "copious washing, instillation of anesthetics",
65: "apply an aseptic bandage, send it to the eye department",
66: "urgently send for hospitalization in the eye department",
67: "apply an aseptic eye patch, send for hospitalization in the eye department",
68: "primary surgical treatment",
69: "local instillation anesthesia, removal of a foreign body",
70: "perform primary surgical treatment of the wound, instill disinfecting drops",
71: "prescribe diacarb 250-500 mg, 1 ml of 2% promedol",
72: "lipoma removal",
73: "opening of a boil in a purulent dressing",
74: "send for consultation to an oncologist, puncture with biopsy",
75: "diprospan",
76: "instrumental and laboratory research in a polyclinic",
77: "diacarb 250-500 mg, 1 ml of 2% promedol solution once, conjunctivally",
78: "ear washing with a 1:5000 furacilin solution from a janet syringe",
79: "infusion of warm oil",
80: "rinse the ear with a warm solution of furacilin",
}
# Read the document XML
doc_path = "/home/daytona/workspace/surgery-output/unpacked/word/document.xml"
with open(doc_path, "r", encoding="utf-8") as f:
content = f.read()
# We'll use a Python XML parser approach
from lxml import etree
W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
def w(tag):
return f"{{{W}}}{tag}"
tree = etree.fromstring(content.encode("utf-8"))
body = tree.find(w("body"))
paragraphs = body.findall(".//" + w("p"))
def get_para_text(para):
texts = []
for r in para.findall(".//" + w("r")):
for t in r.findall(w("t")):
if t.text:
texts.append(t.text)
return "".join(texts)
# Build list of (para_index, text) for all paragraphs
para_texts = [(i, get_para_text(p)) for i, p in enumerate(paragraphs)]
# Identify question start paragraphs: paragraphs containing "#N"
import re
question_starts = []
for i, (pi, text) in enumerate(para_texts):
m = re.match(r'^#(\d+)\s*$', text.strip())
if m:
question_starts.append((int(m.group(1)), pi, i)) # (q_num, para_index, list_index)
print(f"Found {len(question_starts)} question headers")
def add_highlight_to_para(para, color="green"):
"""Add highlight to all runs in a paragraph."""
for r in para.findall(w("r")):
rpr = r.find(w("rPr"))
if rpr is None:
rpr = etree.SubElement(r, w("rPr"))
r.insert(0, rpr)
# Check if highlight already exists
hl = rpr.find(w("highlight"))
if hl is None:
hl = etree.SubElement(rpr, w("highlight"))
hl.set(w("val"), color)
# Also add shading for better visibility
ppr = para.find(w("pPr"))
if ppr is None:
ppr = etree.SubElement(para, w("pPr"))
para.insert(0, ppr)
shd = ppr.find(w("shd"))
if shd is None:
shd = etree.SubElement(ppr, w("shd"))
shd.set(w("val"), "clear")
shd.set(w("color"), "auto")
shd.set(w("fill"), "92D050") # green fill
marked = 0
for q_idx, (q_num, para_idx_start, list_idx_start) in enumerate(question_starts):
if q_num not in correct_answers:
continue
correct_text = correct_answers[q_num].lower()
# Find end of this question (start of next question or end of doc)
if q_idx + 1 < len(question_starts):
para_idx_end = question_starts[q_idx + 1][1]
list_idx_end = question_starts[q_idx + 1][2]
else:
para_idx_end = len(paragraphs)
list_idx_end = len(para_texts)
# Among answer paragraphs for this question, find matching one
# The structure is: #N / !question text / answer1 / answer2 / ...
# We look for the answer paragraph whose text matches correct_text
best_match_para = None
best_score = 0
for li in range(list_idx_start + 1, list_idx_end):
pi, text = para_texts[li]
if not text.strip():
continue
# Skip if this is the question text (starts with !)
if text.strip().startswith("!"):
continue
# Check similarity
text_lower = text.strip().lower()
# Simple substring match
if correct_text in text_lower:
score = len(correct_text)
if score > best_score:
best_score = score
best_match_para = pi
# Partial match: check how many words of correct_text appear in text
elif best_match_para is None:
words = correct_text.split()
matches = sum(1 for w_ in words if w_ in text_lower)
if matches > best_score and matches >= max(1, len(words) // 2):
best_score = matches
best_match_para = pi
if best_match_para is not None:
add_highlight_to_para(paragraphs[best_match_para])
marked += 1
print(f"Q{q_num}: marked para {best_match_para} -> '{para_texts[best_match_para][1][:80]}'")
else:
print(f"Q{q_num}: NO MATCH for '{correct_text}'")
print(f"\nTotal marked: {marked}/{len(correct_answers)}")
# Write back
with open(doc_path, "wb") as f:
f.write(etree.tostring(tree, xml_declaration=True, encoding="UTF-8", standalone=True))
print("Saved document.xml")
cd /home/daytona/workspace/surgery-output && python mark_answers.py 2>&1
python -c "import xml.etree.ElementTree; import re; print('stdlib ok')" && pytho…
python -c "import xml.etree.ElementTree as ET; print('ET ok')" && node -e "const…
~/surgery-output/mark_answers.py
#!/usr/bin/env python3
"""Mark correct answers in Surgery.docx using stdlib xml.etree.ElementTree"""
import xml.etree.ElementTree as ET
import re, os, shutil
# Correct answers - text fragments to match
correct_answers = {
1: "penetration",
2: "pyloroduodenal stenosis",
3: "deep ulcerative niche",
4: "pneumoperitoneum",
5: "deep ulcerative niche",
6: "malignancy of the ulcer",
7: "hemorrhagic shock",
8: "pneumoperitoneum",
9: "perforated ulcer",
10: "Kloiber",
11: "atheroma",
12: "acute pancreatitis",
13: "pancreatogenic shock",
14: "omental bursa abscess",
15: "acute appendicitis",
16: "appendicular infiltrate",
17: "abscess formation",
18: "subungual panaritium",
19: "paronychia",
20: "postoperative ventral hernia",
21: "without ulcers and inflammation",
22: "Ischemia 2B",
23: "acute thrombophlebitis of superficial veins",
24: "chronic external hemorrhoids",
25: "acute subcutaneous paraproctitis",
26: "crack of the anus",
27: "rectal prolapse",
28: "chronic paraproctitis",
29: "rectal prolapse, stage III",
30: "pinched inguinal hernia on the right",
31: "pinched femoral hernia on the left",
32: "postoperative ventral hernia",
33: "unrecoverable umbilical hernia",
34: "dropsy of the testicle",
35: "direct inguinal hernia on the right",
36: "fracture of the radius in a typical location",
37: "phlegmon of the foot",
38: "furuncle",
39: "late adhesive intestinal obstruction",
40: "retinal hemorrhage",
41: "FGDS with biopsy",
42: "FGDS",
43: "overview radiography of the abdominal cavity",
44: "fibrogastroduodenoscopy",
45: "FGDS",
46: "fibrogastroduodenoscopy",
47: "call of the JV team",
48: "referral to a planned operation in a hospital",
49: "transfer to the surgical department",
50: "call of the JV team, referral to the emergency surgery department",
51: "JV call and referral to the hospital in an emergency",
52: "Ultrasound of the abdominal cavity",
53: "Ultrasound of the abdominal cavity",
54: "JV call and referral to the hospital in an emergency",
55: "referral for surgical treatment to a hospital as planned",
56: "referral to emergency surgery",
57: "overview radiography of the abdominal cavity",
58: "overview radiography of the abdominal cavity",
59: "immediate referral to a surgical hospital by ambulance",
60: "immediate referral to a surgical hospital by ambulance",
61: "remove the foreign body after 2 times local drip anesthesia with a spear",
62: "removal of a foreign body after 2-fold local drip anesthesia with a wet swab",
63: "instillation of an anesthetic solution, removal of a foreign body, washing with water",
64: "copious washing, instillation of anesthetics",
65: "apply an aseptic bandage, send it to the eye department",
66: "urgently send for hospitalization in the eye department",
67: "apply an aseptic eye patch, send for hospitalization in the eye department",
68: "primary surgical treatment",
69: "local instillation anesthesia, removal of a foreign body",
70: "perform primary surgical treatment of the wound, instill disinfecting drops",
71: "prescribe diacarb 250-500 mg, 1 ml of 2% promedol",
72: "lipoma removal",
73: "opening of a boil in a purulent dressing",
74: "send for consultation to an oncologist, puncture with biopsy",
75: "diprospan",
76: "instrumental and laboratory research in a polyclinic",
77: "diacarb 250-500 mg, 1 ml of 2% promedol solution once, conjunctivally",
78: "ear washing with a 1:5000 furacilin solution from a janet syringe",
79: "infusion of warm oil",
80: "rinse the ear with a warm solution",
}
doc_path = "/home/daytona/workspace/surgery-output/unpacked/word/document.xml"
# Register namespaces to preserve them
namespaces = {
'wpc': 'http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas',
'cx': 'http://schemas.microsoft.com/office/drawing/2014/chartex',
'cx1': 'http://schemas.microsoft.com/office/drawing/2015/9/8/chartex',
'cx2': 'http://schemas.microsoft.com/office/drawing/2015/10/21/chartex',
'cx3': 'http://schemas.microsoft.com/office/drawing/2016/5/9/chartex',
'cx4': 'http://schemas.microsoft.com/office/drawing/2016/5/10/chartex',
'cx5': 'http://schemas.microsoft.com/office/drawing/2016/5/11/chartex',
'cx6': 'http://schemas.microsoft.com/office/drawing/2016/5/12/chartex',
'cx7': 'http://schemas.microsoft.com/office/drawing/2016/5/13/chartex',
'cx8': 'http://schemas.microsoft.com/office/drawing/2016/5/14/chartex',
'mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
'aink': 'http://schemas.microsoft.com/office/drawing/2016/ink',
'am3d': 'http://schemas.microsoft.com/office/drawing/2017/model3d',
'o': 'urn:schemas-microsoft-com:office:office',
'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
'm': 'http://schemas.openxmlformats.org/officeDocument/2006/math',
'v': 'urn:schemas-microsoft-com:vml',
'wp14': 'http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing',
'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing',
'w10': 'urn:schemas-microsoft-com:office:word',
'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
'w14': 'http://schemas.microsoft.com/office/word/2010/wordml',
'w15': 'http://schemas.microsoft.com/office/word/2012/wordml',
'w16cex': 'http://schemas.microsoft.com/office/word/2018/wordml/cex',
'w16cid': 'http://schemas.microsoft.com/office/word/2016/wordml/cid',
'w16': 'http://schemas.microsoft.com/office/word/2018/wordml',
'w16sdtdh': 'http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash',
'w16se': 'http://schemas.microsoft.com/office/word/2015/wordml/symex',
'wpg': 'http://schemas.microsoft.com/office/word/2010/wordprocessingGroup',
'wpi': 'http://schemas.microsoft.com/office/word/2010/wordprocessingInk',
'wne': 'http://schemas.microsoft.com/office/word/2006/wordml',
'wps': 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape',
}
for prefix, uri in namespaces.items():
ET.register_namespace(prefix, uri)
W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
def wtag(name):
return f"{{{W}}}{name}"
with open(doc_path, "r", encoding="utf-8") as f:
raw = f.read()
tree = ET.fromstring(raw.encode("utf-8"))
body = tree.find(wtag("body"))
paragraphs = list(body.iter(wtag("p")))
def get_para_text(para):
parts = []
for t in para.iter(wtag("t")):
if t.text:
parts.append(t.text)
return "".join(parts)
para_texts = [get_para_text(p) for p in paragraphs]
# Find question starts (#N)
question_starts = []
for i, text in enumerate(para_texts):
m = re.match(r'^#(\d+)\s*$', text.strip())
if m:
question_starts.append((int(m.group(1)), i))
print(f"Found {len(question_starts)} questions")
def highlight_para(para):
"""Add green shading to paragraph background via pPr/shd."""
pPr = para.find(wtag("pPr"))
if pPr is None:
pPr = ET.Element(wtag("pPr"))
para.insert(0, pPr)
# Add/update shd
shd = pPr.find(wtag("shd"))
if shd is None:
shd = ET.SubElement(pPr, wtag("shd"))
shd.set(wtag("val"), "clear")
shd.set(wtag("color"), "auto")
shd.set(wtag("fill"), "00B050")
# Also highlight each run
for run in para.findall(wtag("r")):
rPr = run.find(wtag("rPr"))
if rPr is None:
rPr = ET.Element(wtag("rPr"))
run.insert(0, rPr)
hl = rPr.find(wtag("highlight"))
if hl is None:
hl = ET.SubElement(rPr, wtag("highlight"))
hl.set(wtag("val"), "green")
marked = 0
unmatched = []
for q_idx, (q_num, q_para_idx) in enumerate(question_starts):
if q_num not in correct_answers:
continue
correct_fragment = correct_answers[q_num].lower()
# range of paragraphs for this question
if q_idx + 1 < len(question_starts):
end_idx = question_starts[q_idx + 1][1]
else:
end_idx = len(paragraphs)
best_para_idx = None
best_score = -1
for pi in range(q_para_idx + 1, end_idx):
text = para_texts[pi].strip()
if not text:
continue
if text.startswith("!"):
continue # skip question stem
text_lower = text.lower()
if correct_fragment in text_lower:
score = len(correct_fragment)
if score > best_score:
best_score = score
best_para_idx = pi
if best_para_idx is None:
# try word-by-word
words = correct_fragment.split()
for pi in range(q_para_idx + 1, end_idx):
text = para_texts[pi].strip().lower()
if not text or text.startswith("!"):
continue
score = sum(1 for w_ in words if w_ in text)
if score > best_score:
best_score = score
best_para_idx = pi
if best_para_idx is not None and best_score > 0:
highlight_para(paragraphs[best_para_idx])
marked += 1
print(f" Q{q_num:2d}: '{para_texts[best_para_idx][:80]}'")
else:
unmatched.append(q_num)
print(f" Q{q_num:2d}: NO MATCH for '{correct_fragment}'")
print(f"\nMarked {marked}, Unmatched: {unmatched}")
# Serialize back
ET.indent(tree, space=" ")
out_xml = ET.tostring(tree, encoding="unicode", xml_declaration=False)
out_xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' + out_xml
with open(doc_path, "w", encoding="utf-8") as f:
f.write(out_xml)
print("Saved document.xml")
~/surgery-output/mark_answers.py
Error: String not found in file: '#!/usr/bin/env python3 """Mark correct answers in Surgery.docx using stdlib xml.etree.ElementTree"""'
cat > /home/daytona/workspace/surgery-output/mark2.py << 'PYEOF' #!/usr/bin/env …