Skip to content

Monday, November 9, 2026

Apache Ossie: what a common format for metrics can carry, and what it can't yet

I recently added a semantic layer to a small dbt project: four metrics, each defined once, with a description and an owner. Then the obvious question came up. Those definitions live in dbt's format. If the clinic also used a BI tool, a warehouse with its own semantic views and an AI assistant, would each of them need its own copy?

Today the answer is mostly yes. Every tool has its own way of describing a metric, so the same "new patients" gets written three or four times, and sooner or later one copy drifts. Apache Ossie is the attempt to stop that.

What it is

Ossie is a specification, not a product. It describes a semantic model in YAML or JSON: datasets, the fields in them, the relationships between them and the metrics on top. Any tool that can read and write that format can, in principle, receive a definition from another tool instead of having it rebuilt by hand.

It started in September 2025 as the Open Semantic Interchange, launched by Snowflake with dbt Labs, Salesforce and other companies. The first version of the specification was published in January 2026 under an Apache 2.0 licence. In July 2026 the project entered the Apache Incubator and changed its name to Apache Ossie, partly because "OSI" was already taken by the Open Source Initiative. The specification didn't change with the name.

Being in the incubator matters more than the name. The project is now run under the Apache Software Foundation's rules, not by one vendor, which is what you want from a format that competing vendors are supposed to share.

What I checked

I didn't want to write about it from the press releases, so I read the specification and the reference converter for dbt, with my four metrics in mind. I haven't run the converter yet, so this is what the documentation and the code say, not a test.

A metric in Ossie has 

- a name, 

- a SQL expression, 

- a description, 

- a data type and 

- an ai_context field 

where you can add synonyms, instructions and example questions for AI tools. That last field fits my case well. I could tell an assistant that "new patients" means people, not forms, in the definition itself, and not in a prompt someone has to remember to write.

Ratios come through. The converter writes my readmission rate as one expression, the numerator divided by the denominator. What's lost is the link: in Ossie a metric can't refer to another metric, so the rate no longer knows it's built from readmissions_30d and eligible_discharges. If someone changes one of those, nothing tells the rate.

Some things don't come through at all. Conversion funnel metrics are dropped, as are metrics that depend on time windows or offsets. Time dimensions arrive without their granularity. The converter records each loss as a warning, which I appreciate: it says what it dropped instead of failing silently.

The gap I care about most is ownership. The specification has no field for an owner. In my project each metric belongs to a team through a dbt group, and the converter doesn't carry groups across. It carries a metric's meta as a vendor extension, so the owner could travel there, but then it depends on the tool at the other end reading dbt's extension. A definition that arrives without saying who agreed it is only half governed.

Is it ready?

Not for production, and the project says so. The released version is 0.1.1, from December 2025. The next one, 0.2.0, is still a draft with breaking changes, and the specification itself warns not to depend on it in production. The roadmap lists what's still open: derived and cumulative metrics, grain, reusable filters, and versioning and access control through catalogues.

There is a lot of support around it, though. The converters repository already has converters for more than twenty tools, including dbt, Snowflake, Databricks, BigQuery, Power BI, Sigma, ThoughtSpot and Cube.

What I'd do now

I wouldn't migrate anything to Ossie yet. But I'd write metric definitions as if they were going to travel. That means a description on every metric that says what it counts and what it doesn't, owners kept in the catalogue and in meta, and as few tool-specific tricks as possible in the expressions. Those habits pay off today, inside one tool, and they'll make the move easier when the format settles.

The next step for me is to run the converter on the clinic project and compare what comes out with what I've written here. I'll update this post with the result.

Sunday, November 1, 2026

Consent Mode on more than 100 websites: how to check that consent works

In the first half of 2026 I led the consent management platform (CMP) migration at Ingersoll Rand, covering 60 brands and more than 100 websites. This post is about the part that decides whether the data can be trusted afterwards: checking, region by region, that Consent Mode does what the banner promises.

The situation

I joined Ingersoll Rand in January 2026 as Web Data Analyst in the Customer Data Analytics Center of Excellence. The web estate covered 60 brands and more than 100 websites in every region, China included, spread across 5 Google Tag Manager containers. Part of my role was leading the move to a new CMP, and that included validating Google Consent Mode under GDPR and CCPA.

