Metric formulas

Every number this product puts on a screen is calculated by one of the formulas below. They are taken word for word from the single versioned document the product renders to its own users, and changing any calculation means changing that document first.

Version 0.100 — ratified 2026-09-04

What is not here: the notes beside each formula, and the document’s change log. Those discuss one business’s own measurements and this product’s pricing internals, so they stay inside the product. Nothing about how a figure is calculated is held back. How this is built.

3. Sales metrics

Gross sales
Σ (order_lines.unit_price_cents × qty) for orders in period
Discounts
Σ order_lines.discount_cents for orders in period
Refunds (value)
Σ refunds.amount_cents where refund_at in period
Net sales
Gross sales − Discounts − Refunds
Shipping income
Σ orders.shipping_income_cents
Total sales
Net sales + Shipping income + Taxes
Orders
COUNT of non-test, non-cancelled orders with order_at in period
Units sold
Σ order_lines.qty
AOV
Net sales ÷ Orders

4. Cost and margin metrics

COGS per unit
cogs_history — landed cost effective at the order date
Supplier unit cost (reference)
sku_cost_readings — Shopify InventoryItem.unitCost, snapshotted per SKU per store-timezone day (v0.73, R246)
Packaging per order
settings.packaging_cost_cents
Fulfillment cost
order_costs.fulfillment_cents; if unknown, settings.default_fulfillment_cost_cents with fulfillment_estimated = true
Payment fee
order_costs.payment_fee_cents — MEASURED (v0.70, R232) from order_transaction_fees, Σ OrderTransaction.fees[].amount over the order's successful SALE or CAPTURE transactions, when Shopify Payments processed it; otherwise the default estimate = settings.payment_fee_pct × order total + settings.payment_fee_fixed_cents
Platform commission
order_costs.platform_fee_cents; TikTok orders default estimate = settings.tiktok_commission_pct × order total
Affiliate commission
order_costs.affiliate_commission_cents, from TikTok affiliate CSV. A zero carries a KNOWN/UNKNOWN state (v0.47, R108): known when the value is non-zero, when the order is not tiktok_shop (affiliate commission is a TikTok Shop concept, so a Shopify zero is a definition), or when the order's STORE-TIMEZONE day falls inside an imported creator_sales period; unknown otherwise.
Contribution margin (order)
Order net revenue − COGS − packaging − fulfillment − payment fee − platform commission − affiliate commission
CM %
CM ÷ order net revenue
CM per unit
CM ÷ units in order
Channel contribution after marketing
Σ CM for channel − channel ad spend in period
Supplier cost change
Over sku_cost_readings for one SKU: the signed difference between the latest reading and the most recent EARLIER reading that differs from it, in integer cents. There is no threshold — unit cost is a price a merchant typed, so it carries no rounding noise to tolerate and any non-zero difference is a reprice. Status is changed where such a pair exists, steady where every earlier reading equals the latest, one reading only where the series has a single entry — a LEVEL, which this metric never reads — and nothing comparable yet where there is no reading at all, carrying which of never probed / no cost served / no variant matched applies. Separately and independently, landed cost < supplier unit cost is a contradiction: landed cost CONTAINS manufacturing cost, so the state is impossible and is stated wherever it holds, whatever the series is doing.
Payment fee drift
Over the successful SALE/CAPTURE rows of order_transaction_fees whose order falls in the period: |Σ measured fee − Σ constants-priced fee| ÷ |Σ constants-priced fee|, a fraction on integer-cent inputs (0.005 = 0.5%, the §9 convention), where constants-priced = round(settings.payment_fee_pct × order_transaction_fees.transaction_amount_cents) + settings.payment_fee_fixed_cents, applied once per transaction. Status is no drift at ≤ 0.005 and drift above it; where no summable fee row exists in the period, or where no such row carries a rate, there is no figure and the status is nothing comparable yet — which is NEVER no drift. ⚠️ The estimate side is priced on the PROCESSOR'S OWN transaction_amount_cents, never on chargedTotal(), so the figure isolates the two constants from any disagreement about the base. Priced the other way this deployment's own seeded corpus read 13.38% — a base artifact, against a true component drift of zero. It is NOT §9's drift_pct: different measurement, its own threshold constant, and it raises no banner.

5. Customer metrics

New customers
Count of customer_keys whose first-ever order falls in period
Repeat purchase rate (90d)
Of customers whose FIRST order was in cohort month M: % who placed a second order within 90 days
Time to second order
Median days between first and second order, per cohort
LTV(90) / LTV(180)
Cumulative net sales per customer at day 90/180 after first order, cohort average
Cohort contribution margin
Of the customers whose FIRST eligible order falls in cohort month M: cumulative Σ of the §4 order contribution margin of their eligible orders placed in calendar months M through M+k, ÷ the number of customers in M

