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:
1 parent
46b5da00ce
commit
c5b6483b88
2 files changed
+47
No files matched your search
@@ -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]))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user