@sellable/mcp 0.1.907 → 0.1.908
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools/refill-executors.d.ts +42 -0
- package/dist/tools/refill-executors.js +194 -22
- package/dist/tools/refill-v3-attention-packet-contract.d.ts +8 -0
- package/dist/tools/refill-v3-attention-packet-contract.js +8 -0
- package/dist/tools/refill-v3-source-family-dispatch.d.ts +5 -0
- package/dist/tools/refill-v3-source-family-dispatch.js +51 -15
- package/package.json +1 -1
- package/skills/providers/signal-discovery.md +11 -8
- package/skills/refill-sends-work-campaign/SKILL.md +4 -4
|
@@ -269,6 +269,48 @@ export declare function validateAgentSuppliedBroadenKeywords(rawKeywords: unknow
|
|
|
269
269
|
ok: false;
|
|
270
270
|
reason: string;
|
|
271
271
|
};
|
|
272
|
+
type RefillV3SignalPostCandidate = {
|
|
273
|
+
postId: string;
|
|
274
|
+
postUrl: string;
|
|
275
|
+
totalEngagers: number;
|
|
276
|
+
recencyDays: number;
|
|
277
|
+
};
|
|
278
|
+
type RefillV3SignalPostEstimate = {
|
|
279
|
+
postId?: unknown;
|
|
280
|
+
sampleSize?: unknown;
|
|
281
|
+
passCount?: unknown;
|
|
282
|
+
fitPercentage?: unknown;
|
|
283
|
+
estimatedFits?: unknown;
|
|
284
|
+
};
|
|
285
|
+
export type RefillV3SignalPostSample = {
|
|
286
|
+
postId: string;
|
|
287
|
+
sampleSize: number;
|
|
288
|
+
passCount: number;
|
|
289
|
+
fitRate: number;
|
|
290
|
+
totalEngagers: number;
|
|
291
|
+
projectedFits: number;
|
|
292
|
+
qualifies: boolean;
|
|
293
|
+
};
|
|
294
|
+
export declare function selectRefillV3SignalPostsFromSamples(params: {
|
|
295
|
+
candidates: readonly RefillV3SignalPostCandidate[];
|
|
296
|
+
estimates: readonly RefillV3SignalPostEstimate[];
|
|
297
|
+
maxPosts: number;
|
|
298
|
+
}): {
|
|
299
|
+
samples: RefillV3SignalPostSample[];
|
|
300
|
+
selectedPostIds: string[];
|
|
301
|
+
yieldEvidence: {
|
|
302
|
+
family: "signal_discovery";
|
|
303
|
+
evaluated: number;
|
|
304
|
+
passes: number;
|
|
305
|
+
};
|
|
306
|
+
qualityGateEvidence: {
|
|
307
|
+
family: "signal_discovery";
|
|
308
|
+
evaluated: number;
|
|
309
|
+
passes: number;
|
|
310
|
+
};
|
|
311
|
+
engagerSum: number;
|
|
312
|
+
projectedYield: number;
|
|
313
|
+
};
|
|
272
314
|
export declare function recentBroadenablePostIdsFromSignalSearch(summary: Awaited<ReturnType<typeof searchSignals>>, options: {
|
|
273
315
|
maxAgeDays: number;
|
|
274
316
|
excludePostIds?: string[];
|
|
@@ -6,6 +6,7 @@ import { startPrepareCampaignMessages } from "./campaign-message-preparation.js"
|
|
|
6
6
|
import { normalizeMessageTemplateRevision, normalizeRefillRunHandle, normalizeRefillTargetConfig, reviseMessageTemplateAndRerun, } from "./campaign-processing.js";
|
|
7
7
|
import { startCampaign } from "./campaigns.js";
|
|
8
8
|
import { confirmLeadList, importLeads, searchProspeo, searchSalesNav, searchSignals, selectPromisingPosts, } from "./leads.js";
|
|
9
|
+
import { REFILL_V3_SIGNAL_POST_MIN_ICP_RATE, REFILL_V3_SIGNAL_POST_MIN_SAMPLE_SIZE, REFILL_V3_SIGNAL_POST_SAMPLE_CANDIDATE_CAP, } from "./refill-v3-attention-packet-contract.js";
|
|
9
10
|
import { refreshSenderEngagementCommand } from "./refresh-sender-engagement.js";
|
|
10
11
|
import { runSchedulerSweep, } from "./scheduler-run.js";
|
|
11
12
|
import { refreshPaidInmailCredits } from "./senders.js";
|
|
@@ -132,10 +133,26 @@ export function normalizeRefillV3SourceOwnerReceipt(params) {
|
|
|
132
133
|
const signalKeywords = stringArray(rawRecord.keywordsTried);
|
|
133
134
|
const signalSelectedPostIds = stringArray(rawRecord.selectedPostIds);
|
|
134
135
|
const signalExcludedPostIds = stringArray(rawRecord.excludedPostIds);
|
|
136
|
+
const signalImportResult = recordValue(rawRecord.importResult);
|
|
137
|
+
const signalImportJobId = stringValue(signalImportResult?.jobId);
|
|
138
|
+
if (isSignalOwner &&
|
|
139
|
+
rawRecord.rowsGainedPending === true &&
|
|
140
|
+
signalImportJobId) {
|
|
141
|
+
// Signal import is asynchronous. Starting its scrape is not a zero-row
|
|
142
|
+
// terminal: return a retained effect identity so the coordinator reobserves
|
|
143
|
+
// the live source job instead of recording a dry round and spending the
|
|
144
|
+
// keyword avenue before any rows can arrive.
|
|
145
|
+
return {
|
|
146
|
+
kind: "pending",
|
|
147
|
+
effectIdentity: signalImportJobId,
|
|
148
|
+
detail: `Signal import ${signalImportJobId} started and rows are pending; reobserve the source job before classifying yield.`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
135
151
|
const signalRowsGained = Math.max(0, numberValue(rawRecord.rowsGained) ?? 0);
|
|
136
152
|
const signalProjection = recordValue(rawRecord.yieldProjection);
|
|
137
|
-
const
|
|
138
|
-
const
|
|
153
|
+
const signalYieldRecord = recordValue(rawRecord.yieldEvidence);
|
|
154
|
+
const signalYieldEvaluated = Math.max(0, Math.floor(numberValue(signalYieldRecord?.evaluated) ?? 0));
|
|
155
|
+
const signalYieldPasses = Math.min(signalYieldEvaluated, Math.max(0, Math.floor(numberValue(signalYieldRecord?.passes) ?? 0)));
|
|
139
156
|
const synthesizedBelowFloorRecord = signalBelowFloor && signalCampaignId && priorSourceFingerprint
|
|
140
157
|
? {
|
|
141
158
|
...rawRecord,
|
|
@@ -156,6 +173,10 @@ export function normalizeRefillV3SourceOwnerReceipt(params) {
|
|
|
156
173
|
importedCount: 0,
|
|
157
174
|
effects: [
|
|
158
175
|
"signal_yield_below_floor",
|
|
176
|
+
...(stringValue(rawRecord.failureCode) ===
|
|
177
|
+
"signal_post_icp_below_floor"
|
|
178
|
+
? ["signal_post_icp_below_floor"]
|
|
179
|
+
: []),
|
|
159
180
|
`keywords_searched:${signalKeywords.length}`,
|
|
160
181
|
`candidate_posts:${Math.max(0, numberValue(signalProjection?.candidatePostCount) ?? 0)}`,
|
|
161
182
|
`projected_yield:${Math.max(0, numberValue(signalProjection?.projectedYield) ?? 0)}`,
|
|
@@ -234,11 +255,14 @@ export function normalizeRefillV3SourceOwnerReceipt(params) {
|
|
|
234
255
|
}
|
|
235
256
|
: { available: false, reason: "not_applicable" },
|
|
236
257
|
postRationales: null,
|
|
237
|
-
|
|
258
|
+
// `yieldProjection` is volume planning (`engagers * assumed pass rate`),
|
|
259
|
+
// not observed candidate qualification. The dispatcher attaches actual
|
|
260
|
+
// candidate verdict evidence when it exists.
|
|
261
|
+
yieldEvidence: isSignalOwner && signalYieldEvaluated > 0
|
|
238
262
|
? {
|
|
239
263
|
family: "signal_discovery",
|
|
240
|
-
evaluated:
|
|
241
|
-
passes:
|
|
264
|
+
evaluated: signalYieldEvaluated,
|
|
265
|
+
passes: signalYieldPasses,
|
|
242
266
|
}
|
|
243
267
|
: null,
|
|
244
268
|
};
|
|
@@ -950,6 +974,90 @@ export function validateAgentSuppliedBroadenKeywords(rawKeywords, negativeList)
|
|
|
950
974
|
}
|
|
951
975
|
return { ok: true, keywords: freshKeywords };
|
|
952
976
|
}
|
|
977
|
+
function refillV3SignalPostCandidates(summary, options) {
|
|
978
|
+
const excluded = new Set((options.excludePostIds ?? []).map((postId) => postId.toLowerCase()));
|
|
979
|
+
const topPosts = Array.isArray(summary?.topPosts)
|
|
980
|
+
? summary.topPosts
|
|
981
|
+
: [];
|
|
982
|
+
const seen = new Set();
|
|
983
|
+
const candidates = [];
|
|
984
|
+
for (const post of topPosts) {
|
|
985
|
+
const postId = stringValue(post.id);
|
|
986
|
+
const postUrl = stringValue(post.url);
|
|
987
|
+
const recencyDays = numberValue(post.recencyDays);
|
|
988
|
+
if (!postId ||
|
|
989
|
+
!postUrl ||
|
|
990
|
+
recencyDays === null ||
|
|
991
|
+
recencyDays > options.maxAgeDays ||
|
|
992
|
+
excluded.has(postId.toLowerCase()) ||
|
|
993
|
+
seen.has(postId.toLowerCase())) {
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
const totalEngagers = scrapablePostEngagers(post);
|
|
997
|
+
if (totalEngagers < 1)
|
|
998
|
+
continue;
|
|
999
|
+
seen.add(postId.toLowerCase());
|
|
1000
|
+
candidates.push({ postId, postUrl, totalEngagers, recencyDays });
|
|
1001
|
+
if (candidates.length >= REFILL_V3_SIGNAL_POST_SAMPLE_CANDIDATE_CAP)
|
|
1002
|
+
break;
|
|
1003
|
+
}
|
|
1004
|
+
return candidates;
|
|
1005
|
+
}
|
|
1006
|
+
export function selectRefillV3SignalPostsFromSamples(params) {
|
|
1007
|
+
const estimatesByPost = new Map(params.estimates
|
|
1008
|
+
.map((estimate) => [stringValue(estimate.postId), estimate])
|
|
1009
|
+
.filter((entry) => Boolean(entry[0])));
|
|
1010
|
+
const samples = params.candidates.flatMap((candidate) => {
|
|
1011
|
+
const estimate = estimatesByPost.get(candidate.postId);
|
|
1012
|
+
const sampleSize = Math.max(0, Math.floor(numberValue(estimate?.sampleSize) ?? 0));
|
|
1013
|
+
const passCount = Math.min(sampleSize, Math.max(0, Math.floor(numberValue(estimate?.passCount) ?? 0)));
|
|
1014
|
+
if (sampleSize === 0)
|
|
1015
|
+
return [];
|
|
1016
|
+
const fitRate = passCount / sampleSize;
|
|
1017
|
+
return [
|
|
1018
|
+
{
|
|
1019
|
+
postId: candidate.postId,
|
|
1020
|
+
sampleSize,
|
|
1021
|
+
passCount,
|
|
1022
|
+
fitRate,
|
|
1023
|
+
totalEngagers: candidate.totalEngagers,
|
|
1024
|
+
projectedFits: Math.floor(candidate.totalEngagers * fitRate),
|
|
1025
|
+
qualifies: sampleSize >= REFILL_V3_SIGNAL_POST_MIN_SAMPLE_SIZE &&
|
|
1026
|
+
fitRate >= REFILL_V3_SIGNAL_POST_MIN_ICP_RATE,
|
|
1027
|
+
},
|
|
1028
|
+
];
|
|
1029
|
+
});
|
|
1030
|
+
const candidateById = new Map(params.candidates.map((candidate) => [candidate.postId, candidate]));
|
|
1031
|
+
const selected = samples
|
|
1032
|
+
.filter((sample) => sample.qualifies)
|
|
1033
|
+
.sort((left, right) => {
|
|
1034
|
+
if (right.fitRate !== left.fitRate)
|
|
1035
|
+
return right.fitRate - left.fitRate;
|
|
1036
|
+
if (right.totalEngagers !== left.totalEngagers) {
|
|
1037
|
+
return right.totalEngagers - left.totalEngagers;
|
|
1038
|
+
}
|
|
1039
|
+
return ((candidateById.get(left.postId)?.recencyDays ?? Infinity) -
|
|
1040
|
+
(candidateById.get(right.postId)?.recencyDays ?? Infinity));
|
|
1041
|
+
})
|
|
1042
|
+
.slice(0, Math.max(1, params.maxPosts));
|
|
1043
|
+
const sampleSizeEligible = samples.filter((sample) => sample.sampleSize >= REFILL_V3_SIGNAL_POST_MIN_SAMPLE_SIZE);
|
|
1044
|
+
return {
|
|
1045
|
+
samples,
|
|
1046
|
+
selectedPostIds: selected.map((sample) => sample.postId),
|
|
1047
|
+
yieldEvidence: {
|
|
1048
|
+
family: "signal_discovery",
|
|
1049
|
+
evaluated: selected.reduce((sum, sample) => sum + sample.sampleSize, 0),
|
|
1050
|
+
passes: selected.reduce((sum, sample) => sum + sample.passCount, 0),
|
|
1051
|
+
},
|
|
1052
|
+
qualityGateEvidence: {
|
|
1053
|
+
family: "signal_discovery",
|
|
1054
|
+
evaluated: sampleSizeEligible.reduce((sum, sample) => sum + sample.sampleSize, 0),
|
|
1055
|
+
passes: sampleSizeEligible.reduce((sum, sample) => sum + sample.passCount, 0),
|
|
1056
|
+
},
|
|
1057
|
+
engagerSum: selected.reduce((sum, sample) => sum + sample.totalEngagers, 0),
|
|
1058
|
+
projectedYield: selected.reduce((sum, sample) => sum + sample.projectedFits, 0),
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
953
1061
|
// feat(110-23c): the broadenable NEW-post frontier. A post counts only when its
|
|
954
1062
|
// engagement recency is inside the 45-day window (unknown recency is treated as
|
|
955
1063
|
// out-of-window), it carries meaningful engagement (>=1 commenter or reactor), and
|
|
@@ -1741,6 +1849,8 @@ export async function broadenSignalSearch(action, workspaceId) {
|
|
|
1741
1849
|
let newPostIds;
|
|
1742
1850
|
let carriedRemainder;
|
|
1743
1851
|
let yieldProjectionReceipt = null;
|
|
1852
|
+
let postYieldSamples = null;
|
|
1853
|
+
let observedYieldEvidence = null;
|
|
1744
1854
|
if (carriedRound) {
|
|
1745
1855
|
// Consume the carried frontier first: these posts were already found inside
|
|
1746
1856
|
// the 45-day window by a prior round whose projection cleared the floor.
|
|
@@ -1801,21 +1911,67 @@ export async function broadenSignalSearch(action, workspaceId) {
|
|
|
1801
1911
|
},
|
|
1802
1912
|
};
|
|
1803
1913
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1914
|
+
const yieldProjection = isRefillV3SinglePassAction(action)
|
|
1915
|
+
? await (async () => {
|
|
1916
|
+
const candidates = refillV3SignalPostCandidates(searchSummary, {
|
|
1917
|
+
maxAgeDays,
|
|
1918
|
+
excludePostIds: excludedPostIds,
|
|
1919
|
+
});
|
|
1920
|
+
if (candidates.length === 0) {
|
|
1921
|
+
return {
|
|
1922
|
+
candidatePostCount: 0,
|
|
1923
|
+
engagerSum: 0,
|
|
1924
|
+
observedPassRate: 0,
|
|
1925
|
+
projectedYield: 0,
|
|
1926
|
+
floor: Math.min(SIGNAL_YIELD_FLOOR_CAP, Math.max(1, remainingPrepGap)),
|
|
1927
|
+
scrapePostIds: [],
|
|
1928
|
+
carriedPostIds: [],
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
const estimateResponse = await api.post("/api/v1/signal-discovery/estimate-icp-fits", {
|
|
1932
|
+
posts: candidates.map((candidate) => ({
|
|
1933
|
+
id: candidate.postId,
|
|
1934
|
+
url: candidate.postUrl,
|
|
1935
|
+
totalEngagers: candidate.totalEngagers,
|
|
1936
|
+
})),
|
|
1937
|
+
campaignOffer: {
|
|
1938
|
+
id: campaignOfferId,
|
|
1939
|
+
name: campaign.name ?? "",
|
|
1940
|
+
},
|
|
1941
|
+
}, requestOptions);
|
|
1942
|
+
const sampled = selectRefillV3SignalPostsFromSamples({
|
|
1943
|
+
candidates,
|
|
1944
|
+
estimates: estimateResponse.estimates ?? [],
|
|
1945
|
+
maxPosts: scrapeCap,
|
|
1946
|
+
});
|
|
1947
|
+
postYieldSamples = sampled.samples;
|
|
1948
|
+
observedYieldEvidence =
|
|
1949
|
+
sampled.selectedPostIds.length > 0
|
|
1950
|
+
? sampled.yieldEvidence
|
|
1951
|
+
: sampled.qualityGateEvidence;
|
|
1952
|
+
const evaluated = observedYieldEvidence.evaluated;
|
|
1953
|
+
return {
|
|
1954
|
+
candidatePostCount: candidates.length,
|
|
1955
|
+
engagerSum: sampled.engagerSum,
|
|
1956
|
+
observedPassRate: evaluated > 0 ? observedYieldEvidence.passes / evaluated : 0,
|
|
1957
|
+
projectedYield: sampled.projectedYield,
|
|
1958
|
+
floor: Math.min(SIGNAL_YIELD_FLOOR_CAP, Math.max(1, remainingPrepGap)),
|
|
1959
|
+
scrapePostIds: sampled.selectedPostIds,
|
|
1960
|
+
carriedPostIds: [],
|
|
1961
|
+
};
|
|
1962
|
+
})()
|
|
1963
|
+
: projectSignalBroadenYield(searchSummary, {
|
|
1964
|
+
maxAgeDays,
|
|
1965
|
+
excludePostIds: excludedPostIds,
|
|
1966
|
+
observedPassRate,
|
|
1967
|
+
remainingPrepGap,
|
|
1968
|
+
maxPosts: maxPostsToScrape,
|
|
1969
|
+
});
|
|
1970
|
+
const noQualifyingV3Post = isRefillV3SinglePassAction(action) &&
|
|
1971
|
+
yieldProjection.scrapePostIds.length === 0;
|
|
1972
|
+
if (noQualifyingV3Post ||
|
|
1973
|
+
(!isRefillV3SinglePassAction(action) &&
|
|
1974
|
+
yieldProjection.projectedYield < yieldProjection.floor)) {
|
|
1819
1975
|
const runScopedCleanup = await cleanupRunScopedDerivedTabs();
|
|
1820
1976
|
// fix(110-23e): the yield floor was evaluated on the ACTUAL candidates this
|
|
1821
1977
|
// search round returned (zero candidates being the projection=0 special case).
|
|
@@ -1826,7 +1982,9 @@ export async function broadenSignalSearch(action, workspaceId) {
|
|
|
1826
1982
|
// terminalizing the lane with the agent tier untried.
|
|
1827
1983
|
return {
|
|
1828
1984
|
status: "refused",
|
|
1829
|
-
refusalReason:
|
|
1985
|
+
refusalReason: noQualifyingV3Post
|
|
1986
|
+
? `signal_yield_below_floor: signal_post_icp_below_floor: none of ${yieldProjection.candidatePostCount} sampled recent posts had at least ${REFILL_V3_SIGNAL_POST_MIN_SAMPLE_SIZE} evaluated engagers and a ${REFILL_V3_SIGNAL_POST_MIN_ICP_RATE * 100}% observed headline-ICP pass rate`
|
|
1987
|
+
: `signal_yield_below_floor: projected new-lead yield ${yieldProjection.projectedYield} (${yieldProjection.engagerSum} scrapable engagers x ${yieldProjection.observedPassRate} observed headline-ICP pass rate across ${yieldProjection.candidatePostCount} NEW candidate posts) is below the floor ${yieldProjection.floor} (min(remaining prep gap, 100))${effectiveKeywordSource === "agent_supplied" ? "; the 45-day relevant Signal universe cannot fill the remaining gap" : "; escalate to the next keyword-source tier"}`,
|
|
1830
1988
|
result: {
|
|
1831
1989
|
campaignOfferId,
|
|
1832
1990
|
keywordSource: effectiveKeywordSource,
|
|
@@ -1849,6 +2007,13 @@ export async function broadenSignalSearch(action, workspaceId) {
|
|
|
1849
2007
|
projectedYield: yieldProjection.projectedYield,
|
|
1850
2008
|
floor: yieldProjection.floor,
|
|
1851
2009
|
},
|
|
2010
|
+
...(postYieldSamples ? { postYieldSamples } : {}),
|
|
2011
|
+
...(observedYieldEvidence
|
|
2012
|
+
? { yieldEvidence: observedYieldEvidence }
|
|
2013
|
+
: {}),
|
|
2014
|
+
...(noQualifyingV3Post
|
|
2015
|
+
? { failureCode: "signal_post_icp_below_floor" }
|
|
2016
|
+
: {}),
|
|
1852
2017
|
newPostCount: 0,
|
|
1853
2018
|
maxAgeDays,
|
|
1854
2019
|
universeExhausted: effectiveKeywordSource === "agent_supplied",
|
|
@@ -1873,7 +2038,10 @@ export async function broadenSignalSearch(action, workspaceId) {
|
|
|
1873
2038
|
campaignOfferId,
|
|
1874
2039
|
selections: newPostIds.map((postId) => ({
|
|
1875
2040
|
postId,
|
|
1876
|
-
reason:
|
|
2041
|
+
reason: postYieldSamples
|
|
2042
|
+
? `refill_sends Signal post sample: ${Math.round((postYieldSamples.find((sample) => sample.postId === postId)
|
|
2043
|
+
?.fitRate ?? 0) * 1000) / 10}% observed headline-ICP fit (minimum ${REFILL_V3_SIGNAL_POST_MIN_ICP_RATE * 100}%)`
|
|
2044
|
+
: "refill_sends signal broadening: NEW recent post (<=45 days) with meaningful engagement, derived from fresh keywords within the campaign's existing search intent",
|
|
1877
2045
|
})),
|
|
1878
2046
|
headlineICPCriteria,
|
|
1879
2047
|
currentStep: null,
|
|
@@ -1972,6 +2140,10 @@ export async function broadenSignalSearch(action, workspaceId) {
|
|
|
1972
2140
|
...(yieldProjectionReceipt
|
|
1973
2141
|
? { yieldProjection: yieldProjectionReceipt }
|
|
1974
2142
|
: {}),
|
|
2143
|
+
...(postYieldSamples ? { postYieldSamples } : {}),
|
|
2144
|
+
...(observedYieldEvidence
|
|
2145
|
+
? { yieldEvidence: observedYieldEvidence }
|
|
2146
|
+
: {}),
|
|
1975
2147
|
// The run-scoped progress gate reads newPostCount: a productive round keeps
|
|
1976
2148
|
// the rung re-emittable; a zero round would have terminalized above.
|
|
1977
2149
|
newPostCount: newPostIds.length,
|
|
@@ -171,6 +171,14 @@ export declare const REFILL_V3_DIAGNOSTIC_SAMPLE_CAP = 10;
|
|
|
171
171
|
export declare const REFILL_V3_MIN_SOURCE_YIELD = 0.08;
|
|
172
172
|
/** The acceptance sample is a fixed number of EVALUATED candidates. */
|
|
173
173
|
export declare const REFILL_V3_MIN_SOURCE_YIELD_SAMPLE = 20;
|
|
174
|
+
/**
|
|
175
|
+
* Signal Discovery has a stricter post-level gate than the shared source
|
|
176
|
+
* receipt floor: each individual post must clear 10% on a real sample before
|
|
177
|
+
* that post may be selected for scraping.
|
|
178
|
+
*/
|
|
179
|
+
export declare const REFILL_V3_SIGNAL_POST_MIN_ICP_RATE = 0.1;
|
|
180
|
+
export declare const REFILL_V3_SIGNAL_POST_MIN_SAMPLE_SIZE = 20;
|
|
181
|
+
export declare const REFILL_V3_SIGNAL_POST_SAMPLE_CANDIDATE_CAP = 5;
|
|
174
182
|
/** `post_engager` is excluded: it keeps posts-refresh preconditions, not a rate. */
|
|
175
183
|
export declare const REFILL_V3_YIELD_FLOOR_FAMILIES: readonly ["signal_discovery", "cold"];
|
|
176
184
|
export type RefillV3YieldFloorFamily = (typeof REFILL_V3_YIELD_FLOOR_FAMILIES)[number];
|
|
@@ -196,6 +196,14 @@ export const REFILL_V3_DIAGNOSTIC_SAMPLE_CAP = 10;
|
|
|
196
196
|
export const REFILL_V3_MIN_SOURCE_YIELD = 0.08;
|
|
197
197
|
/** The acceptance sample is a fixed number of EVALUATED candidates. */
|
|
198
198
|
export const REFILL_V3_MIN_SOURCE_YIELD_SAMPLE = 20;
|
|
199
|
+
/**
|
|
200
|
+
* Signal Discovery has a stricter post-level gate than the shared source
|
|
201
|
+
* receipt floor: each individual post must clear 10% on a real sample before
|
|
202
|
+
* that post may be selected for scraping.
|
|
203
|
+
*/
|
|
204
|
+
export const REFILL_V3_SIGNAL_POST_MIN_ICP_RATE = 0.1;
|
|
205
|
+
export const REFILL_V3_SIGNAL_POST_MIN_SAMPLE_SIZE = 20;
|
|
206
|
+
export const REFILL_V3_SIGNAL_POST_SAMPLE_CANDIDATE_CAP = 5;
|
|
199
207
|
/** `post_engager` is excluded: it keeps posts-refresh preconditions, not a rate. */
|
|
200
208
|
export const REFILL_V3_YIELD_FLOOR_FAMILIES = [
|
|
201
209
|
"signal_discovery",
|
|
@@ -143,6 +143,11 @@ export type RefillV3SourceOwnerOutcome = {
|
|
|
143
143
|
} | {
|
|
144
144
|
kind: "no_effect";
|
|
145
145
|
receipt: RefillV3SourceOwnerReceipt;
|
|
146
|
+
} | {
|
|
147
|
+
/** The source mutation started an asynchronous import that has not settled. */
|
|
148
|
+
kind: "pending";
|
|
149
|
+
effectIdentity: string;
|
|
150
|
+
detail: string;
|
|
146
151
|
} | {
|
|
147
152
|
/** A closed provider-source business terminal, never infrastructure. */
|
|
148
153
|
kind: "source_terminal";
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* fingerprint-identified, and carries an explicit typed RELEASE CONDITION, so a
|
|
22
22
|
* bounded attempt can never harden into a standing claim.
|
|
23
23
|
*/
|
|
24
|
-
import { evaluateRefillV3SourceYield, REFILL_V3_EXHAUSTION_PREDICATE, REFILL_V3_LEAD_SOURCE_FAMILIES, REFILL_V3_MIN_SOURCE_YIELD, REFILL_V3_MIN_SOURCE_YIELD_SAMPLE, } from "./refill-v3-attention-packet-contract.js";
|
|
24
|
+
import { evaluateRefillV3SourceYield, REFILL_V3_EXHAUSTION_PREDICATE, REFILL_V3_LEAD_SOURCE_FAMILIES, REFILL_V3_MIN_SOURCE_YIELD, REFILL_V3_MIN_SOURCE_YIELD_SAMPLE, REFILL_V3_SIGNAL_POST_MIN_ICP_RATE, } from "./refill-v3-attention-packet-contract.js";
|
|
25
25
|
import { REFILL_V3_POSTS_KEYWORD_BATCH_SIZE, REFILL_V3_POSTS_MAX_SELECTED, REFILL_V3_POSTS_ROUND_BUDGET, } from "./refill-v3-campaign-work.js";
|
|
26
26
|
// ───────────────────────────────────────────────────────────────────────────
|
|
27
27
|
// The constant owner table
|
|
@@ -450,26 +450,62 @@ export async function dispatchRefillV3SourceFamily(input) {
|
|
|
450
450
|
reobserveRequired: true,
|
|
451
451
|
};
|
|
452
452
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
453
|
+
if (outcome.kind === "pending") {
|
|
454
|
+
return {
|
|
455
|
+
kind: "scoped_failure",
|
|
456
|
+
family: command.family,
|
|
457
|
+
failureClass: "uncertain",
|
|
458
|
+
retryable: true,
|
|
459
|
+
effectIdentity: outcome.effectIdentity,
|
|
460
|
+
detail: `${ownerKey}: ${outcome.detail}`,
|
|
461
|
+
exhausted: false,
|
|
462
|
+
reobserveRequired: true,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const ownerReceipt = outcome.receipt;
|
|
466
|
+
if (!isNonEmpty(ownerReceipt.sourceBeforeFingerprint) ||
|
|
467
|
+
!isNonEmpty(ownerReceipt.sourceAfterFingerprint) ||
|
|
468
|
+
!isNonEmpty(ownerReceipt.requestFingerprint) ||
|
|
469
|
+
!isNonEmpty(ownerReceipt.idempotencyKey)) {
|
|
458
470
|
return refused("receipt_incomplete", `${ownerKey} returned a receipt without complete source before/after and request evidence; uncertain evidence is never success.`);
|
|
459
471
|
}
|
|
460
472
|
// 14. The AI's recorded per-candidate verdicts feed the SHARED floor. The
|
|
461
473
|
// arithmetic, the comparison, and the routing are deterministic; there is
|
|
462
474
|
// no deterministic candidate judge.
|
|
463
475
|
const verdicts = input.candidateVerdicts ?? null;
|
|
464
|
-
const
|
|
465
|
-
?
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
:
|
|
476
|
+
const auditedYieldEvidence = command.family === "signal_discovery" && ownerReceipt.yieldEvidence
|
|
477
|
+
? ownerReceipt.yieldEvidence
|
|
478
|
+
: verdicts && verdicts.length > 0
|
|
479
|
+
? {
|
|
480
|
+
family: command.family,
|
|
481
|
+
evaluated: verdicts.length,
|
|
482
|
+
passes: verdicts.filter((entry) => entry.verdict === "pass").length,
|
|
483
|
+
}
|
|
484
|
+
: ownerReceipt.yieldEvidence;
|
|
485
|
+
// Signal's owner-level `yieldProjection` is volume planning based on an
|
|
486
|
+
// assumed pass rate. It is never a sampled qualification result. The owner
|
|
487
|
+
// receipt now exposes only real post-level sample evidence, which is
|
|
488
|
+
// authoritative over an agent's pre-search candidate judgement.
|
|
489
|
+
const receipt = {
|
|
490
|
+
...ownerReceipt,
|
|
491
|
+
yieldEvidence: auditedYieldEvidence,
|
|
492
|
+
};
|
|
493
|
+
const signalPostFloorRejected = ownerReceipt.effects.includes("signal_post_icp_below_floor");
|
|
494
|
+
const yieldVerdict = auditedYieldEvidence
|
|
495
|
+
? signalPostFloorRejected
|
|
496
|
+
? auditedYieldEvidence.evaluated < REFILL_V3_MIN_SOURCE_YIELD_SAMPLE
|
|
497
|
+
? {
|
|
498
|
+
decision: "sample_incomplete",
|
|
499
|
+
evaluated: auditedYieldEvidence.evaluated,
|
|
500
|
+
required: REFILL_V3_MIN_SOURCE_YIELD_SAMPLE,
|
|
501
|
+
}
|
|
502
|
+
: {
|
|
503
|
+
decision: "below_floor",
|
|
504
|
+
ratio: auditedYieldEvidence.passes / auditedYieldEvidence.evaluated,
|
|
505
|
+
floor: REFILL_V3_SIGNAL_POST_MIN_ICP_RATE,
|
|
506
|
+
}
|
|
507
|
+
: evaluateRefillV3DispatchYield(auditedYieldEvidence)
|
|
508
|
+
: null;
|
|
473
509
|
// A round that MEETS the floor is a qualifying source: import and stop
|
|
474
510
|
// broadening, so its fingerprint is not a refusal for later rounds. Every
|
|
475
511
|
// other completed round registers so it cannot repeat unchanged.
|
package/package.json
CHANGED
|
@@ -136,8 +136,8 @@ You must estimate:
|
|
|
136
136
|
floor is a viability reject threshold, not the denominator when the sample
|
|
137
137
|
rate is higher.
|
|
138
138
|
- `planningFloor`
|
|
139
|
-
- minimum acceptable
|
|
140
|
-
Below this, do not
|
|
139
|
+
- minimum acceptable observed headline-fit rate for each sampled post;
|
|
140
|
+
default 10%. Below this, do not scrape that post.
|
|
141
141
|
- `projectedRange`
|
|
142
142
|
- the conservative range implied by the observed sample
|
|
143
143
|
|
|
@@ -164,9 +164,11 @@ Use conservative logic:
|
|
|
164
164
|
- preferred: "`sampledCount`: 40, `passCount`: 9, `passRate`: ~22%, `goodFitPer100Engagers`: ~22 headline-fit prospects, `requiredEngagersToScrape`: ~1,365 raw engagers for a 300 headline-fit target before a practical coverage buffer, `avgReachableEngagersPerPost`: 240, `goodFitProspectsPerPost`: ~53, `postsNeededForTarget`: ~6, `recentStrongPostCount`: 15-25, `freshEnoughPostCount`: 8-12, `projectedRange`: 300-380 from selected posts"
|
|
165
165
|
- fallback: "I could not fetch engagers, so this is inferred from post and author quality only"
|
|
166
166
|
5. If the evidence is too weak, say so and return a low-confidence estimate instead of pretending precision.
|
|
167
|
-
6.
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
6. Decide per post. Keep only posts whose own real engager sample clears the
|
|
168
|
+
10% planning floor; never pool all candidate posts or substitute a default
|
|
169
|
+
planning rate for an observed sample. If no sampled post clears 10%, reject
|
|
170
|
+
this Signal Discovery round and recommend Sales Nav recent activity as the
|
|
171
|
+
next source.
|
|
170
172
|
7. Do not manually enumerate dozens of engagers in the final answer. Summarize the sample by `sampledCount`, `passCount`, `passRate`, `projectedRange`, and 3-6 representative examples.
|
|
171
173
|
|
|
172
174
|
</yield_estimation>
|
|
@@ -448,7 +450,8 @@ recommendation, estimate source capacity from real sample math:
|
|
|
448
450
|
- average reachable engagers per right-content post
|
|
449
451
|
- expected headline-fit leads per selected post
|
|
450
452
|
- posts needed to reach the target
|
|
451
|
-
-
|
|
453
|
+
- each selected post's sampled headline-fit rate and whether it clears the 10%
|
|
454
|
+
planning floor
|
|
452
455
|
|
|
453
456
|
Then select the smallest right-content post set that plausibly hits the source
|
|
454
457
|
target. Do not scrape every promoted sample post by default; promoted sampling
|
|
@@ -457,8 +460,8 @@ state and final scrape plan are separate. If one 1,200+ engager post clears a
|
|
|
457
460
|
sample set. If the math says the warm post lane only supports a smaller first
|
|
458
461
|
batch, say that and name the Sales Nav or Prospeo scale fallback rather than
|
|
459
462
|
padding the selection with noisy posts. If the
|
|
460
|
-
sampled
|
|
461
|
-
`import_leads
|
|
463
|
+
sampled headline-fit rate for a post is below 10%, exclude that post from
|
|
464
|
+
`import_leads`. If no sampled post clears 10%, move to Sales Nav recent activity.
|
|
462
465
|
|
|
463
466
|
```json
|
|
464
467
|
select_promising_posts({
|
|
@@ -285,10 +285,10 @@ For a signal-discovery posts search:
|
|
|
285
285
|
receipt's `remainingCorrectionAttempts: 1` is consumed by that one revised
|
|
286
286
|
decision; a second collision is terminal and must never loop.
|
|
287
287
|
- Apply both quality gates from the Signal provider contract: recent relevant
|
|
288
|
-
post quality first, then a 10% minimum
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
288
|
+
post quality first, then a 10% minimum observed headline-ICP pass rate on a
|
|
289
|
+
real engager sample for each specific post. Never pool candidate-post
|
|
290
|
+
engagement, apply a default rate, or relabel projected volume as observed
|
|
291
|
+
qualification. Import only the individual posts that clear 10%.
|
|
292
292
|
- select AT MOST 5 candidate posts from that round's results;
|
|
293
293
|
- pick the BEST posts in this order: (1) expected ICP fit of the post's engagers,
|
|
294
294
|
(2) engagement volume high enough that scraping is efficient — qualified
|