6. Acquisition and marketing metrics

Marketing spend
Ad spend (manual entry) + creator sample costs (product landed cost + outbound shipping of samples)
Blended CAC
Marketing spend in period ÷ new customers in period
MER
Total net sales ÷ total marketing spend
Ingested ad spend
Σ ad_insights.spend_cents per platform and per currency, over the ad account’s own days
Per-creative ad spend
Σ ad_insights.spend_cents per AD, inside a platform-and-currency lane, over the ad account’s own days. An ad is identified by ad_account_external_id + ad_external_id — never by a name
Per-creative impressions
Σ ad_insights.impressions per AD, inside a platform-and-currency lane, over the ad-days that carried a figure. Null — “unmeasured” — when no ad-day did
Per-creative clicks
Σ ad_insights.clicks per AD, inside a platform-and-currency lane, over the ad-days that carried a figure. Null — “unmeasured” — when no ad-day did
Ad spend by day
Σ ad_insights.spend_cents per insight_date, inside a platform-and-currency lane, over the ad account's own days
Per-campaign ad spend
Σ ad_insights.spend_cents per CAMPAIGN, inside a platform-and-currency lane, over the ad account's own days. A campaign is identified by ad_account_external_id + campaign_external_id — never by a name
Per-campaign impressions
Σ ad_insights.impressions per campaign, inside a platform-and-currency lane, over the ad-days that carried a figure. Null — "unmeasured" — when no ad-day did
Per-campaign clicks
Σ ad_insights.clicks per campaign, inside a platform-and-currency lane, over the ad-days that carried a figure. Null — "unmeasured" — when no ad-day did
Platform ROAS
Platform-reported attributed revenue ÷ platform spend
Creator effective CAC
Samples cost (creator) ÷ orders attributed to that creator (from affiliate data)
Creator post rate
Creators sent samples with ≥1 post ÷ creators sent samples
GMV per creator
Σ creator_sales.gmv_cents per creator
Views (creator)
Σ creator_posts.views per creator
Samples cost (creator)
Σ creator_samples.cost_cents per creator
Commission owed (creator)
Σ creator_sales.commission_cents per creator, cumulative
Code-attributed orders (creator)
Count of §2-eligible shopify orders whose orders.discount_codes holds a code assigned to that creator in creator_discount_codes, matched on that table’s generated code_normalized (upper(btrim(code))) against the order’s own code trimmed and uppercased. Retired codes included. Null — “no codes assigned” — for a creator holding no code.

7. Operations and inventory metrics

Refund rate (count)
Refunded orders ÷ orders in period
Refund rate (value)
Refund value ÷ gross sales in period
Units on hand
Σ inventory_movements.qty_delta per SKU
Available
On hand − units in unshipped orders
Daily velocity
Trailing 14-day units sold ÷ 14. If the store has fewer than 14 days of sales history, divide trailing units sold by the actual days of history (minimum 1) instead.
Days of cover
Available ÷ daily velocity
Reorder alert
Fires when days of cover < settings.supplier_lead_time_days × settings.reorder_safety_factor (default 1.5)
Reorder quantity
Per SKU. Null when daily velocity is 0 (no rate, no plan — never a fake zero). Otherwise: expected on hand at arrival = max(available − daily velocity × supplier_lead_time_days, 0); shortfall = daily velocity × supplier_lead_time_days × reorder_safety_factor − expected on hand at arrival; quantity = 0 when shortfall ≤ 0, else ⌈shortfall⌉ raised to settings.supplier_moq_units when that setting is non-null
Sell-through
Units sold in period ÷ (units sold + ending on hand)

8. Cash metrics

Pending payouts
Shopify: unpaid payout balance (from payout export or estimated schedule). TikTok: unsettled amounts from settlement CSV
Ad spend to date
Σ ad_spend.amount_cents
Cash position
Latest cash_snapshots.cash_cents
Runway
Cash position ÷ trailing 3-month average net burn

8.5 Social metrics

