Add a 5-word Description column to the Posts table
LinkedIn's exports have no title/text field for a post anywhere (neither Aggregate Analytics nor SinglePostAnalytics) - only the URL, whose slug is a URL-safe truncation of the post's own opening words. post_title_from_url() decodes it (handles accented names like Krutošíková), strips the trailing -share-/-ugcPost-<id>-<code> suffix, and takes the first 5 words as a readable stand-in for a title. Added to both the live dashboard's posts table and the downloadable report. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3de9143e46
commit
0335e17e89
2 files changed
+24
-3
No files matched your search
@@ -2876,6 +2876,7 @@ def _team_top_posts_table(top_posts_df, post_details):
|
||||
details = post_details.get(r["Post URL"]) or {}
|
||||
table_rows.append(html.Tr([
|
||||
html.Td(r["Post Publish Date"]),
|
||||
html.Td(team_exports.post_title_from_url(r["Post URL"])),
|
||||
html.Td(dbc.Badge(r["Type"], color="secondary" if r["Type"] == "Repost" else "primary")),
|
||||
html.Td(int(r["Impressions"]) if pd.notna(r["Impressions"]) else "—"),
|
||||
html.Td(int(r["Engagements"]) if pd.notna(r["Engagements"]) else "—"),
|
||||
@@ -2888,13 +2889,13 @@ def _team_top_posts_table(top_posts_df, post_details):
|
||||
]))
|
||||
return dbc.Table([
|
||||
html.Thead(html.Tr([html.Th(c) for c in [
|
||||
"Date", "Type", "Impressions", "Engagements (export)", "Profile Viewers*",
|
||||
"Date", "Description", "Type", "Impressions", "Engagements (export)", "Profile Viewers*",
|
||||
"Followers Gained*", "Reactions*", "Comments*", "Reposts*", "Link",
|
||||
]])),
|
||||
html.Tbody(table_rows),
|
||||
html.Tfoot(html.Tr(html.Td(
|
||||
"* from each post's own \"Export\" button (SinglePostAnalytics file) — not in the Aggregate Analytics export",
|
||||
colSpan=10, style={"fontSize": "0.7rem", "color": "#6b7a8d", "fontStyle": "italic"}))),
|
||||
colSpan=11, style={"fontSize": "0.7rem", "color": "#6b7a8d", "fontStyle": "italic"}))),
|
||||
], bordered=True, size="sm", style={"fontSize": "0.85rem"})
|
||||
|
||||
|
||||
|
||||
+21
-1
@@ -20,6 +20,7 @@ hasn't, and app.py renders a placeholder for that case.
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
from urllib.parse import unquote
|
||||
|
||||
import pandas as pd
|
||||
from docx import Document
|
||||
@@ -106,6 +107,24 @@ def _post_is_repost(url):
|
||||
return m.group(1).lower() == "share" if m else None
|
||||
|
||||
|
||||
_POST_SLUG_RE = re.compile(r"_(.+?)-(?:share|ugcpost)-\d+-\w+$", re.I)
|
||||
|
||||
|
||||
def post_title_from_url(url, word_count=5):
|
||||
"""A short, readable stand-in for an actual post title - LinkedIn's export has
|
||||
no title/text field anywhere (Aggregate Analytics or SinglePostAnalytics), only
|
||||
the URL, whose slug is a URL-safe truncation of the post's own opening words.
|
||||
Decodes %-escapes first (needed for names with accents, e.g. Krutošíková)."""
|
||||
if not url:
|
||||
return ""
|
||||
m = _POST_SLUG_RE.search(unquote(url))
|
||||
if not m:
|
||||
return ""
|
||||
words = m.group(1).replace("-", " ").split()
|
||||
title = " ".join(words[:word_count])
|
||||
return title[0].upper() + title[1:] if title else ""
|
||||
|
||||
|
||||
def _parse_top_posts(fp):
|
||||
raw = pd.read_excel(fp, sheet_name="TOP POSTS", header=None)
|
||||
body = raw.iloc[3:]
|
||||
@@ -221,13 +240,14 @@ def build_person_report_docx(name, data, data_dir, upload_dir):
|
||||
if posts.empty:
|
||||
doc.add_paragraph("No posts in this window.")
|
||||
else:
|
||||
rows = [["Date", "Type", "Impressions", "Engagements (export)", "Profile Viewers*",
|
||||
rows = [["Date", "Description", "Type", "Impressions", "Engagements (export)", "Profile Viewers*",
|
||||
"Followers Gained*", "Reactions*", "Comments*", "Reposts*", "Link"]]
|
||||
for _, r in posts.iterrows():
|
||||
post_type = "Repost" if r["Is Repost"] else ("Original" if r["Is Repost"] is False else "Unknown")
|
||||
d = post_details.get(r["Post URL"]) or {}
|
||||
rows.append([
|
||||
r["Post Publish Date"].strftime("%b %d, %Y"),
|
||||
post_title_from_url(r["Post URL"]),
|
||||
post_type,
|
||||
int(r["Impressions"]) if pd.notna(r["Impressions"]) else "—",
|
||||
int(r["Engagements"]) if pd.notna(r["Engagements"]) else "—",
|
||||
|
||||
Reference in new issue
Block a user