Wire CTA Campaign phases into Reports as real computable periods

CUSTOM_PERIODS in report.py now drives _filtered/_period_label/
compute_period_stats for the 3 CTA phases (date ranges from the
proposed plan), and the campaign-report title treatment applies to
them the same way it does for July-September. Removed the app.py
placeholder guards (reports tab + docx download) now that real period
computation exists instead of an alert box, and fixed the docx
filename label lookup to check all campaigns' periods, not just the
old single flat list. Added Core Message (reused) and Roadmap
(per-phase) abstracts to monthly_report_notes.py for the 3 phases -
no executive_summary yet since the campaign hasn't run and isn't
approved, so KPI/chart cards render on real, currently-empty data
rather than fabricated findings.
This commit is contained in:
beantownbabe committed 2026-10-26 12:22:40 +01:00
1 parent 3a7a47e3f9
commit 03e6d7287e
3 files changed
+71 -26

No files matched your search

+13 -23
View File
@@ -2200,9 +2200,9 @@ _REPORTS_CAMPAIGNS = {
},
"cta-campaign": {
"label": "CTA Campaign",
# Placeholder periods only - dates from the proposed plan, not yet approved.
# Selecting one shows a "not built yet" placeholder (_render_reports_tab),
# same treatment "End of Campaign" itself got before it was built out.
# Dates from the proposed plan - campaign isn't approved yet, so these
# render as real (currently empty/near-zero) reports once the period
# starts, same as any other month would before it has data.
"periods": [
("cta-phase-1", "Nov 3 - Nov 26 (Failure Analysis)"),
("cta-phase-2", "Dec 1 - Jan 7 (Standardization)"),
@@ -2212,6 +2212,10 @@ _REPORTS_CAMPAIGNS = {
}
_REPORTS_CAMPAIGN_OPTIONS = [{"label": v["label"], "value": k} for k, v in _REPORTS_CAMPAIGNS.items()]
_REPORTS_DEFAULT_CAMPAIGN = "linkedin-branding"
# Flat lookup across every campaign's periods, for the PDF/docx filename label -
# the Period dropdown only ever holds one campaign's periods at a time (via the
# cascade callback), so this is the one place that needs all of them at once.
_REPORTS_ALL_PERIODS = [kv for camp in _REPORTS_CAMPAIGNS.values() for kv in camp["periods"]]
reports_tab = dbc.Container([
html.Div([
@@ -4522,28 +4526,16 @@ def _render_reports_tab(month_value):
if month_value == "campaign-end":
return _render_campaign_end_summary() + (store_value,)
if month_value.startswith("cta-phase"):
# CTA Campaign is proposed, not yet approved — placeholder only, same
# treatment "End of Campaign" itself got before it was built out.
period_label = next((lbl for v, lbl in _REPORTS_CAMPAIGNS["cta-campaign"]["periods"]
if v == month_value), month_value)
title = html.H3(period_label,
style={"color": "#1b3a5c", "marginBottom": "0", "fontWeight": "700",
"fontSize": "2rem", "whiteSpace": "nowrap"})
placeholder = dbc.Alert(
"The CTA Campaign hasn't been approved yet, so there's no report to show. "
"This period will build out the same way the other reports do once it's live.",
color="info")
return title, placeholder, no_update
stats = report.compute_period_stats(_build_report_ctx(), month_value)
notes = monthly_report_notes.NOTES.get(month_value)
fts_f, posts_f = stats["fts_f"], stats["posts_f"]
# Only July/August/September were part of the 3-month branding campaign — a
# later month like October gets a plain title, no active campaign stage
# Only July/August/September were part of the 3-month branding campaign, and
# CTA Campaign phases (CUSTOM_PERIODS) once that campaign is live — any other
# month like October gets a plain title, no active campaign stage
# (goal_line/core_message/roadmap) behind it.
_is_campaign_month = False
_is_campaign_month = month_value in report.CUSTOM_PERIODS
if not _is_campaign_month:
try:
_year, _month = (int(p) for p in month_value.split("-"))
_month_start = pd.Timestamp(year=_year, month=_month, day=1)
@@ -4770,12 +4762,10 @@ def _render_reports_tab(month_value):
prevent_initial_call=True,
)
def _download_reports_pdf(_n_clicks, month_value):
if month_value and month_value.startswith("cta-phase"):
return no_update # CTA Campaign not approved yet, nothing to download
ctx_data = _build_report_ctx()
ctx_data["monthly_notes"] = monthly_report_notes.NOTES.get(month_value)
docx_bytes = report.build_report_docx(ctx_data, month_value)
label = next((o["label"] for o in _REPORTS_MONTH_OPTIONS if o["value"] == month_value), month_value)
label = next((lbl for v, lbl in _REPORTS_ALL_PERIODS if v == month_value), month_value)
filename = f"TXproducts_LinkedIn_Report_{label.replace(' ', '_')}.docx"
return dcc.send_bytes(lambda buf: buf.write(docx_bytes), filename=filename)
+31
View File
@@ -516,4 +516,35 @@ NOTES = {
],
},
},
# CTA Campaign phases (Nov 2026 - Feb 2027), pending approval. Core Message is
# the same constant every other campaign reuses (TXproducts' value proposition
# doesn't change); the Roadmap abstract is what changes per phase, drawn
# directly from the proposed plan's own theme framing. No executive_summary yet
# - that would mean reporting findings from a campaign that hasn't run, so the
# KPI table and charts render on real (currently empty) data instead until
# there's something real to write up, same as any other not-yet-run period.
"cta-phase-1": {
"core_message_abstract": _CORE_MESSAGE_ABSTRACT,
"roadmap_abstract": [
("Failure Analysis.", True),
(" Power electronics fail under real operating stress, and time "
"resolution shows the failure as it happens.", False),
],
},
"cta-phase-2": {
"core_message_abstract": _CORE_MESSAGE_ABSTRACT,
"roadmap_abstract": [
("Standardization.", True),
(" Failure results are only useful when they're comparable: same "
"stress, same measurement, same pipeline on every sample.", False),
],
},
"cta-phase-3": {
"core_message_abstract": _CORE_MESSAGE_ABSTRACT,
"roadmap_abstract": [
("WaveGate.", True),
(" The technology that makes standardized, time-resolved failure "
"analysis possible.", False),
],
},
}
+27 -3
View File
@@ -30,12 +30,30 @@ import engagement_audit
CAMPAIGN_START = pd.Timestamp("2026-07-01")
CAMPAIGN_END = pd.Timestamp("2026-09-30")
# Custom (non-calendar-month) reporting periods, keyed the same way the Reports
# dropdown keys them. "campaign-end" isn't listed here since it's handled directly
# above - everything after that is the CTA Campaign's 3 phases (dates from the
# proposed plan, pending approval). Each period is a plain date range with its own
# label, not tied to a calendar month, since the CTA Campaign's own phases span
# across month boundaries (Dec 1-Jan 7) on purpose, unlike July/August/September.
CUSTOM_PERIODS = {
"cta-phase-1": (pd.Timestamp("2026-11-03"), pd.Timestamp("2026-11-26"),
"Nov 3 - Nov 26 (Failure Analysis)"),
"cta-phase-2": (pd.Timestamp("2026-12-01"), pd.Timestamp("2027-01-07"),
"Dec 1 - Jan 7 (Standardization)"),
"cta-phase-3": (pd.Timestamp("2027-01-12"), pd.Timestamp("2027-02-04"),
"Jan 12 - Feb 4 (WaveGate)"),
}
def _filtered(df, date_col, value):
if not value or value == "all":
return df
if value == "campaign-end":
return df[(df[date_col] >= CAMPAIGN_START) & (df[date_col] <= CAMPAIGN_END)]
if value in CUSTOM_PERIODS:
start, end, _ = CUSTOM_PERIODS[value]
return df[(df[date_col] >= start) & (df[date_col] <= end)]
if value.startswith("year-"):
year = int(value.split("-", 1)[1])
return df[df[date_col].dt.year == year]
@@ -51,6 +69,8 @@ def _period_label(value):
return "All Time"
if value == "campaign-end":
return "3-Month Campaign (Jul-Sep 2026)"
if value in CUSTOM_PERIODS:
return CUSTOM_PERIODS[value][2]
if value.startswith("year-"):
return value.split("-", 1)[1]
import calendar as calmod
@@ -123,6 +143,8 @@ def compute_period_stats(ctx, timeframe_value="all"):
first_day = None
if timeframe_value == "campaign-end":
first_day = CAMPAIGN_START
elif timeframe_value in CUSTOM_PERIODS:
first_day = CUSTOM_PERIODS[timeframe_value][0]
elif timeframe_value and timeframe_value != "all" and not timeframe_value.startswith("year-"):
year, month = (int(p) for p in timeframe_value.split("-"))
first_day = pd.Timestamp(year=year, month=month, day=1)
@@ -248,10 +270,12 @@ def build_report_docx(ctx, timeframe_value="all"):
# Only July/August/September were part of the 3-month branding campaign
# (CAMPAIGN_START-CAMPAIGN_END) — a later month like October gets a plain
# title, not "...Campaign Report", since there's no active campaign stage
# (no goal_line/core_message/roadmap) behind it.
is_campaign_month = False
# (no goal_line/core_message/roadmap) behind it. CTA Campaign phases
# (CUSTOM_PERIODS) get the same campaign-report treatment once that
# campaign is approved and running.
is_campaign_month = timeframe_value in CUSTOM_PERIODS
if timeframe_value and timeframe_value != "all" and not timeframe_value.startswith("year-") \
and timeframe_value != "campaign-end":
and timeframe_value != "campaign-end" and timeframe_value not in CUSTOM_PERIODS:
try:
year, month = (int(p) for p in timeframe_value.split("-"))
month_start = pd.Timestamp(year=year, month=month, day=1)