TEASEDocs
ProductsClub

Connections

The specific places Club's code actually reaches across product boundaries today, cited to the file.

Boundaries says what Club doesn't own. This page is narrower: the actual, current places in the code where Club's own logic touches another product's data or another product's math. Four of them.

Club's attribution_kind classifier resolves every conversion's cid against the clicks table — a table Club doesn't own (see Boundaries). The read isn't a separate query you could point at and audit in isolation; it's a correlated EXISTS built directly into the UPDATE statement:

# app/products/club/core/attribution/kind.py
resolves = exists(
    select(clk.id).where(
        clk.owner_id == oid,
        clk.cid == conv.cid,
        clk.cid != "",
    )
)
...
n_cid = _run(has_cid & resolves, KIND_CID_ATTRIBUTED)
n_unmatched = _run(has_cid & ~resolves, KIND_UNMATCHED)

A conversion's cid is cid_attributed only if a matching row exists in clicks for the same owner; otherwise it's unmatched. That's a real, open cross-schema coupling — Club's classification of "did this conversion's click resolve?" is only as correct as Link's clicks table on that day. It isn't wrapped behind an interface; it's a live Click import (app.core.models) inside Club's own reclassify_owner.

The dashboard's Traffic tab is mid-migration

The dashboard's Money and Traffic modes used to be one Club-owned package. As of today they're split, and the split is visible in three different files:

  • Route + compute for Traffic now live outside Club: GET /dashboard/traffic and GET /dashboard/source-fans are registered in app/api/admin/domain_links/dashboard_traffic.py, and the actual number-crunching, compute_dashboard_traffic, lives in app/core/dashboard_vitrina/traffic.py. Club's own package, app/products/club/core/dashboard_vitrina/__init__.py, still re-exports that function for backward compatibility (its module docstring notes the traffic half was handed back out of Club's tree and is waiting on the Link product to pull it into its own package) but doesn't compute it anymore.
  • Money mode stays Club's. The same route file's sibling, app/api/admin/domain_links/dashboard_money.py, still calls into Club's own app.products.club.core.attributed_scope — the domain_links folder name isn't a signal of ownership here, the import is.
  • The frontend hasn't moved. DashboardTraffic.tsx — the whole Traffic tab's UI — is still physically inside Club's own component tree, admin/src/products/club/components/dashboard/DashboardTraffic.tsx, and its header comment points straight at the now-external backend, confirming live numbers come from GET /dashboard/traffic, which now resolves to app/core/dashboard_vitrina/traffic.py.

Don't treat this as settled

The Traffic tab today is a Club-owned screen calling an already-migrated backend. That's not a bug to fix in these docs — it's the actual current state, and it's worth documenting as exactly that: mid-migration, not finished.

Tribute (Private) is an external integration, not a TEASE product

Private (the panel's label for this) is a private Telegram subscription channel run through Tribute (tribute.tg), a third-party bot — not something TEASE built. Club integrates with it two ways:

  • The money path is a signed webhook, app/products/club/api/tg_tribute.py — Tribute posts subscription/payment events to Club, and that's the authoritative source of Tribute revenue.
  • A thin REST client (app/products/club/core/tribute/api.py) reads Tribute's product catalog and backfills orders for reconciliation. Its own docstring is explicit that list_subscribers/list_revenue are deliberately unimplemented stubs — "no stable public REST schema published," so the client refuses to guess an endpoint shape rather than fabricate one. The webhook, not this client, is the real feed.

Tribute money shows up in Club's revenue numbers as a second source alongside OnlyFans and Fansly — but Tribute itself lives entirely outside TEASE.

Club owns the agreementapp/products/club/api/admin/traffer_payouts.py is the owner-facing CRUD for a TrafferPayout row (a flat fee or a percent of a source's revenue). It does not compute the split itself. That math — turning a source's gross revenue into the traffer's cut, the model's net, and the profit after ad spend — is apply_traffer_economics, defined in app/core/traffer_econ.py and treated as Link-owned. Club's own leaderboard code calls it directly:

# app/products/club/core/stats/blocks/deep.py
from app.core.traffer_econ import apply_traffer_economics
...
for e in apply_traffer_economics(...):

So the number an owner sees on the source leaderboard — revenue → traffer cut → net — is Club's own TrafferPayout row, run through Link's economics function, inside Club's own stats module. It's a real dependency, not a copy: if Link changes how the split is computed, Club's leaderboard changes with it.

A naming collision worth knowing about

app/products/club/core/chatterapi.py is a client for a third-party Telegram sales-tracking service that happens to be called "Chatter" — it has nothing to do with Club's own chatter/team-member model. It's a name collision, not an internal connection.

What's next

The Concepts section covers how each of these pieces works on its own terms — start with source attribution or the dashboard, depending on which of the above you're chasing.

On this page