@rubytech/create-realagent-code 0.1.118 → 0.1.120
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/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +93 -1
- package/payload/platform/package-lock.json +141 -125
- package/payload/platform/plugins/aeo/PLUGIN.md +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts +12 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js +34 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js.map +1 -1
- package/payload/platform/plugins/docs/references/aeo.md +2 -2
- package/payload/platform/plugins/docs/references/visitor-graph.md +82 -0
- package/payload/platform/plugins/memory/references/schema-base.md +44 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/.claude-plugin/plugin.json +1 -1
- package/payload/premium-plugins/real-agent/plugins/buyers/PLUGIN.md +33 -3
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.d.ts +2 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.js +97 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/index.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/neo4j.js +40 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.d.ts +24 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.js +156 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-backfill-from-logs.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-engagement-score.d.ts +21 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-engagement-score.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-engagement-score.js +65 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-engagement-score.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.d.ts +26 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.js +78 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-page.d.ts +19 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-page.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-page.js +41 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-page.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-person.d.ts +20 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-person.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-person.js +44 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-person.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recommendation-ctr.d.ts +22 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recommendation-ctr.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recommendation-ctr.js +41 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recommendation-ctr.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-session-detail.d.ts +32 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-session-detail.d.ts.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-session-detail.js +87 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-session-detail.js.map +1 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/package-lock.json +2566 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/package.json +21 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/index.ts +145 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/lib/neo4j.ts +45 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-backfill-from-logs.ts +189 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-engagement-score.ts +87 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-event-ingest.ts +119 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-recent-by-page.ts +61 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-recent-by-person.ts +65 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-recommendation-ctr.ts +67 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/src/tools/visitor-session-detail.ts +127 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/mcp/tsconfig.json +20 -0
- package/payload/premium-plugins/real-agent/plugins/buyers/skills/property-recommender/SKILL.md +11 -0
- package/payload/premium-plugins/real-agent/plugins/leads/skills/lead-nurturing/SKILL.md +9 -0
- package/payload/premium-plugins/real-agent/plugins/leads/skills/morning-round/SKILL.md +11 -0
- package/payload/premium-plugins/real-agent/plugins/vendors/skills/vendor-updates/SKILL.md +13 -0
- package/payload/server/{chunk-FJCI6X3H.js → chunk-HCYM5FLU.js} +2 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/privacy.html +66 -0
- package/payload/server/public/v.js +191 -0
- package/payload/server/server.js +473 -85
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// visitor-backfill-from-logs — one-shot importer.
|
|
2
|
+
//
|
|
3
|
+
// Parses [property-recommended] and [property-card-click] log lines from
|
|
4
|
+
// the platform log and writes them into the visitor graph:
|
|
5
|
+
//
|
|
6
|
+
// [property-recommended] sessionKey=<sk> listingSlug=<slug> ts=<iso>
|
|
7
|
+
// -> MERGE (Session {sessionKey})
|
|
8
|
+
// CREATE (Recommendation {sessionKey, listingSlug, recommendedAt})
|
|
9
|
+
// MERGE (Recommendation)-[:FOR_SESSION]->(Session)
|
|
10
|
+
//
|
|
11
|
+
// [property-card-click] sessionKey=<sk> listingSlug=<slug> ts=<iso>
|
|
12
|
+
// -> MATCH/MERGE Session, CREATE Click {label:'card', listingSlug}
|
|
13
|
+
// MERGE Session-[:HAS_EVENT]->Click
|
|
14
|
+
//
|
|
15
|
+
// Parity check after backfill: graph counts vs log-scan counts for the
|
|
16
|
+
// same window. parityCheck is 'ok' iff both deltas are zero, otherwise
|
|
17
|
+
// 'mismatch' with the delta surfaced for diagnosis.
|
|
18
|
+
//
|
|
19
|
+
// Idempotency: re-running over the same log window does not duplicate
|
|
20
|
+
// Recommendation nodes (MERGE on the exact sessionKey + slug + ts tuple).
|
|
21
|
+
// Click nodes are immutable event records by design; if you re-run on a
|
|
22
|
+
// window already processed you get duplicates. Operator runs once per
|
|
23
|
+
// cutover.
|
|
24
|
+
import { readFileSync } from "node:fs";
|
|
25
|
+
import { getSession } from "../lib/neo4j.js";
|
|
26
|
+
const REC_RE = /\[property-recommended\]\s+sessionKey=(\S+)\s+listingSlug=(\S+)\s+ts=(\S+)/;
|
|
27
|
+
const CLICK_RE = /\[property-card-click\]\s+sessionKey=(\S+)\s+listingSlug=(\S+)\s+ts=(\S+)/;
|
|
28
|
+
const ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
|
|
29
|
+
function parseLine(line) {
|
|
30
|
+
let m = REC_RE.exec(line);
|
|
31
|
+
if (m)
|
|
32
|
+
return { kind: "rec", sessionKey: m[1], listingSlug: m[2], ts: m[3] };
|
|
33
|
+
m = CLICK_RE.exec(line);
|
|
34
|
+
if (m)
|
|
35
|
+
return { kind: "click", sessionKey: m[1], listingSlug: m[2], ts: m[3] };
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function inWindow(iso, sinceMs, untilMs) {
|
|
39
|
+
if (!ISO_RE.test(iso))
|
|
40
|
+
return false;
|
|
41
|
+
const t = Date.parse(iso);
|
|
42
|
+
if (Number.isNaN(t))
|
|
43
|
+
return false;
|
|
44
|
+
if (sinceMs !== null && t < sinceMs)
|
|
45
|
+
return false;
|
|
46
|
+
if (untilMs !== null && t >= untilMs)
|
|
47
|
+
return false;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
export async function visitorBackfillFromLogs(p) {
|
|
51
|
+
const sinceMs = p.sinceIso ? Date.parse(p.sinceIso) : null;
|
|
52
|
+
const untilMs = p.untilIso ? Date.parse(p.untilIso) : null;
|
|
53
|
+
const batchSize = Math.max(10, Math.min(p.batchSize ?? 200, 2000));
|
|
54
|
+
let body;
|
|
55
|
+
try {
|
|
56
|
+
body = readFileSync(p.logPath, "utf-8");
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
throw new Error(`[visitor-backfill] log read failed path=${p.logPath} err=${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
const rec = [];
|
|
62
|
+
const click = [];
|
|
63
|
+
let unparseable = 0;
|
|
64
|
+
let windowStart = null;
|
|
65
|
+
let windowEnd = null;
|
|
66
|
+
for (const raw of body.split("\n")) {
|
|
67
|
+
if (!raw || (!raw.includes("[property-recommended]") && !raw.includes("[property-card-click]")))
|
|
68
|
+
continue;
|
|
69
|
+
const parsed = parseLine(raw);
|
|
70
|
+
if (!parsed) {
|
|
71
|
+
unparseable += 1;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!inWindow(parsed.ts, sinceMs, untilMs))
|
|
75
|
+
continue;
|
|
76
|
+
if (parsed.kind === "rec")
|
|
77
|
+
rec.push(parsed);
|
|
78
|
+
else
|
|
79
|
+
click.push(parsed);
|
|
80
|
+
if (windowStart === null || parsed.ts < windowStart)
|
|
81
|
+
windowStart = parsed.ts;
|
|
82
|
+
if (windowEnd === null || parsed.ts > windowEnd)
|
|
83
|
+
windowEnd = parsed.ts;
|
|
84
|
+
}
|
|
85
|
+
const session = getSession();
|
|
86
|
+
let recommendedWritten = 0;
|
|
87
|
+
let recommendedSkippedDuplicate = 0;
|
|
88
|
+
let clickedWritten = 0;
|
|
89
|
+
console.error(`[visitor-backfill] start accountId=${p.accountId} window=${windowStart}..${windowEnd} rec=${rec.length} click=${click.length}`);
|
|
90
|
+
try {
|
|
91
|
+
for (let i = 0; i < rec.length; i += batchSize) {
|
|
92
|
+
const slice = rec.slice(i, i + batchSize);
|
|
93
|
+
const result = await session.run(`UNWIND $rows AS row
|
|
94
|
+
MERGE (s:Session {accountId: $accountId, sessionId: row.sessionKey})
|
|
95
|
+
ON CREATE SET s.startedAt = datetime(row.ts), s.lastSeenAt = datetime(row.ts), s.backfilled = true
|
|
96
|
+
ON MATCH SET s.lastSeenAt = CASE WHEN s.lastSeenAt > datetime(row.ts) THEN s.lastSeenAt ELSE datetime(row.ts) END
|
|
97
|
+
MERGE (r:Recommendation {accountId: $accountId, sessionKey: row.sessionKey, listingSlug: row.listingSlug, recommendedAt: datetime(row.ts)})
|
|
98
|
+
ON CREATE SET r.createdAt = datetime(), r.created = true
|
|
99
|
+
ON MATCH SET r.created = false
|
|
100
|
+
MERGE (r)-[:FOR_SESSION]->(s)
|
|
101
|
+
RETURN sum(CASE WHEN r.created THEN 1 ELSE 0 END) AS written,
|
|
102
|
+
sum(CASE WHEN r.created THEN 0 ELSE 1 END) AS skipped`, { accountId: p.accountId, rows: slice });
|
|
103
|
+
const row = result.records[0];
|
|
104
|
+
recommendedWritten += Number(row?.get("written") ?? 0);
|
|
105
|
+
recommendedSkippedDuplicate += Number(row?.get("skipped") ?? 0);
|
|
106
|
+
}
|
|
107
|
+
for (let i = 0; i < click.length; i += batchSize) {
|
|
108
|
+
const slice = click.slice(i, i + batchSize);
|
|
109
|
+
const result = await session.run(`UNWIND $rows AS row
|
|
110
|
+
MERGE (s:Session {accountId: $accountId, sessionId: row.sessionKey})
|
|
111
|
+
ON CREATE SET s.startedAt = datetime(row.ts), s.lastSeenAt = datetime(row.ts), s.backfilled = true
|
|
112
|
+
ON MATCH SET s.lastSeenAt = CASE WHEN s.lastSeenAt > datetime(row.ts) THEN s.lastSeenAt ELSE datetime(row.ts) END
|
|
113
|
+
CREATE (cl:Click {accountId: $accountId, label: 'card', href: '', listingSlug: row.listingSlug, pageViewId: '', occurredAt: datetime(row.ts), backfilled: true})
|
|
114
|
+
MERGE (s)-[:HAS_EVENT]->(cl)
|
|
115
|
+
RETURN count(cl) AS written`, { accountId: p.accountId, rows: slice });
|
|
116
|
+
const row = result.records[0];
|
|
117
|
+
clickedWritten += Number(row?.get("written") ?? 0);
|
|
118
|
+
}
|
|
119
|
+
const parityRes = await session.run(`MATCH (rec:Recommendation {accountId: $accountId})
|
|
120
|
+
WHERE rec.recommendedAt >= datetime($since) AND rec.recommendedAt < datetime($until)
|
|
121
|
+
WITH count(DISTINCT rec) AS recCount
|
|
122
|
+
OPTIONAL MATCH (cl:Click {accountId: $accountId, label: 'card'})
|
|
123
|
+
WHERE cl.occurredAt >= datetime($since) AND cl.occurredAt < datetime($until)
|
|
124
|
+
AND cl.backfilled = true
|
|
125
|
+
RETURN recCount, count(DISTINCT cl) AS clickCount`, {
|
|
126
|
+
accountId: p.accountId,
|
|
127
|
+
since: windowStart ?? new Date(0).toISOString(),
|
|
128
|
+
until: windowEnd ? new Date(Date.parse(windowEnd) + 1).toISOString() : new Date().toISOString(),
|
|
129
|
+
});
|
|
130
|
+
const head = parityRes.records[0];
|
|
131
|
+
const recGraph = Number(head?.get("recCount") ?? 0);
|
|
132
|
+
const clickGraph = Number(head?.get("clickCount") ?? 0);
|
|
133
|
+
const parityDelta = {
|
|
134
|
+
recommended: recGraph - rec.length,
|
|
135
|
+
clicked: clickGraph - click.length,
|
|
136
|
+
};
|
|
137
|
+
const parityCheck = parityDelta.recommended === 0 && parityDelta.clicked === 0 ? "ok" : "mismatch";
|
|
138
|
+
console.error(`[visitor-backfill] done parityCheck=${parityCheck} recParsed=${rec.length} recWritten=${recommendedWritten} clickParsed=${click.length} clickWritten=${clickedWritten} unparseable=${unparseable}`);
|
|
139
|
+
return {
|
|
140
|
+
windowStart,
|
|
141
|
+
windowEnd,
|
|
142
|
+
recommendedParsed: rec.length,
|
|
143
|
+
recommendedWritten,
|
|
144
|
+
recommendedSkippedDuplicate,
|
|
145
|
+
clickedParsed: click.length,
|
|
146
|
+
clickedWritten,
|
|
147
|
+
unparseable,
|
|
148
|
+
parityCheck,
|
|
149
|
+
parityDelta,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
await session.close();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=visitor-backfill-from-logs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-backfill-from-logs.js","sourceRoot":"","sources":["../../src/tools/visitor-backfill-from-logs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface EngagementParams {
|
|
2
|
+
accountId: string;
|
|
3
|
+
sinceDays?: number;
|
|
4
|
+
limit?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface EngagementRow {
|
|
7
|
+
personId: string;
|
|
8
|
+
personName: string | null;
|
|
9
|
+
email: string | null;
|
|
10
|
+
telephone: string | null;
|
|
11
|
+
visitCount: number;
|
|
12
|
+
lastSeenAt: string;
|
|
13
|
+
daysSinceLast: number;
|
|
14
|
+
avgScrollDepth: number;
|
|
15
|
+
totalDwellMs: number;
|
|
16
|
+
score: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function visitorEngagementScore(p: EngagementParams): Promise<{
|
|
19
|
+
persons: EngagementRow[];
|
|
20
|
+
}>;
|
|
21
|
+
//# sourceMappingURL=visitor-engagement-score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-engagement-score.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-engagement-score.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC,CAsDvG"}
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-engagement-score.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// visitor-engagement-score — engagement-ranked Person list.
|
|
2
|
+
//
|
|
3
|
+
// score = visit_count * recency_weight * depth_weight * dwell_weight
|
|
4
|
+
// visit_count = number of :Session in the window
|
|
5
|
+
// recency_weight = exp(-daysSinceLast / 14) (half-life 14d)
|
|
6
|
+
// depth_weight = avg(maxScrollDepth / 100)
|
|
7
|
+
// dwell_weight = log1p(totalDwellMs / 60000) (per minute)
|
|
8
|
+
//
|
|
9
|
+
// Score is unitless; the nurture-list skill consumes it for ranking, not
|
|
10
|
+
// absolute meaning. Returns top N most engaged Persons within sinceDays.
|
|
11
|
+
import { getSession } from "../lib/neo4j.js";
|
|
12
|
+
export async function visitorEngagementScore(p) {
|
|
13
|
+
const since = p.sinceDays ?? 30;
|
|
14
|
+
const limit = Math.max(1, Math.min(p.limit ?? 50, 200));
|
|
15
|
+
const session = getSession();
|
|
16
|
+
try {
|
|
17
|
+
const result = await session.run(`MATCH (per:Person {accountId: $accountId})-[:VISITED]->(s:Session)
|
|
18
|
+
WHERE s.startedAt >= datetime() - duration({days: $since})
|
|
19
|
+
OPTIONAL MATCH (s)-[:HAS_EVENT]->(sm:ScrollMilestone)
|
|
20
|
+
OPTIONAL MATCH (s)-[:HAS_EVENT]->(pv:PageView)
|
|
21
|
+
WITH per, s,
|
|
22
|
+
coalesce(avg(sm.maxDepth), 0) AS avgDepth,
|
|
23
|
+
coalesce(sum(pv.dwellMs), 0) AS dwellMs
|
|
24
|
+
WITH per, count(s) AS visits, max(s.lastSeenAt) AS lastSeenAt,
|
|
25
|
+
avg(avgDepth) AS avgDepth, sum(dwellMs) AS totalDwellMs
|
|
26
|
+
WITH per, visits, lastSeenAt, avgDepth, totalDwellMs,
|
|
27
|
+
duration.between(lastSeenAt, datetime()).days AS daysSinceLast
|
|
28
|
+
WITH per, visits, lastSeenAt, avgDepth, totalDwellMs, daysSinceLast,
|
|
29
|
+
visits
|
|
30
|
+
* exp(-daysSinceLast / 14.0)
|
|
31
|
+
* (avgDepth / 100.0 + 0.1)
|
|
32
|
+
* log(1.0 + totalDwellMs / 60000.0)
|
|
33
|
+
AS score
|
|
34
|
+
RETURN elementId(per) AS personId,
|
|
35
|
+
per.name AS personName,
|
|
36
|
+
per.email AS email,
|
|
37
|
+
per.telephone AS telephone,
|
|
38
|
+
visits AS visitCount,
|
|
39
|
+
toString(lastSeenAt) AS lastSeenAt,
|
|
40
|
+
daysSinceLast,
|
|
41
|
+
avgDepth AS avgScrollDepth,
|
|
42
|
+
totalDwellMs,
|
|
43
|
+
score
|
|
44
|
+
ORDER BY score DESC
|
|
45
|
+
LIMIT $limit`, { accountId: p.accountId, since, limit });
|
|
46
|
+
return {
|
|
47
|
+
persons: result.records.map((r) => ({
|
|
48
|
+
personId: r.get("personId"),
|
|
49
|
+
personName: r.get("personName"),
|
|
50
|
+
email: r.get("email"),
|
|
51
|
+
telephone: r.get("telephone"),
|
|
52
|
+
visitCount: Number(r.get("visitCount") ?? 0),
|
|
53
|
+
lastSeenAt: r.get("lastSeenAt"),
|
|
54
|
+
daysSinceLast: Number(r.get("daysSinceLast") ?? 0),
|
|
55
|
+
avgScrollDepth: Number(r.get("avgScrollDepth") ?? 0),
|
|
56
|
+
totalDwellMs: Number(r.get("totalDwellMs") ?? 0),
|
|
57
|
+
score: Number(r.get("score") ?? 0),
|
|
58
|
+
})),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await session.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=visitor-engagement-score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-engagement-score.js","sourceRoot":"","sources":["../../src/tools/visitor-engagement-score.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,EAAE;AACF,qEAAqE;AACrE,sDAAsD;AACtD,+DAA+D;AAC/D,+CAA+C;AAC/C,+DAA+D;AAC/D,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AAEzE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAqB7C,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,CAAmB;IAC9D,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA4Bc,EACd,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CACzC,CAAC;QACF,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAClC,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC;gBAC3B,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;gBAC/B,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;gBACrB,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC;gBAC7B,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;gBAC5C,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;gBAC/B,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;gBAClD,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;gBACpD,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;gBAChD,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;aACnC,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface IngestParams {
|
|
2
|
+
accountId: string;
|
|
3
|
+
type: "pageview" | "click" | "scroll" | "dwell";
|
|
4
|
+
sessionId: string;
|
|
5
|
+
visitorId: string;
|
|
6
|
+
/** optional Person elementId — overrides identity resolution */
|
|
7
|
+
personId?: string | null;
|
|
8
|
+
url: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
pageViewId?: string;
|
|
12
|
+
site?: string;
|
|
13
|
+
referrer?: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
href?: string;
|
|
16
|
+
depth?: number;
|
|
17
|
+
dwellMs?: number;
|
|
18
|
+
occurredAt?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface IngestResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
type: string;
|
|
23
|
+
sessionId: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function visitorEventIngest(p: IngestParams): Promise<IngestResult>;
|
|
26
|
+
//# sourceMappingURL=visitor-event-ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-event-ingest.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-event-ingest.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAqF/E"}
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// visitor-event-ingest — admin-side companion to POST /v/event for test
|
|
2
|
+
// harness work and manual replay. Takes a single event payload identical
|
|
3
|
+
// in shape to what the pixel sends, writes it into the graph against the
|
|
4
|
+
// caller's accountId. Not exposed on the public agent surface.
|
|
5
|
+
import { getSession } from "../lib/neo4j.js";
|
|
6
|
+
export async function visitorEventIngest(p) {
|
|
7
|
+
const session = getSession();
|
|
8
|
+
const occurredAt = p.occurredAt ?? new Date().toISOString();
|
|
9
|
+
try {
|
|
10
|
+
if (p.type === "pageview") {
|
|
11
|
+
await session.run(`MERGE (s:Session {accountId: $accountId, sessionId: $sid})
|
|
12
|
+
ON CREATE SET s.startedAt = datetime($occurredAt), s.lastSeenAt = datetime($occurredAt)
|
|
13
|
+
ON MATCH SET s.lastSeenAt = datetime($occurredAt)
|
|
14
|
+
MERGE (pg:Page {accountId: $accountId, url: $url})
|
|
15
|
+
ON CREATE SET pg.firstSeenAt = datetime($occurredAt), pg.title = $title
|
|
16
|
+
CREATE (pv:PageView {
|
|
17
|
+
accountId: $accountId, pageViewId: $pvId, occurredAt: datetime($occurredAt),
|
|
18
|
+
referrer: $referrer, path: $path
|
|
19
|
+
})
|
|
20
|
+
MERGE (s)-[:HAS_EVENT]->(pv)
|
|
21
|
+
MERGE (pv)-[:OF_PAGE]->(pg)`, {
|
|
22
|
+
accountId: p.accountId,
|
|
23
|
+
sid: p.sessionId,
|
|
24
|
+
url: p.url,
|
|
25
|
+
title: p.title ?? "",
|
|
26
|
+
pvId: p.pageViewId ?? `${p.sessionId}_${Date.parse(occurredAt)}`,
|
|
27
|
+
path: p.path ?? "",
|
|
28
|
+
referrer: p.referrer ?? "",
|
|
29
|
+
occurredAt,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else if (p.type === "click") {
|
|
33
|
+
await session.run(`MERGE (s:Session {accountId: $accountId, sessionId: $sid})
|
|
34
|
+
ON CREATE SET s.startedAt = datetime($occurredAt), s.lastSeenAt = datetime($occurredAt)
|
|
35
|
+
ON MATCH SET s.lastSeenAt = datetime($occurredAt)
|
|
36
|
+
CREATE (cl:Click {accountId: $accountId, label: $label, href: $href, pageViewId: $pvId, occurredAt: datetime($occurredAt)})
|
|
37
|
+
MERGE (s)-[:HAS_EVENT]->(cl)`, {
|
|
38
|
+
accountId: p.accountId,
|
|
39
|
+
sid: p.sessionId,
|
|
40
|
+
label: (p.label ?? "unknown").slice(0, 64),
|
|
41
|
+
href: (p.href ?? "").slice(0, 2048),
|
|
42
|
+
pvId: p.pageViewId ?? "",
|
|
43
|
+
occurredAt,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else if (p.type === "scroll") {
|
|
47
|
+
await session.run(`MERGE (s:Session {accountId: $accountId, sessionId: $sid})
|
|
48
|
+
MERGE (sm:ScrollMilestone {accountId: $accountId, sessionId: $sid, pageViewId: $pvId})
|
|
49
|
+
ON CREATE SET sm.maxDepth = $depth, sm.firstAt = datetime($occurredAt), sm.lastAt = datetime($occurredAt)
|
|
50
|
+
ON MATCH SET sm.maxDepth = CASE WHEN sm.maxDepth >= $depth THEN sm.maxDepth ELSE $depth END,
|
|
51
|
+
sm.lastAt = datetime($occurredAt)
|
|
52
|
+
MERGE (s)-[:HAS_EVENT]->(sm)`, { accountId: p.accountId, sid: p.sessionId, pvId: p.pageViewId ?? "", depth: p.depth ?? 0, occurredAt });
|
|
53
|
+
}
|
|
54
|
+
else if (p.type === "dwell") {
|
|
55
|
+
await session.run(`MATCH (pv:PageView {accountId: $accountId, pageViewId: $pvId})
|
|
56
|
+
SET pv.dwellMs = coalesce(pv.dwellMs, 0) + $ms,
|
|
57
|
+
pv.dwellLastAt = datetime($occurredAt)`, { accountId: p.accountId, pvId: p.pageViewId ?? "", ms: p.dwellMs ?? 0, occurredAt });
|
|
58
|
+
}
|
|
59
|
+
if (p.personId) {
|
|
60
|
+
await session.run(`MATCH (per:Person {accountId: $accountId}) WHERE elementId(per) = $personId
|
|
61
|
+
MATCH (s:Session {accountId: $accountId, sessionId: $sid})
|
|
62
|
+
MERGE (per)-[:VISITED {accountId: $accountId}]->(s)`, { accountId: p.accountId, personId: p.personId, sid: p.sessionId });
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
await session.run(`MERGE (av:AnonVisitor {accountId: $accountId, visitorId: $vid})
|
|
66
|
+
ON CREATE SET av.firstSeenAt = datetime($occurredAt), av.lastSeenAt = datetime($occurredAt)
|
|
67
|
+
ON MATCH SET av.lastSeenAt = datetime($occurredAt)
|
|
68
|
+
WITH av
|
|
69
|
+
MATCH (s:Session {accountId: $accountId, sessionId: $sid})
|
|
70
|
+
MERGE (av)-[:VISITED {accountId: $accountId}]->(s)`, { accountId: p.accountId, vid: p.visitorId, sid: p.sessionId, occurredAt });
|
|
71
|
+
}
|
|
72
|
+
return { ok: true, type: p.type, sessionId: p.sessionId };
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
await session.close();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=visitor-event-ingest.js.map
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-event-ingest.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-event-ingest.js","sourceRoot":"","sources":["../../src/tools/visitor-event-ingest.ts"],"names":[],"mappings":"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"}
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-page.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface RecentByPageParams {
|
|
2
|
+
accountId: string;
|
|
3
|
+
/** Either listingSlug or url; one is required. */
|
|
4
|
+
listingSlug?: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
sinceDays?: number;
|
|
7
|
+
limit?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface VisitorSummary {
|
|
10
|
+
personId: string | null;
|
|
11
|
+
personName: string | null;
|
|
12
|
+
anonVisitorId: string | null;
|
|
13
|
+
pageviewCount: number;
|
|
14
|
+
lastSeenAt: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function visitorRecentByPage(p: RecentByPageParams): Promise<{
|
|
17
|
+
visitors: VisitorSummary[];
|
|
18
|
+
}>;
|
|
19
|
+
//# sourceMappingURL=visitor-recent-by-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-recent-by-page.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-recent-by-page.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,cAAc,EAAE,CAAA;CAAE,CAAC,CAsCxG"}
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-page.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// visitor-recent-by-page — who viewed a given page/listing recently.
|
|
2
|
+
// Operator-side query: "who has shown interest in 12 Oak Street this week?"
|
|
3
|
+
import { getSession } from "../lib/neo4j.js";
|
|
4
|
+
export async function visitorRecentByPage(p) {
|
|
5
|
+
if (!p.listingSlug && !p.url) {
|
|
6
|
+
throw new Error("visitor-recent-by-page requires listingSlug or url");
|
|
7
|
+
}
|
|
8
|
+
const since = p.sinceDays ?? 7;
|
|
9
|
+
const limit = Math.max(1, Math.min(p.limit ?? 50, 200));
|
|
10
|
+
const session = getSession();
|
|
11
|
+
try {
|
|
12
|
+
const result = await session.run(`MATCH (pv:PageView {accountId: $accountId})
|
|
13
|
+
WHERE pv.occurredAt >= datetime() - duration({days: $since})
|
|
14
|
+
AND ($url IS NULL OR EXISTS { MATCH (pv)-[:OF_PAGE]->(:Page {accountId: $accountId, url: $url}) })
|
|
15
|
+
AND ($slug IS NULL OR EXISTS { MATCH (pv)-[:OF_LISTING]->(:Listing {accountId: $accountId, slug: $slug, scope:'public'}) })
|
|
16
|
+
MATCH (s:Session)-[:HAS_EVENT]->(pv)
|
|
17
|
+
OPTIONAL MATCH (per:Person)-[:VISITED]->(s)
|
|
18
|
+
OPTIONAL MATCH (av:AnonVisitor)-[:VISITED]->(s)
|
|
19
|
+
WITH per, av, s, count(pv) AS pageviews
|
|
20
|
+
RETURN coalesce(elementId(per), null) AS personId,
|
|
21
|
+
coalesce(per.name, null) AS personName,
|
|
22
|
+
coalesce(av.visitorId, null) AS anonVisitorId,
|
|
23
|
+
sum(pageviews) AS totalPageviews,
|
|
24
|
+
toString(max(s.lastSeenAt)) AS lastSeenAt
|
|
25
|
+
ORDER BY lastSeenAt DESC
|
|
26
|
+
LIMIT $limit`, { accountId: p.accountId, url: p.url ?? null, slug: p.listingSlug ?? null, since, limit });
|
|
27
|
+
return {
|
|
28
|
+
visitors: result.records.map((r) => ({
|
|
29
|
+
personId: r.get("personId"),
|
|
30
|
+
personName: r.get("personName"),
|
|
31
|
+
anonVisitorId: r.get("anonVisitorId"),
|
|
32
|
+
pageviewCount: Number(r.get("totalPageviews") ?? 0),
|
|
33
|
+
lastSeenAt: r.get("lastSeenAt"),
|
|
34
|
+
})),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
await session.close();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=visitor-recent-by-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-recent-by-page.js","sourceRoot":"","sources":["../../src/tools/visitor-recent-by-page.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,4EAA4E;AAE5E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAmB7C,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,CAAqB;IAC7D,IAAI,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B;;;;;;;;;;;;;;oBAcc,EACd,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,IAAI,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAC1F,CAAC;QACF,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACnC,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC;gBAC3B,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;gBAC/B,aAAa,EAAE,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC;gBACrC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;gBACnD,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;aAChC,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface RecentByPersonParams {
|
|
2
|
+
accountId: string;
|
|
3
|
+
personId: string;
|
|
4
|
+
sinceDays?: number;
|
|
5
|
+
limit?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface SessionSummary {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
startedAt: string;
|
|
10
|
+
lastSeenAt: string;
|
|
11
|
+
pageviewCount: number;
|
|
12
|
+
clickCount: number;
|
|
13
|
+
maxScrollDepth: number;
|
|
14
|
+
listingSlugs: string[];
|
|
15
|
+
dwellMs: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function visitorRecentByPerson(p: RecentByPersonParams): Promise<{
|
|
18
|
+
sessions: SessionSummary[];
|
|
19
|
+
}>;
|
|
20
|
+
//# sourceMappingURL=visitor-recent-by-person.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-recent-by-person.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-recent-by-person.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,cAAc,EAAE,CAAA;CAAE,CAAC,CAwC5G"}
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-recent-by-person.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// visitor-recent-by-person — list recent :Session activity for a :Person.
|
|
2
|
+
// Walks (Person)-[:VISITED]->(Session)-[:HAS_EVENT]->(*) and returns a
|
|
3
|
+
// chronological summary the morning-round skill folds into its briefing.
|
|
4
|
+
import { getSession } from "../lib/neo4j.js";
|
|
5
|
+
export async function visitorRecentByPerson(p) {
|
|
6
|
+
const since = p.sinceDays ?? 30;
|
|
7
|
+
const limit = Math.max(1, Math.min(p.limit ?? 50, 200));
|
|
8
|
+
const session = getSession();
|
|
9
|
+
try {
|
|
10
|
+
const result = await session.run(`MATCH (per:Person {accountId: $accountId}) WHERE elementId(per) = $personId
|
|
11
|
+
MATCH (per)-[:VISITED]->(s:Session)
|
|
12
|
+
WHERE s.startedAt >= datetime() - duration({days: $since})
|
|
13
|
+
OPTIONAL MATCH (s)-[:HAS_EVENT]->(pv:PageView)
|
|
14
|
+
OPTIONAL MATCH (s)-[:HAS_EVENT]->(cl:Click)
|
|
15
|
+
OPTIONAL MATCH (s)-[:HAS_EVENT]->(sm:ScrollMilestone)
|
|
16
|
+
OPTIONAL MATCH (pv)-[:OF_LISTING]->(l:Listing)
|
|
17
|
+
WITH s, count(DISTINCT pv) AS pageviews, count(DISTINCT cl) AS clicks,
|
|
18
|
+
coalesce(max(sm.maxDepth), 0) AS maxDepth,
|
|
19
|
+
collect(DISTINCT l.slug) AS slugs,
|
|
20
|
+
coalesce(sum(pv.dwellMs), 0) AS dwellMs
|
|
21
|
+
RETURN s.sessionId AS sessionId,
|
|
22
|
+
toString(s.startedAt) AS startedAt,
|
|
23
|
+
toString(s.lastSeenAt) AS lastSeenAt,
|
|
24
|
+
pageviews, clicks, maxDepth, slugs, dwellMs
|
|
25
|
+
ORDER BY s.lastSeenAt DESC
|
|
26
|
+
LIMIT $limit`, { accountId: p.accountId, personId: p.personId, since, limit });
|
|
27
|
+
return {
|
|
28
|
+
sessions: result.records.map((r) => ({
|
|
29
|
+
sessionId: r.get("sessionId"),
|
|
30
|
+
startedAt: r.get("startedAt"),
|
|
31
|
+
lastSeenAt: r.get("lastSeenAt"),
|
|
32
|
+
pageviewCount: Number(r.get("pageviews") ?? 0),
|
|
33
|
+
clickCount: Number(r.get("clicks") ?? 0),
|
|
34
|
+
maxScrollDepth: Number(r.get("maxDepth") ?? 0),
|
|
35
|
+
listingSlugs: (r.get("slugs") ?? []).filter((s) => typeof s === "string"),
|
|
36
|
+
dwellMs: Number(r.get("dwellMs") ?? 0),
|
|
37
|
+
})),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await session.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=visitor-recent-by-person.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-recent-by-person.js","sourceRoot":"","sources":["../../src/tools/visitor-recent-by-person.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,uEAAuE;AACvE,yEAAyE;AAEzE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAoB7C,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,CAAuB;IACjE,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B;;;;;;;;;;;;;;;;oBAgBc,EACd,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAC/D,CAAC;QACF,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACnC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC;gBAC7B,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC;gBAC7B,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;gBAC/B,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;gBAC9C,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACxC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBAC9C,YAAY,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAU,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;gBAC/F,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;aACvC,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface RecommendationCtrParams {
|
|
2
|
+
accountId: string;
|
|
3
|
+
/** Days back from now. Defaults to 7. */
|
|
4
|
+
sinceDays?: number;
|
|
5
|
+
/** Optional listingSlug filter. */
|
|
6
|
+
listingSlug?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface CtrRow {
|
|
9
|
+
listingSlug: string;
|
|
10
|
+
recommended: number;
|
|
11
|
+
clicked: number;
|
|
12
|
+
ctr: number;
|
|
13
|
+
}
|
|
14
|
+
export interface CtrSummary {
|
|
15
|
+
windowDays: number;
|
|
16
|
+
totalRecommended: number;
|
|
17
|
+
totalClicked: number;
|
|
18
|
+
ctr: number;
|
|
19
|
+
perListing: CtrRow[];
|
|
20
|
+
}
|
|
21
|
+
export declare function visitorRecommendationCtr(p: RecommendationCtrParams): Promise<CtrSummary>;
|
|
22
|
+
//# sourceMappingURL=visitor-recommendation-ctr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-recommendation-ctr.d.ts","sourceRoot":"","sources":["../../src/tools/visitor-recommendation-ctr.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,MAAM;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,wBAAsB,wBAAwB,CAAC,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,UAAU,CAAC,CAmC9F"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// visitor-recommendation-ctr — graph-backed CTR replacement for the
|
|
2
|
+
// log-scan aggregation that ships with Task 336. Operates on Recommendation
|
|
3
|
+
// nodes (materialised from [property-recommended] log lines via backfill)
|
|
4
|
+
// joined to :Click events. Output schema matches the log-scan tool's so
|
|
5
|
+
// the cutover (separate follow-up task) is a one-line swap.
|
|
6
|
+
import { getSession } from "../lib/neo4j.js";
|
|
7
|
+
export async function visitorRecommendationCtr(p) {
|
|
8
|
+
const since = p.sinceDays ?? 7;
|
|
9
|
+
const session = getSession();
|
|
10
|
+
try {
|
|
11
|
+
const result = await session.run(`MATCH (rec:Recommendation {accountId: $accountId})
|
|
12
|
+
WHERE rec.recommendedAt >= datetime() - duration({days: $since})
|
|
13
|
+
AND ($slug IS NULL OR rec.listingSlug = $slug)
|
|
14
|
+
OPTIONAL MATCH (rec)-[:FOR_SESSION]->(s:Session)-[:HAS_EVENT]->(cl:Click {label: 'card'})
|
|
15
|
+
WITH rec.listingSlug AS slug,
|
|
16
|
+
count(DISTINCT rec) AS recommended,
|
|
17
|
+
count(DISTINCT cl) AS clicked
|
|
18
|
+
RETURN slug AS listingSlug, recommended, clicked,
|
|
19
|
+
CASE WHEN recommended > 0 THEN toFloat(clicked) / recommended ELSE 0.0 END AS ctr
|
|
20
|
+
ORDER BY recommended DESC`, { accountId: p.accountId, since, slug: p.listingSlug ?? null });
|
|
21
|
+
const perListing = result.records.map((r) => ({
|
|
22
|
+
listingSlug: r.get("listingSlug") ?? "",
|
|
23
|
+
recommended: Number(r.get("recommended") ?? 0),
|
|
24
|
+
clicked: Number(r.get("clicked") ?? 0),
|
|
25
|
+
ctr: Number(r.get("ctr") ?? 0),
|
|
26
|
+
}));
|
|
27
|
+
const totalRecommended = perListing.reduce((a, b) => a + b.recommended, 0);
|
|
28
|
+
const totalClicked = perListing.reduce((a, b) => a + b.clicked, 0);
|
|
29
|
+
return {
|
|
30
|
+
windowDays: since,
|
|
31
|
+
totalRecommended,
|
|
32
|
+
totalClicked,
|
|
33
|
+
ctr: totalRecommended > 0 ? totalClicked / totalRecommended : 0,
|
|
34
|
+
perListing,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
await session.close();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=visitor-recommendation-ctr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-recommendation-ctr.js","sourceRoot":"","sources":["../../src/tools/visitor-recommendation-ctr.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,4EAA4E;AAC5E,0EAA0E;AAC1E,wEAAwE;AACxE,4DAA4D;AAE5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAyB7C,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,CAA0B;IACvE,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B;;;;;;;;;iCAS2B,EAC3B,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,IAAI,IAAI,EAAE,CAC/D,CAAC;QACF,MAAM,UAAU,GAAa,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACtD,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE;YACvC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAC9C,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACtC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAC/B,CAAC,CAAC,CAAC;QACJ,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAC3E,MAAM,YAAY,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACnE,OAAO;YACL,UAAU,EAAE,KAAK;YACjB,gBAAgB;YAChB,YAAY;YACZ,GAAG,EAAE,gBAAgB,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;YAC/D,UAAU;SACX,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
|
package/payload/premium-plugins/real-agent/plugins/buyers/mcp/dist/tools/visitor-session-detail.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface SessionDetailParams {
|
|
2
|
+
accountId: string;
|
|
3
|
+
sessionId: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SessionEvent {
|
|
6
|
+
kind: "pageview" | "click" | "scroll";
|
|
7
|
+
occurredAt: string;
|
|
8
|
+
/** PageView only */
|
|
9
|
+
url?: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
pageViewId?: string;
|
|
12
|
+
referrer?: string;
|
|
13
|
+
dwellMs?: number;
|
|
14
|
+
/** Click only */
|
|
15
|
+
label?: string;
|
|
16
|
+
href?: string;
|
|
17
|
+
/** Scroll only */
|
|
18
|
+
maxDepth?: number;
|
|
19
|
+
/** Optional listing link */
|
|
20
|
+
listingSlug?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface SessionDetail {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
startedAt: string | null;
|
|
25
|
+
lastSeenAt: string | null;
|
|
26
|
+
personId: string | null;
|
|
27
|
+
personName: string | null;
|
|
28
|
+
anonVisitorId: string | null;
|
|
29
|
+
events: SessionEvent[];
|
|
30
|
+
}
|
|
31
|
+
export declare function visitorSessionDetail(p: SessionDetailParams): Promise<SessionDetail>;
|
|
32
|
+
//# sourceMappingURL=visitor-session-detail.d.ts.map
|