Followers (per platform)
Latest social_snapshots.followers for the platform (the most recent snapshot with a non-null followers count)
Follower change (per platform)
Latest followers − the prior non-null followers reading for the platform
Percent change (per platform)
For two readings of ONE platform's single §8.5 series: (later − earlier) ÷ earlier, displayed beside the absolute change
Total audience
Σ latest followers across platforms that have data
Following (per platform)
Latest non-null social_account_snapshots.following_count for the platform
TikTok likes (api)
Latest non-null social_account_snapshots.likes_count
TikTok public videos (api)
Latest non-null social_account_snapshots.video_count
IG media count (api)
Latest non-null social_account_snapshots.media_count
TikTok top videos (api)
Per-video point-in-time totals from social_video_snapshots — view / like / comment / share counts per observation ((platform, video_id, observed_at); as_of is the derived store-timezone day label, not the key), ranked by view count at each video's latest snapshot, within TikTok only
TikTok views at 7 days (api)
For one TikTok video, the view_count of the social_video_snapshots observation whose observed_at is nearest that video's own 168-hour mark (posted_at + 168h), provided that observation carries a non-null view_count and lies within ±12 hours of the mark; ties break on the lower id. The figure exists only once the window has CLOSED (posted_at + 180h has passed); before then, and whenever the window closed with no qualifying reading inside it, there is no figure and the reason is reported as a NAMED state rather than a blank
TikTok windowed views (derived)
Σ of the per-video Δ views defined in the row above, across every TikTok video whose Δ is non-null inside the selected window — the same window, the same PLATFORM-anchored cutoff and the same two-qualifying-readings rule, consumed rather than recomputed. Null, never 0, when no video has a non-null Δ
IG top posts (api)
Per-post point-in-time totals from social_video_snapshots (platform instagram) — view / like / comment / share counts per observation ((platform, video_id, observed_at); as_of is the derived store-timezone day label, not the key, and since v0.79 each row also records the LANE that wrote it in observation_lane), ranked by view count at each post's latest snapshot, within Instagram only
IG views at 7 days (api)
For one Instagram post, the view_count of the social_video_snapshots observation whose observed_at is nearest that post's own 168-hour mark (posted_at + 168h), provided that observation carries a non-null view_count and lies within ±12 hours of the mark; ties break on the lower id. The figure exists only once the window has CLOSED (posted_at + 180h has passed); before then, and whenever the window closed with no qualifying reading inside it, there is no figure and the reason is reported as a NAMED state rather than a blank. A figure whose anchoring reading is less than 48 hours old is labelled provisional
TikTok growth shape (derived)
For one TikTok video, which of five states its stored view series is in, computed over every social_video_snapshots observation of that video carrying a non-null view_count and over nothing else. Let the window be the trailing 14 days, first and last the earliest and latest readings inside it, and net = last - first. Not yet classifiable: fewer than 14 readings in the whole trail, a trail spanning under 14 days, or fewer than two readings inside the window — the third clause being what a lane that has stopped reading the item looks like, which is a different fact from a shape and is never reported as one. Flat throughout: every reading in the trail carries the same count. Stopped gaining: the trail holds more than one distinct count, and net = 0. Still gaining: net > 0. Readings decreased: net < 0. The five states are exhaustive and disjoint and every item carries exactly one - a NAMED state, never a blank. Independently of the state, an item is flagged dipped when any reading inside the window is lower than a reading before it
IG growth shape (derived)
For one Instagram post, the same five-state rule as "TikTok growth shape (derived)" above, over the same stored series, the same trailing window of 14 days, the same net = last - first, the same three classifiability floors and the same dipped flag
IG views (api)
social_insight_snapshots value for metric views over the pinned window (above); every stored value carries its period_start/period_end
IG total interactions (api)
social_insight_snapshots value for metric total_interactions, same storage and window
IG follower demographics (api)
Latest social_demographic_snapshots rows for platform instagram, metric follower_demographics — one (dimension, bucket, value) set per as_of; dimensions age / gender / country / city
IG engaged audience demographics (api)
Latest social_demographic_snapshots rows for platform instagram, metric engaged_audience_demographics — same dimensions, windowed by the pinned upstream timeframe last_90_days (stored per row in timeframe; the pin lives as IG_ENGAGED_TIMEFRAME in lib/instagram.ts)
IG saves (api)
social_insight_snapshots value for metric saves over the pinned window (above); every stored value carries its period_start/period_end
IG profile visits (api)
social_insight_snapshots value for metric profile_views, same storage and window
IG follows and unfollows (api)
TWO stored scalar metrics in social_insight_snapshots, same window: follows = upstream follows_and_unfollows breakdown bucket FOLLOWER; unfollows = bucket NON_FOLLOWER (breakdown=follow_type is required upstream, and this metric needs its OWN request — combining it with the scalar metrics is an upstream HTTP 500)
IG most-active times (api)
social_demographic_snapshots rows for platform instagram, metric online_followers, dimension hour, buckets '0'..'23' — followers online per hour, one bucket set per upstream day (as_of)
IG Reels skip rate (api)
Latest non-null social_video_snapshots.reels_skip_rate_pct for the reel (platform instagram) — a percent, 0–100
IG Reels average watch time (api)
Latest non-null social_video_snapshots.reels_avg_watch_time_ms for the reel — in milliseconds
IG Reels total watch time (api)
Latest non-null social_video_snapshots.reels_total_watch_time_ms for the reel — in milliseconds
IG post reach (api)
Latest non-null social_video_snapshots.post_reach for the post (platform instagram)
IG post saves (api)
Latest non-null social_video_snapshots.post_saves for the post
IG post total interactions (api)
Latest non-null social_video_snapshots.post_total_interactions for the post
IG post profile visits (api)
Latest non-null social_video_snapshots.post_profile_visits for the post
IG post follows (api)
Latest non-null social_video_snapshots.post_follows for the post
IG Reels modeled retention curve
A MODELED curve r(t) fitted to the two measured Reels metrics above — never measured per-second data. With q = 1 − (skip rate ÷ 100) and M = average watch time ÷ 1000 (seconds): A₀ = 3(1−q) ÷ ln(1/q) — which tends to 3 as q → 1 (a 0% skip rate: the hook is flat at 1, so its area is the full 3 seconds), the limit the code carries explicitly because the closed form divides by ~0 there — λ = q ÷ (M − A₀); then r(t) = q^(t/3) for 0 < t ≤ 3 and r(t) = q·e^(−λ(t−3)) for t > 3, drawn to the horizon where r = 1% — solved in whichever segment contains it, which is 3 + ln(q ÷ 0.01) ÷ λ whenever q > 0.01 and lands INSIDE the measured 0–3s segment when q ≤ 0.01 (a skip rate at or above 99%, where the closed form above would return a negative horizon)
TikTok retention curve (api)
The MEASURED per-second series in social_video_insights.view_retention (upstream video_view_retention): one point per second of PLAYBACK POSITION, second 0…floor(video_duration), carrying the FRACTION of this video's viewers still watching at that second. Read latest non-null per video; sorted numerically at read
TikTok like-timing curve (api)
The measured per-second series in social_video_insights.engagement_likes — the identical {second, percentage} shape as the retention curve above, carrying WHERE IN THE TIMELINE viewers hit like. Read latest non-null per video; sorted numerically at read
TikTok watch time (api)
TWO stored values per video, both upstream-reported in seconds: social_video_insights.average_time_watched (mean seconds watched per view) and social_video_insights.total_time_watched (summed seconds across all views). Read latest non-null per video
TikTok completion rate (api)
Latest non-null social_video_insights.full_video_watched_rate for the video — upstream's share of viewers who watched the whole video, a FRACTION 0–1
TikTok video reach (api)
Latest non-null social_video_insights.reach for the video — accounts that saw THIS video, lifetime, as reported by the TikTok API for Business
TikTok video views (api)
Latest non-null social_video_insights.video_views for the video — upstream video_views from the API for Business
TikTok video traffic sources (api)
Latest non-null social_video_insights.impression_sources for the video — upstream impression_sources, an aggregate distribution of {impression_source, percentage} over For You / Personal Profile / Search / Sound / Follow / Direct Message / Others
TikTok video audience composition (api)
Latest non-null social_video_insights.audience_countries and social_video_insights.audience_genders for the video — two aggregate distributions, {country, percentage} (with an upstream Others bucket) and {gender, percentage} over female_vv / male_vv / other_vv
TikTok new vs returning viewers (api)
Latest non-null social_video_insights.audience_types for the video — upstream audience_types, an aggregate distribution of {type, percentage} over FOLLOWER_PERCENT / NON_FOLLOWER_PERCENT / NEW_VIEWER / RETURN_VIEWER
TikTok account views (api)
Σ of social_account_insights.video_views across every upstream day inside the selected window, taking each day's LATEST observation. Null, never 0, when no day in the window served the field
TikTok account profile views (api)
Σ of social_account_insights.profile_views across every upstream day inside the selected window, taking each day's latest observation. Null, never 0, when no day served it
TikTok account engagement counts (api)
Σ of social_account_insights.likes, .comments and .shares — three separate sums over the same window, each taking a day's latest observation, each null rather than 0 when no day served it
TikTok account contact clicks (api)
social_account_insights.email_clicks, .phone_number_clicks, .address_clicks, .app_download_clicks and .lead_submissions — five per-day counters, stored and NOT displayed
TikTok audience activity by hour (api)
For each of the 24 store-timezone clock hours, the mean of social_account_insights.audience_activity's bucket counts across every upstream day in the window that carried a profile. An hour with no reading is null, never 0
TikTok account audience composition (api)
The newest social_account_audience observation's audience_countries, audience_genders, audience_ages and audience_cities — four aggregate distributions describing the account over the window that observation requested
TikTok viewer mix across videos (derived)
For each of the two dimensions upstream serves per video — countries and genders — the view-WEIGHTED pooling of every tracked TikTok video whose social_video_insights_current row carries that distribution: pooled(bucket) = Σ_v share_v(bucket) × views_v ÷ Σ_v views_v, where views_v is that same row's video_views. Only a bucket named by EVERY contributing video is reported; everything else, plus each video's own upstream Others, is one computed residual, 1 − Σ(reported). A video whose video_views is null or non-positive is EXCLUDED, never weighted as 1. Null — never an empty mix — when no video contributes
TikTok traffic mix across videos (derived)
The view-WEIGHTED pooling of social_video_insights_current.impression_sources across every tracked TikTok video, by exactly the rule "TikTok viewer mix across videos (derived)" defines (v0.48) — same weight, same every-video clause, same computed residual, same exclusion of an unweighable video. Null when no video contributes
TikTok viewer type mix across videos (derived)
The view-WEIGHTED pooling of social_video_insights_current.audience_types across every tracked TikTok video, by the same rule (v0.48) — but as TWO SEPARATE POOLS, follower status (FOLLOWER_PERCENT / NON_FOLLOWER_PERCENT) and viewer recency (NEW_VIEWER / RETURN_VIEWER), never one. Null when no video contributes
Caption hook (classified)
The rhetorical move THIS POST'S CAPTION makes, as one member of the frozen v1 vocabulary — question / pov / claim / listicle / reference / none — classified by a language model from the caption text this dashboard already stores in social_video_snapshots.title, and held in social_caption_tags keyed (platform, video_id, taxonomy_version, caption_sha256, model). The displayed label is SELECTED, never defaulted: of that post's stored rows it is the one whose model is the classifier's CURRENT model, whose taxonomy_version is the frozen version, and whose caption_sha256 equals the hash of the caption the post carries TODAY. When no row satisfies all three the post reads "not classified" — never a stale label describing text the post no longer carries, and never a fallback member. Each member, in the words the classifier is given (verbatim from the guide the prompt is generated from, and pinned in BOTH directions by tests/label-member-definitions.test.ts — so a prompt edit cannot silently redefine a published label, and this cell cannot describe an instruction the model was never given): question — opens by asking something, rhetorical or literal; pov — frames a scene the reader is inside — "POV:", "when you…", second-person setup; claim — asserts something declaratively — a thesis, a boast, a stated fact; listicle — presents itself as an enumerated set or a recurring series entry; reference — leads on a named person, property, place or event outside the brand; none — no discernible rhetorical opening — a bare label, a fragment, or empty
Caption theme (classified)
What THIS POST'S CAPTION is about, as one member of product_benefit / ingredient_or_proof / routine_or_habit / humor_or_skit / launch_or_offer / lifestyle_or_scene / other. Same source, same storage and the same three-clause selection rule as Caption hook (classified) above. Each member, in the words the classifier is given (verbatim from the guide the prompt is generated from, and pinned in BOTH directions by tests/label-member-definitions.test.ts — so a prompt edit cannot silently redefine a published label, and this cell cannot describe an instruction the model was never given): product_benefit — what the product does for the user — speed, simplicity, result; ingredient_or_proof — formulation, ingredients, science, or credentials as evidence; routine_or_habit — the routine itself — steps, time taken, consistency, comparison to other routines; humor_or_skit — a joke, bit or sketch; the caption serves the comedy, not the product; launch_or_offer — launch timing, waitlist, early access, discount, giveaway; lifestyle_or_scene — an occasion or setting — travel, gym, work, dates, nightlife; other — none of the above fits; do not force one that nearly fits
Caption CTA (classified)
What THIS POST'S CAPTION asks the reader to do, as one member of link_in_bio / waitlist / purchase / follow_or_engage / none. Same source, storage and selection rule as the two rows above. The KIND is what is stored and shown; the related ctaPresent is DERIVED at read time as cta_kind other than none and is never stored beside it, because two fields that must agree are two fields that can disagree. Each member, in the words the classifier is given (verbatim from the guide the prompt is generated from, and pinned in BOTH directions by tests/label-member-definitions.test.ts — so a prompt edit cannot silently redefine a published label, and this cell cannot describe an instruction the model was never given): link_in_bio — directs the reader to a link in the bio or profile; waitlist — asks the reader to join a waitlist or sign up for access; purchase — asks the reader to buy, shop or order; follow_or_engage — asks for a follow, like, comment, share or save; none — the caption asks the reader to do nothing. The derived ctaPresent has two members, which the slate groups by as well: present — the stored kind is any member other than none; absent — the stored kind is none
Speech onset band (classified)
WHEN speech begins in THIS ITEM'S OWN VIDEO, as one member of the frozen v1 vocabulary — within_3s / after_3s / no_speech — derived from social_video_transcripts.speech_onset_ms and held in social_video_content_labels keyed (business_id, platform, video_id, account_role, axis, label_version). The derivation is speech_onset_ms < 3000 and nothing else: arithmetic on one stored integer, with no model call, no prompt and no vendor, which is the whole difference from the three caption rows above. The label is SELECTED, never defaulted: of an item's stored rows it is the one whose label_version is the frozen version AND whose source_version equals the transcript version in force today. When no row satisfies both, the item reads as unlabelled and is counted as UNMEASURED — never a stale band describing audio timing that has since been re-measured, and never a fallback member. no_speech is a GROUP and not an absence (the cta_kind precedent): a reel where nobody speaks answers the question as informatively as one where somebody does. no-media, failed and a served row carrying no onset are each real states and none of them is evidence about when speech began, so all three read as unlabelled. Each member, by the rule that derives it (arithmetic, so there is no guide to quote — the pin in tests/label-member-definitions.test.ts is against the threshold constant and the frozen vocabulary): within_3s — the first spoken word begins before 3000 ms into the video (speech_onset_ms < 3000); after_3s — the first spoken word begins at or after 3000 ms; no_speech — the transcript lane measured silence, a no-speech disposition carrying no onset, so the item answers when does speech begin with it does not rather than sitting unlabelled
Spoken opening (classified)
The rhetorical move THIS ITEM'S SPOKEN AUDIO opens on, as one member of the frozen v1 vocabulary — question / pov / claim / listicle / reference / none — classified by a language model from the transcript text stored in social_video_transcripts.transcript_text, and held in social_video_content_labels keyed (business_id, platform, video_id, account_role, axis, label_version) with the classifier's model and the source_sha256 of the classified text carried as attributes. The label is SELECTED, never defaulted: of an item's stored rows it is the one whose label_version is the frozen version, whose source_version equals the transcript version in force today, and whose model is the classifier's CURRENT model. When no row satisfies all three the item reads as unlabelled and is counted as UNMEASURED — never a stale label describing words the item no longer carries, and never a fallback member. An item with no speech has NO spoken opening and is unlabelled rather than none: none means the speech opened on nothing discernible, and folding silence into it would put "said nothing" and "opened on a fragment" in one group and call the result a measurement. Each member, in the words the classifier is given (verbatim from the SPEECH guide the prompt is generated from — the members are the caption row's, the sentences are re-guided for a transcript and are not — and pinned in BOTH directions by tests/label-member-definitions.test.ts — so a prompt edit cannot silently redefine a published label, and this cell cannot describe an instruction the model was never given): question — opens by asking something, rhetorical or literal; pov — frames a scene the listener is inside — "when you…", "imagine…", second-person setup; claim — asserts something declaratively — a thesis, a boast, a stated fact; listicle — announces an enumerated set or a recurring series entry — "three things", "part two"; reference — leads on a named person, property, place or event outside the brand; none — no discernible rhetorical opening — a greeting, filler, a bare label, or a fragment
Reel visual description (classified)
What THIS ITEM'S OWN VIDEO SHOWS, as eight fields describing one reel and nothing else, derived by a vision model from still frames sampled out of the item's own media_url file and held in social_video_visual_descriptions keyed (business_id, platform, video_id, account_role, description_version, model). The eight fields, in the words the model is given (⚠️ HAND-TRANSCRIBED ON BOTH SIDES, and v0.96 corrects a claim this cell made about itself: it said the list was generated from the same guides the prompt is built from, which is true only of productVisibility's members. visualDescriptionPrompt() is one hand-written template literal, so the honest guarantee is a test — tests/reel-visuals.test.ts now loops the schema's required array against the RENDERED prompt and fails if a field the schema demands is one the model was never told about, which retro-covers the seven fields that predate R394): openingShot — what the FIRST frame alone shows AND HOW IT IS FRAMED, in a short phrase: how close the camera is (close-up, mid, wide), what is centred or fills the frame, and whether anyone is facing the camera; openingText — the text OVERLAID on that same FIRST frame, transcribed exactly as it reads, where overlaid means ADDED IN EDITING (a caption, a sticker, a title card, the graphic text layer on top of the footage) and text that is part of the scene — printed on clothing, on packaging, on a sign in the room — was filmed rather than added and is never transcribed here, every overlaid line in reading order separated by " / ", and the literal [no overlaid text] when the first frame carries none; composition — how the reel is SHOT, across all the frames, in two or three sentences: the dominant shot scale and whether it holds or changes, what is most prominent and where in the frame it sits, where any on-screen text sits and roughly how much of the frame it takes, and the strongest colour or contrast focal point; setting — where the video takes place, the location and anything notable about light or staging; onScreenTextPresent — true if any overlaid text, caption or sticker is visible in any frame, where text on packaging does not count; productVisibility — one of the frozen v1 vocabulary — prominent, the product is a focus of the video, held, applied, demonstrated or centered; visible, the product appears somewhere in frame but is not the focus; absent, the product does not appear in any sampled frame; formatStyle — the video's format in a short phrase; storyboard — one or two sentences narrating what happens across the frames, first to last. The frames are SIX and their positions are definitional, not incidental: one at a FIXED ~1 second and five at 10/30/50/70/90% of duration. The fixed one is what openingShot is derived from and it is a fixed SECOND rather than a fraction because IG Reels skip rate (api) is Meta's own THREE-SECOND metric — at a tenth of duration the first frame lands at 6s on a 60s reel, past the window, and drifts with length so that no two items' opening frames describe the same moment. On a reel short enough that the two coincide the run takes five frames and the opening one IS the first sample. The description is SELECTED, never defaulted: of an item's stored rows it is the one whose description_version is the version in force today AND whose model is the lane's CURRENT model, and which is not a retryable failure. When no row satisfies all three the item reads as NOT DESCRIBED and is never given a stale description derived from frames the lane no longer takes — six superseded instruments are already in the table, since the frame SIZE moved at R340b, the frame SET at R361, the field set and the opening-frame instruction at R368, composition's stated length at R368b, the framing half of the #95 bar at R368c, and the eighth field at R394. There is deliberately no source-hash clause, which is the difference from the three caption rows above: a caption is text a post can be edited to change, and a published reel's file cannot, so the item is re-described when OUR instrument moves and nothing else. Four absences, each its own fact and none of them a blank: no-media — Instagram served no file, permanently and at any credential tier; not-served-by-platform — the platform serves no video file at all, TikTok, permanently; failed — an attempt went wrong; not described yet — a queue position
Caption-label group comparison (analysis)
For ONE population — a (platform, account_role) pair, never pooled — one frozen label axis (caption_hook, the derived ctaPresent, cta_kind and caption_theme since v0.35, speech_onset since v0.59, and spoken_opening since v0.61) and one PERMITTED outcome: group that population's classified items by label, then for every pair of groups reaching the minimum size report mean(a) − mean(b), a two-sided 95% Welch confidence interval (unequal variances, Welch–Satterthwaite degrees of freedom, Student-t) and the detectable floor defined in the row below. Permitted outcomes are exactly three: log₁₀(views), IG Reels skip rate, IG Reels average watch time. Groups under 10 items do not enter a comparison and are reported as excluded; with fewer than two groups reaching 10 the result is null, stated as "not enough items". ⚠️ AMENDED AT v0.89 (R373), NOT DELETED: since v0.89 an item the paid-distribution ledger (social_paid_distribution) marks does not enter the population at all, from the day it is recorded — its readings from paid_from onward are paid-inclusive (a boost or a Spark Ad lands paid views, likes and retention on the organic post, and nothing upstream flags it), every input to this row is the item's CURRENT reading, and the partition happens ONCE in shaping (captionAnalysisRows), before a label is read, so the slate's census and the registrar's group sizes inherit it with the comparison — and the number of marked items in the population is reported beside every result as a NAMED count, never folded into the analysed or unmeasured items and never silent. That count is the one act v0.89 authorizes; comparing marked against unmarked items is not authorized and is a new row first (never-blend, interpretation #71: a paid-inclusive reading is a different measurement from an organic one on the same platform)
Detectable difference floor (analysis)
(z₀.₉₇₅ + z₀.₈₀) × √(s₁²/n₁ + s₂²/n₂) = 2.801585 × the same Welch standard error the confidence interval uses — the smallest difference the two groups could have detected at 80% power, α = 0.05, expressed in the outcome's own units
Boost candidate (derived)
Within ONE (platform, account role) population, over the items that carry that platform's governed retention rate — Instagram: IG Reels skip rate (api); TikTok: TikTok completion rate (api) — the modified z-score M = 0.6745 × (rate − median) ÷ MAD, signed so that positive always means better retention on that axis (LOWER skip, HIGHER completion). An item is a boost candidate when M > 3.5. The population median of the same rate is reported beside every result. Requires ≥ 10 measured items in the population; a population whose MAD is 0 reports no-spread rather than dividing by it. ⚠️ AMENDED AT v0.89 (R373), NOT DELETED: since v0.89 an item the paid-distribution ledger (social_paid_distribution) marks is removed from the population BEFORE the median, the MAD and the count of measured items are taken, from the day it is recorded — the mechanism this row rests on, the same creative shown to more people, has already been applied to it, its retention reading is no longer organic, and it is no longer a candidate — and the number of marked items is reported beside every result as a SECOND named count, never folded into the measured count and never silent; a population every item of which is marked reports too-few with that count beside it, which is a different fact from an account with no tracked posts
Ad Library reference (stored)
ONE competitor ad as a PERSON read it off Meta's Ad Library web card and recorded it in the committed scope document — per business, per platform, per Library ID (the re-check key: facebook.com/ads/library/?id= followed by the id): the advertiser (companies only), the day the read saw it and the day the library said it started, the landing host, the primary text, the on-screen hook and the format notes VERBATIM (markdown markers and elisions kept), and its angle as one member of the ad-angle vocabulary v1 (problem_solution / testimonial / product_demonstration / founder_story / other) with the qualifier the read wrote beside it. Stored in ad_library_references (R372), written only by the hand-run ingest script under the service role, read only by the production-brief cron. Displayed since v0.90 (R374) on exactly one surface — the paid-creative slate of the weekly production brief on /app/production — where each row is listed under the slot whose angle it carries as the five things this row names it by: the Library ID as the re-check link, the advertiser, the on-screen hook, the angle with its qualifier, and the day it was read; inside a slot the rows run in advertiser then Library ID order, an order no count decides, and rows whose angle is other are listed under no slot with the sentence that says why. That display is the whole of what is DISPLAYED, and since v0.94 (R379) the analyst READS the same rows through read_screen('production') — amended in place rather than deleted (the v0.36 convention), because "exactly one surface" and "reads them anywhere else" were true from v0.90 until this version and a reader needs to see when they stopped being. What the read serves is the rows AS THE MONDAY CRON FROZE THEM into the stored brief, under the slot whose angle each carries, named by the same five things and in the same order — no row the display does not already show, and the payload states that the snapshot is dated and may now disagree with the table. Nothing else moves: nothing counts these rows onto a surface, ranks them or compares them, no count per angle, advertiser or cluster, no "longest-running", no studio tool that READS THE TABLE — that is still its own #67 ceremony, and ask_select still refuses ad_library_references by name — and no further surface without its own clause here.

8.6 Workspace account metrics

AI credit balance
Credits remaining in the allowance period currently in force = ⌊(credits granted − credits consumed in that period) ⌋, taken as a single rounding of the underlying ledger amount and rounded DOWN. Consumption counts every AI call the workspace has made inside the period, at its settled amount where the call has settled and at the amount reserved for it where it has not. A workspace whose plan is unmetered has no allowance and therefore no balance; a workspace with no allowance period in force has no plan on file, which is a refusing state and not a balance of zero.

8.7 Support metrics

Support conversations opened (per channel)
COUNT of support_conversations rows of ONE channel whose opened_at, read in the store timezone, falls in the period
Support open and waiting conversations (per channel)
For ONE channel, TWO current-state counts: its conversations whose state is open, and whose state is waiting
Support first response time (per channel)
Per conversation: the interval from its first direction='inbound' message's received_at to its first direction='outbound' message's received_at, in whole seconds. Aggregate: the MEDIAN of that interval over the channel's conversations whose opened_at falls in the period and that HAVE such a reply
Support unattended replies that ended the thread (per channel)
For ONE channel, the DENOMINATOR is its conversations whose first reply through this product was an UNATTENDED one that the provider accepted, whose sent_at falls in the period, and whose settling window has since fully elapsed. The NUMERATOR is those of them that received no further direction='inbound' message within the settling window after that reply AND on which no member-approved reply was ever accepted. The rate is numerator ÷ denominator, stored as a FRACTION (0.005 = 0.5%, the _pct convention §9 uses). The settling window is 72 hours