From 348fe65b888284ef2b31ed66945ceefda81e6b93 Mon Sep 17 00:00:00 2001 From: beantownbabe <156287695+beantownbabe@users.noreply.github.com> Date: Tue, 21 Jul 2026 15:14:28 +0200 Subject: [PATCH] Initial commit: TXproducts LinkedIn Analytics Dashboard Dash app with Overview, Content, Followers, Visitors, Competitors, and Growth tabs. Includes weekly-pace tracking, PDF report generation, data upload with live refresh, and a static HTML export builder for sharing via NextCloud. --- .gitignore | 14 + app.py | 1898 ++++++++++++++++++++++++++++++++ assets/custom.css | 113 ++ assets/linkedin_logo.svg | 20 + assets/txp_logo_horizontal.png | Bin 0 -> 86693 bytes assets/txp_logo_vertical.png | Bin 0 -> 130943 bytes build_static_export.py | 569 ++++++++++ growth.py | 193 ++++ report.py | 251 +++++ requirements.txt | 6 + 10 files changed, 3064 insertions(+) create mode 100644 .gitignore create mode 100644 app.py create mode 100644 assets/custom.css create mode 100644 assets/linkedin_logo.svg create mode 100644 assets/txp_logo_horizontal.png create mode 100644 assets/txp_logo_vertical.png create mode 100644 build_static_export.py create mode 100644 growth.py create mode 100644 report.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f255fde --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Real LinkedIn export data — not shared code, uploaded fresh each week via the dashboard +*.xls +*.xlsx +growth_history.json + +# Generated output — regenerate locally with build_static_export.py, not shared code +TXproducts_Dashboard_Snapshot.html + +# Python +__pycache__/ +*.pyc + +# OS +.DS_Store diff --git a/app.py b/app.py new file mode 100644 index 0000000..9d47eb7 --- /dev/null +++ b/app.py @@ -0,0 +1,1898 @@ +"""TXproducts LinkedIn Analytics Dashboard""" +import warnings; warnings.filterwarnings("ignore") + +import base64 +import os + +from dash import Dash, dcc, html, dash_table, Input, Output, State, ctx, no_update +import dash_bootstrap_components as dbc +import plotly.express as px +import plotly.graph_objects as go +import pandas as pd +import report +import growth + +# Overridable via env var so the same code runs unmodified in Docker/internal deployments — +# defaults to this machine's path for local dev. +DATA_DIR = os.environ.get("LINKEDIN_DATA_DIR", "/Users/mckinleyblack/Documents") +UPLOAD_DIR = os.path.join(DATA_DIR, "linkedin_analytics") + +# LinkedIn's export only gives daily gains, never an absolute follower total. This is +# the one manual anchor point: the count going into the very first day this export +# ever covered (2026-06-16). Every other day's exact total is baseline + cumulative +# gains since, computed automatically — this constant should never need to change. +BASELINE_FOLLOWERS = 84 +BASELINE_DATE = "2026-06-16" + +# LinkedIn exports no caption text for some posts (image/video-only). Fill in titles by hand here. +MANUAL_POST_TITLES = { + "2026-07-09": "How it all started", + "2026-07-14": "Time resolution", +} + +# LinkedIn's export doesn't distinguish carousel/document posts from generic Image — override by hand. +MANUAL_CONTENT_TYPES = { + "2026-07-09": "Carousel", + "2026-07-16": "Carousel", +} + +# ── Palette — matched to txdata_project/portal.py's navy/blue design language ── +BLUE = "#1b3a5c" # navy — primary accent (was LinkedIn blue #0A66C2) +NAVY2 = "#2d6a9f" # secondary blue, used in gradients alongside BLUE +ORANGE = "#E8825A" +GREEN = "#52C41A" +AMBER = "#FFA726" +TEAL = "#2d6a9f" # aligned to the secondary blue instead of a separate teal +TMPL = "plotly_white" +BG = "rgba(0,0,0,0)" + +FORMAT_COLORS = { + "Video": "#E8825A", + "Image": "#1b3a5c", + "Article": "#52C41A", + "Text": "#FFA726", + "Carousel": "#9C27B0", + "Unknown": "#888888", +} + +# ── Chart helper ────────────────────────────────────────────────────────────── +def _std(fig): + fig.update_layout( + plot_bgcolor=BG, paper_bgcolor=BG, + margin=dict(t=30, b=30, l=10, r=10), + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0), + font=dict(size=11), + ) + return fig + +def _finalize_fig(fig, margin=None, legend=None): + """Apply the shared _std() styling plus any per-chart margin/legend overrides — + used both when building a chart inline and when rebuilding one inside a callback, + so the two paths always render identically.""" + fig = _std(fig) + if margin: + fig.update_layout(margin=margin) + if legend: + fig.update_layout(legend=legend) + return fig + +def G(fig, style=None, margin=None, legend=None, **kw): + s = {"height": "320px"} + if style: + s.update(style) + fig = _finalize_fig(fig, margin=margin, legend=legend) + return dcc.Graph(figure=fig, config={"displayModeBar": False}, style=s, **kw) + +def mark_posts(fig, fts_df, posts_df): + """Overlay a star marker on days a post was published, with the post title on hover.""" + if posts_df.empty: + return fig + day_totals = fts_df.set_index("Date")["Total followers"] + matched = posts_df[posts_df["Created date"].isin(fts_df["Date"])] + if matched.empty: + return fig + fig.add_trace(go.Scatter( + x=matched["Created date"], + y=[day_totals.get(d, 0) for d in matched["Created date"]], + mode="markers", + marker=dict(symbol="star", size=13, color="#E53935", line=dict(width=1, color="white")), + name="Post published", + customdata=matched["Short title"], + hovertemplate="Post published — %{x|%b %d, %Y}
%{customdata}", + )) + return fig + + +def _find_latest_file(keyword, extensions=(".xls", ".xlsx")): + """Most recently modified file across DATA_DIR and UPLOAD_DIR whose name contains + `keyword` (case-insensitive) — replaces matching one exact hardcoded filename, so any + correctly-named LinkedIn export (including freshly uploaded ones) is picked up without + needing the file's exact auto-generated export ID.""" + candidates = [] + for folder in (DATA_DIR, UPLOAD_DIR): + if not os.path.isdir(folder): + continue + for fname in os.listdir(folder): + if keyword.lower() in fname.lower() and fname.lower().endswith(extensions): + candidates.append(os.path.join(folder, fname)) + if not candidates: + raise FileNotFoundError( + f"No file containing '{keyword}' found in {DATA_DIR} or {UPLOAD_DIR}. " + "Upload the matching LinkedIn export to get the dashboard running.") + return max(candidates, key=os.path.getmtime) + + +def _find_all_files(keyword, extensions=(".xls", ".xlsx")): + """Like _find_latest_file, but returns every match sorted oldest-to-newest instead of + just the newest — Content merges all historical exports rather than replacing them.""" + candidates = [] + for folder in (DATA_DIR, UPLOAD_DIR): + if not os.path.isdir(folder): + continue + for fname in os.listdir(folder): + if keyword.lower() in fname.lower() and fname.lower().endswith(extensions): + candidates.append(os.path.join(folder, fname)) + return sorted(set(candidates), key=os.path.getmtime) + + +# ── Load data ───────────────────────────────────────────────────────────────── +def load(): + d = {} + file_mtimes = [] + + # Followers + fp = _find_latest_file("followers") + file_mtimes.append(os.path.getmtime(fp)) + fts = pd.read_excel(fp, sheet_name="New followers") + fts["Date"] = pd.to_datetime(fts["Date"]) + fts = fts.sort_values("Date").reset_index(drop=True) + # Exact follower count on each day = the one manual baseline anchor + every gain since. + fts["Cumulative Followers"] = BASELINE_FOLLOWERS + fts["Total followers"].cumsum() + d["fts"] = fts + d["floc"] = pd.read_excel(fp, sheet_name="Location").sort_values("Total followers", ascending=False) + d["fjob"] = pd.read_excel(fp, sheet_name="Job function").sort_values("Total followers") + d["fsen"] = pd.read_excel(fp, sheet_name="Seniority").sort_values("Total followers") + d["find"] = pd.read_excel(fp, sheet_name="Industry").sort_values("Total followers", ascending=False) + d["fcs"] = pd.read_excel(fp, sheet_name="Company size").sort_values("Total followers") + + # Visitors + vp = _find_latest_file("visitors") + file_mtimes.append(os.path.getmtime(vp)) + vts = pd.read_excel(vp, sheet_name="Visitor metrics") + vts["Date"] = pd.to_datetime(vts["Date"]) + d["vts"] = vts + d["vloc"] = pd.read_excel(vp, sheet_name="Location").sort_values("Total views", ascending=False) + d["vsen"] = pd.read_excel(vp, sheet_name="Seniority").sort_values("Total views") + d["vind"] = pd.read_excel(vp, sheet_name="Industry") + d["vcs"] = pd.read_excel(vp, sheet_name="Company size").sort_values("Total views") + + # Visitor -> Follower conversion: correlates daily unique visitors against daily new followers. + # Not a true funnel — LinkedIn doesn't expose which specific visitor became a follower, only + # daily totals for each, so this is a same-day correlation, not individual-level attribution. + conv = pd.merge( + fts[["Date", "Total followers"]].rename(columns={"Total followers": "New Followers"}), + vts[["Date", "Total unique visitors (desktop)", "Total unique visitors (mobile)"]], + on="Date", how="inner", + ) + conv["Unique Visitors"] = conv["Total unique visitors (desktop)"] + conv["Total unique visitors (mobile)"] + conv["Conversion Rate"] = conv.apply( + lambda r: (r["New Followers"] / r["Unique Visitors"] * 100) if r["Unique Visitors"] > 0 else None, axis=1 + ) + d["conv"] = conv + + # Content — merge all content files from both locations, newest data wins on duplicate dates + content_files = _find_all_files("content") + file_mtimes.extend(os.path.getmtime(cp) for cp in content_files) + metrics_frames, posts_frames = [], [] + for cp in content_files: + m = pd.read_excel(cp, sheet_name="Metrics", header=1) + m["Date"] = pd.to_datetime(m["Date"]) + m["_src"] = os.path.getmtime(cp) + metrics_frames.append(m) + p = pd.read_excel(cp, sheet_name="All posts", header=1) + p["_src"] = os.path.getmtime(cp) + posts_frames.append(p) + + # Merge timeseries: keep newest file's row when dates overlap + cts = (pd.concat(metrics_frames) + .sort_values("_src") + .drop_duplicates(subset=["Date"], keep="last") + .drop(columns=["_src"]) + .sort_values("Date") + .reset_index(drop=True)) + cts["CTR (organic) %"] = (cts["Clicks (organic)"] / cts["Impressions (organic)"] * 100).where( + cts["Impressions (organic)"] > 0, 0) + cts["CTR (sponsored) %"] = (cts["Clicks (sponsored)"] / cts["Impressions (sponsored)"] * 100).where( + cts["Impressions (sponsored)"] > 0, 0) + d["cts"] = cts + + # Merge posts: deduplicate by Post link, keep newest file's version + posts = (pd.concat(posts_frames) + .sort_values("_src") + .drop_duplicates(subset=["Post link"], keep="last") + .drop(columns=["_src"]) + .reset_index(drop=True)) + posts["Created date"] = pd.to_datetime(posts["Created date"], errors="coerce") + posts["Eng %"] = (posts["Engagement rate"] * 100).round(2) + posts["CTR %"] = (posts["Click through rate (CTR)"] * 100).round(2) + posts["Content Type"] = posts["Content Type"].fillna("Text/Image") + posts["Post title"] = posts["Post title"].fillna("(no text — image/video-only post)") + # Manual titles for posts LinkedIn exported with no caption text (image/video-only posts). + for post_date, manual_title in MANUAL_POST_TITLES.items(): + posts.loc[posts["Created date"].dt.date == pd.Timestamp(post_date).date(), "Post title"] = manual_title + # Manual content-type corrections (e.g. carousels LinkedIn exports as plain Image). + for post_date, manual_type in MANUAL_CONTENT_TYPES.items(): + posts.loc[posts["Created date"].dt.date == pd.Timestamp(post_date).date(), "Content Type"] = manual_type + posts["Short title"] = posts["Post title"].str[:75] + "…" + # Table should always read newest post first, regardless of which file/upload order + # contributed each row. + posts = posts.sort_values("Created date", ascending=False).reset_index(drop=True) + d["posts"] = posts + + # Competitors + comp_path = _find_latest_file("competitor") + file_mtimes.append(os.path.getmtime(comp_path)) + d["comp"] = pd.read_excel(comp_path, sheet_name="COMPETITORS", header=1) + + # Most recent export drop across all sources — shown in the header + d["last_updated"] = pd.Timestamp(max(file_mtimes), unit="s") + + return d + + +def date_range_label(df, col="Date"): + if df.empty: + return "" + lo, hi = df[col].min(), df[col].max() + return f"{lo.strftime('%b %d')} – {hi.strftime('%b %d')}" + + +def refresh_data(): + global D, GROWTH_SNAPSHOT, current_total_followers, current_total_followers_as_of + global new_followers_30d, total_impressions, total_page_views, best_eng, num_posts + global total_clicks, avg_eng_rate, conv_total_followers, conv_total_visitors + global overall_conversion_rate, overview, posts, post_table_cols, content_tab + global followers_tab, visitors_tab, comp, comp_ordered, competitors_tab, growth_tab + global TAB_CONTENT_MAP, _breakdown_figure, _visitor_breakdown_figure + global _posts_table_records, _content_posts_filtered, _content_cts_filtered + global impressions_by_format_figure, engagement_by_format_figure, ctr_by_format_figure + global impressions_vs_engagement_figure, reactions_figure, clicks_figure + global FOLLOWER_BREAKDOWNS, VISITOR_BREAKDOWNS, CONTENT_BAR_MARGIN, CONTENT_SCATTER_LEGEND + global _content_month_options, post_highlight_styles + + D = load() + GROWTH_SNAPSHOT = growth.backfill_history(D["fts"], UPLOAD_DIR)[-1] + + current_total_followers = int(D["fts"]["Cumulative Followers"].iloc[-1]) + current_total_followers_as_of = D["fts"]["Date"].max().strftime("%b %d, %Y") + new_followers_30d = int(D["fts"]["Total followers"].sum()) + total_impressions = int(D["cts"]["Impressions (total)"].sum()) + total_page_views = int(D["vts"]["Total page views (total)"].sum()) + best_eng = float(D["posts"]["Eng %"].max()) if len(D["posts"]) else 0.0 + num_posts = len(D["posts"]) + total_clicks = int(D["cts"]["Clicks (total)"].sum()) + avg_eng_rate = float(D["cts"]["Engagement rate (total)"].replace(0, float("nan")).mean()) + conv_total_followers = int(D["conv"]["New Followers"].sum()) + conv_total_visitors = int(D["conv"]["Unique Visitors"].sum()) + overall_conversion_rate = (conv_total_followers / conv_total_visitors * 100) if conv_total_visitors else None + + _ov_year = int(D["fts"]["Date"].dt.year.max()) + _ov_month = _default_active_month(D["fts"], D["posts"], _ov_year) + + overview = dbc.Container([ + html.Div([ + html.Div(kpi("Starting Followers", f"{BASELINE_FOLLOWERS:,}", f"as of {pd.Timestamp(BASELINE_DATE).strftime('%b %d, %Y')}", "#999"), style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("Total Followers", f"{current_total_followers:,}", f"as of {current_total_followers_as_of}", TEAL), style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("New Followers", f"{new_followers_30d:,}", date_range_label(D["fts"])), style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("Total Impressions", f"{total_impressions:,}", date_range_label(D["cts"])), style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("Total Page Views", f"{total_page_views:,}", date_range_label(D["vts"])), style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("Total Clicks", f"{total_clicks:,}", date_range_label(D["cts"])), style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("Best Post Eng.", f"{best_eng:.1f}%", f"across {num_posts} posts", GREEN), style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("Avg. Eng. Rate", f"{avg_eng_rate:.2f}%" if not pd.isna(avg_eng_rate) else "—", "daily avg (organic)", TEAL), style={"flex": "1", "minWidth": "0"}), + ], style={"display": "flex", "gap": "0.75rem", "marginBottom": "1rem"}), + + dbc.Row([ + dbc.Col(section("Follower Gains — Calendar (★ = post published)", + followers_calendar(D["fts"], D["posts"], id_prefix="overview", height="270px") + ), md=8), + dbc.Col(section("New Followers per Day (★ = post published)", + G(daily_followers_figure(D["fts"], D["posts"], _ov_year, _ov_month), + id="overview-daily-followers-graph", style={"height": "270px"}) + ), md=4), + ], className="gx-3 mb-3"), + + dbc.Row([ + dbc.Col(section("Impressions", + G(impressions_figure(_filtered_by_month(D["cts"], _ov_year, _ov_month)), + id="overview-impressions-graph", style={"height": "240px"}), + info=("impressions", IMPRESSIONS_EXPLAINER), + ), md=3), + dbc.Col(section("Daily Engagement Rate", + G(engagement_figure(_filtered_by_month(D["cts"], _ov_year, _ov_month)), + id="overview-engagement-graph", style={"height": "240px"}), + info=("engagement", ENGAGEMENT_EXPLAINER), + ), md=3), + dbc.Col(section("Daily CTR", + G(ctr_figure(_filtered_by_month(D["cts"], _ov_year, _ov_month)), + id="overview-ctr-graph", style={"height": "240px"}), + info=("ctr", CTR_EXPLAINER), + ), md=3), + dbc.Col(section("Page Views — Desktop vs Mobile", + G(page_views_figure(_filtered_by_month(D["vts"], _ov_year, _ov_month)), + id="overview-pageviews-graph", style={"height": "240px"}), + info=("page-views", PAGE_VIEWS_EXPLAINER), + ), md=3), + ], className="gx-3"), + ], fluid=True, className="pb-3") + + + # ───── CONTENT PERFORMANCE ──────────────────────────────────────────────────── + posts = D["posts"] + post_table_cols = ["Short title", "Created date", "Post type", "Content Type", + "Impressions", "Clicks", "Eng %", "CTR %", "Likes", "Comments", "Reposts"] + + # Highlight the top-engagement post in pale yellow, and the top-CTR post in + # teal if it's a different post from the top-engagement one. + post_highlight_styles = [{"if": {"row_index": "odd"}, "backgroundColor": "#f8fafc"}] + if not posts.empty: + best_eng_val = posts["Eng %"].max() + best_ctr_val = posts["CTR %"].max() + same_post_has_both = not posts[ + (posts["Eng %"] == best_eng_val) & (posts["CTR %"] == best_ctr_val) + ].empty + + post_highlight_styles.append({ + "if": {"filter_query": "{Eng %} = " + str(best_eng_val)}, + "backgroundColor": "#FFF9C4", + }) + if not same_post_has_both: + post_highlight_styles.append({ + "if": {"filter_query": "{CTR %} = " + str(best_ctr_val)}, + "backgroundColor": "#B2EBF2", + }) + + CONTENT_HIGHLIGHT_EXPLAINER = html.Div([ + html.Div([color_dot("#FFF9C4"), html.B("Highest engagement rate")], style={"marginBottom": "3px"}), + html.Div("The metric LinkedIn's algorithm weighs most heavily when deciding whether to keep " + "distributing a post further.", style={"color": "#6b7a8d", "marginBottom": "10px"}), + + html.Div([color_dot("#B2EBF2"), html.B("Highest CTR")], style={"marginBottom": "3px"}), + html.Div("Best at driving clicks off LinkedIn — matters most when the goal is traffic to a " + "website rather than reach.", style={"color": "#6b7a8d", "marginBottom": "10px"}), + + html.Hr(style={"margin": "8px 0"}), + + html.B("Engagement rate vs. impressions vs. CTR — which one matters?"), + html.Div("Impressions mostly reflect audience size and algorithmic luck, not content quality.", + style={"marginTop": "6px", "marginBottom": "8px"}), + html.Div("Engagement rate is what LinkedIn uses as its main signal in the first hour to decide " + "how far to keep pushing a post, so it's the closest thing to a leading indicator of " + "future reach.", style={"marginBottom": "8px"}), + html.Div("CTR only matters when the goal is specifically driving off-platform traffic. LinkedIn " + "doesn't reward outbound clicks the way it rewards on-platform engagement, so a " + "high-CTR post can still get less reach than a high-engagement one with zero clicks."), + ]) + + IMPRESSIONS_VS_ENGAGEMENT_EXPLAINER = html.Div([ + html.Div("Each dot is a post — x is reach, y is engagement rate, color is content type, and bubble " + "size is clicks.", style={"marginBottom": "8px"}), + html.Div('It answers "does reach actually translate into resonance, or are some posts getting ' + 'distributed widely without anyone caring?"', style={"marginBottom": "8px"}), + html.Div("You can spot high-impression/low-engagement posts (reach without resonance) versus " + "low-impression/high-engagement posts (content LinkedIn under-distributed but people liked).", + style={"color": "#6b7a8d"}), + ]) + + def _posts_table_records(selected_month): + """Rows for the All Posts table, optionally filtered to one 'YYYY-MM' month value.""" + df = posts[post_table_cols].copy() + if selected_month and selected_month != "all": + year, month = (int(part) for part in selected_month.split("-")) + df = df[(posts["Created date"].dt.year == year) & (posts["Created date"].dt.month == month)] + df["Created date"] = df["Created date"].dt.strftime("%d/%m/%Y") + return df.to_dict("records") + + + def _content_month_options(): + import calendar as calmod + months = sorted({(d.year, d.month) for d in posts["Created date"].dropna()}) + return [{"label": "All Months", "value": "all"}] + [ + {"label": f"{calmod.month_name[m]} {y}", "value": f"{y}-{m:02d}"} for y, m in months + ] + + + def _content_posts_filtered(selected_month): + if not selected_month or selected_month == "all": + return posts + year, month = (int(part) for part in selected_month.split("-")) + return posts[(posts["Created date"].dt.year == year) & (posts["Created date"].dt.month == month)] + + + def _content_cts_filtered(selected_month): + cts = D["cts"] + if not selected_month or selected_month == "all": + return cts + year, month = (int(part) for part in selected_month.split("-")) + return cts[(cts["Date"].dt.year == year) & (cts["Date"].dt.month == month)] + + + def _format_breakdown(posts_df, col, agg): + if posts_df.empty: + return pd.DataFrame(columns=["Content Type", col]) + grouped = posts_df.groupby("Content Type", as_index=False)[col] + return (grouped.sum() if agg == "sum" else grouped.mean()).sort_values(col) + + + def impressions_by_format_figure(posts_df): + df = _format_breakdown(posts_df, "Impressions", "sum") + fig = px.bar(df, x="Impressions", y="Content Type", orientation="h", template=TMPL, + color="Content Type", color_discrete_map=FORMAT_COLORS) + return fig.update_layout(showlegend=False, bargap=0.6) + + + def engagement_by_format_figure(posts_df): + df = _format_breakdown(posts_df, "Eng %", "mean") + fig = px.bar(df, x="Eng %", y="Content Type", orientation="h", template=TMPL, + color="Content Type", color_discrete_map=FORMAT_COLORS, + labels={"Eng %": "Engagement Rate (%)"}) + return fig.update_layout(showlegend=False, bargap=0.6) + + + def ctr_by_format_figure(posts_df): + df = _format_breakdown(posts_df, "CTR %", "mean") + fig = px.bar(df, x="CTR %", y="Content Type", orientation="h", template=TMPL, + color="Content Type", color_discrete_map=FORMAT_COLORS, + labels={"CTR %": "Click-Through Rate (%)"}) + return fig.update_layout(showlegend=False, bargap=0.6) + + + def impressions_vs_engagement_figure(posts_df): + fig = px.scatter(posts_df, x="Impressions", y="Eng %", color="Content Type", size="Clicks", + hover_name="Short title", template=TMPL, color_discrete_map=FORMAT_COLORS, + labels={"Eng %": "Engagement Rate (%)", "Content Type": ""}) + return fig.update_traces(marker=dict(sizemin=8)) + + + def reactions_figure(cts_df): + df = cts_df[["Date", "Reactions (organic)", "Comments (organic)", "Reposts (organic)"]].rename( + columns={"Reactions (organic)": "Reactions", "Comments (organic)": "Comments", "Reposts (organic)": "Reposts"}) + df["Sponsored"] = cts_df["Reactions (sponsored)"] + cts_df["Comments (sponsored)"] + cts_df["Reposts (sponsored)"] + return px.line(df, x="Date", y=["Reactions", "Comments", "Reposts", "Sponsored"], + template=TMPL, color_discrete_sequence=[BLUE, GREEN, "#9C27B0", AMBER], + labels={"value": "Count", "variable": ""}) + + + def clicks_figure(cts_df): + df = cts_df[["Date", "Clicks (organic)", "Clicks (sponsored)"]].rename( + columns={"Clicks (organic)": "Organic", "Clicks (sponsored)": "Sponsored"}) + return px.line(df, x="Date", y=["Organic", "Sponsored"], template=TMPL, + color_discrete_sequence=[BLUE, AMBER], labels={"value": "Clicks", "variable": ""}) + + + CONTENT_BAR_MARGIN = dict(t=10, b=20, l=10, r=10) + CONTENT_SCATTER_LEGEND = dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0, + font=dict(size=8), itemwidth=30, tracegroupgap=0) + + + _posts_table_df = posts[post_table_cols].copy() + _posts_table_df["Created date"] = _posts_table_df["Created date"].dt.strftime("%d/%m/%Y") + + content_tab = dbc.Container([ + dbc.Card([ + dbc.CardHeader([ + html.Strong("Posts"), + dcc.Dropdown( + id="content-month-select", + options=_content_month_options(), value="all", clearable=False, searchable=False, + className="compact-dropdown", + style={"width": "150px", "fontWeight": "700", "color": "#1a2533"}, + ), + info_popover("content-highlight", CONTENT_HIGHLIGHT_EXPLAINER), + ], style={"display": "flex", "alignItems": "center", "gap": "8px"}), + dbc.CardBody( + dash_table.DataTable( + id="posts-table", + data=_posts_table_df.to_dict("records"), + columns=[{"name": c, "id": c} for c in post_table_cols], + page_size=20, + style_table={"overflowX": "auto"}, + style_header={"backgroundColor": "#f0f4f8", "color": "#333", "fontWeight": "bold", "border": "1px solid #ccc", + "fontSize": "11px", "padding": "3px 6px", "height": "22px", "lineHeight": "16px"}, + style_cell={"backgroundColor": "#ffffff", "color": "#333", "border": "1px solid #e0e0e0", + "fontSize": "11px", "padding": "3px 6px", "height": "22px", "lineHeight": "16px", + "textAlign": "left", + "maxWidth": "300px", "overflow": "hidden", "textOverflow": "ellipsis"}, + style_data_conditional=post_highlight_styles, + tooltip_data=[{c: {"value": str(row[c]), "type": "markdown"} for c in post_table_cols} + for row in _posts_table_df.to_dict("records")], + tooltip_duration=None, + )), + ], className="mb-3 shadow-sm"), + + dbc.Row([ + dbc.Col(section("Impressions by Content Format", + G(impressions_by_format_figure(posts), id="content-impressions-fig", + style={"height": "180px"}, margin=CONTENT_BAR_MARGIN), + info=("content-impressions", IMPRESSIONS_EXPLAINER), + title_style={"fontSize": "0.72rem"}, + ), md=3), + dbc.Col(section("Avg. Engagement Rate by Content Format", + G(engagement_by_format_figure(posts), id="content-engagement-fig", + style={"height": "180px"}, margin=CONTENT_BAR_MARGIN), + info=("content-engagement", ENGAGEMENT_EXPLAINER), + title_style={"fontSize": "0.72rem"}, + ), md=3), + dbc.Col(section("Avg. CTR by Content Format", + G(ctr_by_format_figure(posts), id="content-ctr-fig", + style={"height": "180px"}, margin=CONTENT_BAR_MARGIN), + info=("content-ctr", CTR_EXPLAINER), + title_style={"fontSize": "0.72rem"}, + ), md=3), + dbc.Col(section("Impressions vs Engagement Rate", + G(impressions_vs_engagement_figure(posts), id="content-scatter-fig", + style={"height": "180px"}, legend=CONTENT_SCATTER_LEGEND), + info=("impressions-vs-engagement", IMPRESSIONS_VS_ENGAGEMENT_EXPLAINER), + title_style={"fontSize": "0.72rem"}, + ), md=3), + ], className="gx-3 mb-3"), + + dbc.Row([ + dbc.Col(section("Daily Reactions, Comments & Reposts", + G(reactions_figure(D["cts"]), id="content-reactions-fig", style={"height": "180px"}) + ), md=6), + dbc.Col(section("Daily Clicks", + G(clicks_figure(D["cts"]), id="content-clicks-fig", style={"height": "180px"}) + ), md=6), + ], className="gx-3"), + ], fluid=True, className="pb-3") + + + # ───── FOLLOWERS ────────────────────────────────────────────────────────────── + # key -> (label, dataframe sorted largest-first, category column, bar color) + # All breakdowns share one light-blue accent — the color was never meaningful data, just visual noise. + LIGHT_BLUE = "#4a90d9" + FOLLOWER_BREAKDOWN_MAP = { + "company_size": ("Company Size", D["fcs"].sort_values("Total followers", ascending=False), "Company size", LIGHT_BLUE), + "job_function": ("Job Function", D["fjob"].sort_values("Total followers", ascending=False), "Job function", LIGHT_BLUE), + "industry": ("Industry", D["find"], "Industry", LIGHT_BLUE), + "location": ("Location", D["floc"], "Location", LIGHT_BLUE), + "seniority": ("Seniority", D["fsen"].sort_values("Total followers", ascending=False), "Seniority", LIGHT_BLUE), + } + FOLLOWER_BREAKDOWNS = [(key, label) for key, (label, *_rest) in FOLLOWER_BREAKDOWN_MAP.items()] + + def _breakdown_figure(key, expanded): + label, df, col, color = FOLLOWER_BREAKDOWN_MAP[key] + total_n = len(df) + show_df = df if expanded else df.head(10) + fig = px.bar(show_df.sort_values("Total followers"), x="Total followers", y=col, + orientation="h", template=TMPL, color_discrete_sequence=[color]) + fig.update_layout(showlegend=False) + return label, fig, total_n + + # Approximate geographic centroid for each country that currently appears in the export — + # used to place one marker per country rather than one per city. + _COUNTRY_CENTROIDS = { + "Germany": (51.1657, 10.4515), + "United Kingdom": (55.3781, -3.4360), + "Austria": (47.5162, 14.5501), + "Netherlands": (52.1326, 5.2913), + "France": (46.6034, 1.8883), + "Sweden": (60.1282, 18.6435), + "Norway": (60.4720, 8.4689), + "Poland": (51.9194, 19.1451), + "Czechia": (49.8175, 15.4730), + "India": (20.5937, 78.9629), + "China": (35.8617, 104.1954), + "Canada": (56.1304, -106.3468), + "Tunisia": (33.8869, 9.5375), + } + + + def followers_country_map_figure(floc_df): + """One marker per country (not per city) showing follower count, on a plain + white-land / gray-water base map — no country borders, no choropleth shading.""" + by_country = floc_df.copy() + by_country["Country"] = by_country["Location"].str.split(",").str[-1].str.strip() + by_country = by_country.groupby("Country", as_index=False)["Total followers"].sum() + by_country["lat"] = by_country["Country"].map(lambda c: _COUNTRY_CENTROIDS.get(c, (None, None))[0]) + by_country["lon"] = by_country["Country"].map(lambda c: _COUNTRY_CENTROIDS.get(c, (None, None))[1]) + by_country = by_country.dropna(subset=["lat", "lon"]) + + fig = go.Figure(go.Scattergeo( + lat=by_country["lat"], lon=by_country["lon"], + hovertext=by_country["Country"] + ": " + by_country["Total followers"].astype(str), + mode="markers", + marker=dict(size=14, color="#E53935", line=dict(width=1, color="white"), opacity=0.85), + hovertemplate="%{hovertext}", + )) + # Explicit lon/lat window + a linear (equirectangular) projection, sized to approximate + # this card's aspect ratio (md=8 column at 240px tall) — fitbounds="locations" left large + # blank side margins here, since our data's true shape (Canada-to-China, ~5.8:1) didn't + # match the box's shape and Plotly pads rather than crops to preserve true geographic + # proportions. This is tuned to an estimated box width, not a measured one (no browser + # available to verify pixel dimensions here) — nudge lataxis_range if it's still off. + # No `template=` here deliberately — applying plotly_white to a geo figure resets/breaks + # its geo styling (landcolor/oceancolor/projection all get overridden). We already control + # every visual aspect of this map explicitly via update_geos() above. + fig.update_geos(showland=True, landcolor="white", showocean=True, oceancolor="#d9d9d9", + showcountries=False, showcoastlines=False, showframe=False, + projection_type="equirectangular", + lonaxis_range=[-115, 133], lataxis_range=[3, 78]) + return fig + + + _init_label, _init_fig, _init_total = _breakdown_figure("company_size", False) + + FOLLOWERS_BY_EXPLAINER = html.Div([ + html.Div("These are LinkedIn's standard demographic splits of who is following your page — the " + "point is to check whether your content is reaching the audience you actually care about, " + "not just growing follower count in the abstract.", style={"marginBottom": "10px"}), + html.Div([html.B("Company Size"), " and ", html.B("Industry"), " tell you what kind of " + "organizations are watching."], style={"marginBottom": "6px"}), + html.Div([html.B("Seniority"), " tells you whether you're reaching people who can actually " + "greenlight a deal — if most followers sit at Entry level or student tier, your content " + "is resonating with people who use the tools but can't approve budget. Growth in " + "Director/VP/Owner tiers is a stronger signal for business development than raw " + "follower count."], style={"color": "#6b7a8d"}), + ]) + + followers_tab = dbc.Container([ + dbc.Row([ + dbc.Col(section("Follower Gains — Calendar (★ = post published)", + followers_calendar(D["fts"], D["posts"], id_prefix="followers", height="300px") + ), md=8), + dbc.Col(section("New Followers per Day (★ = post published)", + G(daily_followers_figure(D["fts"], D["posts"], + int(D["fts"]["Date"].dt.year.max()), + _default_active_month(D["fts"], D["posts"], int(D["fts"]["Date"].dt.year.max()))), + id="followers-daily-followers-graph", style={"height": "300px"}) + ), md=4), + ], className="gx-3 mb-3"), + + dbc.Row([ + dbc.Col(dbc.Card([ + dbc.CardHeader([ + html.Strong("Followers by"), + dcc.Dropdown( + id="fb-select", + options=[{"label": label, "value": key} for key, label in FOLLOWER_BREAKDOWNS], + value="company_size", clearable=False, searchable=False, + className="compact-dropdown", + style={"width": "150px", "fontWeight": "700", "color": "#1a2533"}, + ), + html.Button("Show all", id="fb-expand-btn", n_clicks=0, + style={"display": "inline-block" if _init_total > 10 else "none", + "marginLeft": "auto", "fontSize": "0.72rem", "border": "none", + "background": "none", "color": "white", "textDecoration": "underline", + "cursor": "pointer", "padding": "0"}), + info_popover("followers-by", FOLLOWERS_BY_EXPLAINER), + ], style={"display": "flex", "alignItems": "center", "gap": "8px"}), + dbc.CardBody(G(_init_fig, style={"height": "240px"}), id="followers-breakdown-area"), + dcc.Store(id="fb-expanded", data=False), + ], className="mb-3 shadow-sm h-100"), md=4), + dbc.Col(section("Followers by Country", + G(followers_country_map_figure(D["floc"]), style={"height": "240px"}, + margin=dict(l=0, r=0, t=0, b=0)), + card_class="h-100", + ), md=8), + ], className="gx-3"), + ], fluid=True, className="pb-3") + + + # ───── VISITORS ─────────────────────────────────────────────────────────────── + CONVERSION_EXPLAINER = html.Div([ + html.Div(f"{conv_total_followers} new followers / {conv_total_visitors} unique visitors, " + f"{date_range_label(D['conv'])}.", style={"marginBottom": "8px"}), + html.Div( + "This correlates each day's total unique visitors against that day's net new followers — it is not a " + "true funnel. LinkedIn doesn't expose which specific visitor became a follower, only daily totals for " + "each, so treat this as a directional signal rather than individual-level attribution.", + style={"color": "#6b7a8d"}, + ), + ]) + + _conv_rate_df = D["conv"].dropna(subset=["Conversion Rate"]).copy() + _conv_rate_df["Sign"] = _conv_rate_df["Conversion Rate"].apply(lambda v: "Positive" if v >= 0 else "Negative") + + # key -> (label, dataframe sorted largest-first, category column) + # Same single light-blue accent as Followers By — color was never meaningful data here either. + VISITOR_BREAKDOWN_MAP = { + "seniority": ("Seniority", D["vsen"].sort_values("Total views", ascending=False), "Seniority"), + "company_size": ("Company Size", D["vcs"].sort_values("Total views", ascending=False), "Company size"), + "location": ("Location", D["vloc"].sort_values("Total views", ascending=False), "Location"), + } + VISITOR_BREAKDOWNS = [(key, label) for key, (label, *_rest) in VISITOR_BREAKDOWN_MAP.items()] + + def _visitor_breakdown_figure(key): + label, df, col = VISITOR_BREAKDOWN_MAP[key] + fig = px.bar(df.sort_values("Total views"), x="Total views", y=col, + orientation="h", template=TMPL, color_discrete_sequence=[LIGHT_BLUE]) + fig.update_layout(showlegend=False) + return label, fig + + + _vb_init_label, _vb_init_fig = _visitor_breakdown_figure("seniority") + + VISITOR_VS_FOLLOWER_EXPLAINER = html.Div([ + html.Div([html.B("Visitor"), " = anyone who viewed the Page, for any reason — searched for " + "TXproducts, clicked a link from a post, a recruiter checking the company out. It's just " + "a page view, no relationship implied."], style={"marginBottom": "8px"}), + html.Div([html.B("Follower"), " = someone who explicitly clicked Follow, opting in to see future " + "posts in their feed — a persistent subscription, separate from visiting."], + style={"marginBottom": "8px"}), + html.Div("A visitor can browse without ever following, and a follower doesn't need to revisit the " + "Page to keep getting content in their feed — that's why these track fundamentally " + "different behavior.", style={"color": "#6b7a8d"}), + ]) + + UNIQUE_VISITORS_EXPLAINER = html.Div([ + html.Div("Counts each distinct person who visited the Page that day once — no matter how many times " + "they viewed it, or how many sections (Overview/Life/Jobs) they looked at.", + style={"marginBottom": "8px"}), + html.Div("This differs from Page Views, which counts every view/session — one person browsing " + "multiple sections in a single visit still counts as one unique visitor but several page " + "views.", style={"color": "#6b7a8d"}), + ]) + + VISITOR_TERMINOLOGY_EXPLAINER = html.Div([ + html.Div([html.B("Unique Visitor"), " = a distinct person, counted once per day no matter how many " + "tabs they looked at or how many times they viewed the page."], style={"marginBottom": "8px"}), + html.Div([html.B("Page View"), " = every individual view — one person browsing 3 tabs generates 3 " + "page views but is still 1 unique visitor."], style={"marginBottom": "8px"}), + html.Div(["Plain ", html.B("\"Visitor\""), " (as in the section below) isn't a third, shallower " + "category — it's just shorthand. But it's worth knowing this specific breakdown " + "(Seniority/Company Size/Location) is built from LinkedIn's ", + html.B("page-view totals"), " by category, not deduplicated unique-visitor counts — " + "LinkedIn doesn't provide a demographic breakdown of unique people, only of views."], + style={"color": "#6b7a8d"}), + ]) + + visitors_tab = dbc.Container([ + dbc.Row([ + dbc.Col(section("Daily Unique Visitors vs New Followers", + G(px.bar(D["conv"], x="Date", y=["Unique Visitors", "New Followers"], + barmode="group", template=TMPL, color_discrete_sequence=[BLUE, GREEN], + labels={"value": "Count", "variable": ""}, + ), style={"height": "300px"}), + info=("visitor-vs-follower", VISITOR_VS_FOLLOWER_EXPLAINER), + ), md=6), + dbc.Col(dbc.Card([ + dbc.CardHeader([ + html.Strong("Daily Conversion Rate"), + html.Span(f"{overall_conversion_rate:.1f}%" if overall_conversion_rate is not None else "—", + style={"color": "#FFD600", "fontWeight": "800", "marginLeft": "8px"}), + info_popover("conversion-rate", CONVERSION_EXPLAINER), + ]), + dbc.CardBody(G(px.bar(_conv_rate_df, x="Date", y="Conversion Rate", template=TMPL, + color="Sign", color_discrete_map={"Positive": GREEN, "Negative": "#d43f3a"}, + labels={"Conversion Rate": "Conversion Rate (%)"}, + ).update_layout(showlegend=False), style={"height": "300px"})), + ], className="mb-3 shadow-sm"), md=6), + ], className="gx-3 mb-3"), + + dbc.Row([ + dbc.Col(dbc.Card([ + dbc.CardHeader([ + html.Strong("Visitors by"), + dcc.Dropdown( + id="vb-select", + options=[{"label": label, "value": key} for key, label in VISITOR_BREAKDOWNS], + value="seniority", clearable=False, searchable=False, + className="compact-dropdown", + style={"width": "150px", "fontWeight": "700", "color": "#1a2533"}, + ), + info_popover("visitor-terminology", VISITOR_TERMINOLOGY_EXPLAINER), + ], style={"display": "flex", "alignItems": "center", "gap": "8px"}), + dbc.CardBody(G(_vb_init_fig, style={"height": "240px"}), id="visitor-breakdown-area"), + ], className="mb-3 shadow-sm h-100"), md=4), + dbc.Col(section("Page Views over Time", + G(px.line(D["vts"][["Date", "Overview page views (total)", "Jobs page views (total)"]] + .rename(columns={"Overview page views (total)": "Overview", "Jobs page views (total)": "Jobs"}), + x="Date", y=["Overview", "Jobs"], + template=TMPL, color_discrete_sequence=[BLUE, AMBER], + labels={"value": "Page Views", "variable": "Section"}, + ), style={"height": "240px"}), + card_class="h-100", + ), md=4), + dbc.Col(section("Unique Visitors — Desktop vs Mobile", + G(px.area(D["vts"][["Date", "Total unique visitors (desktop)", "Total unique visitors (mobile)"]] + .rename(columns={"Total unique visitors (desktop)": "Desktop", + "Total unique visitors (mobile)": "Mobile"}), + x="Date", y=["Desktop", "Mobile"], + template=TMPL, color_discrete_sequence=[BLUE, ORANGE], + labels={"value": "Unique Visitors", "variable": ""}, + ), style={"height": "240px"}), + info=("unique-visitors", UNIQUE_VISITORS_EXPLAINER), + card_class="h-100", + ), md=4), + ], className="gx-3"), + ], fluid=True, className="pb-3") + + + # ───── COMPETITORS ──────────────────────────────────────────────────────────── + # Styled after LinkedIn's own "Compare growth" table (Analytics > Competitors), with + # TXproducts pinned to the top row and competitors listed underneath. LinkedIn's native + # % badges compare each page to ITS OWN previous period — we don't have prior-period + # snapshots to diff against, so ours instead compares each page to the OTHER tracked + # pages' average for that same metric, this period. Same visual language, different math. + comp = D["comp"] + tracked_competitors = [p for p in comp["Page"] if p != "TXproducts"] + COMP_METRIC_COLS = ["New Followers", "Posts", "Comments", "Comments per day", "Reactions"] + comp_ordered = pd.concat([comp[comp["Page"] == "TXproducts"], comp[comp["Page"] != "TXproducts"]], + ignore_index=True) + + COMPARE_GROWTH_EXPLAINER = html.Div([ + html.Div([color_dot(GREEN), html.B("Green"), " = ahead of the other tracked pages' average for " + "that metric, this period."], style={"marginBottom": "3px"}), + html.Div([color_dot("#d43f3a"), html.B("Red"), " = behind that average."], style={"marginBottom": "10px"}), + html.Hr(style={"margin": "8px 0"}), + html.B("Not the same as LinkedIn's own \"Compare growth\" %"), + html.Div("LinkedIn's native comparison (shown in the LinkedIn Page Admin UI) is each page's change " + "from ITS OWN previous period. We can't replicate that here because our export only has " + "one time snapshot — there's no prior-period data to diff against unless we start saving " + "snapshots over time.", style={"marginTop": "6px", "color": "#6b7a8d"}), + ]) + + def _comp_metric_cell(value, others_avg): + parts = [html.Div(f"{int(value):,}", style={"fontWeight": "700", "fontSize": "0.95rem", "color": "#1a2533"})] + if others_avg is not None: + if others_avg == 0: + if value > 0: + parts.append(html.Div("▲ ahead", style={"fontSize": "0.68rem", "color": GREEN, "fontWeight": "700"})) + else: + pct = (value - others_avg) / others_avg * 100 + arrow, color = ("▲", GREEN) if pct >= 0 else ("▼", "#d43f3a") + parts.append(html.Div(f"{arrow} {abs(pct):.0f}%", style={"fontSize": "0.68rem", "color": color, "fontWeight": "700"})) + return html.Td(parts, style={"textAlign": "center", "padding": "12px 10px", "borderBottom": "1px solid #eef1f5"}) + + def _comp_company_cell(rank, name, is_self): + if is_self: + avatar = html.Img(src="/assets/txp_logo_vertical.png", + style={"width": "32px", "height": "32px", "objectFit": "contain"}) + else: + avatar = html.Div(name[0].upper(), style={ + "width": "32px", "height": "32px", "borderRadius": "50%", "backgroundColor": LIGHT_BLUE, + "color": "white", "display": "flex", "alignItems": "center", "justifyContent": "center", + "fontWeight": "800", "fontSize": "0.85rem", "flexShrink": "0", + }) + name_row = [html.Span(name, style={"fontWeight": "700"})] + if is_self: + name_row.append(html.Span("You", style={ + "marginLeft": "6px", "fontSize": "0.65rem", "fontWeight": "700", "color": "#6b7a8d", + "backgroundColor": "#eef1f5", "padding": "1px 7px", "borderRadius": "10px", + })) + info_children = [html.Div(name_row)] + if is_self: + info_children.append(html.Div(f"{current_total_followers:,} followers", + style={"fontSize": "0.72rem", "color": "#6b7a8d", "marginTop": "2px"})) + return html.Td(html.Div([ + html.Span(str(rank), style={"fontWeight": "700", "color": "#6b7a8d", "marginRight": "12px", "fontSize": "0.85rem"}), + avatar, + html.Div(info_children, style={"marginLeft": "10px"}), + ], style={"display": "flex", "alignItems": "center"}), style={"padding": "12px 10px", "borderBottom": "1px solid #eef1f5"}) + + def compare_growth_table(): + header = html.Tr( + [html.Th("Competitors", style={"textAlign": "left", "padding": "8px 10px", "color": "#6b7a8d", + "fontSize": "0.78rem", "fontWeight": "700"})] + + [html.Th(m, style={"textAlign": "center", "padding": "8px 10px", "color": "#6b7a8d", + "fontSize": "0.78rem", "fontWeight": "700"}) for m in COMP_METRIC_COLS] + ) + rows = [] + for i, row in comp_ordered.reset_index(drop=True).iterrows(): + is_self = row["Page"] == "TXproducts" + others = comp_ordered[comp_ordered["Page"] != row["Page"]] + cells = [_comp_company_cell(i + 1, row["Page"], is_self)] + for m in COMP_METRIC_COLS: + others_avg = float(others[m].mean()) if not others.empty else None + cells.append(_comp_metric_cell(row[m], others_avg)) + rows.append(html.Tr(cells)) + return html.Table([html.Thead(header), html.Tbody(rows)], style={"width": "100%", "borderCollapse": "collapse"}) + + competitors_tab = dbc.Container([ + dbc.Row([ + dbc.Col(dbc.Card([ + dbc.CardHeader([ + html.Strong("Compare Growth"), + info_popover("compare-growth", COMPARE_GROWTH_EXPLAINER), + ]), + dbc.CardBody( + [html.Div("Last 30 days", style={"fontSize": "0.78rem", "color": "#6b7a8d", "marginBottom": "10px"}), + compare_growth_table()] + if tracked_competitors else + [dbc.Alert("Only TXproducts data is currently tracked. Add competitor pages in LinkedIn " + "Page Admin under Analytics > Competitors to enable full benchmarking.", color="info")] + ), + ], className="mb-3 shadow-sm"), md=12), + ]), + ], fluid=True, className="pb-3") + + growth_tab = build_growth_tab() + + TAB_CONTENT_MAP = { + "overview": overview, "content": content_tab, "followers": followers_tab, + "visitors": visitors_tab, "competitors": competitors_tab, "growth": growth_tab, + } + + + +# ── Component builders ──────────────────────────────────────────────────────── +def kpi(label, value, note="", color=BLUE): + return dbc.Card(dbc.CardBody([ + html.P(label, className="text-muted mb-1", + style={"fontSize": "0.62rem", "textTransform": "uppercase", "letterSpacing": "0.05em"}), + html.H3(value, className="mb-0", style={"color": color, "fontWeight": "800", "fontSize": "1.2rem"}), + html.P(note, className="text-muted mb-0", style={"fontSize": "0.62rem"}), + ], style={"padding": "0.6rem 0.8rem"}), className="h-100 shadow-sm", style={"borderLeft": f"4px solid {color}"}) + + +def color_dot(color): + """Small inline color swatch used inside popover content instead of describing the color in words.""" + return html.Span(style={"width": "10px", "height": "10px", "backgroundColor": color, "display": "inline-block", + "borderRadius": "2px", "marginRight": "5px", "verticalAlign": "middle"}) + +def info_popover(id_suffix, content): + """A clickable (?) badge that reveals an explanation popover — used to annotate + metrics throughout the dashboard without cluttering the page with permanent text.""" + return html.Span([ + html.Span("?", id=f"info-{id_suffix}", style={ + "display": "inline-flex", "alignItems": "center", "justifyContent": "center", + "width": "22px", "height": "22px", "borderRadius": "50%", + "backgroundColor": "#FFD600", "color": "#1a2533", "fontSize": "0.8rem", + "fontWeight": "800", "cursor": "pointer", "marginLeft": "8px", "verticalAlign": "middle", + "boxShadow": "0 1px 4px rgba(0,0,0,0.3)", "border": "2px solid white", + }), + dbc.Popover( + dbc.PopoverBody(content, style={"fontSize": "0.82rem", "maxWidth": "360px", "color": "#1a2533", "lineHeight": "1.55"}), + target=f"info-{id_suffix}", trigger="legacy", placement="right", + ), + ]) + +def section(title, *children, info=None, title_style=None, card_class=""): + title_span_style = {"whiteSpace": "nowrap", "overflow": "hidden", "textOverflow": "ellipsis", + "display": "inline-block", "maxWidth": "calc(100% - 32px)", "verticalAlign": "middle"} + if title_style: + title_span_style.update(title_style) + header_children = [html.Strong(title, style=title_span_style)] + if info: + info_id, info_text = info + header_children.append(info_popover(info_id, info_text)) + return dbc.Card([ + dbc.CardHeader(header_children), + dbc.CardBody(list(children)), + ], className=f"mb-3 shadow-sm {card_class}".strip()) + + +def _available_years(fts_df, posts_df): + """All years with export data, most recent first — powers the calendar's year picker.""" + years = set(fts_df["Date"].dt.year) + if not posts_df.empty: + years |= set(posts_df["Created date"].dropna().dt.year) + return sorted(years, reverse=True) + + +def _default_active_month(fts_df, posts_df, year): + """The most recent month (within `year`) that has export data — used to decide which + month's posts to show underneath the calendar before any month box has been clicked.""" + months = set(fts_df.loc[fts_df["Date"].dt.year == year, "Date"].dt.month) + if not posts_df.empty: + months |= set(posts_df.loc[posts_df["Created date"].dt.year == year, "Created date"].dt.month) + return max(months) if months else None + + +def _monthly_summary(fts_df, posts_df, year): + """Net follower change and post count per month, within `year`.""" + monthly_net = {} + for d, v in zip(fts_df["Date"].dt.date, fts_df["Total followers"]): + if d.year == year: + monthly_net[d.month] = monthly_net.get(d.month, 0) + int(v) + + monthly_posts = {} + if not posts_df.empty: + for d in posts_df["Created date"].dropna().dt.date: + if d.year == year: + monthly_posts[d.month] = monthly_posts.get(d.month, 0) + 1 + + months_with_data = set(monthly_net) | set(monthly_posts) + max_gain = max((v for v in monthly_net.values() if v > 0), default=0) + return monthly_net, monthly_posts, months_with_data, max_gain + + +def _month_box_style(monthly_net, months_with_data, max_gain, month, selected): + if month not in months_with_data: + bg = "#f8f9fa" + else: + net = monthly_net.get(month, 0) + if net < 0: + bg = "#FFCDD2" + elif net == 0: + bg = "#f0f4f8" + else: + intensity = 0.15 + 0.55 * min(net / max(max_gain, 1), 1.0) + bg = f"rgba(82,196,26,{intensity:.2f})" + return { + "flex": "1", "minWidth": "0", "textAlign": "center", + "border": f"2px solid {BLUE}" if selected else "1px solid #e8e8e8", + "borderRadius": "6px", "padding": "8px 4px", "backgroundColor": bg, + "cursor": "pointer", "outline": "none", "fontFamily": "inherit", "color": "inherit", + } + + +def _month_box_children(monthly_net, monthly_posts, months_with_data, month): + import calendar as calmod + has_data = month in months_with_data + net = monthly_net.get(month) + post_count = monthly_posts.get(month, 0) + net_label = "—" if net is None else (f"+{net}" if net > 0 else str(net)) + return [ + html.Div(calmod.month_abbr[month], style={"fontWeight": "700", "fontSize": "13px"}), + html.Div(net_label, style={"fontWeight": "700", "fontSize": "16px", "marginTop": "4px", + "color": "#333" if has_data else "#bbb"}), + html.Div("followers", style={"fontSize": "9px", "color": "#888"}), + html.Div(f"{post_count} post{'s' if post_count != 1 else ''}", + style={"fontSize": "10px", "color": "#666", "marginTop": "3px"}), + ] + + +def _month_day_tiles(fts_df, posts_df, year, month): + """One tile per active day in `month` — weekday/day, running total, delta, and a + star + content type for days a post went out. No post titles, just date + action.""" + import calendar as calmod + + if month is None: + return html.Div("No activity yet.", className="text-muted", style={"padding": "8px 0", "fontSize": "0.8rem"}) + + month_fts = fts_df[(fts_df["Date"].dt.year == year) & (fts_df["Date"].dt.month == month)] + gains = {d: int(v) for d, v in zip(month_fts["Date"].dt.day, month_fts["Total followers"])} + totals = {d: int(v) for d, v in zip(month_fts["Date"].dt.day, month_fts["Cumulative Followers"])} + + post_types_by_day = {} + if not posts_df.empty: + pmask = (posts_df["Created date"].dt.year == year) & (posts_df["Created date"].dt.month == month) + for d, ct in zip(posts_df.loc[pmask, "Created date"].dt.day, posts_df.loc[pmask, "Content Type"]): + post_types_by_day.setdefault(d, []).append(ct) + post_days = set(post_types_by_day) + + active_days = {d for d, v in gains.items() if v != 0} | post_days + if not active_days: + return html.Div(f"No activity in {calmod.month_name[month]}.", className="text-muted", + style={"padding": "8px 0", "fontSize": "0.8rem"}) + + max_gain = max((v for v in gains.values() if v > 0), default=0) + + def cell_color(v): + if v is None: + return "#f0f4f8" + if v < 0: + return "#FFCDD2" + intensity = 0.15 + 0.55 * min(v / max(max_gain, 1), 1.0) + return f"rgba(82,196,26,{intensity:.2f})" + + tiles = [] + for day in sorted(active_days): + v = gains.get(day) + total = totals.get(day) + starred = day in post_days + types = post_types_by_day.get(day, []) + type_label = ", ".join(sorted(set(types))) if types else None + weekday_abbr = calmod.day_abbr[calmod.weekday(year, month, day)] + if v is None: + delta_label = "" + elif v > 0: + delta_label = f"+{v}" + elif v < 0: + delta_label = str(v) + else: + delta_label = "" + tiles.append(html.Div([ + html.Div(f"{weekday_abbr} {day}", style={"fontSize": "10px", "color": "#999"}), + html.Div(str(total) if total is not None else "—", style={"fontWeight": "700", "fontSize": "15px"}), + html.Div(delta_label, style={"fontSize": "10px", "color": "#666"}), + html.Div([ + html.Span("★ ", style={"color": "#E53935"}), + html.Span(type_label, style={"color": FORMAT_COLORS.get(type_label, "#666")}), + ], style={"fontSize": "9px", "fontWeight": "600", "marginTop": "2px"}) if starred else None, + ], style={ + "minWidth": "72px", "minHeight": "66px", "border": "1px solid #e8e8e8", "borderRadius": "4px", + "padding": "4px", "backgroundColor": cell_color(v), "position": "relative", "flex": "0 0 auto", + })) + return html.Div(tiles, style={"display": "flex", "gap": "4px", "flexWrap": "wrap"}) + + +def followers_calendar(fts_df, posts_df, id_prefix, height="320px"): + """Whole-year overview: one clickable box per month (Jan-Dec), each showing net + follower change and post count. Clicking a month reveals its day tiles underneath. + A year dropdown sits above — only one year exists today, but this keeps the + calendar ready for whenever a second year of export data shows up.""" + if fts_df.empty: + return html.Div("No follower data.", className="text-muted") + + available_years = _available_years(fts_df, posts_df) + year = available_years[0] + monthly_net, monthly_posts, months_with_data, max_gain = _monthly_summary(fts_df, posts_df, year) + default_month = _default_active_month(fts_df, posts_df, year) + + boxes = [] + for m in range(1, 13): + boxes.append(html.Button( + _month_box_children(monthly_net, monthly_posts, months_with_data, m), + id=f"{id_prefix}-month-btn-{m}", n_clicks=0, + style=_month_box_style(monthly_net, months_with_data, max_gain, m, selected=(m == default_month)))) + + return html.Div([ + dcc.Dropdown( + id=f"{id_prefix}-year-select", + options=[{"label": str(y), "value": y} for y in available_years], + value=year, clearable=False, searchable=False, + className="compact-dropdown", + style={"width": "90px", "marginBottom": "4px", "fontWeight": "800", "fontSize": "0.85rem"}, + ), + html.Div(boxes, style={"display": "flex", "gap": "6px"}), + html.Div(_month_day_tiles(fts_df, posts_df, year, default_month), id=f"{id_prefix}-posts-row", + style={"flex": "1", "overflowY": "auto", "marginTop": "8px"}), + calendar_legend(), + ], style={"height": height, "display": "flex", "flexDirection": "column"}) + + +def daily_followers_figure(fts_df, posts_df, year=None, month=None): + """The 'New Followers per Day' bar chart, optionally scoped to one calendar month + so it can track whichever month box is selected in the calendar next to it.""" + df, pdf = fts_df, posts_df + if year is not None: + mask = df["Date"].dt.year == year + if month is not None: + mask &= df["Date"].dt.month == month + df = df[mask] + if not pdf.empty: + pmask = pdf["Created date"].dt.year == year + if month is not None: + pmask &= pdf["Created date"].dt.month == month + pdf = pdf[pmask] + fig = px.bar(df, x="Date", y=["Organic followers", "Auto-invited followers"], + template=TMPL, barmode="stack", + color_discrete_sequence=[BLUE, AMBER], + labels={"value": "Followers", "variable": ""}) + return _std(mark_posts(fig, df, pdf)) + + +def _legend_swatch(color, label, shape="square"): + swatch_style = {"width": "12px", "height": "12px", "backgroundColor": color, "display": "inline-block", + "marginRight": "5px", "verticalAlign": "middle"} + if shape == "square": + swatch_style["borderRadius"] = "2px" + return html.Span([ + html.Span(style=swatch_style) if shape != "text" else html.Span(color, style={"marginRight": "5px", "color": color, "fontWeight": "700"}), + html.Span(label, style={"fontSize": "0.72rem", "color": "#6b7a8d"}), + ], style={"marginRight": "16px", "whiteSpace": "nowrap"}) + +def calendar_legend(): + return html.Div([ + _legend_swatch("rgba(82,196,26,0.7)", "Follower gain"), + _legend_swatch("#FFCDD2", "Follower loss"), + ], style={"marginTop": "10px", "display": "flex", "flexWrap": "wrap", "alignItems": "center"}) + + +# ── Overview metric explainers ───────────────────────────────────────────────── +SPONSORED_NOTE = html.Div("Sponsored covers paid/boosted promotion — currently flat at 0 since no ads are " + "running, but will populate automatically once they start.", + style={"marginTop": "8px", "fontStyle": "italic", "color": "#8a97a8"}) + +IMPRESSIONS_EXPLAINER = html.Div([ + html.Div("How many times your posts were displayed in someone's feed — whether or not " + "they engaged with it.", style={"marginBottom": "8px"}), + html.Div("Reflects reach and algorithmic distribution more than content quality. A high count " + "doesn't guarantee engagement.", style={"color": "#6b7a8d"}), + SPONSORED_NOTE, +]) + +ENGAGEMENT_EXPLAINER = html.Div([ + html.Div("The percentage of impressions that resulted in a like, comment, repost, or click.", + style={"marginBottom": "8px"}), + html.Div("This is the metric LinkedIn's algorithm weighs most heavily in the first hour after a " + "post goes out, when deciding how far to keep distributing it.", style={"color": "#6b7a8d"}), + SPONSORED_NOTE, +]) + +CTR_EXPLAINER = html.Div([ + html.Div("The percentage of impressions that led to an off-platform click.", style={"marginBottom": "8px"}), + html.Div("Matters most when the goal is driving traffic to a website rather than pure reach. LinkedIn " + "doesn't reward outbound clicks the way it rewards on-platform engagement, so a high-CTR day " + "can still mean lower reach.", style={"color": "#6b7a8d"}), + SPONSORED_NOTE, +]) + +PAGE_VIEWS_EXPLAINER = html.Div([ + html.Div("Visits to your LinkedIn Page, split between desktop and mobile.", style={"marginBottom": "8px"}), + html.Div("Counts page views regardless of whether the visitor is logged in or already follows you.", + style={"color": "#6b7a8d"}), +]) + + +def _filtered_by_month(df, year, month, date_col="Date"): + mask = df[date_col].dt.year == year + if month is not None: + mask &= df[date_col].dt.month == month + return df[mask] + + +def impressions_figure(cts_df): + renamed = cts_df[["Date", "Impressions (organic)", "Impressions (sponsored)"]].rename( + columns={"Impressions (organic)": "Organic", "Impressions (sponsored)": "Sponsored"}) + return _std(px.line(renamed, x="Date", y=["Organic", "Sponsored"], + template=TMPL, color_discrete_sequence=[BLUE, AMBER], + labels={"value": "Impressions", "variable": ""}, + )) + + +def engagement_figure(cts_df): + renamed = cts_df[["Date", "Engagement rate (organic)", "Engagement rate (sponsored)"]].rename( + columns={"Engagement rate (organic)": "Organic", "Engagement rate (sponsored)": "Sponsored"}) + return _std(px.line(renamed, x="Date", y=["Organic", "Sponsored"], + template=TMPL, color_discrete_sequence=[GREEN, AMBER], + labels={"value": "Eng. Rate", "variable": ""}, + )) + + +def ctr_figure(cts_df): + renamed = cts_df[["Date", "CTR (organic) %", "CTR (sponsored) %"]].rename( + columns={"CTR (organic) %": "Organic", "CTR (sponsored) %": "Sponsored"}) + return _std(px.line(renamed, x="Date", y=["Organic", "Sponsored"], + template=TMPL, color_discrete_sequence=[ORANGE, AMBER], + labels={"value": "CTR (%)", "variable": ""}, + )) + + +def page_views_figure(vts_df): + renamed = vts_df[["Date", "Total page views (desktop)", "Total page views (mobile)"]].rename( + columns={"Total page views (desktop)": "Desktop", "Total page views (mobile)": "Mobile"}) + return _std(px.area(renamed, x="Date", y=["Desktop", "Mobile"], + template=TMPL, color_discrete_sequence=[BLUE, ORANGE], + labels={"value": "Views", "variable": ""}, + )) + + +# ── Build tabs ──────────────────────────────────────────────────────────────── + +# ───── OVERVIEW ─────────────────────────────────────────────────────────────── + + +def _growth_status_label(snapshot): + if snapshot["on_track"] is None: + return "Building History" + return "On Track" if snapshot["on_track"] else "Behind" + + +def _growth_status_color(snapshot): + if snapshot["on_track"] is None: + return "#6b7a8d" + return GREEN if snapshot["on_track"] else "#d43f3a" + + +def _last_week_comparison(history): + """(last_week_entry, prior_week_entry, delta_or_None) for the sidebar's week-over-week + comparison. delta is last week's rate minus the prior week's; None until there are two + full completed weeks to compare.""" + last_week, prior_week = growth.last_two_completed_weeks(history) + delta = None + if last_week is not None and prior_week is not None: + delta = round(last_week["week_to_date_pct"] - prior_week["week_to_date_pct"], 2) + return last_week, prior_week, delta + + +def _sidebar_last_week_text(history): + last_week, _, _ = _last_week_comparison(history) + return f"{last_week['week_to_date_pct']:.2f}%" if last_week is not None else "—" + + +def _sidebar_delta_text(history): + _, _, delta = _last_week_comparison(history) + if delta is None: + return "" + arrow = "▲" if delta >= 0 else "▼" + return f"{arrow} {abs(delta):.2f}pts" + + +def _sidebar_delta_style(history): + _, _, delta = _last_week_comparison(history) + color = "#5a6b7d" if delta is None else (GREEN if delta >= 0 else "#d43f3a") + return {"fontSize": "0.75rem", "fontWeight": "700", "color": color} + + +def _sidebar_note_text(history): + last_week, prior_week, _ = _last_week_comparison(history) + if last_week is None: + return "Building history" + if prior_week is None: + return "Building history, need one more completed week" + return f"(goal: {prior_week['week_to_date_pct']:.2f}%)" + + +GROWTH_OBJECTIVE_EXPLAINER = html.Div([ + html.Div([ + "YOUR TARGET: ", html.B(f"beat your best week from the last {growth.ROLLING_BEST_WEEKS} weeks."), + ], style={"marginBottom": "8px"}), + html.Div( + f"Whatever your strongest week was in that last {growth.ROLLING_BEST_WEEKS}-week stretch is " + "the number to beat, so topping it becomes the new target, but a lucky one-off week doesn't " + f"haunt you forever, it stops counting once it drops out of the last {growth.ROLLING_BEST_WEEKS} " + "weeks. Either way, you're always racing your recent self, not an old record.", + style={"color": "#6b7a8d"}), +]) + + +def _week_segments(history): + """(start, end) date pairs for each real Monday-Sunday calendar week spanning the + history's date range — not a 7-day block anchored to whatever day-of-week the data + happens to start on (which produced Tuesday-Monday "weeks" here, since the tracked + history starts on a Tuesday). The first segment starts at the earliest available date + (which may be mid-week, so it's a short first week ending that Sunday); the final + segment is capped at the true last date even when shorter than 7 days, so the most + recent entry always gets its own segment rather than being absorbed into the previous + one.""" + dates = pd.to_datetime([h["date"] for h in history]) + start, end = dates.min(), dates.max() + segments = [] + seg_start = start + while seg_start <= end: + week_start = seg_start - pd.Timedelta(days=seg_start.weekday()) + week_end = week_start + pd.Timedelta(days=6) + seg_end = min(week_end, end) + segments.append((seg_start, seg_end)) + seg_start = week_end + pd.Timedelta(days=1) + return segments + + +def _add_week_dividers(fig, history): + """Solid gray vertical line between weeks, so the weekly growth-rate charts show where + one week ends and the next begins. Positioned at the midpoint between the last day of + one week and the first day of the next, not exactly on either day's own position, so + it reads as a boundary between days rather than passing through one.""" + if not history: + return fig + for seg_start, _ in _week_segments(history)[1:]: + divider_x = seg_start - pd.Timedelta(hours=12) + fig.add_shape(type="line", xref="x", yref="paper", x0=divider_x, x1=divider_x, y0=0, y1=1, + line=dict(color="#888888", width=1)) + return fig + + +def _week_range_ticks(history): + """Tick label for each 7-day block, centered under that block and formatted as a + compact day range (e.g. 'Jun 16-22') instead of Plotly's default single-date ticks, + which don't line up with the week divider lines.""" + tickvals, ticktext = [], [] + for seg_start, seg_end in _week_segments(history): + midpoint = seg_start + (seg_end - seg_start) / 2 + if seg_start.date() == seg_end.date(): + label = seg_start.strftime("%b %d") + elif seg_start.month == seg_end.month: + label = f"{seg_start.strftime('%b')} {seg_start.day:02d}-{seg_end.day:02d}" + else: + label = f"{seg_start.strftime('%b %d')}-{seg_end.strftime('%b %d')}" + tickvals.append(midpoint) + ticktext.append(label) + return tickvals, ticktext + + +def growth_trend_figure(history): + df = pd.DataFrame(history) + df["date"] = pd.to_datetime(df["date"]) + fig = px.line(df, x="date", y="total_followers", template=TMPL, markers=True) + fig.update_traces(line=dict(color=BLUE, width=2.5), marker=dict(color=BLUE, size=7), + hovertemplate="%{x|%b %d, %Y}
%{y:,} followers") + tickvals, ticktext = _week_range_ticks(history) + fig.update_layout(xaxis_title=None, yaxis_title="Total followers", + xaxis=dict(tickmode="array", tickvals=tickvals, ticktext=ticktext)) + return _add_week_dividers(fig, history) + + +def growth_pace_figure(history): + df = pd.DataFrame(history) + df["date"] = pd.to_datetime(df["date"]) + bar_colors = ["#6b7a8d" if t is None else (GREEN if t else "#d43f3a") for t in df["on_track"]] + fig = go.Figure() + fig.add_trace(go.Bar(x=df["date"], y=df["week_to_date_pct"], name="Week-to-date growth", + marker_color=bar_colors, + hovertemplate="%{x|%b %d, %Y}
%{y:.2f}%")) + fig.add_trace(go.Scatter(x=df["date"], y=df["rolling_best_weekly_pct"], name="Target (rolling best)", + mode="lines+markers", line=dict(color="#6b7a8d", dash="dash"), + hovertemplate="%{x|%b %d, %Y}
%{y:.2f}%")) + tickvals, ticktext = _week_range_ticks(history) + fig.update_layout(template=TMPL, xaxis_title=None, yaxis_title="Weekly growth %", + xaxis=dict(tickmode="array", tickvals=tickvals, ticktext=ticktext)) + return _add_week_dividers(fig, history) + + +def _daily_breakdown_table(history, seg_start, seg_end): + """Day-by-day rows for one week's date range — the detail revealed when that week's + accordion item is expanded.""" + header = html.Tr([html.Th(h, style={"textAlign": "left" if h == "Date" else "center", + "padding": "6px 8px", "color": "#6b7a8d", + "fontSize": "0.72rem", "fontWeight": "700"}) + for h in ["Date", "Total Followers", "Trailing 7 Days", "Since Last Update", "Pace"]]) + rows = [] + for entry in reversed(history): + d = pd.Timestamp(entry["date"]) + if not (seg_start <= d <= seg_end): + continue + weekly = f"{entry['weekly_pct']:+.2f}%" if entry["weekly_pct"] is not None else "—" + since_last = f"{entry['since_last_pct']:+.2f}%" if entry["since_last_pct"] is not None else "—" + badge = html.Span(_growth_status_label(entry), style={ + "backgroundColor": _growth_status_color(entry), "color": "white", "fontSize": "0.64rem", + "fontWeight": "700", "padding": "1px 7px", "borderRadius": "10px", + }) + rows.append(html.Tr([ + html.Td(d.strftime("%b %d, %Y"), style={"padding": "6px 8px", "borderBottom": "1px solid #eef1f5"}), + html.Td(f"{entry['total_followers']:,}", + style={"padding": "6px 8px", "textAlign": "center", "borderBottom": "1px solid #eef1f5"}), + html.Td(weekly, style={"padding": "6px 8px", "textAlign": "center", "borderBottom": "1px solid #eef1f5"}), + html.Td(since_last, style={"padding": "6px 8px", "textAlign": "center", "borderBottom": "1px solid #eef1f5"}), + html.Td(badge, style={"padding": "6px 8px", "textAlign": "center", "borderBottom": "1px solid #eef1f5"}), + ])) + return html.Table([html.Thead(header), html.Tbody(rows)], + style={"width": "100%", "borderCollapse": "collapse", "fontSize": "0.8rem"}) + + +def growth_history_table(history): + """Grouped by month, then by week — each week is a collapsed accordion item showing + its trailing-7-day summary; clicking it reveals that week's daily breakdown.""" + entries_by_date = {e["date"]: e for e in history} + weeks = [] + for seg_start, seg_end in _week_segments(history): + entry = entries_by_date.get(seg_end.strftime("%Y-%m-%d")) + if entry is None: + continue + label = (f"{seg_start.strftime('%b')} {seg_start.day:02d}-{seg_end.day:02d}" + if seg_start.month == seg_end.month + else f"{seg_start.strftime('%b %d')}-{seg_end.strftime('%b %d')}") + weeks.append((seg_end.strftime("%B %Y"), label, seg_start, seg_end, entry)) + + months = [] + current_month, current_items = None, [] + for month_label, week_label, seg_start, seg_end, entry in reversed(weeks): + if month_label != current_month: + if current_items: + months.append((current_month, current_items)) + current_month, current_items = month_label, [] + weekly = f"{entry['week_to_date_pct']:+.2f}%" if entry["week_to_date_pct"] is not None else "—" + title = f"{week_label} | {entry['total_followers']:,} followers | {weekly} | {_growth_status_label(entry)}" + current_items.append(dbc.AccordionItem( + _daily_breakdown_table(history, seg_start, seg_end), + title=title, item_id=f"week-{seg_end.strftime('%Y-%m-%d')}", + )) + if current_items: + months.append((current_month, current_items)) + + month_items = [ + dbc.AccordionItem( + dbc.Accordion(items, start_collapsed=True, flush=True), + title=month_label, item_id=f"month-{month_label}", + ) + for month_label, items in months + ] + return dbc.Accordion(month_items, start_collapsed=True, flush=True) + + +def build_growth_tab(): + history = growth.load_history(UPLOAD_DIR) + latest = history[-1] + return dbc.Container([ + html.Div([ + html.Div(kpi("Starting Followers", f"{BASELINE_FOLLOWERS:,}", + f"as of {pd.Timestamp(BASELINE_DATE).strftime('%b %d, %Y')}", "#999"), + style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("Total Followers", f"{latest['total_followers']:,}", + f"as of {pd.Timestamp(latest['date']).strftime('%b %d, %Y')}"), + style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("This Week", f"{latest['week_to_date_pct']:+.2f}%" if latest["week_to_date_pct"] is not None else "—", + "growth so far this week", color=GREEN if (latest["week_to_date_pct"] or 0) >= 0 else "#d43f3a"), + style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("Since Last Update", f"{latest['since_last_pct']:+.2f}%" if latest["since_last_pct"] is not None else "—", + "vs previous recorded snapshot", + color=GREEN if (latest["since_last_pct"] or 0) >= 0 else "#d43f3a"), + style={"flex": "1", "minWidth": "0"}), + html.Div(kpi("Rolling Best", f"{latest['rolling_best_weekly_pct']:+.2f}%" if latest["rolling_best_weekly_pct"] is not None else "—", + _growth_status_label(latest), color=_growth_status_color(latest)), + style={"flex": "1", "minWidth": "0"}), + ], style={"display": "flex", "gap": "0.75rem", "marginBottom": "1rem"}), + dbc.Row([ + dbc.Col(dbc.Card([ + dbc.CardHeader("Total Followers Over Time"), + dbc.CardBody(G(growth_trend_figure(history), style={"height": "260px"})), + ], className="mb-3 shadow-sm"), md=6), + dbc.Col(dbc.Card([ + dbc.CardHeader([html.Strong("Rolling Best"), + info_popover("growth-pace", GROWTH_OBJECTIVE_EXPLAINER)]), + dbc.CardBody(G(growth_pace_figure(history), style={"height": "260px"})), + ], className="mb-3 shadow-sm"), md=6), + ]), + dbc.Row([ + dbc.Col(dbc.Card([ + dbc.CardHeader("History Log"), + dbc.CardBody(growth_history_table(history), id="growth-history-table-body"), + ], className="mb-3 shadow-sm"), md=12), + ]), + ], fluid=True, className="pb-3") + + +refresh_data() + + +# ── App layout ──────────────────────────────────────────────────────────────── +app = Dash( + __name__, + external_stylesheets=[dbc.themes.FLATLY], + title="TXproducts LinkedIn Analytics", +) + +NAV_ITEMS = [ + ("overview", "Overview", overview), + ("content", "Content", content_tab), + ("followers", "Followers", followers_tab), + ("visitors", "Visitors", visitors_tab), + ("competitors", "Competitors", competitors_tab), + ("growth", "Growth", growth_tab), +] +TAB_CONTENT_MAP = {key: content for key, _, content in NAV_ITEMS} + +TAB_HERO = { + "overview": ("Overview", "Follower growth, content performance, and visitor traffic for the TXproducts LinkedIn Page."), + "content": ("Content", "Every post's impressions, clicks, engagement rate, and CTR — sortable and searchable."), + "followers": ("Followers", "Daily follower gains and losses, with the exact running total for every active day."), + "visitors": ("Visitors", "Page views and unique visitors to the TXproducts LinkedIn Page, broken down by device and section."), + "competitors": ("Competitors", "Benchmarking TXproducts against tracked competitor pages on LinkedIn."), + "growth": ("Growth", "Historical follower growth, tracked weekly against your own rolling best."), +} + +_SIDEBAR_GROWTH_HISTORY = growth.load_history(UPLOAD_DIR) + +# Sidebar — vertical logo, LinkedIn+Analytics brand mark, description, vertical nav, footer +sidebar = html.Div([ + html.Img(src="/assets/txp_logo_vertical.png", style={"maxWidth": "200px", "width": "100%", "display": "block", "margin": "0 auto 12px"}), + + html.Div([ + html.Img(src="/assets/linkedin_logo.svg", style={"height": "30px", "marginBottom": "4px"}), + html.Span("Analytics", style={"fontSize": "1.5rem", "fontWeight": "800", "color": BLUE}), + ], style={"display": "flex", "flexDirection": "column", "alignItems": "center", "margin": "4px 0 10px"}), + html.Hr(style={"borderColor": "#e0e7ef", "margin": "6px 0"}), + + html.Div([ + html.Div("Data Last Updated", style={ + "fontSize": "0.68rem", "textTransform": "uppercase", "letterSpacing": "0.05em", + "textAlign": "center", "color": "rgba(255,255,255,0.85)", + }), + html.Div(D["last_updated"].strftime("%b %d, %Y"), id="sidebar-last-updated", style={ + "fontSize": "1.05rem", "fontWeight": "800", "textAlign": "center", "color": "white", + }), + ], style={ + "background": "linear-gradient(90deg, #1b3a5c 0%, #2d6a9f 100%)", + "borderRadius": "8px", "padding": "8px 10px", "marginBottom": "12px", + }), + + html.Div([ + html.Div("Growth", style={ + "fontSize": "0.72rem", "fontWeight": "800", "textTransform": "uppercase", + "letterSpacing": "0.05em", "textAlign": "center", "color": "#1b3a5c", "marginBottom": "2px", + }), + html.Div("Last Week vs. Prior Week", style={ + "fontSize": "0.68rem", "textTransform": "uppercase", "letterSpacing": "0.05em", + "textAlign": "center", "color": "#5a6b7d", + }), + html.Div([ + html.Span(_sidebar_last_week_text(_SIDEBAR_GROWTH_HISTORY), id="sidebar-growth-lastweek", + style={"fontSize": "1.3rem", "fontWeight": "800", "color": "#1b3a5c"}), + html.Span(_sidebar_delta_text(_SIDEBAR_GROWTH_HISTORY), id="sidebar-growth-delta", + style=_sidebar_delta_style(_SIDEBAR_GROWTH_HISTORY)), + ], style={"display": "flex", "alignItems": "baseline", "justifyContent": "center", "gap": "8px"}), + html.Div(_sidebar_note_text(_SIDEBAR_GROWTH_HISTORY), id="sidebar-growth-note", style={ + "fontSize": "0.68rem", "color": "#5a6b7d", "textAlign": "center", + }), + ], id="growth-indicator-btn", n_clicks=0, title="View full growth history", style={ + "backgroundColor": "#e8f0fe", + "borderRadius": "8px", "padding": "8px 10px", "marginBottom": "12px", "cursor": "pointer", + }), + + html.Div( + [html.Button(label, id=f"nav-{key}", n_clicks=0, + className="sidebar-nav-btn active" if key == "overview" else "sidebar-nav-btn") + for key, label, _ in NAV_ITEMS], + style={"display": "flex", "flexDirection": "column", "gap": "2px"}, + ), + html.Hr(style={"borderColor": "#e0e7ef", "margin": "6px 0"}), + + html.Div([ + html.Div("Upload New Data", style={ + "background": "linear-gradient(90deg, #1b3a5c 0%, #2d6a9f 100%)", + "color": "white", "fontWeight": "700", "fontSize": "0.78rem", "textAlign": "center", + "padding": "8px 10px", "borderRadius": "8px", "marginBottom": "6px", + }), + dcc.Upload( + id="data-upload", + children=html.Div("Drag files or a folder here, or click to browse"), + multiple=True, + style={ + "width": "100%", "padding": "10px 6px", "borderWidth": "1px", "borderStyle": "dashed", + "borderColor": "#c7d2e0", "borderRadius": "6px", "textAlign": "center", + "fontSize": "0.72rem", "color": "#6b7a8d", "cursor": "pointer", "backgroundColor": "white", + }, + ), + html.Div(id="upload-status", style={"fontSize": "0.72rem", "marginTop": "6px", "textAlign": "center"}), + ], style={"marginBottom": "4px"}), + + html.Div([ + html.Button("Generate PDF Report", id="generate-report-btn", n_clicks=0, + style={"width": "100%", "padding": "8px", + "background": "linear-gradient(90deg, #1b3a5c 0%, #2d6a9f 100%)", + "color": "white", "border": "none", "borderRadius": "8px", + "fontSize": "0.78rem", "fontWeight": "700", "textAlign": "center", + "cursor": "pointer", "marginBottom": "6px"}), + dcc.Dropdown( + id="report-timeframe-select", + options=report.report_month_options(D["fts"]), value="all", clearable=False, searchable=False, + className="compact-dropdown", + style={"fontSize": "0.8rem"}, + ), + dcc.Download(id="report-download"), + ], style={"marginBottom": "12px"}), + html.Hr(style={"borderColor": "#e0e7ef"}), + + html.Div("TXproducts · Internal Tool · 2026", style={ + "fontSize": "0.75rem", "color": "#aab", "marginTop": "auto", + }), +], style={ + "width": "240px", "minWidth": "240px", "padding": "20px 16px", + "borderRight": "1px solid #e0e7ef", "backgroundColor": "#ffffff", + "display": "flex", "flexDirection": "column", "minHeight": "100vh", +}) + +# Gradient hero banner (matches ONTLEX/txdata's .page-hero) — title/subtitle change with the active tab +hero = html.Div([ + html.Div(TAB_HERO["overview"][0], id="hero-title", + style={"color": "white", "fontSize": "1.45rem", "fontWeight": "800", "lineHeight": "1.1"}), + html.Div(TAB_HERO["overview"][1], id="hero-subtitle", + style={"color": "rgba(255,255,255,0.84)", "fontSize": "0.9rem", "fontWeight": "500", "marginTop": "4px"}), +], style={ + "background": "linear-gradient(90deg,#1b3a5c 0%,#2d6a9f 100%)", + "borderRadius": "8px", "padding": "14px 18px 13px", "marginTop": "4px", "marginBottom": "16px", +}) + +main_content = html.Div([ + hero, + html.Div(overview, id="tab-content-area"), +], style={"flex": "1", "minWidth": "0", "padding": "20px 24px"}) + +app.layout = html.Div([sidebar, main_content], style={"display": "flex", "alignItems": "stretch"}) + + +@app.callback( + [Output(f"nav-{key}", "className") for key, _, _ in NAV_ITEMS] + + [Output("tab-content-area", "children"), Output("hero-title", "children"), Output("hero-subtitle", "children")], + [Input(f"nav-{key}", "n_clicks") for key, _, _ in NAV_ITEMS] + + [Input("growth-indicator-btn", "n_clicks")], +) +def _switch_tab(*_): + triggered = ctx.triggered_id + if triggered == "growth-indicator-btn": + active = "growth" + else: + active = triggered.replace("nav-", "") if triggered else "overview" + classes = ["sidebar-nav-btn active" if key == active else "sidebar-nav-btn" for key, _, _ in NAV_ITEMS] + hero_title, hero_subtitle = TAB_HERO[active] + return classes + [TAB_CONTENT_MAP[active], hero_title, hero_subtitle] + + +FILE_TYPE_KEYWORDS = ["followers", "visitors", "competitor", "content"] + + +@app.callback( + Output("upload-status", "children"), + Output("sidebar-last-updated", "children", allow_duplicate=True), + Output("sidebar-growth-lastweek", "children", allow_duplicate=True), + Output("sidebar-growth-delta", "children", allow_duplicate=True), + Output("sidebar-growth-delta", "style", allow_duplicate=True), + Output("sidebar-growth-note", "children", allow_duplicate=True), + Output("report-timeframe-select", "options", allow_duplicate=True), + [Output(f"nav-{key}", "className", allow_duplicate=True) for key, _, _ in NAV_ITEMS], + Output("tab-content-area", "children", allow_duplicate=True), + Output("hero-title", "children", allow_duplicate=True), + Output("hero-subtitle", "children", allow_duplicate=True), + Input("data-upload", "contents"), + State("data-upload", "filename"), + prevent_initial_call=True, +) +def _handle_upload(contents_list, filename_list): + """Saves every uploaded file whose name matches a recognized LinkedIn export keyword + into UPLOAD_DIR (under its original filename — _find_latest_file/_find_all_files in + load() already do the keyword-based lookup, so no renaming is needed here), then + fully rebuilds the dashboard in place via refresh_data(). Files that don't match a + known keyword are reported back to the uploader as skipped rather than silently + ignored. Forces navigation to the Growth tab afterward so the uploader immediately + sees the new snapshot without needing to reload the page.""" + no_update_rest = [no_update] * (6 + len(NAV_ITEMS) + 3) + if not contents_list: + return [no_update] + no_update_rest + + saved, skipped = [], [] + for content, filename in zip(contents_list, filename_list): + matched = any(kw in filename.lower() for kw in FILE_TYPE_KEYWORDS) + if not matched or not filename.lower().endswith((".xls", ".xlsx")): + skipped.append(filename) + continue + _header, b64data = content.split(",", 1) + with open(os.path.join(UPLOAD_DIR, filename), "wb") as f: + f.write(base64.b64decode(b64data)) + saved.append(filename) + + if not saved: + status = html.Div( + f"No recognized LinkedIn export files found among {len(skipped)} uploaded " + f"({', '.join(skipped)}). Expected filenames containing " + f"'followers', 'visitors', 'competitor', or 'content'.", + style={"color": "#d43f3a"}) + return [status] + no_update_rest + + refresh_data() + + status_children = [html.Div(f"Saved {len(saved)} file(s): {', '.join(saved)}", style={"color": GREEN})] + if skipped: + status_children.append(html.Div( + f"Skipped {len(skipped)} unrecognized file(s): {', '.join(skipped)}", + style={"color": "#d43f3a", "marginTop": "4px"})) + + history = growth.load_history(UPLOAD_DIR) + nav_classes = ["sidebar-nav-btn active" if key == "growth" else "sidebar-nav-btn" for key, _, _ in NAV_ITEMS] + hero_title, hero_subtitle = TAB_HERO["growth"] + + return [ + status_children, + D["last_updated"].strftime("%b %d, %Y"), + _sidebar_last_week_text(history), + _sidebar_delta_text(history), + _sidebar_delta_style(history), + _sidebar_note_text(history), + report.report_month_options(D["fts"]), + *nav_classes, + TAB_CONTENT_MAP["growth"], + hero_title, + hero_subtitle, + ] + + +@app.callback( + Output("fb-expanded", "data", allow_duplicate=True), + Input("fb-expand-btn", "n_clicks"), + State("fb-expanded", "data"), + prevent_initial_call=True, +) +def _toggle_followers_expand(_, expanded): + return not expanded + + +@app.callback( + Output("followers-breakdown-area", "children"), + Output("fb-expand-btn", "children"), + Output("fb-expand-btn", "style"), + Input("fb-select", "value"), + Input("fb-expanded", "data"), +) +def _render_followers_breakdown(active, expanded): + label, fig, total_n = _breakdown_figure(active, expanded) + needs_toggle = total_n > 10 + btn_style = {"display": "inline-block" if needs_toggle else "none", + "marginLeft": "auto", "fontSize": "0.72rem", "border": "none", + "background": "none", "color": "white", "textDecoration": "underline", + "cursor": "pointer", "padding": "0"} + btn_label = "Show top 10" if expanded else f"Show all {total_n}" + return G(fig, style={"height": "240px"}), btn_label, btn_style + + +@app.callback( + Output("visitor-breakdown-area", "children"), + Input("vb-select", "value"), +) +def _render_visitor_breakdown(active): + label, fig = _visitor_breakdown_figure(active) + return G(fig, style={"height": "240px"}) + + +def _register_month_click_callback(id_prefix): + @app.callback( + [Output(f"{id_prefix}-month-btn-{m}", "children") for m in range(1, 13)] + + [Output(f"{id_prefix}-month-btn-{m}", "style") for m in range(1, 13)] + + [Output(f"{id_prefix}-posts-row", "children"), Output(f"{id_prefix}-daily-followers-graph", "figure")], + [Input(f"{id_prefix}-year-select", "value")] + + [Input(f"{id_prefix}-month-btn-{m}", "n_clicks") for m in range(1, 13)], + ) + def _update_month_selection(year, *_): + triggered = ctx.triggered_id + if triggered and triggered.startswith(f"{id_prefix}-month-btn-"): + month = int(triggered.rsplit("-", 1)[-1]) + else: + month = _default_active_month(D["fts"], D["posts"], year) + monthly_net, monthly_posts, months_with_data, max_gain = _monthly_summary(D["fts"], D["posts"], year) + children = [_month_box_children(monthly_net, monthly_posts, months_with_data, m) for m in range(1, 13)] + styles = [_month_box_style(monthly_net, months_with_data, max_gain, m, selected=(m == month)) + for m in range(1, 13)] + return children + styles + [_month_day_tiles(D["fts"], D["posts"], year, month), + daily_followers_figure(D["fts"], D["posts"], year, month)] + + +for _prefix in ("overview", "followers"): + _register_month_click_callback(_prefix) + + +@app.callback( + Output("overview-impressions-graph", "figure"), + Output("overview-engagement-graph", "figure"), + Output("overview-ctr-graph", "figure"), + Output("overview-pageviews-graph", "figure"), + Input("overview-year-select", "value"), + [Input(f"overview-month-btn-{m}", "n_clicks") for m in range(1, 13)], +) +def _sync_overview_metric_graphs(year, *_): + triggered = ctx.triggered_id + if triggered and triggered.startswith("overview-month-btn-"): + month = int(triggered.rsplit("-", 1)[-1]) + else: + month = _default_active_month(D["fts"], D["posts"], year) + cts = _filtered_by_month(D["cts"], year, month) + vts = _filtered_by_month(D["vts"], year, month) + return impressions_figure(cts), engagement_figure(cts), ctr_figure(cts), page_views_figure(vts) + + +@app.callback( + Output("posts-table", "data"), + Output("posts-table", "tooltip_data"), + Input("content-month-select", "value"), +) +def _filter_posts_table(selected_month): + records = _posts_table_records(selected_month) + tooltip_data = [{c: {"value": str(row[c]), "type": "markdown"} for c in post_table_cols} for row in records] + return records, tooltip_data + + +@app.callback( + Output("content-impressions-fig", "figure"), + Output("content-engagement-fig", "figure"), + Output("content-ctr-fig", "figure"), + Output("content-scatter-fig", "figure"), + Output("content-reactions-fig", "figure"), + Output("content-clicks-fig", "figure"), + Input("content-month-select", "value"), +) +def _sync_content_charts(selected_month): + posts_f = _content_posts_filtered(selected_month) + cts_f = _content_cts_filtered(selected_month) + return ( + _finalize_fig(impressions_by_format_figure(posts_f), margin=CONTENT_BAR_MARGIN), + _finalize_fig(engagement_by_format_figure(posts_f), margin=CONTENT_BAR_MARGIN), + _finalize_fig(ctr_by_format_figure(posts_f), margin=CONTENT_BAR_MARGIN), + _finalize_fig(impressions_vs_engagement_figure(posts_f), legend=CONTENT_SCATTER_LEGEND), + _finalize_fig(reactions_figure(cts_f)), + _finalize_fig(clicks_figure(cts_f)), + ) + + +@app.callback( + Output("report-download", "data"), + Input("generate-report-btn", "n_clicks"), + State("report-timeframe-select", "value"), + prevent_initial_call=True, +) +def _generate_report(_n_clicks, timeframe): + ctx_data = { + "D": D, "posts": posts, "comp_ordered": comp_ordered, + "current_total_followers": current_total_followers, + "daily_followers_figure": daily_followers_figure, + "std_fn": _std, "TMPL": TMPL, "FORMAT_COLORS": FORMAT_COLORS, + "growth_snapshot": GROWTH_SNAPSHOT, + } + pdf_bytes = report.build_report_pdf(ctx_data, timeframe) + label = next((o["label"] for o in report.report_month_options(D["fts"]) if o["value"] == timeframe), timeframe) + safe_label = label.replace(" ", "_") + filename = f"TXproducts_LinkedIn_Report_{safe_label}.pdf" + return dcc.send_bytes(lambda buf: buf.write(pdf_bytes), filename=filename) + + +if __name__ == "__main__": + app.run(debug=False, port=8051) diff --git a/assets/custom.css b/assets/custom.css new file mode 100644 index 0000000..e4e8863 --- /dev/null +++ b/assets/custom.css @@ -0,0 +1,113 @@ +/* TXproducts design language, matched to txdata_project/portal.py */ + +body { + background: #f7f9fc; + font-family: -apple-system, "Source Sans Pro", "Segoe UI", Helvetica, Arial, sans-serif; +} + +/* Section cards */ +.card { + border: 1px solid #e0e7ef !important; + border-radius: 8px !important; + box-shadow: 0 1px 3px rgba(27, 58, 92, 0.06) !important; +} + +.card-header { + background: linear-gradient(90deg, #1b3a5c 0%, #2d6a9f 100%) !important; + color: white !important; + border-radius: 7px 7px 0 0 !important; + border: none !important; + font-size: 0.85rem; + letter-spacing: 0.2px; + padding: 0.55rem 1rem !important; +} + +.card-body { + background: #ffffff; + border-radius: 0 0 7px 7px; +} + +hr { + border-color: #e0e7ef !important; +} + +/* Tabs */ +.nav-tabs { + border-bottom: 1px solid #e0e7ef !important; +} +.nav-tabs .nav-link { + color: #6b7a8d !important; + font-weight: 500; + border: none !important; + border-bottom: 2px solid transparent !important; +} +.nav-tabs .nav-link.active { + color: #1b3a5c !important; + font-weight: 700; + border-bottom: 2px solid #2d6a9f !important; + background: transparent !important; +} + +/* DataTable polish */ +.dash-table-container { + border-radius: 8px; + overflow: hidden; +} + +/* Compact year picker on the follower calendar */ +.compact-dropdown .Select-control { + height: 28px !important; + min-height: 28px !important; +} +.compact-dropdown .Select-value, .compact-dropdown .Select-placeholder, .compact-dropdown .Select-input { + line-height: 26px !important; + padding: 0 8px !important; +} +.compact-dropdown .Select-input { + height: 26px !important; +} +.compact-dropdown .Select-arrow-zone { + height: 26px !important; +} + +/* Growth tab history log accordion — match the dashboard's font instead of Bootstrap/FLATLY's default */ +.accordion-button { + font-family: inherit; + font-size: 0.82rem; + color: #1a2533; +} +.accordion-button:not(.collapsed) { + color: #1b3a5c; + background-color: #e8f0fe; + box-shadow: none; +} +.accordion-button:focus { + box-shadow: none; + border-color: #e0e7ef; +} + +/* Sidebar vertical nav */ +.sidebar-nav-btn { + display: block; + width: 100%; + text-align: left; + background: none; + border: none; + border-left: 3px solid transparent; + color: #6b7a8d; + font-weight: 500; + font-size: 0.92rem; + padding: 8px 10px; + border-radius: 4px; + cursor: pointer; +} +.sidebar-nav-btn:hover { + background: #f7f9fc; + color: #1b3a5c; +} +.sidebar-nav-btn.active { + background: #e8f0fe; + color: #1b3a5c; + font-weight: 700; + border-left: 3px solid #2d6a9f; +} diff --git a/assets/linkedin_logo.svg b/assets/linkedin_logo.svg new file mode 100644 index 0000000..d1ebedc --- /dev/null +++ b/assets/linkedin_logo.svg @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/assets/txp_logo_horizontal.png b/assets/txp_logo_horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..b09f894f3a549dee08a76f9ec45840a72161e304 GIT binary patch literal 86693 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 literal 0 HcmV?d00001 diff --git a/build_static_export.py b/build_static_export.py new file mode 100644 index 0000000..579c3e7 --- /dev/null +++ b/build_static_export.py @@ -0,0 +1,569 @@ +"""Builds a single self-contained HTML file snapshot of the live Dash dashboard, for +sharing with people who don't have the app running (e.g. via NextCloud/email). + +Reuses app.py's data loading and figure-builder functions directly. Converts the Dash +component trees (dbc.Card/Row/Col are just Bootstrap-class wrappers) into plain HTML, +so the same Bootstrap CSS renders them identically. Charts render via Plotly.js from +embedded JSON. A handful of interactive elements (calendar month click, breakdown +dropdowns, content month filter) have every possible state precomputed and swapped +client-side with vanilla JS — everything else is a static snapshot of today's data. +""" +import base64 +import html as html_escape_mod +import json +import mimetypes +import os +import re + +import app as A +from dash import html, dcc, dash_table +import dash_bootstrap_components as dbc + +CHARTS = {} # chart div id -> plotly figure json string +CHART_COUNTER = [0] +ASSETS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") +_ASSET_CACHE = {} + + +def esc(s): + return html_escape_mod.escape(str(s), quote=True) + + +def inline_asset(src): + """Rewrite /assets/foo.png into a base64 data: URI so the exported file is fully + self-contained (no separate image files needed alongside it).""" + if not src or not src.startswith("/assets/"): + return src + if src in _ASSET_CACHE: + return _ASSET_CACHE[src] + path = os.path.join(ASSETS_DIR, os.path.basename(src)) + if not os.path.isfile(path): + return src + mime = mimetypes.guess_type(path)[0] or "application/octet-stream" + with open(path, "rb") as f: + b64 = base64.b64encode(f.read()).decode("ascii") + uri = f"data:{mime};base64,{b64}" + _ASSET_CACHE[src] = uri + return uri + + +def style_dict_to_css(style): + if not style: + return "" + parts = [] + for k, v in style.items(): + # camelCase -> kebab-case + css_key = re.sub(r"([A-Z])", r"-\1", k).lower() + parts.append(f"{css_key}:{v}") + return ";".join(parts) + + +def conditional_row_bg(conditional, row, idx): + """Replays a DataTable's style_data_conditional row_index/filter_query rules for one + row — later rules override earlier ones on a match, same as Dash's own precedence — so + the best-engagement/best-CTR highlight still shows up here, not just in the live app. + Only supports the simple "{Col} = value" equality form filter_query uses in this + codebase, not the full Dash query mini-language. Caller applies the result per-, + not on the — Bootstrap 5 paints table backgrounds via a CSS variable applied + directly to each cell, which sits on top of and hides a tr-level background-color.""" + bg = None + for rule in conditional: + cond = rule.get("if", {}) + matched = False + if "row_index" in cond: + ri = cond["row_index"] + if ri == "odd" and idx % 2 == 1: + matched = True + elif ri == "even" and idx % 2 == 0: + matched = True + elif isinstance(ri, int) and idx == ri: + matched = True + elif "filter_query" in cond: + m = re.match(r"\{([^}]+)\}\s*=\s*(.+)", cond["filter_query"]) + if m: + col, val = m.group(1), m.group(2) + cell = row.get(col) + try: + matched = float(cell) == float(val) + except (TypeError, ValueError): + matched = str(cell) == val + if matched and "backgroundColor" in rule: + bg = rule["backgroundColor"] + return bg + + +def next_chart_id(): + CHART_COUNTER[0] += 1 + return f"chart-{CHART_COUNTER[0]}" + + +def register_chart(fig, style=None, comp_id=None): + cid = comp_id or next_chart_id() + CHARTS[cid] = json.loads(fig.to_json()) + height = "320px" + if style and "height" in style: + height = style["height"] + return f'

