All projects

analytics · full-stack · llm · marketing

Complete · 2026

BLOXHUB Analytics Dashboard

Nordic Hub for Sustainable Urbanization, Copenhagen — Marketing Analytics Intern

BLOXHUB Analytics Dashboard

BLOXHUB is a non-profit in Copenhagen, Denmark described as the Nordic hub for sustainable urbanization. BLOXHUB operates as a membership organization of architects, engineers, municipalities, corporations, and startups primarily in Europe. It operates as a super-connector for those in the sustainability and urban space, often taking projects with cities to improve their urban loneliness, well-being, housing crisis, climate adaptation plans, or generally implement circular economy ideals. I spent the summer of 2026 there as a marketing analytics intern. I worked primarily with the communications team, who publish on LinkedIn constantly but had no reliable way to tell what was working, which is a specific and ordinary problem: LinkedIn's native admin dashboard merely offers basic social media insights and metrics such as impressions, CTR, engagement rate, and demographics, with the option to export four spreadsheets of this data. There are no answers to telling you what specifically worked or why specific posts performed better than others.

The questions the team actually had were not how many impressions did we get, but instead: Does opening a post with a question beat opening with an announcement? Do event recaps beat event invitations? Are we posting about the themes we say we care about, or are we skewing more towards specific ones? LinkedIn does not answer any of those questions. If the communications team wanted to answer those questions, they would have had to manually tag each post, and when you post constantly, it's not feasible nor efficient. So the dashboard I built classifies them with an LLM model and answers these deeper questions.

Essentially, the dashboard ingests and classifies posts from LinkedIn spanning however far back you would like, and tags them accordingly. In the LinkedIn export, all the posts from LinkedIn during that specified time period are included, where the rows are each individual post, and the columns are information on the posts, including the text from that post. From here, the dashboard essentially ingests and classifies these posts across six dimensions, including its hook style, CTA, tone, structure, length, content type, and canonical company theme. Findings are then created when the classified post text is aggregated, allowing us to answer questions such as "In the past 30 posts, do posts with data-driven hooks have higher engagement than question based hooks?" or "Does our audience have higher engagement with short-form content or long-form content?" or "As a company, what specifically are we posting the most about?"

The dashboard's import screen after an upload: a drag-and-drop area, the four LinkedIn exports each tagged by type, a parse summary counting competitor pages, daily metrics, demographic breakdowns and posts classified, and reference cards naming the sheets inside each export

I. Four exports in, sixty-three posts classified, none failed

Part I — Deterministic facts, narrated

When designing the dashboard, I was wary of how LLMs can hallucinate, and so I leveraged deterministic code where one rule sits above everything else in the codebase. Deterministic code computes all facts, numbers, selections, and rankings. The model only classifies posts and narrates values it was handed. It never picks an example post or chooses a figure if there is a recommendation being made, and never decides which post won if there is a comparison being made. For context, deterministic code essentially always produces the same output and follows the same steps every single time it runs, as long as it is given the same input. Overall, the deterministic code and the LLM work together to create data-backed recommendations and insights that prevent hallucination while leveraging the power of the LLM's classification abilities.

Here are some examples where the deterministic code (or guardrails) can be enforced. Recommendation cards name which statistic to show and the system, via the deterministic code, fills in the required figure, so an explanation of a chart and the chart itself do not disagree with each other. Posts that the model nominates as specific examples of having a trait when creating recommendations are validated against previously stored classifications and backfilled deterministically, so a recommendation about question hooks cannot illustrate itself with a post classified as anything else. A hook labeled "Data-Driven" is re-classified unless a digit or a percentage actually appears in the post's opening, taking into consideration case-insensitivity (ex. "Eight" versus "eight") and free variation (ex. "8" versus "eight"). A post will not be classified as call-to-action if it cannot provide evidence of the direct quote in the post text. Whether a post has a link attached isn't determined by an LLM model; instead, it is derived from parser columns. And a separate guard blocks any suggestion from the LLM about images, videos, or carousels, because the system and LinkedIn export only ever sees post text and cannot know what media a post carried. Each guard is narrow, added after countless rounds of iteration to increase accuracy. Each one names a specific way the LLM output could have been confidently wrong.

A high-priority recommendation card: the written advice, a why-this-works note, a supporting-data block carrying the pre-event, invitation-hook and baseline engagement rates, and the three example posts the finding rests on

II. The card names the statistic; the system fills in the figure

The page and content performance section: a date range filter, four summary tiles for impressions, engagement rate, new followers and page views, and a daily content volume chart with impressions selected

III. Every tile carries its own caption saying what it counts

One aspect of building this dashboard I was surprised by was how much of LinkedIn's data looked initially like a bug, throwing off some outputs. For example, the daily values are attributed to the day the activity occurred, so a spike can come from a post published months earlier. A day can also report negative engagement when there are deleted comments or undone reposts. Some location strings arrive with the country repeated, which is why a row can read as "London Area, United Kingdom, United Kingdom".

The same chart with three metrics overlaid and normalizing switched on, rescaling each to a 0 to 100 index, sitting above the notes on activity-date attribution and negative daily values

IV. Rescaled to an index, because the shapes are the comparison, not the axis

Daily new follower growth split organic versus paid, daily page visitors, and a followers-by-location breakdown with a donut chart led by Copenhagen

V. Reported as LinkedIn reports it, repeated country names and all

Part II — What the classifier is allowed to say

