Strike through excluded posts in Reports tab's mini All Posts card

The Reports > July landing page's own "All Posts, July 2026" card
(_reports_posts_table, fed by report.compute_period_stats()'s simple
date-range filter, not app.py's REPOST_DATES/NON_CAMPAIGN_DATES-
excluding _reports_posts_f()) showed the 10/07 repost as a plain,
unmarked row with "Pending audit" — inconsistent with the dedicated
All Posts insights page one click away (struck through, "Excluded")
and confusing on its own, since "Pending audit" reads as an
unfinished job rather than a deliberate, permanent exclusion.

Fixed in both places this table renders: the live Reports tab
(app.py's _reports_posts_table) and its PDF equivalent (report.py's
"All Posts" table), both now applying the same pale-red/strikethrough
"Excluded" treatment already used by the register table and the
dedicated All Posts insights page. Added non_campaign_dates to
_build_report_ctx() so the PDF can apply the same exclusion set, not
just repost_dates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
beantownbabeandClaude Sonnet 5 committed 2026-09-30 12:50:48 +02:00
1 parent 9eb636bf79
commit f254a9f10f
2 files changed
+60 -23

No files matched your search

+25 -12
View File
@@ -2734,6 +2734,7 @@ def _build_report_ctx():
"std_fn": _std, "TMPL": TMPL, "FORMAT_COLORS": FORMAT_COLORS,
"growth_snapshot": GROWTH_SNAPSHOT, "directory_data": DIRECTORY_DATA,
"july_register_ratings": _JULY_REGISTER_RATINGS, "repost_dates": REPOST_DATES,
"non_campaign_dates": NON_CAMPAIGN_DATES,
}
@@ -2945,26 +2946,38 @@ def _composition_table(counter, header_left, top_n=None):
def _reports_posts_table(posts_f):
"""Every post in the period, listed chronologically, not just a top-N by engagement.
A monthly campaign report should show the full picture of what was published, since
the point is a complete record, not a highlights reel."""
the point is a complete record, not a highlights reel. A repost/non-campaign date is
still shown here (not silently dropped), but struck through and marked "Excluded" —
same pale-red treatment as the dedicated All Posts insights page and Core Message's
register table, so a reader doesn't see a plain, unmarked row and wonder why its
Real Eng % looks unaudited instead of deliberately out of scope."""
if not len(posts_f):
return html.Div("No posts published in this period.", className="text-muted")
all_posts = posts_f.sort_values("Created date")
_post_title_style = {"whiteSpace": "nowrap", "overflow": "hidden", "textOverflow": "ellipsis",
"maxWidth": "260px"}
_excl_style = {"backgroundColor": "#fbdcdc", "textDecoration": "line-through"}
def _row(r):
excluded = r["Created date"].strftime("%Y-%m-%d") in (REPOST_DATES | NON_CAMPAIGN_DATES)
cell_style = {**_post_title_style, **_excl_style} if excluded else _post_title_style
plain_style = _excl_style if excluded else {}
eng_cell = (html.Span(["Excluded"], style={"fontWeight": "700"}) if excluded else
(f"{r['Real Eng %']:.1f}%" if pd.notna(r["Real Eng %"]) else
html.Span("Pending audit", style={"color": "#8a96a3", "fontStyle": "italic"})))
return html.Tr([
html.Td(str(r["Short title"]), title=str(r["Short title"]), style=cell_style),
html.Td(r["Created date"].strftime("%d/%m"), style=plain_style),
html.Td(r["Content Type"], style=plain_style),
html.Td(f"{int(r['Impressions']):,}", style=plain_style),
html.Td(f"{r['CTR %']:.1f}%", style=plain_style),
html.Td(eng_cell, style=plain_style),
])
return dbc.Table([
html.Thead(html.Tr([html.Th(c) for c in
["Post", "Date", "Type", "Impressions", "CTR %", "Real Eng %"]])),
html.Tbody([
html.Tr([
html.Td(str(r["Short title"]), title=str(r["Short title"]), style=_post_title_style),
html.Td(r["Created date"].strftime("%d/%m")),
html.Td(r["Content Type"]),
html.Td(f"{int(r['Impressions']):,}"),
html.Td(f"{r['CTR %']:.1f}%"),
html.Td(f"{r['Real Eng %']:.1f}%" if pd.notna(r["Real Eng %"]) else
html.Span("Pending audit", style={"color": "#8a96a3", "fontStyle": "italic"})),
]) for _, r in all_posts.iterrows()
]),
html.Tbody([_row(r) for _, r in all_posts.iterrows()]),
], bordered=True, size="sm", style={"fontSize": "0.75rem"})
+35 -11
View File
@@ -198,6 +198,8 @@ def build_report_pdf(ctx, timeframe_value="all"):
directory_data = ctx.get("directory_data") or []
july_register_ratings = ctx.get("july_register_ratings") or []
repost_dates = ctx.get("repost_dates") or set()
non_campaign_dates = ctx.get("non_campaign_dates") or set()
excluded_dates = repost_dates | non_campaign_dates
period = compute_period_stats(ctx, timeframe_value)
fts_f, posts_f, cts_f = period["fts_f"], period["posts_f"], period["cts_f"]
@@ -387,19 +389,38 @@ def build_report_pdf(ctx, timeframe_value="all"):
# to fall back to the raw, Team-inflated number to fill out the top 5.
shown_posts = posts_f.sort_values("Real Eng %", ascending=False).head(5)
if len(posts_f):
# A repost/non-campaign post is still listed (not silently dropped — a monthly
# report is a complete record of what was published), but struck through and
# marked "Excluded", same pale-red treatment as the register table above and
# the live Reports tab's equivalent card, instead of "Pending audit" reading
# like an unfinished audit rather than a deliberate exclusion.
posts_cell_style = ParagraphStyle("TXPostsCell", parent=body_style, fontSize=8)
rows = [["Post", "Date", "Type", "Impressions", "CTR %", "Real Eng %"]]
for _, r in shown_posts.iterrows():
excluded_rows = []
for i, (_, r) in enumerate(shown_posts.iterrows(), start=1):
real_eng = r["Real Eng %"]
rows.append([
Paragraph(str(r["Short title"])[:60], body_style),
r["Created date"].strftime("%d/%m/%Y"),
r["Content Type"],
f"{int(r['Impressions']):,}",
f"{r['CTR %']:.1f}%",
f"{real_eng:.1f}%" if pd.notna(real_eng) else "Pending audit",
])
is_excluded = r["Created date"].strftime("%Y-%m-%d") in excluded_dates
if is_excluded:
excluded_rows.append(i)
rows.append([
Paragraph(f"<strike>{str(r['Short title'])[:60]}</strike>", posts_cell_style),
Paragraph(f"<strike>{r['Created date'].strftime('%d/%m/%Y')}</strike>", posts_cell_style),
Paragraph(f"<strike>{r['Content Type']}</strike>", posts_cell_style),
Paragraph(f"<strike>{int(r['Impressions']):,}</strike>", posts_cell_style),
Paragraph(f"<strike>{r['CTR %']:.1f}%</strike>", posts_cell_style),
Paragraph("<strike>Excluded</strike>", posts_cell_style),
])
else:
rows.append([
Paragraph(str(r["Short title"])[:60], posts_cell_style),
r["Created date"].strftime("%d/%m/%Y"),
r["Content Type"],
f"{int(r['Impressions']):,}",
f"{r['CTR %']:.1f}%",
f"{real_eng:.1f}%" if pd.notna(real_eng) else "Pending audit",
])
posts_table = Table(rows, colWidths=[7 * cm, 2.2 * cm, 2.2 * cm, 2.4 * cm, 1.8 * cm, 1.8 * cm])
posts_table.setStyle(TableStyle([
posts_style = [
("BACKGROUND", (0, 0), (-1, 0), NAVY),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTSIZE", (0, 0), (-1, -1), 8),
@@ -409,7 +430,10 @@ def build_report_pdf(ctx, timeframe_value="all"):
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, LIGHT_GRAY]),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
]))
]
for row_idx in excluded_rows:
posts_style.append(("BACKGROUND", (0, row_idx), (-1, row_idx), colors.HexColor("#fbdcdc")))
posts_table.setStyle(TableStyle(posts_style))
story.append(posts_table)
else:
story.append(Paragraph("No posts published in this period.", body_style))