mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-02 12:54:03 +00:00
Add OpenABTest draft experiment and accounting contracts (#180)
This commit is contained in:
parent
123ba2e39a
commit
d8b3126782
25 changed files with 1410 additions and 12 deletions
|
|
@ -32,3 +32,13 @@ Use `@logicsrc/validators` 0.2.0 or newer for OpenRental validation. See the
|
|||
bindings, exact decimal rental rates and CoinPay settlement metadata.
|
||||
|
||||
MIT © Profullstack, Inc.
|
||||
|
||||
## OpenABTest draft
|
||||
|
||||
Version 0.3.0 adds `OpenABTestManifest`, `OpenABTestEvent`,
|
||||
`createOpenABTestManifest` and `createOpenABTestEvent`. The constructors set
|
||||
`openabtest: "0.1-draft"`; validate their result with `@logicsrc/validators`
|
||||
using `openabtest-manifest` or `openabtest-event` before storing it. They do not
|
||||
assign participants, authenticate events or initiate payments. See the
|
||||
[OpenABTest specification](https://logicsrc.com/docs/openabtest) and its complete
|
||||
Chovy fixture for every referred purchase.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@logicsrc/sdk",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"description": "LogicSRC SDK contract types and client interface.",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
|
|
|||
|
|
@ -94,3 +94,6 @@ export function createAgentSwarmSession(input: {
|
|||
openspec_only: input.openspecOnly ?? false
|
||||
};
|
||||
}
|
||||
|
||||
export { createOpenABTestManifest, createOpenABTestEvent } from "./openabtest.js";
|
||||
export type { OpenABTestManifest, OpenABTestEvent, OpenABTestPrice, OpenABTestReconciliation } from "./openabtest.js";
|
||||
|
|
|
|||
81
packages/sdk/src/openabtest.ts
Normal file
81
packages/sdk/src/openabtest.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/** Draft private contracts; constructors do not assign users, authorize events or pay money. */
|
||||
export interface OpenABTestManifest {
|
||||
openabtest: "0.1-draft";
|
||||
id: string;
|
||||
revision: number;
|
||||
name: string;
|
||||
state: "draft" | "running" | "paused" | "closed";
|
||||
cohort: { id: string; eligibilityRule: string; purchaseScope: "all-referred-purchases" | "all-eligible-purchases" | "first-eligible-purchase" | "custom" };
|
||||
assignment: { unit: "user" | "account" | "session" | "device" | "custom"; authority: "server"; keyVersion: string; algorithm: string; persistence: "sticky" };
|
||||
variants: Array<{ id: string; weight: number; parameters: Record<string, string | number | boolean | null> }>;
|
||||
window: { startsAt: string; endsAt: string };
|
||||
metrics: {
|
||||
primary: "conversion-rate" | "retained-profit-per-eligible-visitor";
|
||||
denominator: "unique-eligible-participants";
|
||||
conversion: "unique-participants-with-confirmed-purchase";
|
||||
profit: "reconciled-net-retained-profit";
|
||||
minimumEligiblePerVariant: number;
|
||||
minimumObservationSeconds: number;
|
||||
winnerPolicy: "manual-review";
|
||||
};
|
||||
guardrails: { preserveAcceptedOffers: true; preserveAccruedCommissions: true; requireReconciledCostsForPayout: true; onBreach: "pause-new-assignments" };
|
||||
economics?: { currency: string; minorUnitExponent: number; unit: string; listUnitPriceMinor: number; modeledUnitCostMinor: number | null; minimumRetainedUnitProfitMinor: number };
|
||||
}
|
||||
|
||||
export interface OpenABTestPrice {
|
||||
offerId: string;
|
||||
acceptedAt: string;
|
||||
currency: string;
|
||||
listUnitPriceMinor: number;
|
||||
chargedUnitPriceMinor: number;
|
||||
discountBps: number;
|
||||
quantityMilliUnits: number;
|
||||
rounding: "half-up";
|
||||
totalMinor: number;
|
||||
}
|
||||
|
||||
type Assigned = { assignmentId: string; variantId: string };
|
||||
type Converted = Assigned & { conversionId: string };
|
||||
export interface OpenABTestReconciliation extends Converted {
|
||||
accountingRevision: number;
|
||||
currency: string;
|
||||
state: "pending" | "reconciled";
|
||||
revenueMinor: number;
|
||||
refundsMinor: number;
|
||||
actualCostMinor: number | null;
|
||||
feesMinor: number | null;
|
||||
affiliateMinor: number | null;
|
||||
retainedProfitMinor: number | null;
|
||||
minimumRetainedProfitMinor: number;
|
||||
payoutStatus: "withheld" | "eligible";
|
||||
proofRefs: string[];
|
||||
}
|
||||
|
||||
type EventDetail =
|
||||
| { kind: "eligibility"; payload: { cohortId: string; eligible: true } }
|
||||
| { kind: "assignment"; payload: Assigned & { cohortId: string } }
|
||||
| { kind: "exposure"; payload: Assigned & { offerId: string; surface: string } }
|
||||
| { kind: "conversion"; payload: Converted & { price: OpenABTestPrice } }
|
||||
| { kind: "adjustment"; payload: Converted & { adjustmentId: string; currency: string; amountMinor: number; reason: "refund" | "chargeback" | "correction"; proofRefs: string[] } }
|
||||
| { kind: "reconciliation"; payload: OpenABTestReconciliation };
|
||||
|
||||
export type OpenABTestEvent = {
|
||||
openabtest: "0.1-draft";
|
||||
id: string;
|
||||
producerId: string;
|
||||
manifestId: string;
|
||||
manifestRevision: number;
|
||||
participantId: string;
|
||||
at: string;
|
||||
} & EventDetail;
|
||||
|
||||
/** Validate with @logicsrc/validators before storing or using the document. */
|
||||
export function createOpenABTestManifest(input: Omit<OpenABTestManifest, "openabtest">): OpenABTestManifest {
|
||||
return { ...input, openabtest: "0.1-draft" };
|
||||
}
|
||||
|
||||
type EventInput = Omit<OpenABTestEvent, "openabtest" | "kind" | "payload"> & EventDetail;
|
||||
/** The caller supplies authenticated context and durable IDs; this helper creates neither. */
|
||||
export function createOpenABTestEvent(input: EventInput): OpenABTestEvent {
|
||||
return { ...input, openabtest: "0.1-draft" };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue