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:
1 parent
9eb636bf79
commit
f254a9f10f
2 files changed
+60
-23
No files matched your search
@@ -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"})
|
||||
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in new issue
Block a user