Skip to content

Commit 245f8dc

Browse files
authored
experiments: prefer the authoritative culledAt stamp over /tracks inference (#82)
* experiments: prefer the authoritative culledAt stamp over /tracks inference lantern-cloud#3126 stamps culled_at/culled_by_experiment_id on the experiment row when a promotion's track is culled, and carries both through the promoted-comparison endpoint. Badging/hiding now unions that stamp with the existing /tracks-absence heuristic: the stamp survives a /tracks fetch failure and attributes the culling promotion on the badge ("culled by #N"), while the heuristic still catches promoted tracks disabled outside a cull, which nothing stamps. * review: parse culledAt defensively for the badge tooltip
1 parent f160c6e commit 245f8dc

2 files changed

Lines changed: 34 additions & 9 deletions

File tree

src/api/client.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,12 @@ export interface PromotedComparisonPoint {
730730
promotedTrackName: string;
731731
originalTrackName: string;
732732
promotedAt?: string; // decided_at (when the experiment was promoted)
733+
// Authoritative cull stamp (lantern-cloud#3126): set when the promoted track
734+
// was later disabled by another promotion's cull, with the culling
735+
// experiment attributed when known (absent on rows stamped by the migration
736+
// backfill, whose cull history lives only in logs).
737+
culledAt?: string;
738+
culledByExperimentId?: number;
733739
}
734740

735741
export interface PromotedComparisonResponse {

src/components/ExperimentsOverview.tsx

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -711,14 +711,25 @@ function PromotionTrafficCard({ point, seriesByTrackCountry, startMs, endMs, log
711711
);
712712
const promotedMs = point.promotedAt ? Date.parse(point.promotedAt) : NaN;
713713
const showMarker = !Number.isNaN(promotedMs) && promotedMs >= startMs && promotedMs <= endMs;
714+
// Parsed defensively like promotedAt above: an unparseable culledAt must
715+
// degrade to no date, not an "Invalid Date" tooltip.
716+
const culledMs = point.culledAt ? Date.parse(point.culledAt) : NaN;
717+
const culledOn = Number.isNaN(culledMs) ? "" : ` on ${new Date(culledMs).toLocaleDateString()}`;
714718
const hasData = rows.some((r) => r.promoted !== undefined || r.original !== undefined);
715719

716720
return (
717721
<div style={{ border: "1px solid #ffffff0d", borderRadius: "var(--radius-sm)", padding: "0.6rem 0.7rem" }}>
718722
<div style={{ ...mono, fontSize: "0.58rem", color: "var(--text-muted)", marginBottom: "0.15rem", display: "flex", alignItems: "center", gap: "0.4rem" }}>
719723
<span>#{point.experimentId} · {point.targetCountry} · {point.protocolName || "—"}{point.providerName ? ` · ${point.providerName}` : ""}</span>
720724
{promotedCulled && (
721-
<span style={deadBadge} title="The promoted track is no longer a live bandit track — most commonly culled by a later promotion in this market — so it carries no traffic. The experiment row still reads 'promoted'.">culled</span>
725+
<span
726+
style={deadBadge}
727+
title={point.culledByExperimentId
728+
? `Culled by experiment #${point.culledByExperimentId}'s promotion${culledOn}: this market promoted a materially faster track, so this one was disabled. The experiment row still reads 'promoted'.`
729+
: "The promoted track is no longer a live bandit track — most commonly culled by a later promotion in this market — so it carries no traffic. The experiment row still reads 'promoted'."}
730+
>
731+
{point.culledByExperimentId ? `culled by #${point.culledByExperimentId}` : "culled"}
732+
</span>
722733
)}
723734
</div>
724735
<div style={{ ...mono, fontSize: "0.62rem", marginBottom: "0.35rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
@@ -770,10 +781,11 @@ function PromotedTraffic({ enabled }: { enabled: boolean }) {
770781
const { data, isLoading, error } = usePromotedComparison(enabled, hours);
771782

772783
// Live bandit track names from the tracks endpoint, fetched once per
773-
// activation. A promoted track can be culled by a LATER promotion in the same
774-
// market while its experiment row stays 'promoted' forever, so without this
775-
// cross-reference the section charts dead tracks as mysteriously silent live
776-
// ones. The liveness signal is PRESENCE, not the payload's disabled field:
784+
// activation — the fallback liveness signal behind the authoritative
785+
// culled_at stamp (see isPromotedCulled): it catches promoted tracks
786+
// disabled outside a cull, which nothing stamps. Without either signal the
787+
// section charts dead tracks as mysteriously silent live ones. The liveness
788+
// signal here is PRESENCE, not the payload's disabled field:
777789
// the backend query (GetTracksWithVPSPool) filters disabled tracks out
778790
// entirely, so `disabled` is always false in this response and a culled track
779791
// simply isn't listed. Presence is only conclusive for PROMOTED tracks —
@@ -804,8 +816,15 @@ function PromotedTraffic({ enabled }: { enabled: boolean }) {
804816
.catch(() => { if (!cancelled) setLiveTracks(null); });
805817
return () => { cancelled = true; };
806818
}, [enabled, isAuthenticated]);
819+
// Culled = the authoritative culled_at stamp (lantern-cloud#3126) OR absence
820+
// from the live-tracks list. The stamp is preferred — it survives a /tracks
821+
// fetch failure and attributes the culling promotion — but the liveness
822+
// fallback still catches promoted tracks disabled outside a cull, which
823+
// nothing stamps.
807824
const isPromotedCulled = useCallback(
808-
(name: string) => liveTracks !== null && name !== "" && !liveTracks.has(name),
825+
(p: PromotedComparisonPoint) =>
826+
Boolean(p.culledAt) ||
827+
(liveTracks !== null && p.promotedTrackName !== "" && !liveTracks.has(p.promotedTrackName)),
809828
[liveTracks],
810829
);
811830

@@ -829,11 +848,11 @@ function PromotedTraffic({ enabled }: { enabled: boolean }) {
829848
// cards are pure noise unless explicitly asked for. Hiding them BEFORE
830849
// byMarket also skips their SigNoz traffic queries.
831850
const culledCount = useMemo(
832-
() => filteredPoints.filter((p) => isPromotedCulled(p.promotedTrackName)).length,
851+
() => filteredPoints.filter(isPromotedCulled).length,
833852
[filteredPoints, isPromotedCulled],
834853
);
835854
const promotions = useMemo(
836-
() => (showCulled ? filteredPoints : filteredPoints.filter((p) => !isPromotedCulled(p.promotedTrackName))),
855+
() => (showCulled ? filteredPoints : filteredPoints.filter((p) => !isPromotedCulled(p))),
837856
[filteredPoints, showCulled, isPromotedCulled],
838857
);
839858

@@ -1024,7 +1043,7 @@ function PromotedTraffic({ enabled }: { enabled: boolean }) {
10241043
startMs={startMs}
10251044
endMs={endMs}
10261045
logScale={logScale}
1027-
promotedCulled={isPromotedCulled(p.promotedTrackName)}
1046+
promotedCulled={isPromotedCulled(p)}
10281047
/>
10291048
))}
10301049
</div>

0 commit comments

Comments
 (0)