Add Executive Summary as the report's first page

A standalone first page ahead of any detailed numbers: the month's
goal (from the Roadmap), a Yes/No/Partial verdict on whether it was
met, and a Pros/Cons list, color-coded (green/red/amber). Hand-
authored per month in monthly_report_notes.py's new
"executive_summary" key, same pattern as the other narrative
sections, since "did we meet our goal" is a judgment call, not
something computed from the export data. Renders only when present,
so August/September are unaffected until written up. A page break
separates it from the rest of the report so it stands alone as
page 1.

Drafted July's as a first pass grounded in facts already established
in this report (the emotional-vs-rational engagement gap, the poll's
zero engagement, the 27/08 audit gap) — needs Mckinley's review since
the goal-met verdict is inherently a judgment call, not something to
assert unilaterally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
beantownbabeandClaude Sonnet 5 committed 2026-09-30 14:33:34 +02:00
1 parent 292bd21abe
commit 40c8ccc01e
2 files changed
+70 -1

No files matched your search

+32
View File
@@ -21,6 +21,38 @@ _CORE_MESSAGE_ABSTRACT = [
NOTES = {
"2026-07": {
"executive_summary": {
"goal": "Find the Voice — establish a consistent brand voice and discover "
"which posts and formats drove the most engagement.",
"goal_met": "Yes",
"goal_met_note": "A clear format/tone winner emerged: Carousels and a "
"personal, emotional-register story (the 14/07-28/07-30/07 "
"trilogy) outperformed everything else, giving a concrete, "
"audited answer to what resonates with this audience.",
"pros": [
"Emotional/personal-voice posts averaged 21.8% Real Eng % vs. Rational "
"posts' 8.1%, a wide, decisive gap.",
"Page grew from 85 to 107 followers (+22, +25.9%).",
"Best single post hit 45.0% Real Eng %, the clear #1 all month.",
"Real Eng % audit methodology (Audience-only, hand-verified, Team "
"excluded) built and rolled out, replacing LinkedIn's raw/Team-inflated "
"numbers with a trustworthy figure everything else in this report reads "
"from.",
],
"cons": [
"The one validation attempt (21/07 poll) got zero responses — whether "
"this audience engages with interactive formats at all is still "
"unanswered.",
"10/07 was a repost, not original content, requiring exclusion logic to "
"keep every number honest — a reminder to plan the full month's content "
"in advance.",
"'Avg. Eng. Rate' (LinkedIn's raw daily figure) is still unreconciled "
"with Real Eng %, an open methodology question.",
"Audit coverage had a real gap (27/08) that wasn't caught until "
"September — the hand-audit process needs to stay current within the "
"month it covers, not catch up later.",
],
},
"core_message_abstract": _CORE_MESSAGE_ABSTRACT,
"roadmap_abstract": [
("Find the Voice.", True),
+38 -1
View File
@@ -21,7 +21,7 @@ from reportlab.lib.units import cm
from reportlab.platypus import (SimpleDocTemplate, Table, TableStyle, Paragraph,
Spacer, Image, HRFlowable)
from docx import Document
from docx.shared import Cm, Pt
from docx.shared import Cm, Pt, RGBColor
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
@@ -897,6 +897,43 @@ def build_report_docx(ctx, timeframe_value="all"):
f"Data as of {D['last_updated'].strftime('%B %d, %Y')}"
).italic = True
# --- Executive Summary: standalone first page (goal, met/not, pros/cons) — a
# leadership-facing read of "did this month work", ahead of any of the detailed
# numbers below it. Hand-authored per month in monthly_report_notes.py, same as
# the other narrative sections, since "did we meet our goal" is a judgment call,
# not something computed from the export data.
exec_summary = (monthly_notes or {}).get("executive_summary")
if exec_summary:
doc.add_heading("Executive Summary", level=1)
goal_p = doc.add_paragraph()
goal_p.add_run("Goal: ").bold = True
goal_p.add_run(exec_summary.get("goal", ""))
met_p = doc.add_paragraph()
met_p.add_run("Goal Met: ").bold = True
met_run = met_p.add_run(exec_summary.get("goal_met", "—"))
met_run.bold = True
goal_met_val = str(exec_summary.get("goal_met", "")).strip().lower()
if goal_met_val == "yes":
met_run.font.color.rgb = RGBColor(0x2E, 0x7D, 0x32)
elif goal_met_val == "no":
met_run.font.color.rgb = RGBColor(0xC6, 0x28, 0x28)
elif goal_met_val == "partial":
met_run.font.color.rgb = RGBColor(0xB2, 0x6A, 0x00)
goal_met_note = exec_summary.get("goal_met_note")
if goal_met_note:
doc.add_paragraph(goal_met_note)
pros = exec_summary.get("pros") or []
if pros:
doc.add_heading("Pros", level=2)
for item in pros:
doc.add_paragraph(item, style="List Bullet")
cons = exec_summary.get("cons") or []
if cons:
doc.add_heading("Cons", level=2)
for item in cons:
doc.add_paragraph(item, style="List Bullet")
doc.add_page_break()
# --- KPI summary table ---
doc.add_heading("Key Metrics", level=1)
kpi_rows = [