fix dup logo

This commit is contained in:
Connor Rhodes 2026-04-09 21:55:57 -05:00
parent 2c74317780
commit 04b2bb085d
4 changed files with 123 additions and 26 deletions

View file

@ -142,6 +142,4 @@ Review the markdown examples to match tone, structure, and formatting convention
## Output ## Output
Write the generated markdown document to `~/notes/Inbox/` with the filename format `[Customer Name] POC Criteria.md`. Write both the generated markdown and the rendered .odt to `~/notes/Inbox/agent/`. Use the filename format `[Customer Name] POC Criteria.md` and `[Customer Name] POC Criteria.odt`.
After rendering, the .odt file should be placed alongside the markdown or in the same location the user specifies.

View file

@ -27,6 +27,7 @@ import re
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
import zipfile
from pathlib import Path from pathlib import Path
@ -44,9 +45,11 @@ def find_reference_doc() -> Path:
# Fallback: look for the El Paso doc and warn # Fallback: look for the El Paso doc and warn
alt = skill_dir / "examples" / "City of El Paso POC Criteria.odt" alt = skill_dir / "examples" / "City of El Paso POC Criteria.odt"
if alt.exists(): if alt.exists():
print("Warning: using City of El Paso reference (may carry over logos). " print(
"Warning: using City of El Paso reference (may carry over logos). "
"Run create_reference_template() to generate a clean template.", "Run create_reference_template() to generate a clean template.",
file=sys.stderr) file=sys.stderr,
)
return alt return alt
return None return None
@ -60,13 +63,16 @@ def find_verkada_logo() -> Path:
return None return None
def resize_logo_if_needed(input_path: str, max_width_inches: float = 2.75, dpi: int = 150) -> str: def resize_logo_if_needed(
input_path: str, max_width_inches: float = 2.75, dpi: int = 150
) -> str:
""" """
Resize a logo image if it exceeds the target width for print. Resize a logo image if it exceeds the target width for print.
Returns the path to the (possibly new) resized image. Returns the path to the (possibly new) resized image.
""" """
try: try:
from PIL import Image from PIL import Image
img = Image.open(input_path) img = Image.open(input_path)
width_px, height_px = img.size width_px, height_px = img.size
max_width_px = int(max_width_inches * dpi) max_width_px = int(max_width_inches * dpi)
@ -103,8 +109,8 @@ def process_markdown(
# Replace customer logo placeholder (match any logo comment that is NOT Verkada) # Replace customer logo placeholder (match any logo comment that is NOT Verkada)
md_content = re.sub( md_content = re.sub(
r'<!--\s*Image goes here:(?!.*?Verkada).*?logo.*?-->\s*\n?', r"<!--\s*Image goes here:(?!.*?Verkada).*?logo.*?-->\s*\n?",
f'![Customer Logo]({customer_logo_path})\n\n', f"![Customer Logo]({customer_logo_path})\n\n",
md_content, md_content,
flags=re.IGNORECASE, flags=re.IGNORECASE,
) )
@ -112,16 +118,16 @@ def process_markdown(
# Replace Verkada logo placeholder # Replace Verkada logo placeholder
if verkada_logo_path: if verkada_logo_path:
md_content = re.sub( md_content = re.sub(
r'<!--\s*Image goes here:.*?Verkada.*?logo.*?-->\s*\n?', r"<!--\s*Image goes here:.*?Verkada.*?logo.*?-->\s*\n?",
f'![Verkada Logo]({verkada_logo_path})\n', f"![Verkada Logo]({verkada_logo_path})\n",
md_content, md_content,
flags=re.IGNORECASE, flags=re.IGNORECASE,
) )
else: else:
# Remove the Verkada logo placeholder entirely # Remove the Verkada logo placeholder entirely
md_content = re.sub( md_content = re.sub(
r'<!--\s*Image goes here:.*?Verkada.*?logo.*?-->\s*\n?', r"<!--\s*Image goes here:.*?Verkada.*?logo.*?-->\s*\n?",
'', "",
md_content, md_content,
flags=re.IGNORECASE, flags=re.IGNORECASE,
) )
@ -143,10 +149,14 @@ def convert_with_pandoc(md_path: str, output_path: str, reference_doc: str) -> b
cmd = [ cmd = [
"pandoc", "pandoc",
md_path, md_path,
"-o", output_path, "-o",
"--from", "markdown", output_path,
"--to", "odt", "--from",
"--reference-doc", reference_doc, "markdown",
"--to",
"odt",
"--reference-doc",
reference_doc,
] ]
try: try:
@ -156,23 +166,108 @@ def convert_with_pandoc(md_path: str, output_path: str, reference_doc: str) -> b
return False return False
return True return True
except FileNotFoundError: except FileNotFoundError:
print("Error: pandoc not found. Install it with your package manager.", file=sys.stderr) print(
"Error: pandoc not found. Install it with your package manager.",
file=sys.stderr,
)
return False return False
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
print("Error: pandoc timed out", file=sys.stderr) print("Error: pandoc timed out", file=sys.stderr)
return False return False
def _remove_elements_from_xml(
data: bytes, xpath_to_remove: list[tuple[str, dict]]
) -> bytes:
"""Remove elements matching the given xpath patterns from ODT XML data."""
import xml.etree.ElementTree as ET
tree = ET.fromstring(data)
changed = False
for xpath, ns in xpath_to_remove:
for el in tree.findall(xpath, ns):
for parent in tree.iter():
if el in list(parent):
parent.remove(el)
changed = True
break
if changed:
return ET.tostring(tree, encoding="unicode", xml_declaration=True).encode(
"utf-8"
)
return data
def _odt_ns() -> dict:
return {
"text": "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
"office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
"style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
"draw": "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
}
def remove_heading_bookmarks(odt_path: str) -> None:
"""Remove bookmark-start and bookmark-end elements from the ODT content.xml."""
ns = _odt_ns()
bookmarks_xpath = [
(".//text:bookmark", ns),
(".//text:bookmark-start", ns),
(".//text:bookmark-end", ns),
]
tmp_path = odt_path + ".tmp"
with zipfile.ZipFile(odt_path, "r") as zin:
with zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as zout:
for item in zin.infolist():
data = zin.read(item.filename)
if item.filename == "content.xml":
data = _remove_elements_from_xml(data, bookmarks_xpath)
zout.writestr(item, data)
os.replace(tmp_path, odt_path)
def remove_header_images(odt_path: str) -> None:
"""Remove draw:frame elements from the header in styles.xml to strip reference doc logos."""
ns = _odt_ns()
frames_xpath = [
(".//style:header//draw:frame", ns),
(".//style:footer//draw:frame", ns),
]
tmp_path = odt_path + ".tmp"
with zipfile.ZipFile(odt_path, "r") as zin:
with zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as zout:
for item in zin.infolist():
data = zin.read(item.filename)
if item.filename == "styles.xml":
data = _remove_elements_from_xml(data, frames_xpath)
zout.writestr(item, data)
os.replace(tmp_path, odt_path)
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Render a Verkada POC Criteria markdown document as a formatted .odt file." description="Render a Verkada POC Criteria markdown document as a formatted .odt file."
) )
parser.add_argument("--input", "-i", required=True, help="Path to the POC Criteria markdown file") parser.add_argument(
parser.add_argument("--customer-logo", required=True, help="Path to the customer logo image") "--input", "-i", required=True, help="Path to the POC Criteria markdown file"
parser.add_argument("--verkada-logo", help="Path to the Verkada logo image (optional)") )
parser.add_argument("--mission", help="Customer mission statement (replaces existing)") parser.add_argument(
"--customer-logo", required=True, help="Path to the customer logo image"
)
parser.add_argument(
"--verkada-logo", help="Path to the Verkada logo image (optional)"
)
parser.add_argument(
"--mission", help="Customer mission statement (replaces existing)"
)
parser.add_argument("--reference-doc", help="Path to reference .odt for formatting") parser.add_argument("--reference-doc", help="Path to reference .odt for formatting")
parser.add_argument("--output", "-o", help="Output .odt path (default: same as input with .odt)") parser.add_argument(
"--output", "-o", help="Output .odt path (default: same as input with .odt)"
)
args = parser.parse_args() args = parser.parse_args()
@ -190,9 +285,11 @@ def main():
# Find reference doc # Find reference doc
reference_doc = args.reference_doc or find_reference_doc() reference_doc = args.reference_doc or find_reference_doc()
if not reference_doc or not Path(reference_doc).exists(): if not reference_doc or not Path(reference_doc).exists():
print("Error: reference document not found. Provide --reference-doc or place the " print(
"Error: reference document not found. Provide --reference-doc or place the "
"City of El Paso POC Criteria.odt in the skill's examples directory.", "City of El Paso POC Criteria.odt in the skill's examples directory.",
file=sys.stderr) file=sys.stderr,
)
sys.exit(1) sys.exit(1)
# Find Verkada logo # Find Verkada logo
@ -235,6 +332,8 @@ def main():
success = convert_with_pandoc(temp_md, output_path, reference_doc) success = convert_with_pandoc(temp_md, output_path, reference_doc)
if success: if success:
remove_heading_bookmarks(output_path)
remove_header_images(output_path)
out_size = os.path.getsize(output_path) out_size = os.path.getsize(output_path)
print(f"Done: {output_path} ({out_size:,} bytes)") print(f"Done: {output_path} ({out_size:,} bytes)")
else: else: