# Geodesix V1 scope

## 1. What V1 is

- **Admin app.** Staff enter retailers, publishers, articles, citations, products, and brands,
  and connect them. Staff also set rates and manage Impact connections.
- **Retailer dashboard.** Browse available citations and select one. A direct placement returns an
  HTML snippet and a script; a paid ad placement returns an Impact tracked link. View analytics
  and cost.
- **Tracking service.** Count impressions and viewable citations per placement from our own script,
  and take clicks from Impact.
- Login, teams, and users for admin and retailers (not publishers).

## 2. Data model

`retailer` · `user` · `publisher` · `article` · `citation` · `brand` · `product` ·
`retailer_product` · `placement` · `event` · `rate` · `impact_connection`

Cut down by review batch 6. `category` left the model, and so did the citation's accuracy score and
its topics. All three are pushed to v2 rather than rejected.

1. **A citation has exactly one subject: a product or a brand.** `product_id` and `brand_id` on the
   citation, exactly one of them filled. Never a category, which V1 does not have.
2. **A product belongs to a brand and to nothing else.** No category on the product, and no
   manufacturer above the brand. A brand is the only grouping a product has, and it is what every
   filter, group by, and rollup that used to offer Category now offers.
3. **The product catalog is global, not per retailer.** A product exists once and is carried by
   zero or more retailers through `retailer_product`, which holds that retailer's product URL. Two
   retailers selling the same moisturizer point at the same product and the same citations. There is
   no SKU: nothing in V1 read it. The URL stays because three things do, the destination of a paid
   ad's tracked link, the outbound link on the product pages, and the page a direct placement's
   snippet runs on.
4. **A `placement` is one retailer's selection of one citation, and it carries a type: direct or
   paid ad.** Its id is what goes in the embed or the tracked link, and what all analytics hang
   off. Every placement list, filter, and rollup is grouped by type.
4b. **A placement also carries an installation status**, derived from what the verification checks
   find rather than set by hand: `Unverified` (nothing detected), `Verified` (the loader ran and the
   placement was seen, as a snippet element for direct or as `subId1` on the landing URL for a paid
   ad), `Viewable` (traffic counted, a viewable impression for direct or arrivals for a paid ad).
   One vocabulary on both surfaces, shown as a column and a filter on every placement list and at
   the top of every placement page.
5. **The citation carries the quote, its article, and its subject. Nothing else.** No accuracy
   score and no topics. Both were in V1 as hand entered fields and both came out in batch 6, so no
   form collects them, no list shows them, and no filter offers them.
6. **`licensed` boolean on the article.** Retailers only see citations from licensed articles. Hand
   set in V1, but the dashboard checks it on every query.
7. **Rates are per type, with a per publisher override.** Defaults are $4.00 per 1,000 viewable
   citations and $0.10 per click. Rates are set in admin only, because they are negotiated.
8. **`event` records where it came from.** Impressions and viewable citations arrive from our
   script; clicks arrive from Impact. One table with a source column, because they roll up together
   and reconcile separately.

## 3. Admin app

Internal staff only. Pages linked below are the V1 wireframes, which are the shape to build.

| Area | Pages |
|---|---|
| Retailers | [retailers](../v1-admin/web/wireframe/retailers.html), [retailer-detail](../v1-admin/web/wireframe/retailer-detail.html), [create-retailer](../v1-admin/web/wireframe/create-retailer.html), [retailer-products](../v1-admin/web/wireframe/retailer-products.html) |
| Retailer users | [team](../v1-admin/web/wireframe/team.html), [invite-user](../v1-admin/web/wireframe/invite-user.html), [edit-user](../v1-admin/web/wireframe/edit-user.html) |
| Publishers | [publishers](../v1-admin/web/wireframe/publishers.html), [publisher-detail](../v1-admin/web/wireframe/publisher-detail.html), [create-publisher](../v1-admin/web/wireframe/create-publisher.html) |
| Articles | [content](../v1-admin/web/wireframe/content.html), [content-detail](../v1-admin/web/wireframe/content-detail.html), [add-article](../v1-admin/web/wireframe/add-article.html), [edit-content](../v1-admin/web/wireframe/edit-content.html) |
| Citations | [citations](../v1-admin/web/wireframe/citations.html), [citation-detail](../v1-admin/web/wireframe/citation-detail.html), [add-citation-from-content](../v1-admin/web/wireframe/add-citation-from-content.html), [edit-citation](../v1-admin/web/wireframe/edit-citation.html), citation-utilization *(to build)* |
| Brands | [catalog-brands](../v1-admin/web/wireframe/catalog-brands.html), [brand-detail](../v1-admin/web/wireframe/brand-detail.html), [create-brand](../v1-admin/web/wireframe/create-brand.html), [edit-brand](../v1-admin/web/wireframe/edit-brand.html) |
| Products | [catalog-products](../v1-admin/web/wireframe/catalog-products.html), [product-detail](../v1-admin/web/wireframe/product-detail.html), [create-product](../v1-admin/web/wireframe/create-product.html), [edit-product](../v1-admin/web/wireframe/edit-product.html) |
| Placements | placements *(to build)* |
| Impact connections | tracking *(to build)* |

