9 Commits
Author SHA1 Message Date
beantownbabeandClaude Sonnet 5 292bd21abe Generate EOM reports as editable .docx instead of fixed-layout PDF
Mckinley's PDFs kept cutting off content mid-page with no way to fix
it short of regenerating the whole report — a fixed-layout PDF isn't
the right format for something meant to be edited. Adds
report.build_report_docx(), a python-docx equivalent of
build_report_pdf() sharing the exact same compute_period_stats() data
and section order (KPI table, section abstracts, weekly pace,
audience composition, follower growth chart, all posts table with
the same excluded-post strikethrough treatment, format performance,
competitor snapshot, and the full Section Deep Dives for a monthly
campaign report), so the two can never silently drift apart on
numbers even though the rendering layer is completely different.
Word reflows content automatically, so there's no more fixed page
grid to fight when editing.

The Reports tab's download button now produces .docx instead of PDF.
Verified via python-docx re-read (KPI values, the 10/07 repost row's
strikethrough, embedded chart images) and strict OOXML schema
validation (including fixing a `w:zoom` attribute python-docx's own
default template omits). build_report_pdf() is left in place, only
report.py's ctx docstring rewritten since the doubly-stale "sidebar's
generic PDF control" no longer exists as an actual call site.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-30 14:19:29 +02:00
beantownbabeandClaude Sonnet 5 eb402c8713 Roll out Real Eng % everywhere, replacing raw reported engagement rate
Every table, chart, and KPI that shows a per-post engagement rate now uses
Real Eng % (Audience-only, Team excluded, Clicks included, hand-audited) —
the live Content/Overview dashboard and every Reports page alike, not just
the Reports family. Audited posts show the real number; unaudited posts show
"Pending audit" in tables or are excluded (with a count) from charts, never
silently falling back to the raw, Team-inflated figure.

- New engagement_audit.py holds the audit data and Real Eng % calculation,
  shared between app.py and report.py (previously app.py-only, which made
  report.py's PDF unable to use it at all).
- compute_period_stats() (shared by the live app and the PDF) now computes
  Real Eng % per post and an audited-only "Best Post Real Eng." max.
- Traction by Format's separate "Reported Avg Eng %" column is gone entirely
  rather than shown side by side with the real one — one number, one meaning.
- Corrected an earlier internal claim from this same audit: 09/07 does not
  drop to near-bottom under Real Eng %, it stays the clear #1 post (45.0%
  real vs. 47.9% raw) since its large Click volume dominates the calculation.
  Re-checked the Core Message "Emotional outperformed Rational" finding
  against the real numbers rather than just relabeling it — it holds (21.8%
  vs. 8.1% average, a wider gap than under raw numbers).
- Fixed two pre-existing bugs in build_static_export.py surfaced while
  regenerating it: a stale 2-value unpack of _render_reports_tab's now
  3-value return, and insights pages read from TAB_CONTENT_MAP instead of
  the _INSIGHTS_PAGE_BUILDERS functions the dynamic-month refactor moved
  them to.
- Documented the rollout, the 09/07 correction, and two remaining open
  items (historical audit backfill scope, Avg. Eng. Rate's future
  redefinition alongside Clicks/Impressions/CTR %) in Monthly Notes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 12:09:47 +02:00
beantownbabeandClaude Sonnet 5 dfb229e192 Build out Roadmap/Core Message insights, mirror dashboard pages, expand Directory analysis
- Roadmap Insights: stage-sequence timeline, per-format traction table with posts named
  inline, best-posting-day calendar with weekday/post detail table flagging the
  format/day confound, engagement-over-time chart (icons throughout, yellow banner
  titles matching Core Message)
- Core Message Insights: icon set for the four-step framework and each card
- Reposts excluded from every format/day/traction breakdown (REPOST_DATES) across
  app.py, report.py and the static export
- Follower Growth Insights and All Posts Insights now mirror the Overview/Content/
  Growth tabs' own charts, scoped to July only, with matching KPI rows
- Monthly Notes moved out of the Reports tab into its own sidebar link (personal
  accounting, not part of the campaign report)
- Audience Composition and Follower Study insights pages expanded: full Sector/
  Function/Seniority/Country breakdowns, a Sector x Seniority cross-tab, and account
  clusters (2+ followers from the same company) for the whole pre-August Directory
- report.py and build_static_export.py mirrored throughout to stay in sync with app.py

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 14:39:30 +02:00
beantownbabe ec0b87ac65 Mirror the 7-page Reports architecture into the static export
Extends build_static_export.py's render() with three things it never needed
before this session's Reports tab work: dcc.Link support (internal routes
become switchTab() calls, since a single static file has no server-side
routing; asset-relative hrefs stay real links), html.A support (href/target
were previously dropped entirely, falling through to the "render children
only" fallback), and Ul/Li in the tag map (bullet lists were silently
degrading to plain unstyled <div>s, a gap already known from the CSS
Ul/Li was never added). Also renders the 7 new insights pages (not in
NAV_ITEMS, reached only via the new hero nav) and adds per-tab hero-action
rendering to switchTab(), which fixes a pre-existing gap where the Followers
tab's Directory/Study links were computed but never actually shown, since
hero-actions was previously baked once for "overview" and never refreshed on
tab switch.

Also fixes a real bug this surfaced: grid-template-areas values used double-
quoted strings ('"cm rm ap" "fg fg ap"'), which silently terminated the HTML
style="..." attribute they were concatenated into (itself double-quoted),
corrupting the entire Reports tab grid layout in the static file only (the
live app was unaffected, since React sets styles via the DOM API, not string
concatenation). Fixed at the source (single-quoted CSS strings) and hardened
style_dict_to_css() itself to escape embedded quotes going forward.

