- Create Economist SubmissionTracking package correctly: * mainArticle = full blog post content * coverLetter = 216-word SIR— letter * Links to blog post via blogPostId - Archive 'Letter to The Economist' from blog posts (it's the cover letter) - Fix date display on article cards (use published_at) - Target publication already displaying via blue badge Database changes: - Make blogPostId optional in SubmissionTracking model - Economist package ID: 68fa85ae49d4900e7f2ecd83 - Le Monde package ID: 68fa2abd2e6acd5691932150 Next: Enhanced modal with tabs, validation, export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
265 lines
6.8 KiB
Python
Executable file
265 lines
6.8 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
Generate PDF from Architectural Safeguards Against LLM Hierarchical Dominance (Prose version)
|
|
"""
|
|
|
|
import markdown
|
|
from weasyprint import HTML, CSS
|
|
from pathlib import Path
|
|
|
|
def generate_pdf():
|
|
"""Convert the prose markdown document to PDF with professional styling"""
|
|
|
|
# Paths
|
|
script_dir = Path(__file__).parent
|
|
project_root = script_dir.parent
|
|
input_file = project_root / 'docs' / 'research' / 'ARCHITECTURAL-SAFEGUARDS-Against-LLM-Hierarchical-Dominance-Prose.md'
|
|
output_file = project_root / 'docs' / 'research' / 'ARCHITECTURAL-SAFEGUARDS-Against-LLM-Hierarchical-Dominance-Prose.pdf'
|
|
|
|
print(f"Reading markdown from: {input_file}")
|
|
|
|
# Read markdown content
|
|
with open(input_file, 'r', encoding='utf-8') as f:
|
|
md_content = f.read()
|
|
|
|
print("Converting markdown to HTML...")
|
|
|
|
# Convert markdown to HTML with extensions
|
|
html_content = markdown.markdown(
|
|
md_content,
|
|
extensions=[
|
|
'markdown.extensions.tables',
|
|
'markdown.extensions.fenced_code',
|
|
'markdown.extensions.toc',
|
|
'markdown.extensions.sane_lists'
|
|
]
|
|
)
|
|
|
|
# Wrap in full HTML document with professional styling
|
|
full_html = f"""
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Architectural Safeguards Against LLM Hierarchical Dominance</title>
|
|
</head>
|
|
<body>
|
|
{html_content}
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# Professional CSS styling
|
|
css = CSS(string="""
|
|
@page {
|
|
size: Letter;
|
|
margin: 1in;
|
|
@bottom-center {
|
|
content: counter(page);
|
|
font-size: 10pt;
|
|
color: #666;
|
|
}
|
|
}
|
|
|
|
body {
|
|
font-family: "Georgia", "Times New Roman", serif;
|
|
font-size: 11pt;
|
|
line-height: 1.6;
|
|
color: #333;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 24pt;
|
|
font-weight: bold;
|
|
color: #1976d2;
|
|
margin-top: 24pt;
|
|
margin-bottom: 12pt;
|
|
page-break-after: avoid;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 18pt;
|
|
font-weight: bold;
|
|
color: #1976d2;
|
|
margin-top: 20pt;
|
|
margin-bottom: 10pt;
|
|
page-break-after: avoid;
|
|
border-bottom: 2px solid #1976d2;
|
|
padding-bottom: 4pt;
|
|
}
|
|
|
|
h3 {
|
|
font-size: 14pt;
|
|
font-weight: bold;
|
|
color: #424242;
|
|
margin-top: 16pt;
|
|
margin-bottom: 8pt;
|
|
page-break-after: avoid;
|
|
}
|
|
|
|
h4 {
|
|
font-size: 12pt;
|
|
font-weight: bold;
|
|
color: #424242;
|
|
margin-top: 12pt;
|
|
margin-bottom: 6pt;
|
|
page-break-after: avoid;
|
|
}
|
|
|
|
p {
|
|
margin-top: 0;
|
|
margin-bottom: 10pt;
|
|
text-align: justify;
|
|
}
|
|
|
|
strong {
|
|
font-weight: bold;
|
|
color: #000;
|
|
}
|
|
|
|
em {
|
|
font-style: italic;
|
|
}
|
|
|
|
ul, ol {
|
|
margin-top: 8pt;
|
|
margin-bottom: 8pt;
|
|
padding-left: 24pt;
|
|
}
|
|
|
|
li {
|
|
margin-bottom: 4pt;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 12pt;
|
|
margin-bottom: 12pt;
|
|
page-break-inside: avoid;
|
|
}
|
|
|
|
th {
|
|
background-color: #1976d2;
|
|
color: white;
|
|
font-weight: bold;
|
|
padding: 8pt;
|
|
text-align: left;
|
|
border: 1px solid #1976d2;
|
|
}
|
|
|
|
td {
|
|
padding: 6pt;
|
|
border: 1px solid #ddd;
|
|
}
|
|
|
|
tr:nth-child(even) {
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
blockquote {
|
|
margin: 12pt 24pt;
|
|
padding: 8pt 12pt;
|
|
background-color: #f5f5f5;
|
|
border-left: 4px solid #1976d2;
|
|
font-style: italic;
|
|
}
|
|
|
|
hr {
|
|
border: none;
|
|
border-top: 2px solid #ddd;
|
|
margin: 20pt 0;
|
|
}
|
|
|
|
/* Prevent orphans and widows */
|
|
p, li, h1, h2, h3, h4 {
|
|
orphans: 3;
|
|
widows: 3;
|
|
}
|
|
|
|
/* Keep related content together */
|
|
h1, h2, h3, h4 {
|
|
page-break-after: avoid;
|
|
}
|
|
|
|
/* Table styling improvements */
|
|
table {
|
|
font-size: 10pt;
|
|
}
|
|
|
|
/* Code/technical terms */
|
|
code {
|
|
font-family: "Courier New", monospace;
|
|
font-size: 10pt;
|
|
background-color: #f5f5f5;
|
|
padding: 2pt 4pt;
|
|
border-radius: 2pt;
|
|
}
|
|
""")
|
|
|
|
print("Generating PDF...")
|
|
|
|
# Generate PDF with metadata
|
|
from weasyprint import __version__ as weasyprint_version
|
|
|
|
# Create HTML object and write PDF with metadata
|
|
html_obj = HTML(string=full_html)
|
|
html_obj.write_pdf(
|
|
output_file,
|
|
stylesheets=[css],
|
|
pdf_forms=True
|
|
)
|
|
|
|
# Add PDF metadata using PyPDF2
|
|
try:
|
|
from PyPDF2 import PdfReader, PdfWriter
|
|
|
|
# Read the generated PDF
|
|
reader = PdfReader(output_file)
|
|
writer = PdfWriter()
|
|
|
|
# Copy all pages
|
|
for page in reader.pages:
|
|
writer.add_page(page)
|
|
|
|
# Add metadata
|
|
writer.add_metadata({
|
|
'/Title': 'Architectural Safeguards Against LLM Hierarchical Dominance',
|
|
'/Author': 'Agentic Governance Research Team',
|
|
'/Subject': 'AI Safety, LLM Governance, Value Pluralism, Deliberative AI',
|
|
'/Keywords': 'AI Safety, LLM, Hierarchical Dominance, Pluralistic Deliberation, Tractatus',
|
|
'/Creator': 'Tractatus Framework',
|
|
'/Producer': f'WeasyPrint {weasyprint_version}',
|
|
})
|
|
|
|
# Write the updated PDF
|
|
with open(output_file, 'wb') as f:
|
|
writer.write(f)
|
|
|
|
print("✓ PDF metadata added successfully")
|
|
except ImportError:
|
|
print("⚠ PyPDF2 not installed - PDF generated without metadata")
|
|
print(" Install with: pip install PyPDF2")
|
|
|
|
print(f"✓ PDF generated successfully: {output_file}")
|
|
print(f" File size: {output_file.stat().st_size / 1024:.1f} KB")
|
|
|
|
return output_file
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
output_path = generate_pdf()
|
|
print("\n" + "="*60)
|
|
print("PDF Generation Complete")
|
|
print("="*60)
|
|
print(f"\nOutput: {output_path}")
|
|
print("\nYou can now:")
|
|
print(" - Open the PDF in any PDF viewer")
|
|
print(" - Share with stakeholders, funders, researchers")
|
|
print(" - Print for offline reading")
|
|
|
|
except Exception as e:
|
|
print(f"\n✗ Error generating PDF: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
exit(1)
|