Apt Analogies Templates Skill
Contains master code templates for generating timeline histograms and HTML output visualizations for the analogies database.
Apt Analogies Templates: Histogram & HTML Formatting
This skill contains the master code templates for generating output visualizations for the analogies database. When the apt-analogies-from-history-literature-movies workflow instructs you to create generate_histogram.py or build_html.py, base your code on these templates.
1. Histogram Formatter
When creating generate_histogram.py, use the following Python boilerplate to ensure exact timeline bucketing and medium consolidation.
import json
from collections import Counter
def get_era_bucket(year):
try:
y = int(year)
except:
return "Unknown"
if y >= 1940:
return f"{(y // 10) * 10}s"
elif y >= 1900:
return "1900 to 1940"
elif y >= 1500:
return f"{(y // 100) * 100}s"
elif y >= 1000:
return "1000 to 1500"
elif y >= 500:
return "500 to 1000"
elif y >= 0:
return "0 to 500"
elif y >= -500:
return "500 BC to Year 0"
else:
return "Before 500 BC"
def get_medium_bucket(medium):
m = medium.lower()
if 'history' in m:
return 'History'
elif 'literature' in m or 'theater' in m or 'lore' in m:
return 'Literature'
elif 'film' in m or 'tv' in m or 'movie' in m or 'animation' in m:
return 'Movies/TV'
else:
return 'Other'
def generate_histogram():
with open('analogies.json', 'r', encoding='utf-8') as f:
data = json.load(f)
included = [e for e in data.get('entries', []) if e.get('status') == 'included']
total = len(included)
if total == 0:
print("No included entries to chart.")
return
# Mediums
mediums = [get_medium_bucket(e.get('medium', '')) for e in included]
medium_counts = Counter(mediums)
# Eras
eras = [get_era_bucket(e.get('year')) for e in included]
era_counts = Counter(eras)
# Era Sort Order strictly mapping the historical buckets
era_order = [
"2020s", "2010s", "2000s", "1990s", "1980s", "1970s", "1960s", "1950s", "1940s",
"1900 to 1940",
"1800s", "1700s", "1600s", "1500s",
"1000 to 1500",
"500 to 1000",
"0 to 500",
"500 BC to Year 0",
"Before 500 BC",
"Unknown"
]
out_blocks = []
out_blocks.append("=== HISTOGRAM 1: MEDIUMS ===")
for m in ['History', 'Literature', 'Movies/TV', 'Other']:
if medium_counts[m] > 0 or m != 'Other':
c = medium_counts[m]
bar = '#' * c
line = f"{m.ljust(15)} | {str(c).rjust(2)} ({c/total*100:4.1f}%) | {bar}"
print(line)
out_blocks.append(line)
out_blocks.append("\n=== HISTOGRAM 2: ERAS ===")
for era in era_order:
if era in era_counts or any(era_counts.get(e, 0) > 0 for e in era_counts):
c = era_counts.get(era, 0)
bar = '#' * c
line = f"{era.ljust(17)} | {str(c).rjust(2)} ({c/total*100:4.1f}%) | {bar}"
print(line)
out_blocks.append(line)
# Automatically save it so the user can easily see it locally in markdown format
with open('histogram.md', 'w', encoding='utf-8') as f:
f.write("# Database Histogram Distributions\n\n```text\n")
f.write("\n".join(out_blocks))
f.write("\n```\n")
if __name__ == '__main__':
generate_histogram()
2. HTML Formatter
When creating build_html.py, use exactly the following HTML/CSS boilerplate and row-container logic to inject the visual styles. This ensures output consistency across all instances of the analogies database.
html_template = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<style>
body {
background-color: #ffffff;
color: #000000;
font-family: Arial, sans-serif;
margin: 0;
padding: 40px;
}
h1 {
font-size: 28px;
color: #000000;
text-transform: uppercase;
border-bottom: 3px solid #000000;
padding-bottom: 10px;
margin-bottom: 30px;
}
.nav-links {
margin-bottom: 30px;
font-size: 16px;
font-weight: bold;
}
.nav-links a {
color: #0000EE;
margin-right: 20px;
text-decoration: none;
}
.nav-links a:hover { text-decoration: underline; }
.row-container {
border: 2px solid #000000;
margin-bottom: 30px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
}
.row-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #cccccc;
padding-bottom: 15px;
}
.row-header h2 {
margin: 0;
font-size: 24px;
color: #000000;
}
.meta {
font-size: 14px;
color: #555555;
font-weight: bold;
margin-top: 5px;
display: block;
}
.score-box {
font-size: 24px;
font-weight: bold;
color: #000000;
border: 2px solid #000000;
padding: 10px 15px;
}
.penalty-text {
color: #cc0000;
font-size: 14px;
display: block;
margin-top: 5px;
}
.exclusion-box {
background-color: #ffeeee;
border: 2px solid #cc0000;
padding: 15px;
color: #cc0000;
font-size: 16px;
}
.row-body {
display: flex;
flex-direction: column;
gap: 20px;
}
.element-grid {
display: grid;
grid-template-columns: 250px 1fr;
gap: 15px 20px;
}
.el-label {
font-weight: bold;
color: #000000;
}
.el-value {
color: #222222;
line-height: 1.4;
}
.el-value ul {
margin: 0;
padding-left: 20px;
}
.el-value li {
margin-bottom: 5px;
}
@media (max-width: 1000px) {
.element-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="nav-links">
<a href="included_analogies.html">View Included (Master Database)</a>
<a href="excluded_analogies.html">View Excluded (Graveyard)</a>
</div>
<h1>{title}</h1>
<div>
{content}
</div>
</body>
</html>"""
def render_entry(entry, is_excluded=False):
content = '<div class="row-container">'
# Header
content += '<div class="row-header"><div class="title-group">'
content += f'<h2>{entry.get("title", "Unknown")}</h2>'
# NOTE: Handle specific schema if elements differ (year/era)
year_or_era = entry.get("year", entry.get("era", "Unknown"))
content += f'<span class="meta">({year_or_era}) | {entry.get("medium", "Unknown")}</span>'
content += '</div>'
# Score Box
content += '<div class="score-box" style="text-align: right;">'
score_key = "score" if "score" in entry else "total_score"
content += f'Score: {entry.get(score_key, 0)}'
penalties = entry.get("penalties", [])
if penalties:
p_text = []
for p in penalties:
points = p.get('points', p.get('amount', 0))
p_text.append(f"{p.get('name')} ({points})")
penalty_str = ", ".join(p_text)
content += f'<span class="penalty-text">Penalties: {penalty_str}</span>'
content += '</div></div>'
content += '<div class="row-body">'
if is_excluded and entry.get("exclusion_reason"):
content += f'<div class="exclusion-box"><strong>Excluded:</strong> {entry.get("exclusion_reason")}</div>'
content += '<div class="element-grid">'
elements = entry.get("elements", [])
# Determine schema structure: list of dicts vs dict of dicts
if isinstance(elements, dict):
for name, data in elements.items():
content += f'<div class="el-label">{name}</div>'
bullets = [data.get("description", "")]
val_html = '<ul>' + ''.join(f'<li>{b}</li>' for b in bullets if b) + '</ul>' if bullets and bullets[0] else ''
content += f'<div class="el-value">{val_html}</div>'
else:
import re
for el in elements:
raw_name = el.get("name", "")
if "Element 4" in raw_name or "Threatener is Evil" in raw_name: # Handle legacy modifiers if present
continue
clean_name = re.sub(r'^Element \d+:\s*', '', raw_name)
content += f'<div class="el-label">{clean_name}</div>'
bullets = el.get("explanation_bullets", [])
val_html = '<ul>' + ''.join(f'<li>{b}</li>' for b in bullets) + '</ul>' if bullets else ''
content += f'<div class="el-value">{val_html}</div>'
content += '</div></div></div>\n'
return content