Those two laws start from opposite defaults. Under GDPR and the EU cookie rules, analytics and advertising storage stay off until the visitor opts in. Under CCPA, measurement can start and the visitor can opt out of the sale or sharing of their data. One banner configuration copied to every site cannot satisfy both.

What I did

I led the project with the development teams who implemented the new CMP on each site. We worked through the regional differences one by one and applied what each privacy regime permits, with no single global setting.

My own focus was validation, because a banner on the page says nothing about what the tags behind it are doing. I validated Consent Mode with Chrome DevTools and Google Tag Assistant. Tag Assistant shows the consent state each Google tag sees when the page loads and after the visitor's choice. DevTools shows the network requests themselves, so I could check what the tags sent before any choice and after an accept or a reject.

What I learned

In the United States, privacy law changes from state to state. There is no general federal law, and around 20 states had a general privacy law in force by January 2026, according to the IAPP tracker. Some of them, California and Colorado among them, require sites to honour the Global Privacy Control (GPC) signal that the browser sends. A single "US" setting in the CMP is too coarse for that.

China has the Great Firewall, and it affects Google tags. In GreatFire's tests the Google Analytics collection endpoint suffers interference from mainland China, while the Tag Manager domain was reachable in its October 2026 test. A container can load and set consent correctly while some of its hits never arrive, and Consent Mode has no control over that.

In Asia, each country has its own legislation, and a single "APAC" region hides large differences. Vietnam has required explicit consent since 1 January 2026 and does not accept implied consent, while Japan allows sending user data to third parties with notice and an opt-out.

What I would do today

