@crmforall/connector 0.1.9 → 0.1.10
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/contracts.d.ts +1 -0
- package/dist/contracts.js +2 -0
- package/dist/targets.d.ts +14 -0
- package/dist/targets.js +33 -0
- package/dist/tools.js +16 -0
- package/package.json +1 -1
package/dist/contracts.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare const MCP_TOOL: {
|
|
|
18
18
|
readonly TRACK_ENGAGEMENT: "track_engagement";
|
|
19
19
|
readonly ONBOARDING_STATUS: "onboarding_status";
|
|
20
20
|
readonly SYNC_ALL: "sync_all";
|
|
21
|
+
readonly AUDIENCE_SUMMARY: "audience_summary";
|
|
21
22
|
};
|
|
22
23
|
export type McpToolName = (typeof MCP_TOOL)[keyof typeof MCP_TOOL];
|
|
23
24
|
export declare const APPROVAL_SCOPE: {
|
package/dist/contracts.js
CHANGED
|
@@ -21,6 +21,7 @@ exports.MCP_TOOL = {
|
|
|
21
21
|
TRACK_ENGAGEMENT: "track_engagement",
|
|
22
22
|
ONBOARDING_STATUS: "onboarding_status",
|
|
23
23
|
SYNC_ALL: "sync_all",
|
|
24
|
+
AUDIENCE_SUMMARY: "audience_summary",
|
|
24
25
|
};
|
|
25
26
|
exports.APPROVAL_SCOPE = {
|
|
26
27
|
TARGET_SNAPSHOT_CREATE: "target_snapshot:create",
|
|
@@ -45,6 +46,7 @@ exports.TOOL_APPROVAL_REQUIREMENT = {
|
|
|
45
46
|
[exports.MCP_TOOL.TRACK_ENGAGEMENT]: null,
|
|
46
47
|
[exports.MCP_TOOL.ONBOARDING_STATUS]: null,
|
|
47
48
|
[exports.MCP_TOOL.SYNC_ALL]: null,
|
|
49
|
+
[exports.MCP_TOOL.AUDIENCE_SUMMARY]: null,
|
|
48
50
|
};
|
|
49
51
|
/** 승인 토큰이 필요한 도구 — 토큰 없이 호출되면 즉시 거부한다. */
|
|
50
52
|
exports.APPROVAL_REQUIRED_TOOLS = new Set(Object.entries(exports.TOOL_APPROVAL_REQUIREMENT)
|
package/dist/targets.d.ts
CHANGED
|
@@ -21,6 +21,20 @@ export interface TargetComputation {
|
|
|
21
21
|
}>;
|
|
22
22
|
previewHash: string;
|
|
23
23
|
}
|
|
24
|
+
export interface AudienceSummary {
|
|
25
|
+
totalCustomers: number;
|
|
26
|
+
withPhone: number | null;
|
|
27
|
+
optInTrue: number | null;
|
|
28
|
+
optInFalse: number | null;
|
|
29
|
+
optInUnknown: number | null;
|
|
30
|
+
/** 발송 가능 후보(전화 보유 + 동의 거부 아님) — 첫 체감용 대략치 */
|
|
31
|
+
reachable: number | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 청중 요약 — 매핑만으로 즉시 가능한 "고객 N명 · 발송 동의 분포" (프로젝트 불필요).
|
|
35
|
+
* 온보딩 직후 첫 체감("보낼 수 있는 고객 N명")을 주기 위한 읽기 전용 집계.
|
|
36
|
+
*/
|
|
37
|
+
export declare function audienceSummary(db: SqlExecutor, mapping: MappingConfig): Promise<AudienceSummary>;
|
|
24
38
|
/**
|
|
25
39
|
* 스냅샷/미리보기 해시 — 같은 대상 집합이면 항상 같은 값.
|
|
26
40
|
* 승인 토큰의 snapshotHash 바인딩과 비교되므로 결정적이어야 한다.
|
package/dist/targets.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.audienceSummary = audienceSummary;
|
|
3
4
|
exports.computePreviewHash = computePreviewHash;
|
|
4
5
|
exports.computeTargets = computeTargets;
|
|
5
6
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -13,6 +14,38 @@ function ident(name) {
|
|
|
13
14
|
function tableRef(t) {
|
|
14
15
|
return `${ident(t.schema)}.${ident(t.table)}`;
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* 청중 요약 — 매핑만으로 즉시 가능한 "고객 N명 · 발송 동의 분포" (프로젝트 불필요).
|
|
19
|
+
* 온보딩 직후 첫 체감("보낼 수 있는 고객 N명")을 주기 위한 읽기 전용 집계.
|
|
20
|
+
*/
|
|
21
|
+
async function audienceSummary(db, mapping) {
|
|
22
|
+
const c = mapping.customer;
|
|
23
|
+
const hasPhone = Boolean(c.fields.phoneNumberHash);
|
|
24
|
+
const hasOptIn = Boolean(c.fields.marketingOptIn);
|
|
25
|
+
const selects = ["COUNT(*)::int AS total"];
|
|
26
|
+
if (hasPhone)
|
|
27
|
+
selects.push(`COUNT(${ident(c.fields.phoneNumberHash)})::int AS with_phone`);
|
|
28
|
+
if (hasOptIn) {
|
|
29
|
+
const o = ident(c.fields.marketingOptIn);
|
|
30
|
+
selects.push(`COUNT(*) FILTER (WHERE ${o} IS TRUE)::int AS optin_true`);
|
|
31
|
+
selects.push(`COUNT(*) FILTER (WHERE ${o} IS FALSE)::int AS optin_false`);
|
|
32
|
+
selects.push(`COUNT(*) FILTER (WHERE ${o} IS NULL)::int AS optin_null`);
|
|
33
|
+
if (hasPhone) {
|
|
34
|
+
selects.push(`COUNT(*) FILTER (WHERE ${o} IS DISTINCT FROM FALSE AND ${ident(c.fields.phoneNumberHash)} IS NOT NULL)::int AS reachable`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const rows = (await db.selectRows(`SELECT ${selects.join(", ")} FROM ${tableRef(c.table)}`));
|
|
38
|
+
const row = rows[0] ?? {};
|
|
39
|
+
const num = (k) => (typeof row[k] === "number" ? row[k] : null);
|
|
40
|
+
return {
|
|
41
|
+
totalCustomers: num("total") ?? 0,
|
|
42
|
+
withPhone: hasPhone ? num("with_phone") : null,
|
|
43
|
+
optInTrue: hasOptIn ? num("optin_true") : null,
|
|
44
|
+
optInFalse: hasOptIn ? num("optin_false") : null,
|
|
45
|
+
optInUnknown: hasOptIn ? num("optin_null") : null,
|
|
46
|
+
reachable: hasOptIn && hasPhone ? num("reachable") : null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
16
49
|
/**
|
|
17
50
|
* 스냅샷/미리보기 해시 — 같은 대상 집합이면 항상 같은 값.
|
|
18
51
|
* 승인 토큰의 snapshotHash 바인딩과 비교되므로 결정적이어야 한다.
|
package/dist/tools.js
CHANGED
|
@@ -357,6 +357,22 @@ function registerTools(server, rt) {
|
|
|
357
357
|
return unexpected(err);
|
|
358
358
|
}
|
|
359
359
|
});
|
|
360
|
+
server.tool(contracts_1.MCP_TOOL.AUDIENCE_SUMMARY, "확정된 매핑으로 고객 수·발송 동의 분포·발송 가능 후보 수를 즉시 집계한다 (프로젝트 불필요, 읽기 전용, 승인 불필요). 온보딩 직후 첫 체감용.", {}, async () => {
|
|
361
|
+
try {
|
|
362
|
+
if (!rt.config.mapping) {
|
|
363
|
+
return ok({
|
|
364
|
+
ready: false,
|
|
365
|
+
message: "먼저 propose_field_mapping → validate_mapping(persist=true)으로 매핑을 확정하세요.",
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
const db = await rt.getDb();
|
|
369
|
+
const summary = await (0, targets_1.audienceSummary)(db, rt.config.mapping);
|
|
370
|
+
return ok(summary);
|
|
371
|
+
}
|
|
372
|
+
catch (err) {
|
|
373
|
+
return unexpected(err);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
360
376
|
server.tool(contracts_1.MCP_TOOL.SYNC_SEND_RESULT, "발송 작업 결과를 플랫폼에서 가져와 CRM 스키마에 동기화한다 (승인 불필요)", { sendJobId: zod_1.z.string() }, async ({ sendJobId }) => {
|
|
361
377
|
try {
|
|
362
378
|
const db = await rt.getDb();
|