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>
Directory grows 143→144. Full recompute of all six Study charts and
affected prose. LEHMANN + PARTNER is a transportation-infrastructure
surveying firm (Erfurt), not photonics/research-related, so classified
as Other named company with industryOther text from a verified web
lookup rather than left blank.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The "campaign-end" dropdown option existed but was a placeholder ("not
built yet") in the live tab and explicitly blocked in the download
callback. Built it out properly, same condensed Executive Summary
philosophy as the monthly reports, not a merge of all three:
- report.py: "campaign-end" is now a real timeframe_value throughout
_filtered/_period_label/compute_period_stats, scoped to the fixed
campaign window (CAMPAIGN_START/CAMPAIGN_END, 2026-07-01 to
2026-09-30) rather than "all" (which would pull in pre-campaign
June data). Its Posts Published excludes the repost and the one
non-campaign post, unlike a single month's own count, since a
capstone number should mean genuine campaign content. Monthly
Growth Rate anchors to the campaign's own start instead of a
calendar month's.
- monthly_report_notes.py: new NOTES["campaign-end"] executive_summary
synthesizing all three months — what each stage actually proved
(Carousel wins, a stable Academia/Research/Germany audience, one
real trigger found but not repeated), the campaign-wide best-posting
-day finding, and a new "recommendations" field: concrete, data-
grounded ideas for October and beyond (bring Carousel back, lead
with named pain points, move asks into direct outreach using the
21 already-engaged non-followers sitting in outreach_contacts.py,
track conversion as its own funnel, properly separate the day/
format confound next time).
- build_report_docx() renders "recommendations" as a new section when
present. app.py's download callback no longer blocks campaign-end,
and the live Reports tab gets a real (if intentionally simple)
preview via a new _render_campaign_end_summary(), built as its own
self-contained renderer rather than stretching the 7 per-month
insights pages (built around a single YYYY-MM, not a 3-month span)
to fit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Grounded in the actual per-post data (Real Eng % by post/format/
weekday) and post titles, not just the abstract pros/cons July
started with:
August (Find the Audience, Goal Met: Yes) — audience composition
answer (Academia/Research-heavy, Germany-heavy, Researcher/Scientist
seniority), the TXServices launch arc across 6 posts, Carousel
repeating July's win but a plain value-prop post (13/08) nearly
matching it, and the Tuesday/Thursday day-vs-format confound still
unresolved.
September (Find the Trigger, Goal Met: Partial) — 08/09's pain-point
post was the only one with real discussion and the month's best Real
Eng %, but that ceiling (18.75%) is the campaign's lowest yet, no
Carousel was tried all month, growth decelerated to the slowest pace
of the campaign (+11.1%), and the direct pilot-partner ask (17/09)
underperformed. Honest "Partial" verdict rather than forcing a Yes:
one trigger instance was found but not turned into a repeatable
pattern, and the numbers moved the wrong direction.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mckinley hand-edited July's report down to a single page and showed
exactly what an Executive Summary should look like: the deep dive's
actual findings pulled straight into prose (which post drove
engagement, the 14/07-28/07-30/07 trilogy story, best posting day and
its format confound, audience composition's three dimensions), the
Key Metrics table flowing right after the goal/verdict with no extra
heading, one chart (Follower Growth), and nothing else. Confirmed
directly: this is the whole report now, not a summary page ahead of
a longer appendix — readers go to the live dashboard for every other
table/chart/register the old 8-page version duplicated.
report.build_report_docx() rewritten from scratch to this shape, now
reading everything from an expanded "executive_summary" dict in
monthly_report_notes.py (goal_line, goal_met, growth_note, engagement
findings, the trilogy, best-posting-day note + caveat, the validation
gap, audience composition line + dimensions) instead of the old
pros/cons list — the function itself is now a pure renderer, every
month-specific fact lives in monthly_report_notes.py same as every
other narrative section.
build_report_pdf() and its reportlab machinery deleted entirely
(genuinely dead code — the only call site switched to docx already),
along with every helper that existed only to serve it. report.py
drops from ~1260 lines to ~300. August and September don't have an
executive_summary yet, so their reports fall back to Key Metrics +
Follower Growth + a note that the summary isn't written up, rather
than silently reusing July's content or crashing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A summary page that leaves out the actual numbers isn't a summary —
moved the Key Metrics table (and its Monthly Growth Rate note) up
onto the same page as the Executive Summary's goal/verdict/pros/cons,
ahead of the page break. A reader who only reads page 1 now gets
both the verdict and the real numbers behind it; everything else
(Core Message, Roadmap, Follower Growth, All Posts, deep dives) stays
as the optional detail past the break for whoever wants more than
one page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Directory grows 142→143. Full recompute of all six Study charts and
affected prose. Also traced and fixed a long-opaque figure: the
"students or trainees" count in Key Findings is Seniority Student/
Trainee + Postdoc/PhD Researcher combined, not Student/Trainee alone -
relabeled now that the derivation is confirmed, not just approximated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real Eng % is Mckinley's own per-post hand audit (name-level Likes/
Comments/Reposts, Team excluded one person at a time). Avg. Eng. Rate
is LinkedIn's own daily page-level aggregate with no name-level
breakdown at all, so there's nothing to audit it against and no way
to exclude Team from a single blended daily number the way it can be
excluded from one post's own Reactions list. That's not a pending
methodology question, it's just two genuinely different metrics, so
every "Avg. Eng. Rate" label (Overview, Roadmap insights, Reports tab
KPI row, PDF/docx tables) now reads "raw, unaudited" instead of
leaving the distinction implicit. Removes the now-resolved item from
July's Executive Summary cons and August's outstanding list.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A standalone first page ahead of any detailed numbers: the month's
goal (from the Roadmap), a Yes/No/Partial verdict on whether it was
met, and a Pros/Cons list, color-coded (green/red/amber). Hand-
authored per month in monthly_report_notes.py's new
"executive_summary" key, same pattern as the other narrative
sections, since "did we meet our goal" is a judgment call, not
something computed from the export data. Renders only when present,
so August/September are unaffected until written up. A page break
separates it from the rest of the report so it stands alone as
page 1.
Drafted July's as a first pass grounded in facts already established
in this report (the emotional-vs-rational engagement gap, the poll's
zero engagement, the 27/08 audit gap) — needs Mckinley's review since
the goal-met verdict is inherently a judgment call, not something to
assert unilaterally.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
The Reports > July landing page's own "All Posts, July 2026" card
(_reports_posts_table, fed by report.compute_period_stats()'s simple
date-range filter, not app.py's REPOST_DATES/NON_CAMPAIGN_DATES-
excluding _reports_posts_f()) showed the 10/07 repost as a plain,
unmarked row with "Pending audit" — inconsistent with the dedicated
All Posts insights page one click away (struck through, "Excluded")
and confusing on its own, since "Pending audit" reads as an
unfinished job rather than a deliberate, permanent exclusion.
Fixed in both places this table renders: the live Reports tab
(app.py's _reports_posts_table) and its PDF equivalent (report.py's
"All Posts" table), both now applying the same pale-red/strikethrough
"Excluded" treatment already used by the register table and the
dedicated All Posts insights page. Added non_campaign_dates to
_build_report_ctx() so the PDF can apply the same exclusion set, not
just repost_dates.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Overview/Content tab's Posts table labeled every unaudited Real
Eng % as "Pending audit", including the 10/07 repost and other
REPOST_DATES/NON_CAMPAIGN_DATES posts that are deliberately never
audited (see engagement_audit.py) rather than simply not gotten to
yet. That read as an incomplete job rather than an intentional
exclusion. Those dates now show "Excluded" instead, matching the
wording already used in the Reports tab's All Posts and Core Message
treatment of the same dates. Fixed in both the initial table render
and the month-dropdown callback path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Month Start and New Followers already existed, but neither answers
"how many followers do we actually have as of this month" without
the reader doing the addition themselves. Adds a third tile, Total
Followers (month end) = Month Start + New Followers, placed between
them so the row reads Start -> New -> End as one story. Pinned to
that specific reporting period like Month Start already is, so it
never drifts with "today" the way the tile removed earlier did.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Track #6's a677056 removed 5 non-follower records (Philipp Brandt,
Arunangshu Debnath, Simon Sebold, Chen Shen, Martin Uhlig) that had
contaminated the 147-record set my previous recompute (572887b) was
built against. Namrata Sharma and Renske M. van der Veen are confirmed
genuine followers and were unaffected by the removal.
Full recompute of all six charts and all affected prose against the
corrected 142-record dataset, verified via script. Re-checked the two
figures flagged as drifted/unreconcilable in the prior commit:
1st-degree connections correctly returns to 6%/94% at 142 (the 5%/95%
was an artifact of the contaminated 147 count); "Research/Engineering
readers 38%" still can't be reconciled against any live field
combination and stays flagged as a pre-existing stale figure.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mckinley checked LinkedIn directly and confirmed Arunangshu Debnath
does not follow the Page, despite being added to the Follower
Directory. Root cause: this session added 15 people to the Directory
straight from post-engagement audits (08/09, 15/09, 22/09) without
verifying follow status — the exact mistake outreach_contacts.py's
own docstring warns against ("assumed every remaining entry was a
real follower"). A separate session (Demographics, which owns the
Directory) picked up 5 of these placeholders this morning, enriched
them further, and recomputed Follower Study's charts against the
resulting inflated 147-record dataset.
Moves all 15 people (Arunangshu Debnath, Simon Sebold, Martin Uhlig,
Chen Shen, Philipp Brandt, Dr. Sonia Utermann, Dr. Jefferson do
Rosario Stelzer, Sani Harouna-Mayer, Shrouk Ehab, Harish
Purushothaman, Mallika K., Sebastian Sell, Azat Khadiev, Ali A.,
Maximilian Harr) from the Directory to outreach_contacts.py, where
engaged-non-follower records belong. Directory count: 156 -> 142
(140 baseline + 2 legitimate new followers the Demographics session
added this morning, Namrata Sharma and Renske M. van der Veen, not
yet reflected in the Sep 26 export). Follower Study's charts/prose
in assets/follower_study.html are now stale against the corrected
dataset — that file belongs to the Demographics session, flagging
rather than editing it here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the 4 missing sections (features_shipped, data_quality,
follower_study, outstanding) for August's End of Month report,
sourced from August's actual commit history (Reports tab build-out,
GOTO Messaging Handbook, Engagement Composition polish, Real Eng %/
CTR % formula callouts, follow-date corrections, the Mihail Petev
repost correction, git-tracking the Follower Directory for the first
time, and the 107-to-126 follower growth). August's audit data was
already complete; this was the only piece blocking its report.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real Eng % audit for September's final post, 22/09: 24 total
reactions (likes A20/T3, TXproducts Page like excluded entirely),
no comments, reposts A0/T3 (Mckinley Black, Peter Gaal, Aleksandra
Kotenko, all internal). Adds 10 new Follower Directory entries
surfaced by this post (Dr. Sonia Utermann, Dr. Jefferson do Rosario
Stelzer, Sani Harouna-Mayer, Shrouk Ehab, Harish Purushothaman,
Mallika K., Sebastian Sell, Azat Khadiev, Ali A., Maximilian Harr)
and extends engagement_notes for 7 existing repeat engagers.
Every September campaign post (03/09, 08/09, 15/09, 17/09, 22/09)
is now audited — September's Real Eng % / Follower Study data is
complete, unblocking that month's outstanding narrative sections.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real Eng % audit for September's 17/09 post: likes A1/T2 (Johannes
Blum, shown as "Johannes Tenten" on this post but the same Hi-Acts/
DESY Innovation Manager as every other audited post, is Audience;
Aleksandra Kotenko and Mckinley Black are Team), no comments, and
reposts A0/T3 (Peter Gaal, Aleksandra Kotenko, Mckinley Black, all
internal). Extends Johannes Blum's Follower Directory engagement_notes
to include this post. Only 22/09 remains unaudited for September.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Directory grows 145→147 (2 new: Namrata Sharma, Renske M. van der Veen,
both Helmholtz-Zentrum Berlin) plus classification completed for 5
followers the End of Month Report session (track #6) added directly
via post-engagement audit but correctly left flagged as pending
(Philipp Brandt, Arunangshu Debnath, Simon Sebold, Chen Shen — all
DESY/academia-affiliated researchers; Martin Uhlig left unclassified,
two real candidates found, couldn't confirm which without a screenshot).
Full recompute of all six Study charts and all affected prose against
the live 147-record dataset. Caught and fixed two long-stale figures
along the way: the "1st-degree connections" stat had drifted to 7%/93%
across several past additions when the data actually rounds to 5%/95%;
flagged a separate "Research/Engineering readers 38%" figure as
unreconcilable against any live field combination, left un-recomputed
rather than guessed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
"Total Followers (as of today)" was confusing on a monthly report
read days or weeks after the month closed, since the live/global
follower count keeps drifting past whatever month is being reported
on. Month Start already gives the one followers figure that's
actually pinned to that specific reporting period, so the redundant
tile is dropped entirely rather than repurposed. Live dashboard tabs
outside Reports are unaffected and keep showing the live total.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real Eng % audit for two more September campaign posts: 08/09
(likes A3/T3, comments A2/T1, reposts A0/T3 - Arunangshu Debnath's
reply thread was the standout) and 15/09 (likes A9/T3, no comments,
reposts A0/T4). Extends engagement_notes for five repeat engagers
(Johannes Blum, Marc Thiry, Dr. Palvan Seyidov, Katrin Schmidt,
Sabine Brock) and adds five placeholder entries (Arunangshu Debnath,
Simon Sebold, Martin Uhlig, Chen Shen, Philipp Brandt) pending full
profile details.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LinkedIn's export has no caption text for this post, so it fell back to
"(no text — image/video-only post)" instead of its actual content, "Come
meet us at SNIB2026" — looked like a missing/unaudited post even though
it was already fully audited underneath.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sabine Brock is the only audience like; the rest (likes and all 3 reposts)
are internal (Daniel Schmidt, Aleksandra Kotenko, Mckinley Black). Page's
own link-drop comment excluded as usual. This closes out August's
post-level auditing entirely, 4 real gaps remain campaign-wide, all in
September (08/09, 15/09, 17/09, 22/09).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Directory grows 139→140. Full recompute of all six Study charts and
Key Findings/limitation/messaging-takeaway prose. HZB already
established as a Research & Collaborations partner via the Simone
Vadilonga precedent, applied consistently here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Web lookup confirmed INFOMOTION GmbH is HQ'd in Frankfurt am Main;
Berlin was his personal location, not the employer's HQ. Same
personal-location-vs-employer-HQ split already documented for
Rajkumar Rengaraj and Maximilian F. Company City field corrected,
Personal Location stays Berlin, confidence on the country/city
assignment downgraded from High to Medium (101/18 split, was 102/17).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Directory grows 138→139. Full recompute of all six Study charts and
Key Findings/limitation/messaging-takeaway prose. INFOMOTION is a
German data/AI consultancy, no fit for the more specific sector
categories, so classified as Other named company.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Directory grows 137→138. Full recompute of all six Study charts,
Key Findings/limitation/messaging-takeaway prose, and the Notable
Clusters table (University of Hamburg row 4→5, Hamburg/DESY cluster
distinct headcount 29→30). Classified as a plain-university case
(Academia industry value), consistent with other Uni Hamburg students.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Directory grows 136→137. Full recompute of all six Study charts,
Key Findings/limitation prose, and the methodology log. No company
listed on his profile, so Sector/Industry stay blank/Unknown per the
established no-guessing rule.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mckinley provided the remaining 8 URLs directly (9 records, since the
two Mckinley Black entries needed separate URLs for their two real
LinkedIn identities), closing out the gap the web research pass
couldn't resolve on its own (namesake collisions, internal accounts).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Web-searched each of the 26 followers with a blank linkedin_url (mostly
the batch added via individual screenshots rather than a bulk export,
which never captured the profile URL). Applied 17 confirmed matches
(verified against name + company + role, not guessed from name alone).
Sabine Brock's URL was derived from her public post slug (linkedin.com/
posts/sabinebrock_...), not a directly-confirmed profile link - flagged
as slightly lower confidence than the rest.
9 remain unresolved: too many namesakes with no way to confirm
(Iryna Platonova, Almira Tolstova, Francesco Marzari, Kilian Frank,
Rishabh Moolya, Dr. Ravinder Reddy Mogulla, Sourav Kumar - blank
profile to begin with), plus 2 internal TXproducts people (Mckinley
Black, Niklas Meidow) better sourced directly than via web search.
Side finding, not yet acted on: search results show Bernhard H. is
CEO of XPLORAYTION GmbH, not an unspecified individual contributor as
currently classified (no title was visible on his original screenshot).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Outreach researched all 39 blank-Industry "Other named company" records
against Twenty's closed Industry enum. 2 genuine enum hits (Stuart Hayes/
European XFEL -> Research & Collaborations, Eleni Mumtsidu/SGS -> Device
Testing), 35 correctly stay Other with a researched industryOther value,
2 left blank as genuinely inconclusive (Muhammad Yousaf/Vital Group,
Iryna Platonova/UMeter - couldn't confirm actual company affiliation).
3 of the 35 flagged as domain-adjacent worth a second look: Bernhard H./
XPLORAYTION GmbH, Alice Autuori/Cycle GmbH, Valentina Shumakova/UltraFast
Innovations GmbH - all synchrotron/ultrafast-laser-adjacent businesses,
potential peers or competitors, not routine "Other" cases.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CRM Research/Outreach caught this in review: the guidebook §8 spec says
notes should be "just the original bucket label, no explanation," but my
last edit put verbose reasoning in these 4 notes, including one that
claimed a specific Mckinley confirmation inline. Fixed to the terse
"LinkedIn Seniority: <bucket>" format the guidebook itself examples.
The reasoning for each call stays in git history, not in CRM-facing data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed by Mckinley 2026-09-20: when a title has a "Senior"/"Sr." qualifier
AND an unambiguous higher-authority base role (Manager, named Group/Ecosystem
Leader), resolve to the highest authority implied by the full title, not
mechanically to IC-Senior/Lead/Principal. Corrected: Dr. Eleni Mumtsidu
(Sr. Business Development Manager), Veronika Reichboth (Innovation Ecosystem
Leader), Bridget Murphy (two named Group Leader titles outrank her secondary
"Senior Research Scientist" credential), Dr. Ravinder Reddy Mogulla (Sr. Dy.
General Manager — confirmed correct, was flagged as a possible rule conflict,
Mckinley confirmed Director is right).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Additive only, per Mckinley's direction not to disturb the Study page's
existing Seniority/Function/Sector taxonomy. New fields prep a clean
Twenty migration export per the CRM Research data model guidebook §8:
- roleLevel: derived from each person's raw job title text via the
guidebook's §1 decision-order rules (not copied from the Seniority
bucket, since it's coarser than Twenty's 9 tiers). Ambiguous cases
left blank or fallback-mapped with a roleLevelNote explaining why.
- department: direct copy of Function, except Admin/Assistant and
Executive/Management (Role Level signals, not department names).
- industry / industryOther: Sector reclassified per-record against
Twenty's Industry enum (Academia/Research Institute split into
Research & Collaborations vs Academia vs blank; Semiconductor/
Aerospace/Defense split per-company; Photonics/Laser/Optics direct;
TXproducts internal routes to Category=Internal, not Industry).
Medical/Life Sciences (not covered by §8) mapped to Other with
industryOther text, flagged back to CRM Research as a spec gap.
"Other named company"/Unknown/no-company buckets left blank per
§8's own no-guessing default.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Directory grows 134→136. Full recompute of all six Study charts,
Key Findings/limitation/messaging-takeaway prose, and the Notable
Clusters DESY row/footnote (10→11 linked entries, 28→29 distinct
Hamburg/DESY cluster headcount).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Notable match for TXproducts' own space: XPLORAYTION provides synchrotron-
based analyses as a service to industry and academia, same business
model category. No explicit personal title stated, so Seniority stays
Other Individual Contributor rather than assuming a founder/executive
role from tagline alone. Full chart/prose recompute across all six
charts, sums verified.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Kilian Frank: PhD physicist (X-ray scattering), no current employer named,
classified as research-adjacent per the Marc Thiry precedent. Rishabh
Moolya: Master's student, University of Hamburg. Artem Zaidman: Research
Assistant at DESY (tagline-over-logo precedent), 12th DESY-linked entry.
Also fixed a pre-existing drift in the Notable Clusters table found while
auditing it for this batch: DESY row's count was correct but Cecilia
Farias Marchant's role was never added to its note text, and the row-sum/
distinct-headcount footnote had accumulated a real error (claimed two
cross-row dual-affiliations, only one exists). Rewrote the DESY row from
the underlying data directly and corrected the footnote (29 row-sum, one
dual-affiliation, 28 distinct headcount, was incorrectly stated as 26).
Full chart/prose recompute across all six charts, sums verified.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
10/07 was fully filtered out of the Engagement Composition table by
_reports_posts_f() (correct for calculations, but invisible to a reader).
Now it appears struck through with a pale-red background and "Excluded"
written next to it, same transparency treatment Core Message's register
table already gives it — visible and clearly marked, not hidden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Was framed as a big "backfill full history or accept the gap" decision;
now that pre/non-campaign posts are properly excluded, the real remaining
gap is just one missed post (27/08) plus the newest post not yet sent over
(08/09) — never a large historical question.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The branding campaign only runs July-September; 19/06 and 24/06 predate it
entirely, same treatment as 02/09. Added to NON_CAMPAIGN_DATES.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Decided against the 3-7 day match-window/weekly-bucket idea for now: at
current follow/visit volumes, a week can have as few as 1-3 real follows,
too small for weekly aggregation to meaningfully reduce noise over daily
data. Moved from "outstanding" to "data_quality" as a recorded decision,
not silently dropped. Revisit if monthly volumes grow enough for the
technique to pay off.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Added NON_CAMPAIGN_DATES (starting with 02/09), excluded the same way
REPOST_DATES already is — a real TXproducts post, but not part of the
24-post branding campaign, so it shouldn't skew "what worked" findings
that are specifically about the campaign's own content.
Also closed out July's "end-of-week boss report" outstanding item per
Mckinley: discarded, no longer tracked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Full profile card confirms Germany/Hamburg, merging him back into the
Geography chart (Germany 69->70, Hamburg 39->40) and restoring Geography's
population to match Sector's exactly (112, both up from 111). No open
items remaining in the follower dataset.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cross-checked today's additions against LinkedIn's own New Followers list:
"Joo William" corrected to William Joo (name order), fuller title added
(Chairman of KAMUG, Professor at Inha University). Added Davood Momeni
(Founder, XDefine, semiconductor reliability/SiC/WBG) - C-Level/Founder,
Executive/Management, Semiconductor/Aerospace/Defense. His location wasn't
shown on the profile card, so he's included in Sector's 112-person
population but explicitly excluded from Geography's 111-person population
until confirmed, flagged in the report rather than guessed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Niklas Meidow: confirmed internal TXproducts employee (Product Engineer),
5th internal follower. Joo William: Inha University Professor, South Korea
(first SK follower), Director/Head/Principal per the Professor precedent.
Francesco Marzari: PhD Student at Università di Trento, Italy (crosses
2-follower threshold, own Geography row), Postdoc/PhD Researcher per the
PhD-candidate precedent. Full chart/prose recompute across all six charts,
sums verified.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Hand-audited against LinkedIn's own Reactions/Comments/Reposts lists.
Denny Droßmann is a new engaged non-follower; Marc Thiry's repeat-engager
streak extends to 3 posts (16/07, 28/07, 03/09).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ablimit Ablez: Los Angeles, no current employer named (Chiba University
is his PhD alma mater, not current company), classified Unknown/Engineering-
Technical/no-company-listed. Cecilia Farias Marchant: DESY (11th DESY-
linked entry, 27th Hamburg/DESY cluster member), Senior IC/Lead, Marketing/
Comms/Ops. Full chart/prose recompute across all six charts, sums verified.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes "no centroid for X" console warning surfaced on the Aug 31 export
that closes out August (a new US-metro follower without a country suffix
in their location string, same pattern as San Francisco Bay Area and
Greater Madison Area).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This was the long-standing unnamed "specific gap" flagged earlier — now
resolved and recorded in features_shipped, since it was never actually
written down as an outstanding item in the first place (tracked only in
conversation until now).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per feedback: a monthly report showing the same near-identical cumulative
tables every month, marginally bigger each time, isn't earning its keep.
Fixed by making the cumulative views themselves show where this month's
people landed, instead of adding that only as a separate summary:
- Account Clusters now annotates each company's growth inline (e.g. "DESY:
5 (+3 this month)") instead of a flat all-time count with no monthly
story.
- Notable Contacts highlights rows added this month in place, in context
with everyone else at that seniority level, instead of only being named
in a separate bullet list above.
- Dropped the cumulative Sector/Function/Seniority/Country table entirely —
Audience Composition already owns that exact breakdown for the month,
so repeating the all-time version here was pure duplication with no
monthly-specific value.
- Kept the Sector x Seniority crosstab as pure cumulative reference (still
needs the full ~100+ scale to mean anything, no individual-level "new"
story to tell).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Feedback: showing "102 followers to date" as the headline of a monthly
report answers the wrong question — a reader wants to know what happened
in the month the report covers, with the cumulative numbers as supporting
context, not the other way around. Audience Composition already did this
correctly (month-scoped "22 new followers" by design); Follower Study now
leads the same way: which new Notable Contacts joined this specific month,
and which Account Clusters grew, computed live for whatever month is
selected. The existing cumulative "bimodal seniority"/"shallow-network"
findings stay, just demoted to a clearly-labeled "full picture, to date"
section beneath.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>