Reordered: calendar row now comes first in the content area (right under the
KPI tiles/Team member dropdown), followed by Visibility (full width), Posts
(full width), then the Impressions/Engagement Rate/Demographics row.
Refactored _build_person_dashboard/_build_empty_person_dashboard to return
their pieces (source_note, calendar_row, charts_row) separately instead of a
pre-assembled Div, so _render_team_tab controls the final ordering directly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
alignItems: flex-end was bottom-aligning the dropdown's column against the KPI
tiles, but the dropdown has a label above it eating vertical space the KPI
tiles don't have, so the dropdown control itself sat visibly lower than the
tiles. Switched to center so both align on the same visual middle line.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
KPIs now render into their own area (team-kpi-area) placed directly next to the
Team member dropdown/Download Report row, instead of scrolled down inside the
main content area - extracted into _team_stat_row()/_team_empty_stat_row() and
wired as a third Output on _render_team_tab's callback.
Visibility and the Posts table now sit side by side in one row instead of each
being a separate full-width section. The Posts table (11 columns) gets its own
horizontal scroll container now that it's at half-width instead of full.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixing the cumulative-total bug (previous commit) meant _team_fts_like now
spans the export's full true window, which includes trailing months from
before the person was active at all (Oct-Dec 2025, all zeros) - those were
showing up as a selectable year with nothing in it. _team_years_with_activity
keeps only years with at least one month of real net change or a real post;
2027 will appear automatically the first time it has either, same mechanism,
no code change needed then.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bug: _team_fts_like truncated to >= TEAM_BASELINE_DATE (June 1) before computing
the cumulative running total, which silently dropped a real +1 gain from May 30
(one day before baseline). Two visible symptoms Mckinley caught directly: every
day's running total was permanently 1 lower than the real current follower
count (e.g. Oct 30 showed 20, not the real 21), and May disappeared from the
calendar entirely instead of showing its real "+1". Fixed by anchoring the
cumulative sum at the export's own true window start instead (confirmed
correct - summing every daily gain across the whole file exactly equals the
reported current total). TEAM_BASELINE_DATE remains intentionally separate,
used only for the Starting/New Followers KPI tiles (when this work started,
not literally the first follower ever).
Also made the calendar fully interactive: a year dropdown (ready for 2027) and
clickable month boxes, via one callback registered once and driven by pattern-
matching ids + State("team-member-select") rather than needing per-person
wiring. Guards against Dash's known quirk where a newly-mounted pattern-matched
button fires the callback once on its own with n_clicks still 0 - checks the
triggered input's actual value, not just which id fired, so a fresh render
doesn't spuriously jump to the wrong month.
Also dropped the Total Page Views / Total Clicks KPI tiles entirely (per
Mckinley) - neither exists in any personal-profile export, so a permanent "—"
placeholder for either was never going to show real data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LinkedIn's exports have no title/text field for a post anywhere (neither
Aggregate Analytics nor SinglePostAnalytics) - only the URL, whose slug is a
URL-safe truncation of the post's own opening words. post_title_from_url()
decodes it (handles accented names like Krutošíková), strips the trailing
-share-/-ugcPost-<id>-<code> suffix, and takes the first 5 words as a readable
stand-in for a title. Added to both the live dashboard's posts table and the
downloadable report.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Turns out every individual post's own analytics page has an "Export" button
(SinglePostAnalytics_<Name>_<id>.xlsx) giving exactly the detail that was being
hand-logged: Profile Viewers/Followers Gained from the post, and the Reactions/
Comments/Reposts/Saves/Sends/Link/Premium-button breakdown behind the Aggregate
Analytics file's single blended "Engagements" number.
Matches each file to a post by its own "Post URL" field, not the numeric id in
the filename - confirmed those two ids are actually different LinkedIn schemes,
so filename-based matching would have been wrong. Loaded once per render
(load_post_details) rather than re-parsed per table row.
All 4 of Mckinley's real posts are now auto-covered (55/80/17/365 impressions),
replacing the 1-of-4 hand-logged coverage from before. Kept showing both
engagement numbers side by side in the table/report - the per-post export's own
breakdown sum has been observed to disagree with the aggregate export's
Engagements figure for the same post more than once now.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Calendar now matches the Overview tab for real: reuses _month_day_tiles to show
the default month's day-by-day breakdown underneath the month-box grid (not just
the month boxes alone), and the New Followers bar chart now overlays star markers
on publish days via the Page's own mark_posts(), using TEAM_BASELINE_DATE-filtered
data the same way the Page's own fts is filtered to >= BASELINE_DATE (so the
calendar/chart show the real June-October window, not the export's full noisy
~365-day span). Month-click-to-expand isn't interactive yet - would need its own
per-person callback wiring - so it always shows the most recent month.
Impressions, Daily Engagement Rate, Audience Demographics, and Content
Demographics now sit on one row (4 cards), and both demographics tables are
capped to their top 5 rows by Percentage instead of showing everything.
Logged two more real posts in post_details_tracker.py from screenshots (the
July 9 and Oct 22 posts) - only one post left unlogged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LinkedIn's export has no per-post Profile Viewers/Followers Gained attribution,
and its "Engagements" column is a single blended number - none of that exists
anywhere in the Aggregate Analytics file. Each individual post's own analytics
panel does show it, so this is a hand-logged, per-post-URL dict
(post_details_tracker.py, same add-an-entry pattern as the other two trackers).
Added as extra columns to the Posts table (both the live dashboard and the
downloadable report) alongside the exported Impressions/Engagements, rather
than replacing them - the two Engagement numbers have been observed to
genuinely disagree for the same post (export said 25, the post's own panel
summed reactions+comments+reposts+saves+sends to 20), so both are shown
instead of one silently overriding the other.
Seeded with one real logged post (the July 2 post: 3 profile viewers, 0
followers gained, 15 reactions, 5 comments).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LinkedIn has no file export for either metric (confirmed - no Download option
exists on the "Who's viewed your profile" / "Profile appearances" pages, unlike
the content analytics file team_exports.py parses), so this is a hand-maintained
log (visibility_tracker.py, same "add an entry, no code change needed" pattern
as conversation_tracker.py) rather than another auto-parsed file.
Shown as its own card above the main dashboard, independent of whether a
person's Aggregate Analytics export exists yet - visibility logging and the
Monday export are two separate data sources that can each arrive on their own
schedule. Seeded with Mckinley's real first week (Oct 20-26: 10 all
appearances +67%, 1 search appearance +0%).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Starting Followers is now a fixed, human-confirmed anchor (everyone started this
work June 1, 2026 with 0 followers) instead of borrowing the Page's own unrelated
BASELINE_DATE or the export's meaningless ~365-day window edge. Lives in
team_exports.TEAM_BASELINE_DATE/TEAM_BASELINE_FOLLOWERS so both the dashboard and
the downloadable report use the same anchor.
Rebuilt the chart section to mirror the Overview tab's actual layout: a static
Follower Gains calendar grid (reuses the Page's own _monthly_summary/_month_box_*
helpers, renamed columns to fit) + New Followers per Day bar chart side by side,
then separate Impressions and Daily Engagement Rate line cards instead of one
combined dual-axis chart. Daily CTR and Page Views (Desktop/Mobile) are left out
entirely - personal-profile exports have no click or device-level data at all,
so those two Overview cards have no real equivalent here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three corrections against the Overview tab's real KPI row:
- Tile set now matches exactly (Total Page Views / Total Clicks included as
"not available for personal profiles" rather than silently substituting
Members Reached in their place).
- Starting/New Followers now anchor to the same BASELINE_DATE (Jun 16, 2026)
the Overview tab uses, not the export's own ~365-day rolling window edge -
that was showing a meaningless "as of last October" date. Passed into
build_person_report_docx() as a parameter (team_exports.py stays free of
any app.py import, same rule report.py already follows).
- Download Report button moved from the tab's own row into the hero banner's
upper-right actions slot, same spot the Followers tab's Directory/Study
links live - the actual "upper right" of the page, not just of the tab.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the ad-hoc stat cards with the same kpi() component the Overview tab
uses (Starting/Total/New Followers, Total Impressions, Members Reached, Best
Post Real Eng., Avg. Eng. Rate) - same visual language across the dashboard,
and "New Followers" in the window is the growth KPI that was missing.
The daily Impressions/Engagements chart now overlays star/diamond markers on
each post's publish day, split by Original vs Repost (mirroring the Page's
own mark_posts() star overlay) - the one thing this export doesn't give you
anywhere else as a labeled field.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Peter, Daniel, Niklas, and Aleksandra were showing a single plain text
alert while Mckinley got the full stat-tile/chart/table layout - now
everyone gets the same boxes (stat tiles, chart cards, posts table,
demographics cards), just with "No data yet" placeholders where a
person hasn't submitted an export. Keeps the tab visually consistent
across all five regardless of who has data yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the empty placeholder with actual parsing of the "AggregateAnalytics_
<Name>_<start>_<end>.xlsx" personal-profile export (DISCOVERY, ENGAGEMENT, TOP
POSTS, FOLLOWERS, and both DEMOGRAPHICS sheets). Weekly submissions are a new
~365-day trailing window each time, so the daily series (engagement, followers)
merge across every file on disk the same way the Page's own Followers export
does - newest file wins on an overlapping date.
Reposts vs original posts are distinguished from the post URL itself
(.../share-<id> vs .../ugcPost-<id>), since LinkedIn's "Engagements" column
doesn't separate them - this was the open design question about measuring
repost/comment activity, and the data turns out to answer it directly.
Verified against Mckinley's own first real export (2026-10-30): Impressions,
Followers, and Engagements all cross-check against the file's own DISCOVERY
summary row. The other four team members still show the "no export yet"
placeholder until their first Monday submission lands.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New tab for analyzing each team member's own personal LinkedIn export
data (separate from the Page-level Content/Followers/Visitors exports),
starting December 2026 per Mckinley. Submenu dropdown lists Peter Gaal,
Daniel Schmidt, Niklas Meidow, Aleksandra Kotenko, Mckinley Black.
Same "starts empty" pattern as the Engagement Response tab: a hand-
maintained team_exports.py module (empty for now) backs the tab, and
each person shows a clean placeholder until their real export data
gets added - no other code changes needed at that point.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Header is now just the date range and lead theme (e.g. "Nov 3 - Nov
26 (Failure Analysis)"), no company-name prefix — the Campaign
dropdown above it already names both the campaign and the company.
CTA phases now get the same plain "TXproducts — {timeframe}" header
every non-branding-campaign period already uses, instead of the
branding campaign's own "...End of Month Campaign Report" phrasing —
the Campaign dropdown above it already names the campaign, no need to
repeat it in the header too.
CUSTOM_PERIODS in report.py now drives _filtered/_period_label/
compute_period_stats for the 3 CTA phases (date ranges from the
proposed plan), and the campaign-report title treatment applies to
them the same way it does for July-September. Removed the app.py
placeholder guards (reports tab + docx download) now that real period
computation exists instead of an alert box, and fixed the docx
filename label lookup to check all campaigns' periods, not just the
old single flat list. Added Core Message (reused) and Roadmap
(per-phase) abstracts to monthly_report_notes.py for the 3 phases -
no executive_summary yet since the campaign hasn't run and isn't
approved, so KPI/chart cards render on real, currently-empty data
rather than fabricated findings.
The Campaign dropdown already says "CTA Campaign" right next to it -
repeating it in the header buried the thing that actually matters,
the timeframe. Title now leads with the date range itself (e.g.
"Nov 3 - Nov 26 (Failure Analysis)").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the single flat "Reporting Month" dropdown with two linked
ones - Campaign (LinkedIn Branding Campaign / Pilot Partner / CTA
Campaign), then Period, populated based on the campaign picked.
Dash's dropdown has no native nested submenu, so this is the standard
"pick a year, then a month" cascade instead of faking nesting in one
control.
LinkedIn Branding Campaign -> July/August/September/End of Campaign
(unchanged, same data as before). Pilot Partner -> October (unchanged).
CTA Campaign -> its 3 proposed phases by date (Nov 3-26, Dec 1-Jan 7,
Jan 12-Feb 4), each showing a "not approved yet" placeholder rather
than crashing - compute_period_stats()'s date parsing doesn't
understand "cta-phase-1" as a period key, so it's guarded the same way
"campaign-end" was guarded before that report was built out. Download
button is guarded the same way.
Dropdown structure only - no CTA Campaign report content built yet,
that's still pending approval.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New tab, separate from the Reports dropdown (which stays tied to
actual calendar-month/campaign-approval data), covering what the new
campaign plan cares about that the old reporting never tracked:
- theme_tracker.py: per-post theme classification (Failure Analysis /
Standardization / WaveGate), same hand-tagged pattern as
MANUAL_CONTENT_TYPES. Powers a Theme Performance chart showing each
theme's Real Eng % across the whole campaign, not just its lead
month - the explicit ask was to see if a theme keeps earning
engagement even when a different theme is leading that month.
- conversation_tracker.py: logs every real conversation (a comment
from an actual person) through to resolution (open -> replied ->
followed_up -> closed). Exists because the old campaign's two real
conversations, Sabine Brock on 20/08 and Dr. Debnath on 08/09, were
both left open with no reply and no follow-up - this makes that
visible instead of silent. An "N open" badge flags anything still
needing a reply.
Both data sources start empty and render a clean "nothing logged yet"
state - nothing to log until the campaign is approved and posts start
going up, but the tooling is ready.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Likes A3/T1 (Katrin Schmidt, Namrata Sharma, Alice Autuori audience;
Mckinley Black team; TXproducts Page like excluded entirely),
comments 0/0 (only comment was the Page's own), reposts A0/T2 (Peter
Gaal, Mckinley Black, both internal). Real Eng % 8.1%. Confirmed the
20/10 audit against the fresh export that now includes it (Likes 8,
Comments 0, Reposts 3) - matches exactly what was recorded from
screenshots.
Alice Autuori's status conflict (flagged last commit) is resolved -
the Demographics session confirmed directly with Mckinley,
independent of this session, that she does follow the Page, verified
against her LinkedIn profile, and already restored her Directory
record and removed the outreach_contacts.py duplicate.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Supply Chain Manager / Procurement at a PCB manufacturer - not a
clean Function match (same honest limitation as Anna Maksakova's
"Data Analyst"), classified as Other/Other named company. First
follower from Ireland, now 13 single-follower countries.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Track #6's commit 3527401 said Mckinley had confirmed Alice Autuori
doesn't follow the Page, and removed her into outreach_contacts.py.
Mckinley has now confirmed directly, independent of that, that she
does follow - checked her LinkedIn profile to settle the conflict.
Restored her original Directory record, removed the outreach_contacts.py
entry, and recomputed the Study page back to 150/130. Kept the
Geography-chart drift fix from the prior correction (Germany/US/India/
no-company counts) since that was independently verified against
source data and unrelated to Alice's status either way. Also caught
and fixed a separate stale figure while re-verifying: the tagline-only
title count had drifted to 19, live data says 20.
Twenty CRM restoration still pending - connector unavailable this
session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Track #6 confirmed Alice Autuori doesn't actually follow the Page and
moved her out of the Directory into outreach_contacts.py (commit
3527401), but the Study page was never recomputed to match - it still
reflected the stale 150-follower state.
Fully recomputed all six charts and prose directly from the live
Directory data rather than hand-decrementing, which also caught a
pre-existing drift unrelated to Alice: the Geography chart's Germany/
US/India counts and its "No company listed" bucket had gone stale.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Likes A5/T2 (Dr. Jefferson do Rosario Stelzer, Philipp Brandt, Alice
Autuori, Sabine Brock, Katrin Schmidt audience; Mckinley Black,
Aleksandra Kotenko team), comments 0/0, reposts A0/T3 (Aleksandra,
Peter, Mckinley, all internal). Post isn't in the LinkedIn export yet
(still caps at 01/10), so Real Eng % will compute automatically once
a fresh export includes it, same as 29/09 earlier.
Mckinley confirmed directly that Alice Autuori does not actually
follow the Page, despite an existing Directory entry (followedSort
2023-04) claiming otherwise - another case of the same
engagement-does-not-imply-follower mistake caught during the campaign
audit. Moved her to outreach_contacts.py with her full profile
preserved and all 3 engagement dates (14/07, 28/07, 20/10).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Likes A2/T3 (vaishakh vasudevan, Yosra Gmati audience; Mckinley
Black, Daniel Schmidt, Aleksandra Kotenko team; TXproducts Page like
excluded entirely), comments A0/T0 (only comment was the Page's own),
reposts A0/T4 (Peter Gaal, Daniel Schmidt, Aleksandra Kotenko,
Mckinley Black, all internal). Real Eng % 5.0%.
Yosra Gmati isn't in the Follower Directory yet and her follow status
isn't confirmed, so added to outreach_contacts.py as an engaged
non-follower rather than assumed into the Directory (per the Carousel-
classification/Arunangshu lesson from the campaign audit). vaishakh
vasudevan (already a confirmed follower) got her engagement_notes
extended.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Only July/August/September were part of the 3-month branding campaign
(CAMPAIGN_START-CAMPAIGN_END). A later month has no active campaign
stage behind it (no goal_line, no core_message/roadmap abstract), so
calling its report "...End of Month Campaign Report" would be wrong.
October (and any future month) now gets a plain "TXproducts — {Month
Year}" title instead, in both the live Reports tab and the docx.
July/August/September's titles are unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Already delivered as its own document, no need for it to stay
selectable in the live dashboard. The underlying generation code
(build_report_docx's campaign-end support) is left in place in case
that document needs regenerating later, just not exposed in the UI.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The 3-month campaign (July-September) wrapped, but posts have kept
going (01/10 already up). October wasn't selectable in the Reports
tab at all - added it to _REPORTS_PERIODS. Falls back to "Executive
Summary not written up yet" until October's narrative is written,
same graceful handling already in place for any unwritten month.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Directory grows 145→147. Full recompute of all six Study charts and
affected prose. Svenja is X-Spectrum's 2nd follower, newly crossing
the Notable Clusters table's 2-follower threshold — added as a
genuine 9th Hamburg/DESY-tied institution row (not excluded like
Umicore/ITCareerHub/HZB, since X-Spectrum is actually Hamburg-based).
Company City set to Hamburg to match the existing X-Spectrum record
(Julian Schmehr), not her personal location (Wedel).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Directory grows 144→145. Full recompute of all six Study charts and
affected prose. 12th DESY-linked entry, Hamburg/DESY cluster distinct
headcount 30→31.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LinkedIn omits the country for this metro area too, same pattern as
San Francisco Bay Area / Greater Madison Area / Los Angeles
Metropolitan Area — was dropping it from the followers map with a
console warning.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TXproducts stated the pain point in 08/09's post; the audience didn't
originate it, they confirmed it was real by engaging in discussion.
"Said" implied the audience brought it up themselves.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Matches the pale yellow (#fdf6d8) already used for analytical table
headers elsewhere in the app. Added an optional header_bg param to
_composition_table (live preview) and passed the same color to
_docx_table (docx) for the Sector/Function/Seniority/Country tables
specifically, leaving the one other _composition_table call site
(Reports tab's compact card) untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mckinley manually added Sector/Function/Seniority/Country breakdown
tables to each monthly report by hand from the live Audience
Composition page; the campaign-end report had no equivalent since
there's no single month's page to pull a 3-month combined table from.
Generated directly in both the docx (report.build_report_docx) and the
live Reports tab preview (app._render_campaign_end_summary): merges
Follower Directory rows whose followedSort falls in {2026-07, 2026-08,
2026-09} (58 new followers profiled across the campaign) and renders
the same 4-table breakdown as the monthly reports use.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Follows the 13/08 Content Type fix (Carousel, not Text/Image).
Recomputed: Carousel now ran 7 times (not 6), averaging 29.1% (not
25.9%) - clearly the campaign's best format, not a close contest with
Text/Image (whose average drops to 9.1% once its one "strong" post
turns out to actually be a Carousel). 13/08 is the campaign's #2 post
at 48.7%, combining Carousel with a concrete cost claim.
This also corrects a now-wrong recommendation: "test the untried
Carousel + concrete claim combination" was framed as unexplored, but
13/08 already proved it works. The real remaining gap is narrower and
more specific - Carousel paired with a named pain point (September's
trigger finding), not just any concrete claim.
Mckinley confirmed this report (unlike the monthly ones) isn't
submitted yet, so updated directly rather than just flagging it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LinkedIn's export doesn't distinguish Carousel/document posts from
generic Image, so MANUAL_CONTENT_TYPES already exists to hand-correct
known misclassifications - 13/08 was simply missing from it. Mckinley
confirmed directly: all top-3 campaign posts by Real Eng % (04/08,
13/08, 09/07) are Carousels, not a mix of Carousel and Text/Image.
Dashboard only per Mckinley's explicit instruction - the July/August/
September Word reports are already submitted and intentionally left
unchanged. Static export regenerated to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mckinley flagged the July/August stage bullets as confusing. Two
separate problems: July's bullet conflated Carousel's 4-post average
with the 14/07-28/07-30/07 trilogy as if they were the same finding,
when the trilogy is mostly a Video story (only 30/07 was Carousel).
And "Carousel's single best post of the campaign (50.8%)" in August's
bullet left it ambiguous whether 50.8% was an average or one post's
score (it's one post, 04/08 - August only had a single Carousel that
month, nothing to average).
Rewrote both to be explicit: July's now separates the format-average
finding from the narrative-arc finding into two sentences, and spells
out the trilogy's actual formats (Video, Video, Carousel). August's
now says directly "the single Carousel this month (04/08) was the
campaign's single best post."
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The same ~4 findings (Carousel's variance, audience stability, direct
asks failing, the untried Carousel+pain-point combination) were each
restated 3 times with near-identical wording across engagement_findings,
audience_dimensions, and recommendations — the opposite of the "simple,
not 8 pages" point of this report format.
Consolidated so each fact lives once, in its most natural place:
engagement_findings keeps the Carousel-variance and the one-trigger
stats (audience stability dropped, already fully covered by
audience_composition_line). trilogy_closing trimmed to the observation
only, the fix moved into recommendations. audience_dimensions rewritten
entirely — it was just restating stats already given elsewhere; now it's
the Prior Knowledge/Decision Context/Trust framing the monthly reports
already use, synthesized across the whole campaign (including a
genuinely new point: the audience's Decision Context shifted in
September, the first month with real budget-holding presence).
recommendations trimmed to action + one-clause justification instead
of re-deriving the full stat block each time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Found while cross-checking the user's own annotated PDF copies on
their Desktop against the live data. August's 06/08 Poll was recorded
as "a small but real response (5.2%)" in the executive summary, but
the actual audit (engagement_audit.py's own "06/08" entry, unchanged
since it was first audited) already had zero audience AND zero team
engagement from Likes/Comments/Reposts - the 5.2% was entirely
unaudited Clicks, unrelated to anyone actually engaging with the poll
itself. The real finding: both poll attempts (21/07, 06/08) got zero
genuine follower response, not an improvement month to month, and the
Poll/Wildcard format was removed from the campaign after the second
attempt - a real decision this file wasn't recording anywhere.
Fixed in August's own executive_summary and in the campaign-end
summary, which was missing August's poll attempt from its validation
narrative entirely (only mentioned July's).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
29/09's Carousel ("The two pillars of TXservices") is now in a fresh
export and audited: likes A1/T2 (Sabine Brock audience; Aleksandra
Kotenko, Mckinley Black team), 1 team comment, 3 team reposts ("the
usual suspects" - Aleksandra, Peter, Mckinley). Real Eng % 10.0%,
September's new growth/KPI numbers: 126->142 (+16, +12.7%).
This surfaces a real correction: July actually had 4 Carousels
(09/07, 16/07, 23/07, 30/07), not the 1 everyone's mental model had
been built around, ranging 6.0%-45.0%. Across all 6 Carousels run
this campaign (4 July + 1 August + 1 September) the format averages
25.9% Real Eng %, still the best of any format, but ranges 6.0% to
50.8% - not the clean "wins every time" story the campaign-end
summary told before this. Rewrote September's and campaign-end's
Executive Summaries throughout: the trilogy/stage narratives, the
best-posting-day findings (Tuesday's edge was never confounded by
format once September separated the variables), the recommendations
(pair Carousel with a named claim instead of trusting the format
alone, test the untried Carousel+pain-point combination).
Also fixed a conflation Mckinley flagged directly: July's poll (a
genuine validation test - does this audience respond to an
interactive format at all) and 17/09's pilot-partner ask (a direct
conversion CTA) are different kinds of things and were wrongly
grouped under one "validation" framing in both September's and the
campaign-end's summaries - now described as what they actually are.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LinkedIn's export doesn't distinguish carousel/document posts from
generic Image, so this needed the usual manual override.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>