If I started the same project now, I would agree these points with legal before the first site went live.

  1. A consent matrix with one row per region, using ISO 3166-2 codes in the region parameter of the consent default. Google applies the most specific match, so US-CA overrides US (Google's consent mode guide). Each row records the default for the four Consent Mode v2 signals and who approved it.
  2. GPC in the states that require it: the CMP reads the signal and updates ad_user_data and ad_personalization to denied. In September 2025 the California Privacy Protection Agency and the attorneys general of California, Colorado and Connecticut ran a joint sweep on GPC (CPPA), so it is enforced in practice.
  3. denied by default where the law is opt-in: China under the PIPL, Vietnam, Thailand and India, whose main DPDP obligations apply from around May 2027.
  4. A collection endpoint that works from mainland China, such as a server-side container or a first-party analytics tool hosted there (it needs its own ICP filing). Forwarding that data to Google is still a cross-border transfer under the PIPL.
  5. The same test in DevTools and Tag Assistant after every container publish: accept, reject and no interaction, with the gcs and gcd parameters compared against the matrix and the consent default set in the Consent Initialization trigger. Consent Mode only controls Google tags, so other vendors need their own blocking in the CMP or in GTM.

There is also a regulatory reason to keep that matrix up to date. On 21 October 2026 EU member states agreed the Council's negotiating position on the Digital Omnibus (IAPP). It updates the ePrivacy rules on cookies and lists cases where consent would not be needed, audience measurement among them. The Council's draft of 2 October also shortened to four months the wait before a site can ask again after a rejection (PPC Land). The European Parliament has not agreed its own position yet, so negotiations between the institutions cannot start. If a text close to this one passes, CMP set-ups across the EU will need new rows: which measurement qualifies for the exemption, and which still waits for consent.

A question to finish

If your legal team asked you tomorrow which tags fire before consent, on which sites and in which countries, how long would it take you to answer? If the answer involves opening containers one at a time, the consent matrix is where to start.

Monday, September 21, 2026

AI literacy under Article 4 is an adoption problem

Since July 2026 the AI Act asks providers and deployers to take measures that support their staff's AI literacy, without requiring any specific level from any individual. I learned how that kind of duty plays out from analytics adoption at JD Sports Group, where getting people trained was the easy part and keeping the habit was the real work.

What changed in Article 4

Article 4 has applied since 2 February 2025. The original text asked providers and deployers to take measures to ensure, to their best extent, a sufficient level of AI literacy among their staff. The Digital Omnibus on AI (Regulation (EU) 2026/1744, in force since 27 July 2026) rewrote it. Providers and deployers now have to take measures to support the development of AI literacy of their staff, the text adds that this does not require any specific level of AI literacy of any individual, and the Commission has to publish practical examples of how to comply (AI Act Explorer).

The Commission's AI literacy Q&A says there is no need for a certificate, that relying on the instructions for use is not sufficient, and that organisations can keep an internal record of their training and guidance.

Read quickly, the new wording sounds lighter. In practice it leaves each organisation to decide what support means for its people and how it will know whether the support is working. I had to answer the same question for analytics.

What analytics adoption taught me

At JD Sports Group I was product owner of a programme of 8 measurement projects. The result I value most from it is a measure of behaviour: adoption of analytics grew 70% year on year. Stakeholder satisfaction was 90%, which was good to hear, but adoption told us whether people used the data when they made decisions.

Two practices were part of the programme from the start. Each implementation came with an operational dashboard, so people learned on the tool they would use. When an implementation moved into business as usual, a short survey checked whether it met the need it was built for.

The checkout project taught me the other half. Customer Care logged the reason for each live chat in Salesforce, and the weekly report built on those reasons was still used in their meetings long after the project closed. As the team hired new people, the logging discipline dropped and the reasons became less reliable, while the report kept arriving every week. The plan had been a one-off handover, so I made periodic retraining of Customer Care part of how the report was maintained.

Why AI tools behave the same way

An AI tool launched with one training session is in the same position as that report. People who join after the session never had it, the tool changes with each model update, and the habits that make output safe to use fade quietly: checking a claim against its source, or knowing which customer data must never go into a prompt. Nothing breaks on the day the habit goes, which is why a course completion rate says little about it.

Day to day I use AI to automate repetitive tasks, and my own time goes on verifying the output before I sign it off. That verification step is the first thing I would teach.

What I would do today

  1. Start with an inventory of who uses which AI tools and for what. The role decides what literacy means: someone drafting customer replies with an assistant needs different knowledge from an analyst building a feature on a large language model.
  2. Put AI guidance into onboarding, and repeat it on a date in the calendar and whenever a tool or its use changes.
  3. Measure what people do with the tools. Two useful signals are the share of AI-assisted outputs that someone reviewed before they went out, and the incidents and near misses that people report.
  4. Keep a dated record of the support each group received. The Q&A asks for no certificate, and a record is the simplest way to show what was done and when.

Monday, August 17, 2026

DBT in action: I broke my own data on purpose to see if the tests would notice.

I built a small dbt project about a fictional clinic with Claude Code, an AI coding agent, and before any query was written the data had errors planted on purpose. The agent wrote the code. This post is about why the project is built that way and what the tests caught.

Most SQL portfolios show clean data and clever queries. My work has mostly been the opposite: the query was easy and the hard part was knowing whether the numbers underneath could be trusted. So I wanted a project where the data quality work is visible, next to the advanced SQL.

The setup

The project answers 14 business questions about a clinic, one dbt model per question: 30-day readmissions by department, daily bed occupancy, medication adherence, cohort retention, which chronic conditions appear together, and a few more. It runs on DuckDB, so anyone can clone it and run dbt build in a few seconds without a database server or credentials.

The data is synthetic. A Python script with a fixed seed generates 600 patients, 1,983 visits, prescriptions and lab results for 2025. Because the generator plants the errors, the project knows exactly what is wrong with the data. That's the point. When you know the true number of defects, you can check whether your tests find all of them.

What I planted

The generator adds the kind of problems I've met in real projects:

  • duplicated intake forms, the same person filling in the form more than once with small differences in name or email
  • two hospital stays where the discharge date comes before the admission date
  • a visit assigned to a physician who doesn't exist in the physicians table
  • three lab results with impossible values
  • encounters with no primary diagnosis, or with two

None of these are exotic. A duplicated form or an orphan ID is what you find in any CRM or GA4 export after a migration.

Keep bad rows, with a flag

The first decision was where to deal with bad rows. In the staging layer I keep every row and add flags such as has_valid_dates and is_plausible. Each final model then decides what to exclude, and a data quality scorecard counts what was excluded and why.

I prefer this to filtering bad rows early. If staging drops them silently, the readmission rate looks clean and nobody can say how many stays were left out. With flags, the exclusion is a visible decision that someone can question.

Tests that check the logic

dbt makes the usual schema tests easy: unique, not null, accepted values, relationships between tables. They caught the orphan physician straight away. But a schema test can pass while the logic is wrong, so I added tests for things that must always be true:

  • merged medication periods never overlap
  • the hospital total equals the sum of its departments
  • deduplicating intake forms doesn't lose any form
  • every patient falls into exactly one cohort

The project has 52 tests in total. The known defects are tested with severity: warn, so every run reports them without blocking the build. A new break fails. That split matters to me: a team stops reading alerts that fire every day for something already known.

The scorecard

The last model is a data quality scorecard with ten rules across completeness, validity, uniqueness, consistency and referential integrity. Each rule has a tolerance. Today it shows six rules failing, two within tolerance and two passing.

The biggest number is uniqueness: 117 of the 439 intake forms are duplicates, 27%. After resolving them, there are 322 people behind those forms. If anyone had counted new patients from the raw forms, they would have reported over a third more than the real figure.

One definition per metric

Clean tables don't stop two people from counting the same thing in different ways. The duplicated forms show how: someone in admissions counts forms and gets 439, someone in finance counts people and gets 322, and both put "new patients" on their slide.

A semantic layer is meant to prevent that. It sits on top of the dbt models and keeps the definition of each metric in one place: which table it comes from, which rows count, how it's added up and who owns it. A dashboard, a spreadsheet or an AI assistant asks for new_patients by name, and the SQL is generated from that definition instead of being written again each time.

I added one with MetricFlow, the engine behind the dbt Semantic Layer, with four metrics. new_patients is built on the deduplicated table, so it can only return 322. The form count is still there, because admissions does need to know how many forms came in, but it's called intake_forms_received and its description says it isn't a patient count. The other two are the 30-day readmission rate and medication adherence.

It goes after the tests, not before. A semantic layer on top of data nobody has checked just sends the same mistake to every tool at once. And so the metrics don't drift away from the tables, a script compares each one with the mart that answers the same question. If they stop matching, the build fails.

What has changed in the last year

Semantic layers aren't new, but two things changed recently. In October 2025 dbt Labs released MetricFlow under an open-source licence, Apache 2.0, so the engine I used here runs on a laptop with no paid account. And the vendors have started to agree on a common format. The Open Semantic Interchange, launched in September 2025 by Snowflake with dbt Labs, Salesforce and others, published its first specification in January 2026 and joined the Apache Incubator in July under a new name, Apache Ossie. The idea is that a metric defined once can be read by a BI tool, a warehouse or an AI agent without rewriting it for each one.

The AI part is why I care. If someone asks an assistant how many new patients we had in March, I want it to ask for new_patients, not write its own SQL. Without a definition to rely on, nothing stops it from counting the raw forms and answering with the 439.

What I'd do differently at work

In a real team I wouldn't set the tolerances alone. A 5% tolerance for duplicate forms is a business decision, and the people who use the number should agree it before the scorecard goes live. The same goes for the 30-day readmission window and the 80% adherence threshold. In this project they're variables in dbt_project.yml, so they can be changed without touching the SQL, but someone still has to own them. The metric definitions are the same: in the YAML each one belongs to a team, but at work I'd want a named person who agreed what "new patient" means before anyone builds a dashboard on it.

If you look after a dbt project, here's a small test you can run this week: pick one defect you know exists in your data and check whether any test reports it. If none does, that's where to start.

The project and its README are on GitHub: advanced-sql-healthcare-analytics. All the data is synthetic.

Monday, July 13, 2026

Travel funnels from the other side: Naver, Google Maps and reviews paid in gifts in Korea

Part of Travel funnels from the other side, a series on what I learn as a traveller about the booking funnels I usually measure.

In June 2026 I travelled around South Korea. After years of measuring how customers search, book and pay, it was the second trip where I watched myself do it in a market that works differently. The first was Chile and Brazil in 2024. Korea was harder, and more interesting for anyone who works in travel.

Finding my way without Google

Google Maps shows places in Korea, but it would not give me walking or driving directions. The reason is a long-running dispute over taking detailed map data out of the country (background on Wikipedia). In February 2026 the government approved Google's request under strict conditions, but in June the directions still did not work for me. So I did what Koreans do: I moved around with Naver Map and KakaoMap, and I searched on Naver.

Within a couple of days most of my trip happened on Korean platforms. The searches I made, the places I saved and the routes I followed never touched the tools a Western travel brand usually measures.

Booking and paying as a foreigner

Two things stopped me again and again. Many websites asked for a Korean phone number to verify the account, and my foreign card was rejected on several of them. I had seen the same pattern in Chile: the customer is there, wants to pay, and the form decides they cannot.

A gift for a review

The thing that surprised me most happened in shops and restaurants. Several of them offered me a small gift if I left a review on Google or on Naver.

Google's rules for Maps reviews do not allow this. Its Maps content policy lists offering incentives in exchange for reviews as fake engagement. I do not know how strictly Naver applies its own rules, and I am not judging the businesses, which were following what looked like a common local practice. As an analyst, what I noticed was the effect on the data: a rating built partly on rewarded reviews means something different from a rating built on unprompted ones.

What a travel company could take from it

If I worked on the data side of a travel brand selling into Korea, I would look at three things.

  1. Search demand outside Google. A brand that measures and bids only on Google sees part of the market. Naver has its own search ads and its own webmaster tool, Naver Search Advisor, and the queries that matter may only show up there.
  2. Review data as a data quality question. If ratings feed a ranking, a recommendation or an analysis that compares markets, they need context: where incentives are common, a 4.8 is a different number. Sudden jumps in the number of reviews for one venue are worth flagging before anyone reads the average.
  3. The checkout for foreign customers. Errors by form field, split by the visitor's country, show quickly when a phone verification step or a card check is turning away people who wanted to buy.

A question to finish

When you compare ratings or search demand across countries, do you know which platform each market really uses, and how its reviews were collected?

Embudos de viaje desde el otro lado: Naver, Google Maps y reseñas a cambio de regalos en Corea

Forma parte de Embudos de viaje desde el otro lado, una serie sobre lo que aprendo como viajera de los embudos de reserva que suelo medir.

En junio de 2026 viajé por Corea del Sur. Después de años midiendo cómo buscan, reservan y pagan los clientes, era el segundo viaje en el que me veía hacerlo en un mercado que funciona de otra manera. El primero fue Chile y Brasil, en 2024. Corea fue más difícil, y más interesante para quien trabaja en viajes.

Orientarse sin Google

Google Maps muestra los sitios en Corea, pero no me daba indicaciones para ir a pie ni en coche. El motivo es un conflicto que viene de lejos sobre sacar del país datos cartográficos detallados (contexto en Wikipedia). En febrero de 2026 el Gobierno aprobó la petición de Google con condiciones estrictas, pero en junio las indicaciones seguían sin funcionarme. Así que hice lo que hacen los coreanos: me moví con Naver Map y KakaoMap, y busqué en Naver.

En un par de días, casi todo mi viaje pasaba por plataformas coreanas. Las búsquedas que hacía, los sitios que guardaba y las rutas que seguía nunca pasaban por las herramientas que suele medir una marca de viajes occidental.

Reservar y pagar como extranjera

Dos cosas me frenaban una y otra vez. Muchas webs pedían un número de teléfono coreano para verificar la cuenta, y mi tarjeta extranjera fue rechazada en varias. Ya lo había visto en Chile: la clienta está ahí, quiere pagar, y el formulario decide que no puede.

Un regalo por una reseña

Lo que más me sorprendió pasó en tiendas y restaurantes. En varios me ofrecieron un pequeño regalo si dejaba una reseña en Google o en Naver.

Las normas de Google para las reseñas de Maps no lo permiten. Su política de contenido de Maps considera interacción falsa ofrecer incentivos a cambio de reseñas. No sé con qué rigor aplica Naver sus propias normas, y no juzgo a los negocios, que seguían lo que parecía una práctica local habitual. Como analista, lo que me llamó la atención fue el efecto en el dato: una valoración construida en parte con reseñas premiadas significa algo distinto de una construida con reseñas espontáneas.

Qué podría sacar de aquí una empresa de viajes

Si trabajara en datos en una marca de viajes que vende en Corea, miraría tres cosas.

  1. La demanda de búsqueda fuera de Google. Una marca que solo mide y puja en Google ve una parte del mercado. Naver tiene sus propios anuncios de búsqueda y su propia herramienta para webmasters, Naver Search Advisor, y las búsquedas que importan pueden aparecer solo ahí.
  2. Los datos de reseñas como un problema de calidad del dato. Si las valoraciones alimentan un ranking, una recomendación o un análisis que compara mercados, necesitan contexto: donde los incentivos son habituales, un 4,8 es otro número. Los saltos bruscos en el número de reseñas de un local merecen una alerta antes de que nadie lea la media.
  3. El checkout para clientes extranjeros. Los errores por campo del formulario, separados por el país del visitante, muestran enseguida cuándo una verificación por teléfono o una comprobación de tarjeta está echando a gente que quería comprar.

Una pregunta para terminar

Cuando comparas valoraciones o demanda de búsqueda entre países, ¿sabes qué plataforma usa de verdad cada mercado y cómo se recogieron sus reseñas?

Monday, June 15, 2026

Writing analytics specs that developers can build

In UK government digital teams I wrote the specifications for my own team's analytics work, as user stories with acceptance criteria. The rule I took from it is easy to state: an analytics request is ready for development when someone other than the analyst can test it.

The situation

I worked as an analyst in UK government digital teams, at GDS and the Department for Education, often in several teams at the same time. The teams were multidisciplinary and worked in Agile, with product and delivery managers, developers, designers and user researchers on the same service.

Part of the work was digital identity. In the UK that is harder than in many countries, because there is no national identity card to check people against, so a service has to build confidence in who someone is from several weaker pieces of evidence. Government is still working on it: in February 2026 HMRC started signing up new customers through GOV.UK One Login, which GOV.UK said was then available for over 200 government services.

In a team like that, an analytics request competes with everything else on the board. If it arrives as "can we track the identity check?", the developer has to guess what to send, when and with which values. The guesses come back at user acceptance testing (UAT), when changing them is expensive.

What I did

For my own team, I wrote each analytics task as a user story with acceptance criteria. The story said who needed the data and which decision it supported. The criteria said what the developer had to build and how we would check it in UAT.

The example below is invented for this post and has no detail from a real service, but it has the shape I used.

As a performance analyst, I want an event when a user leaves the identity check step, so that we can report completion by step and see where people drop out.

  • When the user leaves the step, one event named identity_check_step_end is sent.
  • The event has an outcome parameter with one of these values: passed, failed, exited.
  • The event sends no name, date of birth, document number or free text.
  • In UAT, one attempt with each outcome produces exactly one event per attempt in the test property.

The privacy line sat in the criteria on purpose. In an identity service the safest personal data is the data the event never carries, and a written criterion can be tested in UAT like any other.

Writing the check first also moves the questions earlier. Edge cases come up while the story is being refined, when an answer is a comment on the ticket.

The team also automated its recurring reporting with Python and ETL pipelines. That cut the analytics effort on reports by around three quarters, and weekly reports went out on time to more than 30 stakeholders. It was a team achievement, built in Agile with everyone on the team.

What I learned

Acceptance criteria that cannot fail are a wish list. Each line has to describe something a tester can see happen or not happen. The analyst is the best person to write those lines, because the analyst knows what the data will be used for and which mistakes would make it useless.

What I would do today

The GOV.UK Service Manual already asks teams to have a performance analyst from the start of discovery and to work out their measurements while the service is built (Service Manual). I would keep that and add four things.

  1. Link the tracking plan to the stories, so each event points to the story that created it and to the metric it feeds.
  2. Check event names and allowed values automatically in the test environment, so a typo fails before release.
  3. Agree once with the privacy team a list of fields that never go into analytics, and paste it into every story.
  4. Write acceptance criteria for AI features in the same way. A feature built on a large language model also needs written cases it must pass before release, and someone other than its builder should be able to run them.