A few notes:

1. **V1 will not connect at all to the MongoDB database.** All data is entered by hand.
2. **Adding an article by URL.** To simplify data ingestion, we should be able to paste an article
   URL and read the page head, prefill title, `og:image`, publisher, and published date, then let
   the operator correct anything. It fails softly to a hand typed form.
3. **Adding a citation.** Quote text and a reference to one subject, a product or a brand. The
   subject cards are labelled Subject, which is the canonical word.
4. **Rates live on the publisher.** A Pricing tab on publisher detail: each type with its default
   rate, this publisher's rate, and an override marker where they differ.
5. **Citation utilization** is one citation's uses: the embed code, and performance by retailer and
   type.
6. **Placements** is every placement across all retailers, sortable by type, retailer, publisher,
   subject, impressions, viewable citations, clicks, and cost.
7. **Impact connections** is one row per retailer: the Impact program id, the domain carrying
   `irclickid`, and a status of Pending, Connected, or Disconnected.
8. **View as Retailer** sits on retailer detail and opens that retailer's own dashboard. Publishers
   have no View as, because they have no login and no surface.

## 4. Retailer dashboard

| Area | Pages |
|---|---|
| Citations | [citations](../v1-retailer/web/wireframe/citations.html), [citation-detail](../v1-retailer/web/wireframe/citation-detail.html) |
| Products | [products](../v1-retailer/web/wireframe/products.html), [product-detail](../v1-retailer/web/wireframe/product-detail.html) |
| Placements | [placements](../v1-retailer/web/wireframe/placements.html), [embed](../v1-retailer/web/wireframe/embed.html), tracked-link *(to build)* |
| Performance | [performance](../v1-retailer/web/wireframe/performance.html) |
| Cost | cost *(to build)* |
| Team | [team](../v1-retailer/web/wireframe/team.html), [invite-user](../v1-retailer/web/wireframe/invite-user.html), [edit-user](../v1-retailer/web/wireframe/edit-user.html) |

**Which citations a retailer sees.** The article is licensed, and the citation's subject is either
a product this retailer carries or a brand one of their carried products belongs to.

**Selecting a citation.** Select on the Citations page, pick direct or paid ad, and a `placement`
is created. Direct returns the embed snippet; paid ad returns the tracked link.

**Placements.** Everything selected, grouped by type, with each output available again, its counts,
and a way to deactivate.

**Performance.** Impressions, viewable citations, and clicks per placement, rolled up by type,
product, brand, publisher, and time.

**Cost.** Total spend, spend split by type, the rate table showing what each type costs, and cost
by placement with budgets and pausing. Paid ads cannot be hard capped, because delivery is
external; they can only be paused, with spend alerts. Direct placements can be capped.

## 5. The embed and tracking

**The id is a placement id.** One opaque id per selection. The same citation gets placed by several
retailers on several pages, and only the placement distinguishes them. Citation, subject, publisher,
and type are all reachable from it on our side.

A direct placement is markup plus one script per page, not per citation. The script finds every
`[data-gdx-placement]` and reports on each.

```html
<div data-gdx-placement="plc_912ab03">
  <span data-gdx="quote">This is the best product ever</span>
  <span data-gdx="source">BuzzFeed</span>
</div>
```

```html
<script src="https://cdn.geodesix.com/citations/latest/loader.js" data-gdx-key="ret_brightden" async></script>
```

A paid ad placement is a URL, and since batch 6 it carries the loader too. Impact measures the
click on the retailer's own domain, and `subId1` is what ties it back to the placement. Because
`subId1` is in the landing URL, the same loader tag above reads it off the query string and records
an **arrival** against that placement, so no new markup is needed and a retailer running both types
pastes the tag once.

The loader is **required**, not optional: it is what moves the placement off `Unverified`.

The two numbers are named apart, but they are counted in the same place. Impact tracks on page load
as well, so a **click** and an **arrival** are both counted on the landing page, by two independent
scripts. Clicks are Impact's and are what bills; arrivals are ours and are a check on Impact's. A
small gap is ordinary and comes from load timing, blockers, and stripped parameters. The comparison
is internal: only the admin placement page shows both, and the retailer's shows clicks alone.

```
https://brightden.example.com/aurora-feeder?irclickid={click_id}&subId1=plc_912ab03
```

**What counts**, fixed now because these numbers eventually become invoices:

- **Impression:** the citation is on the page and the script initialized. Once per placement per
  pageview.
- **Viewable:** at least 50% of it in the viewport for one continuous second, the IAB display
  standard. Once per placement per pageview.
- Both are per placement, so a page carrying four citations produces four, not one.
- **Click:** whatever Impact reports. We do not count clicks ourselves, and our number and theirs
  will differ. Theirs is the one that bills.

No cookies, no third party, no personal data on the direct path.

**Where money stops.** V1 measures usage and applies rates. It does not invoice, does not pay
publishers, and does not settle. Settlement runs through Impact.

**Accepted tradeoff.** The quote is static HTML on the retailer's page, and we'd prefer not to edit
a retailer's page. Eventually we will incorporate quotas. If a retailer exceeds their quota, we
will notify them and bill them, but not remove the citation.
