TEASEDocs
ProductsClubTroubleshooting

Attribution doc gaps

Stale or misleading claims we found in the previous attribution guide and in the code's own comments — so future edits don't reintroduce them.

This page exists for whoever edits the attribution docs next. Each item below is a place where a docstring, an old guide, or an intuitive reading of one file disagrees with what the code actually does — verified by opening the current file, not by trusting the comment.

The leaderboard has a fourth bucket the old guide never mentions

A previous version of this documentation described the leaderboard as three buckets: sources, organic, pending. core/source_leaderboard/materialise.py returns four top-level rows — sources, organic, pending, and checking — with matching checking_net_cents and checking_subs counters in reconciliation/coverage. checking holds a brand-new paying fan the engine's event-driven source check hasn't settled yet. A doc (or an integration) built from only three buckets will silently drop this money out of whatever total it reconstructs.

attribution_kind's own docstring says "write-only" — it isn't

core/attribution/kind.py's module docstring states: "WRITE-ONLY — nothing in the leaderboard/stats/dashboard reads this yet." That was true once; it no longer is. At least ten call sites read OfConversion.attribution_kind today, including the leaderboard package this docstring itself claims is clear of it:

  • core/conversions_report.py — filters rows by attribution_kind == "link_attributed" to build the per-fan link-conversion view.
  • core/country_revenue.py's compute_campaign_revenue — defaults its kinds filter to (KIND_LINK_ATTRIBUTED,) and always queries attribution_kind.in_(kinds).
  • core/free_pool.py and core/of/campaigns.py both call compute_campaign_revenue with an explicit kinds=(KIND_CID_ATTRIBUTED, KIND_LINK_ATTRIBUTED, KIND_PENDING) to decide whether a code has ever earned money before resurfacing/hiding it.
  • core/source_leaderboard/resolve.py — its own inline comment says as much: attribution_kind is read here, not derived — feeding the L4 campaign stage of the priority ladder this very docstring's package is supposedly untouched by.
  • core/source_leaderboard/fan_card.py and core/source_leaderboard/collect_people.py — both filter/select on it directly.
  • core/fan/country.py and core/fan/classify.py — both query it to resolve a fan's country and classification.
  • core/offer/attribution.py — filters on attribution_kind == "link_attributed" when deciding which promo-offer entries to promote.

A doc (or a future change) written from the docstring alone would badly underestimate the blast radius of touching attribution_kind — it isn't dead weight, and the package the docstring names as unaffected is itself one of the readers.

reconciliation.balanced is a tautology, not proof

The old guide told readers to "trust the balanced flag." Read literally, that overstates what it checks:

balanced is guaranteed by construction

_lb_residuals dumps any leftover, unbucketed money into organic specifically so that sources + organic + pending + checking == total_net_cents always holds. The code's own comment in materialise.py calls this "a tautology, not proof" — balanced: true cannot, by itself, catch a fan who was double-counted into two buckets.

The check that actually catches that class of bug is reconciliation.fans_disjoint (fans_counted == fans_distinct — every fan's id appears in exactly one bucket's fan set). It's computed right next to balanced in the same response but wasn't documented at all in the old guide. If you're writing an integrity check against this endpoint, assert fans_disjoint, not just balanced.

resolver.py's docstring undersells what actually decides a fan's source

core/source/resolver.py calls itself "the canonical fan→source resolver... ONE source of truth" and documents a 4-stage ladder (cid → link → pending → organic). Read on its own, that describes a smaller system than what actually runs: the leaderboard's _lb_attribute adds campaign, D3b-code, and LLM-guess stages between "mapped link" and "pending," and resolver.py's own inline comment admits KIND_LLM is deliberately kept out of its ranking. Documenting "how a fan's source is decided" from resolver.py alone — as opposed to reading _lb_attribute too — undercounts the real ladder by three stages. See Source attribution for the full, code-verified ladder.

What's next

On this page