paisa-mcp 0.0.3 → 0.0.5
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/index.js +223 -4
- package/dist/index.js.map +7 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42597,8 +42597,7 @@ function registerGoalTools(server, client) {
|
|
|
42597
42597
|
startDate: exports_external.string().optional().describe("[emi] YYYY-MM-DD"),
|
|
42598
42598
|
endDate: exports_external.string().optional().describe("[emi] YYYY-MM-DD last payment date"),
|
|
42599
42599
|
totalMonths: exports_external.number().optional().describe("[emi] Tenure in months"),
|
|
42600
|
-
paidMonths: exports_external.number().optional().describe("[emi] Months already paid")
|
|
42601
|
-
monthlyContribution: exports_external.string().optional().describe("[sinking_fund] Monthly contribution (optional)")
|
|
42600
|
+
paidMonths: exports_external.number().optional().describe("[emi] Months already paid")
|
|
42602
42601
|
}
|
|
42603
42602
|
}, async ({ type, ...fields }) => {
|
|
42604
42603
|
const require2 = (keys) => {
|
|
@@ -43279,6 +43278,7 @@ var MERCHANT_ALIASES = [
|
|
|
43279
43278
|
["LivPure", ["livpure smart homes", "livpure"]],
|
|
43280
43279
|
["HundredX", ["hundredx school", "hundredx"]]
|
|
43281
43280
|
];
|
|
43281
|
+
var GATEWAY_PREFIX = /^(paytm|payu|razorpay|cashfree|billdesk|easebuzz|rbl|pyu)\*/i;
|
|
43282
43282
|
var aliasIndex = [];
|
|
43283
43283
|
for (const [canonical, aliases] of MERCHANT_ALIASES) {
|
|
43284
43284
|
for (const alias of aliases) {
|
|
@@ -43286,6 +43286,24 @@ for (const [canonical, aliases] of MERCHANT_ALIASES) {
|
|
|
43286
43286
|
}
|
|
43287
43287
|
}
|
|
43288
43288
|
aliasIndex.sort((a, b) => b.alias.length - a.alias.length);
|
|
43289
|
+
function resolveAlias(name) {
|
|
43290
|
+
let lower = name.toLowerCase();
|
|
43291
|
+
const gwMatch = lower.match(GATEWAY_PREFIX);
|
|
43292
|
+
if (gwMatch) {
|
|
43293
|
+
lower = lower.slice(gwMatch[0].length).trim();
|
|
43294
|
+
}
|
|
43295
|
+
for (const { alias, canonical } of aliasIndex) {
|
|
43296
|
+
if (alias.length <= 4) {
|
|
43297
|
+
const re = new RegExp(`\\b${alias}\\b`);
|
|
43298
|
+
if (re.test(lower))
|
|
43299
|
+
return canonical;
|
|
43300
|
+
} else {
|
|
43301
|
+
if (lower.includes(alias))
|
|
43302
|
+
return canonical;
|
|
43303
|
+
}
|
|
43304
|
+
}
|
|
43305
|
+
return null;
|
|
43306
|
+
}
|
|
43289
43307
|
// ../../node_modules/.bun/fuzzball@2.2.3/node_modules/fuzzball/dist/esm/fuzzball.esm.min.js
|
|
43290
43308
|
var e;
|
|
43291
43309
|
var t = typeof globalThis != "undefined" ? globalThis : typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : {};
|
|
@@ -45000,9 +45018,152 @@ var Yo = Ro.process_and_sort;
|
|
|
45000
45018
|
var ea = Ro.unique_tokens;
|
|
45001
45019
|
var ta = Ro.dedupe;
|
|
45002
45020
|
|
|
45021
|
+
// ../reconciliation/src/normalise.ts
|
|
45022
|
+
var CORPORATE_SUFFIXES = /\b(PRIVATE|PVT\.?|LIMITED|LTD\.?)\s*\w*/gi;
|
|
45023
|
+
function normaliseName(name) {
|
|
45024
|
+
return name.replace(CORPORATE_SUFFIXES, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
|
45025
|
+
}
|
|
45026
|
+
|
|
45003
45027
|
// ../reconciliation/src/entity.ts
|
|
45028
|
+
var FUZZBALL_THRESHOLD = 92;
|
|
45004
45029
|
var _aliasReCache = new Map;
|
|
45030
|
+
function getAliasRegex(lower) {
|
|
45031
|
+
let re2 = _aliasReCache.get(lower);
|
|
45032
|
+
if (!re2) {
|
|
45033
|
+
const escaped = lower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45034
|
+
re2 = new RegExp(`\\b${escaped}\\b`);
|
|
45035
|
+
_aliasReCache.set(lower, re2);
|
|
45036
|
+
}
|
|
45037
|
+
return re2;
|
|
45038
|
+
}
|
|
45005
45039
|
var _normCache = new WeakMap;
|
|
45040
|
+
function cachedNormaliseName(opt) {
|
|
45041
|
+
let cached2 = _normCache.get(opt);
|
|
45042
|
+
if (cached2 === undefined) {
|
|
45043
|
+
cached2 = normaliseName(opt.name);
|
|
45044
|
+
_normCache.set(opt, cached2);
|
|
45045
|
+
}
|
|
45046
|
+
return cached2;
|
|
45047
|
+
}
|
|
45048
|
+
function matchUserAlias(normalisedName, aliases) {
|
|
45049
|
+
const sorted = [...aliases].sort((a2, b2) => b2.pattern.length - a2.pattern.length);
|
|
45050
|
+
for (const alias of sorted) {
|
|
45051
|
+
const lower = alias.pattern.toLowerCase();
|
|
45052
|
+
if (lower.length <= 4) {
|
|
45053
|
+
if (getAliasRegex(lower).test(normalisedName))
|
|
45054
|
+
return alias;
|
|
45055
|
+
} else if (normalisedName.includes(lower)) {
|
|
45056
|
+
return alias;
|
|
45057
|
+
}
|
|
45058
|
+
}
|
|
45059
|
+
return null;
|
|
45060
|
+
}
|
|
45061
|
+
function fuzzMatch(normalisedName, options) {
|
|
45062
|
+
let best = null;
|
|
45063
|
+
for (const opt of options) {
|
|
45064
|
+
const candidate = cachedNormaliseName(opt);
|
|
45065
|
+
if (!candidate)
|
|
45066
|
+
continue;
|
|
45067
|
+
const score = Math.max(Fo(normalisedName, candidate), Bo(normalisedName, candidate));
|
|
45068
|
+
if (score >= FUZZBALL_THRESHOLD && (!best || score > best.score)) {
|
|
45069
|
+
best = { row: opt, score };
|
|
45070
|
+
}
|
|
45071
|
+
}
|
|
45072
|
+
return best?.row ?? null;
|
|
45073
|
+
}
|
|
45074
|
+
function resolveMerchant(normalisedName, ctx) {
|
|
45075
|
+
const canonical = resolveAlias(normalisedName);
|
|
45076
|
+
if (canonical) {
|
|
45077
|
+
const normCanonical = normaliseName(canonical);
|
|
45078
|
+
const match = ctx.globalMerchants.find((m2) => cachedNormaliseName(m2) === normCanonical);
|
|
45079
|
+
if (match)
|
|
45080
|
+
return { type: "merchant", merchantId: match.id };
|
|
45081
|
+
}
|
|
45082
|
+
const userAlias = matchUserAlias(normalisedName, ctx.userMerchantAliases);
|
|
45083
|
+
if (userAlias)
|
|
45084
|
+
return { type: "merchant", merchantId: userAlias.merchantId };
|
|
45085
|
+
const fuzzMerchant = fuzzMatch(normalisedName, [...ctx.globalMerchants, ...ctx.localMerchants]);
|
|
45086
|
+
if (fuzzMerchant)
|
|
45087
|
+
return { type: "merchant", merchantId: fuzzMerchant.id };
|
|
45088
|
+
return null;
|
|
45089
|
+
}
|
|
45090
|
+
function resolvePerson(normalisedName, ctx) {
|
|
45091
|
+
const userAlias = matchUserAlias(normalisedName, ctx.userPersonAliases);
|
|
45092
|
+
if (userAlias)
|
|
45093
|
+
return { type: "person", personId: userAlias.personId };
|
|
45094
|
+
const fuzzPerson = fuzzMatch(normalisedName, ctx.persons);
|
|
45095
|
+
if (fuzzPerson)
|
|
45096
|
+
return { type: "person", personId: fuzzPerson.id };
|
|
45097
|
+
return null;
|
|
45098
|
+
}
|
|
45099
|
+
function resolveEntity(name, hint, ctx) {
|
|
45100
|
+
const normalised = normaliseName(name);
|
|
45101
|
+
if (!normalised)
|
|
45102
|
+
return null;
|
|
45103
|
+
if (hint === "person") {
|
|
45104
|
+
return resolvePerson(normalised, ctx) ?? resolveMerchant(normalised, ctx) ?? null;
|
|
45105
|
+
}
|
|
45106
|
+
if (hint === "merchant") {
|
|
45107
|
+
return resolveMerchant(normalised, ctx) ?? null;
|
|
45108
|
+
}
|
|
45109
|
+
return resolveMerchant(normalised, ctx) ?? resolvePerson(normalised, ctx) ?? null;
|
|
45110
|
+
}
|
|
45111
|
+
|
|
45112
|
+
// ../reconciliation/src/categorize.ts
|
|
45113
|
+
function autoCategorizeFromDescriptions(ctx) {
|
|
45114
|
+
const resolveCtx = {
|
|
45115
|
+
globalMerchants: ctx.globalMerchants,
|
|
45116
|
+
localMerchants: ctx.localMerchants,
|
|
45117
|
+
persons: ctx.persons,
|
|
45118
|
+
userMerchantAliases: ctx.bag.merchantAliases,
|
|
45119
|
+
userPersonAliases: ctx.bag.personAliases
|
|
45120
|
+
};
|
|
45121
|
+
const groups = new Map;
|
|
45122
|
+
let unresolvedCount = 0;
|
|
45123
|
+
let totalAffected = 0;
|
|
45124
|
+
for (const row of ctx.rows) {
|
|
45125
|
+
if (row.isTransfer || row.isIgnored)
|
|
45126
|
+
continue;
|
|
45127
|
+
let entity = null;
|
|
45128
|
+
if (row.merchantId)
|
|
45129
|
+
entity = { type: "merchant", merchantId: row.merchantId };
|
|
45130
|
+
else if (row.personId)
|
|
45131
|
+
entity = { type: "person", personId: row.personId };
|
|
45132
|
+
else
|
|
45133
|
+
entity = resolveEntity(row.description, "unknown", resolveCtx);
|
|
45134
|
+
if (!entity) {
|
|
45135
|
+
unresolvedCount += 1;
|
|
45136
|
+
continue;
|
|
45137
|
+
}
|
|
45138
|
+
const rule = entityRule(entity, ctx.bag);
|
|
45139
|
+
if (row.categoryId && rule && rule.categoryId === row.categoryId)
|
|
45140
|
+
continue;
|
|
45141
|
+
const needsLink = !row.merchantId && !row.personId;
|
|
45142
|
+
const needsCategory = !row.categoryId && rule != null;
|
|
45143
|
+
if (!needsLink && !needsCategory)
|
|
45144
|
+
continue;
|
|
45145
|
+
const key = entityKey(entity);
|
|
45146
|
+
const existing = groups.get(key);
|
|
45147
|
+
const suggestion = existing ?? {
|
|
45148
|
+
entity,
|
|
45149
|
+
source: rule ? "category-rule" : "alias-only",
|
|
45150
|
+
categoryId: rule?.categoryId ?? null,
|
|
45151
|
+
transactionIds: []
|
|
45152
|
+
};
|
|
45153
|
+
suggestion.transactionIds.push(row.id);
|
|
45154
|
+
groups.set(key, suggestion);
|
|
45155
|
+
totalAffected += 1;
|
|
45156
|
+
}
|
|
45157
|
+
return { suggestions: Array.from(groups.values()), totalAffected, unresolvedCount };
|
|
45158
|
+
}
|
|
45159
|
+
function entityKey(e2) {
|
|
45160
|
+
return e2.type === "merchant" ? `m:${e2.merchantId}` : `p:${e2.personId}`;
|
|
45161
|
+
}
|
|
45162
|
+
function entityRule(e2, bag) {
|
|
45163
|
+
if (e2.type === "merchant")
|
|
45164
|
+
return bag.merchantRules[e2.merchantId] ?? null;
|
|
45165
|
+
return bag.personRules[e2.personId] ?? null;
|
|
45166
|
+
}
|
|
45006
45167
|
// ../reconciliation/src/settlement.ts
|
|
45007
45168
|
var SETTLEMENT_PATTERNS = [
|
|
45008
45169
|
/\bCRED\b.*\bPAYMENT\b/i,
|
|
@@ -45080,6 +45241,49 @@ function settlementEnrichedData(description, type, source) {
|
|
|
45080
45241
|
return out;
|
|
45081
45242
|
}
|
|
45082
45243
|
// src/tools/transactions.ts
|
|
45244
|
+
async function runRescan(client) {
|
|
45245
|
+
const EMPTY_BAG2 = {
|
|
45246
|
+
merchantAliases: [],
|
|
45247
|
+
personAliases: [],
|
|
45248
|
+
merchantRules: {},
|
|
45249
|
+
personRules: {}
|
|
45250
|
+
};
|
|
45251
|
+
let bag = EMPTY_BAG2;
|
|
45252
|
+
try {
|
|
45253
|
+
const learningRes = await client.get("/api/household-learning");
|
|
45254
|
+
bag = learningRes.data?.bag ?? EMPTY_BAG2;
|
|
45255
|
+
} catch {}
|
|
45256
|
+
const merchantsRes = await client.get("/api/merchants");
|
|
45257
|
+
const allMerchants = merchantsRes.data ?? [];
|
|
45258
|
+
const globalMerchants = allMerchants.filter((m2) => !m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
|
|
45259
|
+
const localMerchants = allMerchants.filter((m2) => m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
|
|
45260
|
+
const personsRes = await client.get("/api/persons");
|
|
45261
|
+
const persons = (personsRes.data ?? []).filter((p2) => p2.name);
|
|
45262
|
+
const allRows = [];
|
|
45263
|
+
let offset = 0;
|
|
45264
|
+
const limit = 500;
|
|
45265
|
+
while (true) {
|
|
45266
|
+
const res = await client.get("/api/transactions", { limit, offset });
|
|
45267
|
+
const rows = res.data ?? [];
|
|
45268
|
+
allRows.push(...rows);
|
|
45269
|
+
if (!res.hasMore || rows.length === 0)
|
|
45270
|
+
break;
|
|
45271
|
+
offset += limit;
|
|
45272
|
+
}
|
|
45273
|
+
const plan = autoCategorizeFromDescriptions({ rows: allRows, bag, globalMerchants, localMerchants, persons });
|
|
45274
|
+
const links = plan.suggestions.filter((s2) => s2.categoryId).flatMap((s2) => s2.transactionIds.map((txId) => ({
|
|
45275
|
+
transactionId: txId,
|
|
45276
|
+
...s2.entity.type === "merchant" ? { merchantId: s2.entity.merchantId } : { personId: s2.entity.personId },
|
|
45277
|
+
categoryId: s2.categoryId
|
|
45278
|
+
})));
|
|
45279
|
+
if (links.length > 0) {
|
|
45280
|
+
const CHUNK = 500;
|
|
45281
|
+
for (let i2 = 0;i2 < links.length; i2 += CHUNK) {
|
|
45282
|
+
await client.post("/api/transactions/bulk-link-entity", { links: links.slice(i2, i2 + CHUNK) });
|
|
45283
|
+
}
|
|
45284
|
+
}
|
|
45285
|
+
return { linked: links.length, unresolved: plan.unresolvedCount };
|
|
45286
|
+
}
|
|
45083
45287
|
function registerTransactionTools(server, client, crypto3) {
|
|
45084
45288
|
server.registerTool("get_transactions", {
|
|
45085
45289
|
description: "Search and filter transactions. Returns paginated results with merchant and category details.",
|
|
@@ -45204,7 +45408,15 @@ function registerTransactionTools(server, client, crypto3) {
|
|
|
45204
45408
|
granularity,
|
|
45205
45409
|
transactions: rows
|
|
45206
45410
|
});
|
|
45207
|
-
|
|
45411
|
+
const rescan = await runRescan(client);
|
|
45412
|
+
return {
|
|
45413
|
+
content: [
|
|
45414
|
+
{
|
|
45415
|
+
type: "text",
|
|
45416
|
+
text: JSON.stringify({ import: data, rescan })
|
|
45417
|
+
}
|
|
45418
|
+
]
|
|
45419
|
+
};
|
|
45208
45420
|
});
|
|
45209
45421
|
server.registerTool("delete_transactions", {
|
|
45210
45422
|
description: "Hard-delete transactions. This is PERMANENT and cannot be undone. " + "Provide exactly one of: `id` (single transaction), `ids` (a list of transactions), " + "or `filters` (deletes all matching transactions). Returns the number of deleted transactions " + "for the bulk paths.",
|
|
@@ -45233,6 +45445,13 @@ function registerTransactionTools(server, client, crypto3) {
|
|
|
45233
45445
|
const data = await client.delete("/api/transactions/bulk", body);
|
|
45234
45446
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
45235
45447
|
});
|
|
45448
|
+
server.registerTool("rescan_transactions", {
|
|
45449
|
+
description: "Apply the household's saved learning rules (merchant aliases + category rules) to all uncategorized transactions. " + "Equivalent to the browser's auto-categorize flow. Run this after importing statements via MCP to populate merchants and categories.",
|
|
45450
|
+
inputSchema: {}
|
|
45451
|
+
}, async () => {
|
|
45452
|
+
const result = await runRescan(client);
|
|
45453
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
45454
|
+
});
|
|
45236
45455
|
server.registerTool("get_settlement_candidates", {
|
|
45237
45456
|
description: "Find bank debit transactions that look like bill payments for a specific credit card account. " + "Call this after importing a CC statement to identify which existing bank debits should be marked " + "as transfers (to avoid double-counting expenses). Returns transactions with their account name, " + "date, amount, and description.",
|
|
45238
45457
|
inputSchema: {
|
|
@@ -45336,5 +45555,5 @@ main().catch((err) => {
|
|
|
45336
45555
|
process.exit(1);
|
|
45337
45556
|
});
|
|
45338
45557
|
|
|
45339
|
-
//# debugId=
|
|
45558
|
+
//# debugId=66937CD8E27A6C8364756E2164756E21
|
|
45340
45559
|
//# sourceMappingURL=index.js.map
|