' + + +def render(comp): + """Recursively convert a Dash component (or list/str) into an HTML string.""" + if comp is None: + return "" + if isinstance(comp, (str, int, float)): + return esc(comp) + if isinstance(comp, (list, tuple)): + return "".join(render(c) for c in comp) + + cls = type(comp).__name__ + module = type(comp).__module__ + + # --- Plotly graphs --- + if isinstance(comp, dcc.Graph): + return register_chart(comp.figure, getattr(comp, "style", None), getattr(comp, "id", None)) + + # --- Dropdowns: render as {opts}') + return f'' + + # --- Stores/Download: no static equivalent needed (Download requires a live backend) --- + if isinstance(comp, (dcc.Store, dcc.Download)): + return "" + + # --- DataTable: render a plain HTML table from data/columns --- + if isinstance(comp, dash_table.DataTable): + cols = comp.columns + rows = comp.data + conditional = getattr(comp, "style_data_conditional", None) or [] + + thead = "".join(f"{esc(c['name'])}" for c in cols) + trs = [] + for i, r in enumerate(rows): + bg = conditional_row_bg(conditional, r, i) + td_style = f' style="background-color:{bg}"' if bg else "" + tds = "".join(f"{esc(r.get(c['id'], ''))}" for c in cols) + trs.append(f"{tds}") + cid = getattr(comp, "id", "") or next_chart_id() + return (f'
' + f"{thead}{''.join(trs)}
") + + # --- dbc components: map to their underlying Bootstrap markup --- + if module.startswith("dash_bootstrap_components"): + children_html = render(getattr(comp, "children", None)) + extra_class = getattr(comp, "className", "") or "" + style = style_dict_to_css(getattr(comp, "style", None)) + cid = getattr(comp, "id", "") + id_attr = f' id="{esc(cid)}"' if cid else "" + + if cls == "Container": + base = "container-fluid" if getattr(comp, "fluid", False) else "container" + return f'
{children_html}
' + if cls == "Row": + return f'
{children_html}
' + if cls == "Col": + md = getattr(comp, "md", None) + colcls = f"col-md-{md}" if md else "col" + return f'
{children_html}
' + if cls == "Card": + return f'
{children_html}
' + if cls == "CardHeader": + return f'
{children_html}
' + if cls == "CardBody": + return f'
{children_html}
' + if cls == "Alert": + color = getattr(comp, "color", "info") + return f'
{children_html}
' + if cls == "Popover": + target = getattr(comp, "target", "") + body_html = render(getattr(comp, "children", None)) + return f'' + if cls == "PopoverBody": + style = style_dict_to_css(getattr(comp, "style", None)) + return f'
{children_html}
' + if cls == "Accordion": + return f'
{children_html}
' + if cls == "AccordionItem": + title = getattr(comp, "title", "") or "" + return (f'
' + f'{esc(title)}' + f'
{children_html}
' + f'
') + # Fallback: just render children + return children_html + + # --- Plain html.* components --- + if module.startswith("dash.html") or module.startswith("dash_html_components"): + children_html = render(getattr(comp, "children", None)) + extra_class = getattr(comp, "className", "") or "" + style = style_dict_to_css(getattr(comp, "style", None)) + cid = getattr(comp, "id", "") + id_attr = f' id="{esc(cid)}"' if cid else "" + title = getattr(comp, "title", None) + title_attr = f' title="{esc(title)}"' if title else "" + class_attr = f' class="{esc(extra_class)}"' if extra_class else "" + + tag_map = { + "Div": "div", "Span": "span", "Strong": "strong", "B": "b", "P": "p", + "H3": "h3", "Hr": "hr", "Button": "button", + "Table": "table", "Thead": "thead", "Tbody": "tbody", "Tr": "tr", + "Th": "th", "Td": "td", + } + tag = tag_map.get(cls, "div") + + if cls == "Img": + src = inline_asset(getattr(comp, "src", "")) + return f'' + if cls == "Hr": + return f'
' + if cls == "Button": + if cid == "generate-report-btn": + disabled_style = style_dict_to_css({**{k: v for k, v in (getattr(comp, "style", None) or {}).items()}, + "opacity": "0.5", "cursor": "not-allowed"}) + return (f'') + onclick = f' onclick="window.__onClick && window.__onClick(this)"' + return f'' + if cid.startswith("info-"): + onclick = f' onclick="window.__onClick && window.__onClick(this)"' + return f'<{tag}{id_attr}{class_attr} style="{style}"{title_attr}{onclick}>{children_html}' + return f'<{tag}{id_attr}{class_attr} style="{style}"{title_attr}>{children_html}' + + # Unknown component type — best-effort render children + return render(getattr(comp, "children", None)) + + +def fig_json(fig): + return json.loads(fig.to_json()) + + +def precompute_calendar_states(): + """Every month's tile-row + daily graph (+ Overview's 4 metric charts), for both + calendar instances — this is what the month-box click swaps between.""" + states = {} + year = int(A.D["fts"]["Date"].dt.year.max()) + for prefix in ("overview", "followers"): + states[prefix] = {} + for m in range(1, 13): + entry = { + "postsRow": render(A._month_day_tiles(A.D["fts"], A.D["posts"], year, m)), + "dailyFig": fig_json(A.daily_followers_figure(A.D["fts"], A.D["posts"], year, m)), + } + if prefix == "overview": + cts_f = A._filtered_by_month(A.D["cts"], year, m) + vts_f = A._filtered_by_month(A.D["vts"], year, m) + entry["metrics"] = { + "overview-impressions-graph": fig_json(A.impressions_figure(cts_f)), + "overview-engagement-graph": fig_json(A.engagement_figure(cts_f)), + "overview-ctr-graph": fig_json(A.ctr_figure(cts_f)), + "overview-pageviews-graph": fig_json(A.page_views_figure(vts_f)), + } + states[prefix][str(m)] = entry + default_month = A._default_active_month(A.D["fts"], A.D["posts"], year) + return states, year, default_month + + +def precompute_fb_states(): + """Followers-By dropdown x Show-all-toggle states.""" + states = {} + for key, _label in A.FOLLOWER_BREAKDOWNS: + for expanded in (False, True): + _lbl, fig, total_n = A._breakdown_figure(key, expanded) + fig = A._std(fig) + needs_toggle = total_n > 10 + btn_label = "Show top 10" if expanded else f"Show all {total_n}" + states[f"{key}|{str(expanded).lower()}"] = { + "fig": fig_json(fig), + "btnLabel": btn_label, + "btnDisplay": "inline-block" if needs_toggle else "none", + } + return states + + +def precompute_vb_states(): + """Visitors-By dropdown states.""" + states = {} + for key, _label in A.VISITOR_BREAKDOWNS: + _lbl, fig = A._visitor_breakdown_figure(key) + states[key] = {"fig": fig_json(A._std(fig))} + return states + + +def precompute_content_states(): + """Content tab's month-filter dropdown states — 6 charts + the posts table.""" + states = {} + for opt in A._content_month_options(): + value = opt["value"] + posts_f = A._content_posts_filtered(value) + cts_f = A._content_cts_filtered(value) + records = A._posts_table_records(value) + # Same fixed rule set for every month, matching the live app: its month-filter + # callback only swaps `data`, never `style_data_conditional`, so the highlight + # always marks the all-time best post/CTR, not a per-month recompute. + row_htmls = [] + for i, r in enumerate(records): + bg = conditional_row_bg(A.post_highlight_styles, r, i) + td_style = f' style="background-color:{bg}"' if bg else "" + tds = "".join(f"{esc(r.get(c, ''))}" for c in A.post_table_cols) + row_htmls.append(f"{tds}") + rows_html = "".join(row_htmls) + states[value] = { + "content-impressions-fig": fig_json(A._finalize_fig( + A.impressions_by_format_figure(posts_f), margin=A.CONTENT_BAR_MARGIN)), + "content-engagement-fig": fig_json(A._finalize_fig( + A.engagement_by_format_figure(posts_f), margin=A.CONTENT_BAR_MARGIN)), + "content-ctr-fig": fig_json(A._finalize_fig( + A.ctr_by_format_figure(posts_f), margin=A.CONTENT_BAR_MARGIN)), + "content-scatter-fig": fig_json(A._finalize_fig( + A.impressions_vs_engagement_figure(posts_f), legend=A.CONTENT_SCATTER_LEGEND)), + "content-reactions-fig": fig_json(A._finalize_fig(A.reactions_figure(cts_f))), + "content-clicks-fig": fig_json(A._finalize_fig(A.clicks_figure(cts_f))), + "tableRows": rows_html, + } + return states + + +JS_TEMPLATE = r""" +const CHARTS = %(charts)s; +const TAB_HERO = %(tab_hero)s; +const CAL_STATES = %(cal_states)s; +const CAL_YEAR = %(cal_year)s; +const CAL_DEFAULT_MONTH = %(cal_default_month)s; +const FB_STATES = %(fb_states)s; +const VB_STATES = %(vb_states)s; +const CONTENT_STATES = %(content_states)s; + +const calMonth = {overview: CAL_DEFAULT_MONTH, followers: CAL_DEFAULT_MONTH}; +let fbActive = 'company_size', fbExpanded = false; +let vbActive = 'seniority'; + +function renderChart(divId, figSpec) { + if (!figSpec || !document.getElementById(divId)) return; + Plotly.react(divId, figSpec.data, figSpec.layout, {displayModeBar: false, responsive: true}); +} + +function renderChartWithin(containerId, figSpec) { + var container = document.getElementById(containerId); + if (!container || !figSpec) return; + var chartDiv = container.querySelector('.js-plotly-chart'); + if (!chartDiv) return; + Plotly.react(chartDiv.id, figSpec.data, figSpec.layout, {displayModeBar: false, responsive: true}); +} + +document.addEventListener('DOMContentLoaded', function () { + Object.keys(CHARTS).forEach(function (id) { + var spec = CHARTS[id]; + Plotly.newPlot(id, spec.data, spec.layout, {displayModeBar: false, responsive: true}); + }); +}); + +function switchTab(key) { + document.querySelectorAll('.app-tab').forEach(function (el) { el.style.display = 'none'; }); + var el = document.getElementById('tab-' + key); + if (el) el.style.display = 'block'; + document.querySelectorAll('[id^="nav-"]').forEach(function (btn) { + btn.classList.toggle('active', btn.id === 'nav-' + key); + }); + var hero = TAB_HERO[key]; + if (hero) { + document.getElementById('hero-title').textContent = hero[0]; + document.getElementById('hero-subtitle').textContent = hero[1]; + } + window.dispatchEvent(new Event('resize')); +} + +function selectCalendarMonth(prefix, month) { + calMonth[prefix] = month; + var state = CAL_STATES[prefix][String(month)]; + if (!state) return; + for (var m = 1; m <= 12; m++) { + var btn = document.getElementById(prefix + '-month-btn-' + m); + if (btn) btn.style.border = (m === month) ? '2px solid #1b3a5c' : '1px solid #e8e8e8'; + } + var row = document.getElementById(prefix + '-posts-row'); + if (row) row.innerHTML = state.postsRow; + renderChart(prefix + '-daily-followers-graph', state.dailyFig); + if (state.metrics) { + Object.keys(state.metrics).forEach(function (divId) { renderChart(divId, state.metrics[divId]); }); + } +} + +function renderFollowersBreakdown() { + var s = FB_STATES[fbActive + '|' + fbExpanded]; + if (!s) return; + renderChartWithin('followers-breakdown-area', s.fig); + var btn = document.getElementById('fb-expand-btn'); + if (btn) { + btn.textContent = s.btnLabel; + btn.style.display = s.btnDisplay; + } +} + +function renderVisitorBreakdown() { + var s = VB_STATES[vbActive]; + if (!s) return; + renderChartWithin('visitor-breakdown-area', s.fig); +} + +function renderContentState(value) { + var s = CONTENT_STATES[value]; + if (!s) return; + ['content-impressions-fig', 'content-engagement-fig', 'content-ctr-fig', + 'content-scatter-fig', 'content-reactions-fig', 'content-clicks-fig'].forEach(function (id) { + renderChart(id, s[id]); + }); + var tbody = document.querySelector('#posts-table tbody'); + if (tbody) tbody.innerHTML = s.tableRows; +} + +function closeAllPopovers(except) { + document.querySelectorAll('.static-popover').forEach(function (p) { + if (p !== except) p.style.display = 'none'; + }); +} + +window.__onClick = function (el) { + var id = el.id || ''; + var m; + if (id.indexOf('info-') === 0) { + var pop = document.querySelector('.static-popover[data-target="' + id + '"]'); + if (!pop) return; + var willOpen = pop.style.display === 'none' || !pop.style.display; + closeAllPopovers(); + if (willOpen) { + var rect = el.getBoundingClientRect(); + pop.style.position = 'fixed'; + pop.style.top = (rect.bottom + 6) + 'px'; + pop.style.left = Math.min(rect.left, window.innerWidth - 380) + 'px'; + pop.style.display = 'block'; + } + return; + } + if ((m = id.match(/^(overview|followers)-month-btn-(\d+)$/))) { + selectCalendarMonth(m[1], parseInt(m[2], 10)); + return; + } + if (id === 'fb-expand-btn') { + fbExpanded = !fbExpanded; + renderFollowersBreakdown(); + return; + } +}; + +window.__onSelect = function (el) { + if (el.id === 'fb-select') { fbActive = el.value; fbExpanded = false; renderFollowersBreakdown(); return; } + if (el.id === 'vb-select') { vbActive = el.value; renderVisitorBreakdown(); return; } + if (el.id === 'content-month-select') { renderContentState(el.value); return; } +}; + +document.addEventListener('click', function (ev) { + if (ev.target.id && ev.target.id.indexOf('info-') === 0) return; + if (ev.target.closest && ev.target.closest('.static-popover')) return; + closeAllPopovers(); +}); +""" + + +def build(): + tabs_html = {} + for key, _label, comp in A.NAV_ITEMS: + tabs_html[key] = render(comp) + + sidebar_html = render(A.sidebar) + hero_html = render(A.hero) + + cal_states, cal_year, cal_default_month = precompute_calendar_states() + fb_states = precompute_fb_states() + vb_states = precompute_vb_states() + content_states = precompute_content_states() + + with open(os.path.join(ASSETS_DIR, "custom.css")) as f: + custom_css = f.read() + + tab_divs = "\n".join( + f'
' + f'{tabs_html[key]}
' + for key, _label, _c in A.NAV_ITEMS + ) + + js = JS_TEMPLATE % { + "charts": json.dumps(CHARTS), + "tab_hero": json.dumps(A.TAB_HERO), + "cal_states": json.dumps(cal_states), + "cal_year": json.dumps(cal_year), + "cal_default_month": json.dumps(cal_default_month), + "fb_states": json.dumps(fb_states), + "vb_states": json.dumps(vb_states), + "content_states": json.dumps(content_states), + } + + html_doc = f""" + + + +TXproducts LinkedIn Analytics — Snapshot ({A.D['last_updated'].strftime('%b %d, %Y')}) + + + + + + +
Static snapshot as of {A.D['last_updated'].strftime('%B %d, %Y')} — dropdowns, month clicks, + and breakdown selectors work, but this file does not update with new data. Open the live tool for current numbers.
+
+{sidebar_html} +
+{hero_html} +
+{tab_divs} +
+
+
+ + + +""" + out_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "TXproducts_Dashboard_Snapshot.html") + with open(out_path, "w") as f: + f.write(html_doc) + print(f"Wrote {out_path} ({len(html_doc):,} bytes, {len(CHARTS)} charts)") + return out_path + + +if __name__ == "__main__": + build() diff --git a/growth.py b/growth.py new file mode 100644 index 0000000..c48ef70 --- /dev/null +++ b/growth.py @@ -0,0 +1,193 @@ +"""Tracks follower-growth history over time: an append-only-per-day log that powers the +sidebar Growth Statistics indicator, the Growth tab's historical table/chart, and the PDF +report's weekly-pace section. Persisted as JSON in UPLOAD_DIR so it survives restarts and +uploads on any team member's machine, not just the original author's. + +Pace is judged against a rolling personal best rather than a fixed percentage: the current +week's growth-to-date is compared against the best of the last ROLLING_BEST_WEEKS COMPLETED +weeks (never against earlier days within the current week itself). Weeks are real Monday- +Sunday calendar weeks, not a daily trailing window and not a 7-day block anchored to +whichever day-of-week the data happens to start on — an earlier version compared each day's +trailing-7-day rate against the best trailing rate from the past N weeks, which could +include days from later in the SAME displayed week, so a week could get flagged "Behind" due +to its own mid-week peak rather than genuinely falling short of prior weeks. This rises +automatically as performance improves, but a one-off spike ages out of the window after +ROLLING_BEST_WEEKS instead of setting an unreachable target forever. + +Zero import-time dependency on app.py, for the same reason report.py has none: app.py runs +as "__main__" (`python3 app.py`), so `import app` here would re-execute it as a second +module copy that's still mid-import when these functions get called. +""" +import datetime +import json +import os + +import pandas as pd + +ROLLING_BEST_WEEKS = 4 + + +def _history_path(upload_dir): + return os.path.join(upload_dir, "growth_history.json") + + +def load_history(upload_dir): + path = _history_path(upload_dir) + if not os.path.exists(path): + return [] + with open(path) as f: + return json.load(f) + + +def _save_history(upload_dir, history): + with open(_history_path(upload_dir), "w") as f: + json.dump(history, f, indent=2) + + +def _followers_as_of(fts_df, on_or_before): + """Cumulative Followers value from the most recent row on or before `on_or_before`; + falls back to the earliest available row if the whole series starts later.""" + prior = fts_df[fts_df["Date"] <= on_or_before] + if prior.empty: + return float(fts_df["Cumulative Followers"].iloc[0]) + return float(prior["Cumulative Followers"].iloc[-1]) + + +def _week_start_for(date): + """Monday of the standard calendar week `date` falls into — real Monday-Sunday weeks, + not a 7-day block anchored to whatever day-of-week the data happens to start on (which + produced Tuesday-Monday "weeks" here, since the tracked history starts on a Tuesday).""" + return date - pd.Timedelta(days=date.weekday()) + + +def compute_snapshot(fts_df, prior_history, week_boundary_date=None): + """Computes (but does not persist) a growth snapshot as of the last date in fts_df, + given the history entries recorded so far (used for since_last_pct and the rolling-best + pace target). Taking the prior history as a plain list — rather than reading it from + disk — lets backfill_history() replay this against progressively larger slices of + fts_df without hitting disk each time. + + `week_boundary_date` only affects which past weeks are eligible for the rolling-best + lookback below — it does NOT move week-to-date's own week, which always stays the + actual last date in fts_df (so a week with zero new data yet never gets falsely reset + to a hypothetical 0%). Defaults to this entry's own week, which reproduces the original + behavior of excluding the current week from its own target pool. backfill_history() + passes today's real date for the latest entry only, so a week that's actually finished + in real life (e.g. LinkedIn's export lagging a day behind on Mondays) immediately + becomes eligible as a rolling-best candidate, instead of waiting for next week's data + to start arriving before it's ever counted.""" + fts_df = fts_df.sort_values("Date") + today = fts_df["Date"].iloc[-1] + total_followers = float(fts_df["Cumulative Followers"].iloc[-1]) + + # Trailing 7-day rate: a smoother, purely informational "how's it going right now" + # figure. Not used for the pace judgment (see week_to_date_pct below) since a trailing + # window updates every day and doesn't respect week boundaries. + week_ago = today - pd.Timedelta(days=7) + followers_week_ago = _followers_as_of(fts_df, week_ago) + weekly_pct = ((total_followers - followers_week_ago) / followers_week_ago * 100 + if followers_week_ago else None) + weekly_pct = round(weekly_pct, 2) if weekly_pct is not None else None + + if prior_history: + prev_total = prior_history[-1]["total_followers"] + since_last_pct = (total_followers - prev_total) / prev_total * 100 if prev_total else None + else: + since_last_pct = None + + # Discrete week-over-week pace: growth since the start of THIS week (always the real + # last-data week — never moved by week_boundary_date), compared against the best of the + # last ROLLING_BEST_WEEKS COMPLETED weeks. + current_week_start = _week_start_for(today) + week_start_baseline = _followers_as_of(fts_df, current_week_start - pd.Timedelta(days=1)) + week_to_date_pct = ((total_followers - week_start_baseline) / week_start_baseline * 100 + if week_start_baseline else 0.0) + week_to_date_pct = round(week_to_date_pct, 2) + + # Which past weeks are eligible for the rolling-best pool: strictly before the lookback + # boundary, which defaults to this entry's own week (excluding it from its own target, + # since an ONGOING week can't fairly be judged against its own still-accumulating + # total) but can be pushed forward to the real current date for the live/latest entry — + # at that point this week is no longer "ongoing" in real life even if fresh data for it + # hasn't arrived, so it's fair game as a candidate for whatever comes next. + lookback_boundary_week_start = (_week_start_for(week_boundary_date) + if week_boundary_date is not None else current_week_start) + lookback_start = lookback_boundary_week_start - pd.Timedelta(weeks=ROLLING_BEST_WEEKS) + last_entry_per_week = {} + for h in prior_history: + h_week_start = h.get("week_start") + if h_week_start is None or not (lookback_start <= pd.Timestamp(h_week_start) < lookback_boundary_week_start): + continue + existing = last_entry_per_week.get(h_week_start) + if existing is None or h["date"] > existing["date"]: + last_entry_per_week[h_week_start] = h + completed_week_rates = [h["week_to_date_pct"] for h in last_entry_per_week.values()] + rolling_best_weekly_pct = round(max(completed_week_rates), 2) if completed_week_rates else None + on_track = (None if rolling_best_weekly_pct is None + else week_to_date_pct >= rolling_best_weekly_pct) + + return { + "date": today.strftime("%Y-%m-%d"), + "total_followers": int(total_followers), + "weekly_pct": weekly_pct, + "since_last_pct": round(since_last_pct, 2) if since_last_pct is not None else None, + "week_start": current_week_start.strftime("%Y-%m-%d"), + "week_to_date_pct": week_to_date_pct, + "rolling_best_weekly_pct": rolling_best_weekly_pct, + "rolling_best_weeks": ROLLING_BEST_WEEKS, + "on_track": on_track, + } + + +def last_two_completed_weeks(history): + """The most recent COMPLETED week's final entry, and the one before it — both full + Monday-Sunday weeks. Used for an apples-to-apples week-over-week comparison, as opposed + to comparing a full week against the current week, which is always partial until the + next Monday's upload. + + "Completed" is judged against the real calendar (today's actual date), not the last date + present in the export — LinkedIn's export routinely lags a day or two behind (e.g. it may + stop at Saturday), which previously made an already-finished week look "still in + progress" and made the sidebar fall back to showing the week before it.""" + if not history: + return None, None + real_today = pd.Timestamp(datetime.date.today()) + current_week_start = (real_today - pd.Timedelta(days=real_today.weekday())).strftime("%Y-%m-%d") + last_entry_per_week = {} + for h in history: + if h["week_start"] >= current_week_start: + continue + existing = last_entry_per_week.get(h["week_start"]) + if existing is None or h["date"] > existing["date"]: + last_entry_per_week[h["week_start"]] = h + completed_weeks = sorted(last_entry_per_week.values(), key=lambda h: h["week_start"], reverse=True) + last_week = completed_weeks[0] if len(completed_weeks) >= 1 else None + prior_week = completed_weeks[1] if len(completed_weeks) >= 2 else None + return last_week, prior_week + + +def backfill_history(fts_df, upload_dir): + """Ensures the growth-history log has one entry per day already present in fts_df — + backfills any days from before this feature existed (each computed using only the data + that would have been available up to that day, so backfilled entries are historically + accurate, not hindsight-adjusted), while always recomputing the MOST RECENT day fresh so + a same-day re-upload/correction updates that day's entry instead of reusing a stale one. + Returns the full merged history list, sorted chronologically.""" + existing = {e["date"]: e for e in load_history(upload_dir)} + fts_sorted = fts_df.sort_values("Date") + dates = list(fts_sorted["Date"]) + real_today = pd.Timestamp(datetime.date.today()) + history = [] + for i, d in enumerate(dates): + date_str = d.strftime("%Y-%m-%d") + is_latest = (i == len(dates) - 1) + if date_str in existing and not is_latest: + history.append(existing[date_str]) + continue + subset = fts_sorted[fts_sorted["Date"] <= d] + # Only the latest entry gets the real-calendar boundary — backfilled days stay + # reproducible, using just their own date as before. + boundary = max(d, real_today) if is_latest else None + history.append(compute_snapshot(subset, history, week_boundary_date=boundary)) + _save_history(upload_dir, history) + return history diff --git a/report.py b/report.py new file mode 100644 index 0000000..c0ba968 --- /dev/null +++ b/report.py @@ -0,0 +1,251 @@ +"""Builds a leadership-facing PDF summary of LinkedIn performance for a chosen +timeframe. Charts are rendered to PNG via kaleido and embedded as images (reportlab +has no native Plotly support). + +Deliberately has zero import-time dependency on app.py — app.py is run directly +(`python3 app.py`), so it's registered in sys.modules as "__main__", not "app". If this +module did `import app`, Python wouldn't recognize that as the same module and would +re-execute app.py from scratch as a second copy, which is still mid-import when this +module's functions get called — a circular-import AttributeError. Passing everything +in as arguments avoids that entirely. +""" +import io + +import pandas as pd +import plotly.express as px +from reportlab.lib import colors +from reportlab.lib.pagesizes import A4 +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.lib.units import cm +from reportlab.platypus import (SimpleDocTemplate, Table, TableStyle, Paragraph, + Spacer, Image, HRFlowable) + +NAVY = colors.HexColor("#1b3a5c") +LIGHT_GRAY = colors.HexColor("#f0f4f8") +TEXT_GRAY = colors.HexColor("#6b7a8d") + + +def report_month_options(fts_df): + """'All Time', then one entry per year, then one entry per (year, month) — lets the + report be scoped to as broad or as narrow a window as the export data supports.""" + import calendar as calmod + dates = fts_df["Date"] + years = sorted({d.year for d in dates}) + months = sorted({(d.year, d.month) for d in dates}) + options = [{"label": "All Time", "value": "all"}] + options += [{"label": str(y), "value": f"year-{y}"} for y in years] + options += [{"label": f"{calmod.month_name[m]} {y}", "value": f"{y}-{m:02d}"} for y, m in months] + return options + + +def _filtered(df, date_col, value): + if not value or value == "all": + return df + if value.startswith("year-"): + year = int(value.split("-", 1)[1]) + return df[df[date_col].dt.year == year] + year, month = (int(p) for p in value.split("-")) + return df[(df[date_col].dt.year == year) & (df[date_col].dt.month == month)] + + +def _period_label(value, fts_df): + if not value or value == "all": + return "All Time" + return next((o["label"] for o in report_month_options(fts_df) if o["value"] == value), value) + + +def _fig_to_image(fig, width_cm=17, height_px=300, width_px=900): + buf = io.BytesIO() + fig.write_image(buf, format="png", width=width_px, height=height_px, scale=2) + buf.seek(0) + aspect = height_px / width_px + return Image(buf, width=width_cm * cm, height=width_cm * cm * aspect) + + +def build_report_pdf(ctx, timeframe_value="all"): + """ctx is a dict bundling everything the report needs from the live app: + D, posts, comp_ordered, current_total_followers, daily_followers_figure (the + figure-builder function), std_fn (the _std styling helper), TMPL, FORMAT_COLORS.""" + D = ctx["D"] + posts = ctx["posts"] + comp_ordered = ctx["comp_ordered"] + current_total_followers = ctx["current_total_followers"] + daily_followers_figure = ctx["daily_followers_figure"] + std_fn = ctx["std_fn"] + tmpl = ctx["TMPL"] + format_colors = ctx["FORMAT_COLORS"] + growth_snapshot = ctx["growth_snapshot"] + + fts_f = _filtered(D["fts"], "Date", timeframe_value) + posts_f = _filtered(posts, "Created date", timeframe_value) + cts_f = _filtered(D["cts"], "Date", timeframe_value) + vts_f = _filtered(D["vts"], "Date", timeframe_value) + + new_followers = int(fts_f["Total followers"].sum()) if not fts_f.empty else 0 + total_impressions = int(cts_f["Impressions (total)"].sum()) if not cts_f.empty else 0 + total_page_views = int(vts_f["Total page views (total)"].sum()) if not vts_f.empty else 0 + posts_published = len(posts_f) + best_eng = float(posts_f["Eng %"].max()) if len(posts_f) else 0.0 + avg_eng_series = cts_f["Engagement rate (total)"].replace(0, float("nan")) if not cts_f.empty else pd.Series(dtype=float) + avg_eng = float(avg_eng_series.mean()) if not avg_eng_series.dropna().empty else 0.0 + + styles = getSampleStyleSheet() + title_style = ParagraphStyle("TXTitle", parent=styles["Title"], textColor=NAVY, fontSize=20, spaceAfter=2) + subtitle_style = ParagraphStyle("TXSubtitle", parent=styles["Normal"], textColor=TEXT_GRAY, fontSize=10, spaceAfter=16) + h2_style = ParagraphStyle("TXH2", parent=styles["Heading2"], textColor=NAVY, fontSize=13, spaceBefore=16, spaceAfter=8) + body_style = ParagraphStyle("TXBody", parent=styles["Normal"], fontSize=9, textColor=colors.HexColor("#333333")) + note_style = ParagraphStyle("TXNote", parent=styles["Normal"], fontSize=8, textColor=TEXT_GRAY, spaceBefore=4) + + period_label = _period_label(timeframe_value, D["fts"]) + + story = [] + story.append(Paragraph("TXproducts, LinkedIn Performance Report", title_style)) + story.append(Paragraph( + f"Period: {period_label}  |  Generated {pd.Timestamp.now().strftime('%B %d, %Y')} " + f" |  Data as of {D['last_updated'].strftime('%B %d, %Y')}", + subtitle_style)) + story.append(HRFlowable(width="100%", color=NAVY, thickness=1.2)) + + # --- KPI summary table --- + story.append(Paragraph("Key Metrics", h2_style)) + kpi_data = [ + ["Total Followers\n(as of today)", "New Followers", "Posts Published", "Total Impressions", + "Total Page Views", "Best Post Eng.", "Avg. Eng. Rate"], + [f"{current_total_followers:,}", f"{new_followers:,}", f"{posts_published:,}", + f"{total_impressions:,}", f"{total_page_views:,}", f"{best_eng:.1f}%", + f"{avg_eng:.2f}%" if avg_eng else "—"], + ] + kpi_table = Table(kpi_data, colWidths=[2.6 * cm] * 7) + kpi_table.setStyle(TableStyle([ + ("BACKGROUND", (0, 0), (-1, 0), LIGHT_GRAY), + ("TEXTCOLOR", (0, 0), (-1, 0), TEXT_GRAY), + ("FONTSIZE", (0, 0), (-1, 0), 7), + ("FONTSIZE", (0, 1), (-1, 1), 12), + ("FONTNAME", (0, 1), (-1, 1), "Helvetica-Bold"), + ("TEXTCOLOR", (0, 1), (-1, 1), NAVY), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#e0e7ef")), + ("TOPPADDING", (0, 0), (-1, -1), 6), + ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ])) + story.append(kpi_table) + + # --- Weekly growth pace --- + story.append(Paragraph("Weekly Growth Pace", h2_style)) + on_track = growth_snapshot["on_track"] + if on_track is None: + status_color, status_label = TEXT_GRAY, "Building History" + elif on_track: + status_color, status_label = colors.HexColor("#52C41A"), "On Track" + else: + status_color, status_label = colors.HexColor("#d43f3a"), "Behind" + weekly_pct = growth_snapshot["week_to_date_pct"] + target_pct = growth_snapshot["rolling_best_weekly_pct"] + objective_data = [ + ["This Week", f"Target (best of {growth_snapshot['rolling_best_weeks']} wks)", "Pace Status"], + [f"{weekly_pct:+.2f}%" if weekly_pct is not None else "—", + f"{target_pct:+.2f}%" if target_pct is not None else "—", + status_label], + ] + objective_table = Table(objective_data, colWidths=[5 * cm, 6 * cm, 4 * cm]) + objective_table.setStyle(TableStyle([ + ("BACKGROUND", (0, 0), (-1, 0), LIGHT_GRAY), + ("TEXTCOLOR", (0, 0), (-1, 0), TEXT_GRAY), + ("FONTSIZE", (0, 0), (-1, 0), 8), + ("FONTSIZE", (0, 1), (-1, 1), 11), + ("FONTNAME", (0, 1), (-1, 1), "Helvetica-Bold"), + ("TEXTCOLOR", (0, 1), (-1, 1), NAVY), + ("TEXTCOLOR", (2, 1), (2, 1), status_color), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#e0e7ef")), + ("TOPPADDING", (0, 0), (-1, -1), 6), + ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ])) + story.append(objective_table) + story.append(Paragraph( + f"Target is a rolling personal best, the highest trailing-7-day growth rate seen in " + f"the last {growth_snapshot['rolling_best_weeks']} weeks, not a fixed percentage. It " + f"rises automatically as performance improves, but a one-off spike ages out of the " + f"window after {growth_snapshot['rolling_best_weeks']} weeks rather than setting an " + f"unreachable target permanently.", + note_style)) + + # --- Follower growth chart --- + if not fts_f.empty: + story.append(Paragraph("Follower Growth", h2_style)) + fig = daily_followers_figure(fts_f, posts_f) + fig.update_layout(showlegend=True, font=dict(size=14)) + story.append(_fig_to_image(fig)) + + # --- Top posts table --- + story.append(Paragraph("Top Posts" + ("" if timeframe_value == "all" else f", {period_label}"), h2_style)) + if len(posts_f): + top_posts = posts_f.sort_values("Eng %", ascending=False).head(5) + rows = [["Post", "Date", "Type", "Impressions", "Eng %", "CTR %"]] + for _, r in top_posts.iterrows(): + 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['Eng %']:.1f}%", + f"{r['CTR %']:.1f}%", + ]) + 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([ + ("BACKGROUND", (0, 0), (-1, 0), NAVY), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), + ("FONTSIZE", (0, 0), (-1, -1), 8), + ("ALIGN", (1, 0), (-1, -1), "CENTER"), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#e0e7ef")), + ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, LIGHT_GRAY]), + ("TOPPADDING", (0, 0), (-1, -1), 5), + ("BOTTOMPADDING", (0, 0), (-1, -1), 5), + ])) + story.append(posts_table) + else: + story.append(Paragraph("No posts published in this period.", body_style)) + + # --- Content format performance --- + if len(posts_f): + story.append(Paragraph("Impressions by Content Format", h2_style)) + fmt_imp = (posts_f.groupby("Content Type", as_index=False)["Impressions"].sum().sort_values("Impressions")) + fig2 = std_fn(px.bar( + fmt_imp, x="Impressions", y="Content Type", orientation="h", + template=tmpl, color="Content Type", color_discrete_map=format_colors)) + fig2.update_layout(showlegend=False, font=dict(size=14)) + story.append(_fig_to_image(fig2, height_px=220)) + + # --- Competitor snapshot --- + story.append(Paragraph("Competitor Snapshot", h2_style)) + story.append(Paragraph( + "Cumulative export snapshot, not specific to the selected period.", note_style)) + comp_rows = [["Page", "New Followers", "Posts", "Comments", "Comments/day", "Reactions"]] + for _, r in comp_ordered.iterrows(): + label = r["Page"] + (" (You)" if r["Page"] == "TXproducts" else "") + comp_rows.append([label, r["New Followers"], r["Posts"], r["Comments"], r["Comments per day"], r["Reactions"]]) + comp_table = Table(comp_rows, colWidths=[5 * cm, 2.6 * cm, 2 * cm, 2.2 * cm, 2.6 * cm, 2.2 * cm]) + comp_table.setStyle(TableStyle([ + ("BACKGROUND", (0, 0), (-1, 0), NAVY), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.white), + ("FONTSIZE", (0, 0), (-1, -1), 8), + ("ALIGN", (1, 0), (-1, -1), "CENTER"), + ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#e0e7ef")), + ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, LIGHT_GRAY]), + ("TOPPADDING", (0, 0), (-1, -1), 5), + ("BOTTOMPADDING", (0, 0), (-1, -1), 5), + ])) + story.append(comp_table) + + story.append(Spacer(1, 16)) + story.append(Paragraph("TXproducts · Internal Report · Generated automatically from LinkedIn Page Analytics exports.", note_style)) + + buf = io.BytesIO() + doc = SimpleDocTemplate(buf, pagesize=A4, topMargin=1.5 * cm, bottomMargin=1.5 * cm, + leftMargin=1.5 * cm, rightMargin=1.5 * cm) + doc.build(story) + buf.seek(0) + return buf.getvalue() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6b222c3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +dash>=2.0 +dash-bootstrap-components>=1.0 +plotly>=5.0 +pandas>=1.3 +openpyxl>=3.0 +xlrd>=2.0