Verified: all 7 insights pages render and cross-navigate correctly in the
static file, the CSS grid layout now matches the live app exactly, and a
full smoke test of the pre-existing tabs (Overview through Growth) shows no
regressions.
2026-08-08 09:22:22 +02:00
beantownbabeandClaude Sonnet 5 cf0d0c4020 Polish Reports tab, add multi-month support, remove unused sidebar controls
Reports tab polish:
- Title renamed to "{Month} End of Month Campaign Report" (TXproducts dropped,
  the sidebar already identifies the tool), moved inline between the month
  dropdown and Download PDF button, sized to fit on one line.
- All 9 KPI tiles restyled: centered, uniform size, orange accent (distinct
  from the app's navy default), drop shadow. Total Followers/Month Start/New
  Followers show as the headline numbers. Growth Rate's explanation moved
  from a permanent caption into a yellow (?) popover, consistent with the
  Audience Composition coverage note (also moved into its own header badge).
- Follower Growth and All Posts shown side by side, equal height, All Posts
  widened so no title wraps and shows every post with no scrolling.
- New "Month Start" KPI: cumulative followers at the start of the period,
  the denominator the new Monthly Growth Rate is computed against.
- Corrected the Follower Directory coverage note: it's updated by hand each
  time a new follower comes in, so it can run ahead of the dashboard's own
  weekly LinkedIn export, not the other way around.

Multi-month support:
- Reports dropdown now has fixed slots for the whole 3-month campaign
  (July/August/September) plus a distinct "End of Campaign" entry, rather
  than only showing months with notes already written. August/September
  render with the same template as July, gracefully empty until data/notes
  exist. "End of Campaign" shows its own placeholder instead of running the
  normal per-month stats path, it gets a different design once the campaign
  wraps, not just another empty month.
- Fixed _period_label to build "Month Year" directly from the timeframe
  value instead of looking it up in export data, which failed for a future
  month with no export rows yet.

Removed the sidebar's Upload New Data and Generate PDF Report controls
(_handle_upload, _generate_report, the dcc.Upload/report-timeframe-select
components, and the now-unused report_month_options() helper). Neither was
part of the real workflow: data refresh happens by copying files in and
restarting the process, and the sidebar's generic PDF button is fully
superseded by the Reports tab's own per-month PDF download.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 14:23:06 +02:00
beantownbabeandClaude Sonnet 5 f9f27401db Add Reports tab: monthly campaign progress reports
New "Reports" tab for the 3-month LinkedIn campaign, combining dashboard
data with the Follower Directory rather than a standalone HTML/PDF
workflow. For each campaign month it shows KPI tiles (including a new
Monthly Growth Rate, this month's net new followers as a percentage of
the followers already on the page at the start of that month, resets
every month against its own starting count), the follower growth chart,
every post published that month, an Audience Composition breakdown
sourced live from the Follower Directory (sector/seniority/country, both
this month's cohort and the all-time snapshot), and curated monthly
narrative notes (features shipped, data quality fixes, Directory
updates, outstanding items). Reuses the existing reportlab PDF pipeline,
extended with the same sections, and mirrored in the static exporter
with live-only controls disabled.

New files: monthly_report_notes.py (per-month curated narrative,
July populated), follower_directory_data.py (parses the Directory's
DATA array directly so numbers can't drift from the hand-maintained
source of truth).

Also fixes three pre-existing bugs found while building this: average
engagement rate was displaying 100x too small (a 0-1 fraction shown
as-is instead of scaled to a percentage), the Sector/Seniority/Country
breakdown tables were silently truncated below their own stated totals
(a hardcoded top-5 display cap plus a separate top-10 country cap
inside composition_stats itself), and the static exporter had no
support for dbc.Table, dropping the <table> tag entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 09:56:19 +02:00
beantownbabe bc66407068 Fix Follow Rate default-month and add Madison map centroid override
Follow Rate Relative to Reach was defaulting to the newest month even
when it had only a single day of data, making the chart look broken on
load — now defaults to whichever month has the most data instead.

Also add "Greater Madison Area" to the location-country override list;
this week's export introduced a new US follower location with no
country suffix, same pattern as San Francisco Bay Area.
2026-08-03 08:02:11 +02:00
beantownbabeandClaude Sonnet 5 8a72eeb402 Add Follower Directory/Study hero links, fix country-map centroid gaps, and sync accumulated dashboard work
- Hero banner now shows Follower Directory / Follower Study links on the
  Followers tab only (not the sidebar), opening the standalone reports served
  from assets/.
- Followers by Country: _COUNTRY_CENTROIDS expanded from 14 hardcoded entries
  to a near-complete world table, so a country missing from the old dict no
  longer vanishes from the map silently; added a console warning for any
  country still missing a centroid, and a "N countries represented" caption
  computed from the same rollup the map uses so the two can never disagree.
- Added visitor_history.py: append-only observation log for the Visitors
  export's category breakdowns (Location/Seniority/Company size), since
  LinkedIn only shows a category if it clears a per-export minimum-visits
  threshold and otherwise silently drops it.
- Synced other accumulated app.py/build_static_export.py work not yet
  committed: calendar month drill-down tiles, follower-breakdown expand
  toggle, CTR explainer, manual post title overrides, and matching static-
  export precompute/JS for the calendar and content tabs.
- .gitignore: exclude the newer generated snapshot outputs (follower study/
  directory HTML copies, PDF report, visitor_category_history.json) using
  the same rationale as the existing growth_history.json/snapshot rules —
  regenerable from code + data, not itself code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 08:49:19 +02:00
beantownbabe 348fe65b88 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.
2026-07-21 15:14:28 +02:00