There are six layers per post: content category, theme, tone, hook style, structure and length, and calls-to-action and links. These layers include closed lists, meaning a post cannot be classified as both "data-driven" and "data driven." Every label the model returns is coerced onto the fixed enum (type of data using predefined values) by nearest match before it is stored; classifications stick to the closest legitimate value instead of creating hundreds of new categories.

The five themes came from BLOXHUB, not from the model. Circularity, Housing, Urban Regeneration, Wellbeing and Climate Adaptation are the organization's own strategic themes.

The content distribution matrix: the five canonical themes crossed with six content categories, each cell carrying a post count and a share of that theme, shaded by volume

VI. Volume only — no engagement judgment anywhere in this view

Not everything maps into a company theme, which is the reasoning for this "Other" section. For recurring programs that are posted about, such as UNLEASH, Masterclass, or the AEC Hackathon, they are matched on keywords in the post text and moved into a separate heatmap matrix, with the stored classification left untouched. These are programs, not topics, and separating them at this section keeps the canonical heatmap pure.

The Other matrix: posts that map to no canonical theme, grouped into community, other posts, job opportunities, newsletter, masterclass, UNLEASH and AEC Hackathon rows

VII. Programs, not topics — held apart so the canonical matrix stays clean

The layer charts are the payoff. Each layer is a tab, each value a bar, each bar carrying its own post count so a pattern resting on two posts can be read as the thin evidence it is. The classification reference sits underneath, collapsed, listing every value the model is permitted to assign — because a reader who cannot see the vocabulary has no way to judge whether a label means what they think it means.

Layer 1, content category: a bar chart ranking post types by average engagement rate, with a note naming the most-used category and the collapsed classification reference beneath

VIII. L1 — what type of post

Layer 3, tone: a bar chart ranking visionary, conversational, promotional and academic by average engagement rate, with a written read on why conversational led

IX. L3 — how it sounds

Layer 4, hook style: a bar chart ranking invitation, data point, insight, question and story openings by average engagement rate, with a written read on why invitation led

X. L4 — how it opens

The winning formula panel: content category and hook style pairings ranked against the organic baseline, each naming the number of posts it rests on, above three named combinations

XI. Ranked on engagement rate alone, and the panel says so

Month over month: a line naming the partial months excluded from the comparison, four comparison tiles against the previous month, and a multi-month trend chart

XII. Partial months are named and excluded rather than quietly averaged in

Two heatmaps of post counts per calendar month — the five canonical themes above, and the Other groupings below

XIII. Canonical themes above, everything else below

Part III — Measuring the classifier, and what it got wrong

I tested the accuracy of my dashboard's classification by utilizing a test dataset, validation dataset, and training dataset. By the end of my internship, tone classification was put at roughly 80 percent accuracy and hook style at about 92 percent, with Question the most over-assigned label of the set. Content category and links were close to perfect, which makes sense as one is nearly structural (content category) and the other is derived from parser columns rather than LLM judgment (links).

One issue I initially ran into was classifying too many posts at once. In the current iteration, classification runs in batches of eight posts, reduced from an initial fifteen. At fifteen, the per-post output grew long enough that the response was occasionally truncated (cut) mid-JSON, which silently returns fewer classifications than posts. At eight, all posts were classified properly while being mindful of the resources it takes for classification (time + API cost).

Part IV — Built to be handed over

A small point to note is the constraint that shaped most of my decisions was interning for only 10 weeks, and the maintainers of the dashboard would not be natively technical. Because of this, I designed the dashboard in the following manner: Every upload creates a new dataset, activates it, and deletes the rest. There is always only one active dataset. I leveraged markdown files to guide the maintainers for any future changes they would want to make using AI agents. I also used GitHub Actions to automate and verify pushes to main, and ensured that connections between Vercel, Supabase, and GitHub all aligned.

Part V — The rest of the internship

AI literacy. As part of my internship, I educated the BLOXHUB team on how to best leverage AI as a tool. I taught the team how different LLM models work, how many of these AI tools have tools within them, such as deep research capabilities or web search capabilities, and generally explained some tips and tricks when prompting AI. For example, to be detailed with your prompt and to specify as much as you can, whether it's:

  • what you've uploaded as context
  • what your goal is
  • what you would like the output to be
  • the style that you would like
Presenting the AI literacy session to the BLOXHUB team, with the prompt-example slide on screen comparing a one-line request against a fully specified one

XIV. The same request, written twice — the slide everyone remembered

Competitor benchmarking. I looked at how comparable innovation hubs operate, such as Station F in Paris, MaRS in Toronto, Tech Barcelona, Here East and Plus X in the UK, Marineholmen in Bergen, and the New York campuses at Brooklyn Navy Yard and Industry City. I offered four recommendations: a predictable recurring events calendar, a Nordic Urban Innovators recognition campaign, an annual public impact report with quantified outcomes, and curated member discounts on legal, IP and software services, which raises the value of membership without touching the price or the building.

A thought leaders list. I created a list of individuals and organizations worth tracking and engaging, split into three lists because outreach tactics differ with various audiences: individuals for general audience growth, communities/organization pages for the same, and individuals for specific lead generation.

Looking out over the canal at Christians Brygge in central Copenhagen from the BLOXHUB terrace

XV. The view from the terrace, central Copenhagen

A note on what is shown here. The screenshots are the live internal dashboard, running on sample data of a real LinkedIn Export. The source repository is private to BLOXHUB and the deployed app renders empty without an upload, so neither is linked. Nothing here includes individual team members' performance, although there is a portion of the dashboard dedicated to this. Photographs by JoLyn King for CIEE.