Add campaign-wide Audience Composition table to the campaign-end report

Mckinley manually added Sector/Function/Seniority/Country breakdown
tables to each monthly report by hand from the live Audience
Composition page; the campaign-end report had no equivalent since
there's no single month's page to pull a 3-month combined table from.

Generated directly in both the docx (report.build_report_docx) and the
live Reports tab preview (app._render_campaign_end_summary): merges
Follower Directory rows whose followedSort falls in {2026-07, 2026-08,
2026-09} (58 new followers profiled across the campaign) and renders
the same 4-table breakdown as the monthly reports use.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
beantownbabeandClaude Sonnet 5 committed 2026-10-02 15:24:38 +02:00
1 parent 46b5da00ce
commit c5b6483b88
2 files changed
+47

No files matched your search

+19
View File
@@ -4323,6 +4323,25 @@ def _render_campaign_end_summary():
comp_line = exec_summary.get("audience_composition_line")
if comp_line:
body.append(html.P(comp_line))
campaign_months = {"2026-07", "2026-08", "2026-09"}
cohort_rows = [d for d in DIRECTORY_DATA if d.get("followedSort") in campaign_months]
if cohort_rows:
from collections import Counter
sector = Counter(d.get("sector", "Unknown") for d in cohort_rows)
function = Counter(d.get("function", "Unknown") for d in cohort_rows)
seniority = Counter(d.get("seniority", "Unknown") for d in cohort_rows)
country = Counter(d.get("country") or "Unknown/blank" for d in cohort_rows)
body.append(html.H5(f"Audience Composition ({len(cohort_rows)} new followers "
f"profiled, July-September combined)",
style={"marginTop": "18px", "color": "#1b3a5c"}))
body.append(html.Div([
html.Div(_composition_table(sector, "Sector"), style={"flex": "1", "minWidth": "0"}),
html.Div(_composition_table(function, "Function"), style={"flex": "1", "minWidth": "0"}),
html.Div(_composition_table(seniority, "Seniority"), style={"flex": "1", "minWidth": "0"}),
html.Div(_composition_table(country, "Country"), style={"flex": "1", "minWidth": "0"}),
], style={"display": "flex", "gap": "0.75rem", "flexWrap": "wrap"}))
for dim_title, dim_text in exec_summary.get("audience_dimensions") or []:
body.append(html.P([html.Strong(f"{dim_title}: "), dim_text]))
+28
View File
@@ -217,6 +217,7 @@ def build_report_docx(ctx, timeframe_value="all"):
D = ctx["D"]
daily_followers_figure = ctx["daily_followers_figure"]
monthly_notes = ctx.get("monthly_notes")
directory_data = ctx.get("directory_data") or []
period = compute_period_stats(ctx, timeframe_value)
fts_f, posts_f = period["fts_f"], period["posts_f"]
@@ -324,6 +325,33 @@ def build_report_docx(ctx, timeframe_value="all"):
comp_line = exec_summary.get("audience_composition_line")
if comp_line:
doc.add_paragraph(comp_line)
# campaign-end only: Sector/Function/Seniority/Country breakdown across all
# 3 months combined (followedSort in {2026-07, 2026-08, 2026-09}) — the
# monthly reports don't carry this table (Mckinley adds it by hand from the
# live Audience Composition page), but the campaign-end synthesis has no
# single month's page to pull it from, so it's generated here directly.
if timeframe_value == "campaign-end":
campaign_months = {"2026-07", "2026-08", "2026-09"}
cohort_rows = [d for d in directory_data if d.get("followedSort") in campaign_months]
if cohort_rows:
from collections import Counter
sector = Counter(d.get("sector", "Unknown") for d in cohort_rows)
function = Counter(d.get("function", "Unknown") for d in cohort_rows)
seniority = Counter(d.get("seniority", "Unknown") for d in cohort_rows)
country = Counter(d.get("country") or "Unknown/blank" for d in cohort_rows)
comp_table = doc.add_table(rows=1, cols=4)
comp_table.rows[0].cells[0].merge(comp_table.rows[0].cells[3])
comp_table.rows[0].cells[0].paragraphs[0].add_run(
f"Audience Composition ({len(cohort_rows)} new followers profiled, "
f"July-September combined)"
).bold = True
for label, counter in [("Sector", sector), ("Function", function),
("Seniority", seniority), ("Country", country)]:
doc.add_paragraph(label, style="Heading 2")
rows = [[label, "Count"]] + [[str(k), str(v)] for k, v in counter.most_common()]
_docx_table(doc, rows, font_size=8)
for dim_title, dim_text in exec_summary.get("audience_dimensions") or []:
p = doc.add_paragraph()
p.add_run(f"{dim_title}: ").bold = True