@gabrielerandelli/minus-tracker 0.7.0 → 0.8.0
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/README.md +150 -30
- package/dist/cli/index.js +179 -69
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +179 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +179 -69
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +1885 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +9 -3
package/dist/index.cjs
CHANGED
|
@@ -1042,7 +1042,7 @@ function classifyByType(isin, securityType, product, warnings) {
|
|
|
1042
1042
|
};
|
|
1043
1043
|
}
|
|
1044
1044
|
warnings.push(
|
|
1045
|
-
`Unrecognized type: ${securityType}. Please classify manually.`
|
|
1045
|
+
`Unrecognized type for ${isin}: ${securityType}. Please classify manually.`
|
|
1046
1046
|
);
|
|
1047
1047
|
return {
|
|
1048
1048
|
product,
|
|
@@ -1055,6 +1055,84 @@ function classifyByType(isin, securityType, product, warnings) {
|
|
|
1055
1055
|
source: "user"
|
|
1056
1056
|
};
|
|
1057
1057
|
}
|
|
1058
|
+
var ASSET_CLASS_DEFAULTS = {
|
|
1059
|
+
ETF: {
|
|
1060
|
+
assetClass: "ETF",
|
|
1061
|
+
bucketGain: "A",
|
|
1062
|
+
bucketLoss: "B",
|
|
1063
|
+
taxRate: 0.26,
|
|
1064
|
+
whiteListed: null
|
|
1065
|
+
},
|
|
1066
|
+
Stock: {
|
|
1067
|
+
assetClass: "Stock",
|
|
1068
|
+
bucketGain: "B",
|
|
1069
|
+
bucketLoss: "B",
|
|
1070
|
+
taxRate: 0,
|
|
1071
|
+
whiteListed: null
|
|
1072
|
+
},
|
|
1073
|
+
ETC: {
|
|
1074
|
+
assetClass: "ETC",
|
|
1075
|
+
bucketGain: "B",
|
|
1076
|
+
bucketLoss: "B",
|
|
1077
|
+
taxRate: 0,
|
|
1078
|
+
whiteListed: null
|
|
1079
|
+
},
|
|
1080
|
+
GovtBondWL: {
|
|
1081
|
+
assetClass: "GovtBondWL",
|
|
1082
|
+
bucketGain: "A",
|
|
1083
|
+
bucketLoss: "B",
|
|
1084
|
+
taxRate: 0.125,
|
|
1085
|
+
whiteListed: true
|
|
1086
|
+
},
|
|
1087
|
+
GovtBondOther: {
|
|
1088
|
+
assetClass: "GovtBondOther",
|
|
1089
|
+
bucketGain: "A",
|
|
1090
|
+
bucketLoss: "B",
|
|
1091
|
+
taxRate: 0.26,
|
|
1092
|
+
whiteListed: false
|
|
1093
|
+
},
|
|
1094
|
+
CorpBond: {
|
|
1095
|
+
assetClass: "CorpBond",
|
|
1096
|
+
bucketGain: "B",
|
|
1097
|
+
bucketLoss: "B",
|
|
1098
|
+
taxRate: 0,
|
|
1099
|
+
whiteListed: null
|
|
1100
|
+
},
|
|
1101
|
+
Derivative: {
|
|
1102
|
+
assetClass: "Derivative",
|
|
1103
|
+
bucketGain: "B",
|
|
1104
|
+
bucketLoss: "B",
|
|
1105
|
+
taxRate: 0,
|
|
1106
|
+
whiteListed: null
|
|
1107
|
+
},
|
|
1108
|
+
LeverageCert: {
|
|
1109
|
+
assetClass: "LeverageCert",
|
|
1110
|
+
bucketGain: "B",
|
|
1111
|
+
bucketLoss: "B",
|
|
1112
|
+
taxRate: 0,
|
|
1113
|
+
whiteListed: null
|
|
1114
|
+
},
|
|
1115
|
+
CapProtectedCert: {
|
|
1116
|
+
assetClass: "CapProtectedCert",
|
|
1117
|
+
bucketGain: "A",
|
|
1118
|
+
bucketLoss: "B",
|
|
1119
|
+
taxRate: 0.26,
|
|
1120
|
+
whiteListed: null
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
function buildEntryFromAssetClass(assetClass, product) {
|
|
1124
|
+
const mapped = ASSET_CLASS_DEFAULTS[assetClass];
|
|
1125
|
+
return {
|
|
1126
|
+
product,
|
|
1127
|
+
assetClass: mapped.assetClass,
|
|
1128
|
+
bucketGain: mapped.bucketGain,
|
|
1129
|
+
bucketLoss: mapped.bucketLoss,
|
|
1130
|
+
taxRate: mapped.taxRate,
|
|
1131
|
+
whiteListed: mapped.whiteListed,
|
|
1132
|
+
confirmedByUser: true,
|
|
1133
|
+
source: "user"
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1058
1136
|
var Classifier = class {
|
|
1059
1137
|
interactive;
|
|
1060
1138
|
constructor(options) {
|
|
@@ -1076,7 +1154,7 @@ var Classifier = class {
|
|
|
1076
1154
|
}
|
|
1077
1155
|
return parsed.classifications;
|
|
1078
1156
|
}
|
|
1079
|
-
async classify(transactions, sidecarPath, _httpPost = httpsPost) {
|
|
1157
|
+
async classify(transactions, sidecarPath, options, _httpPost = httpsPost) {
|
|
1080
1158
|
const isinToProduct = /* @__PURE__ */ new Map();
|
|
1081
1159
|
for (const tx of transactions) {
|
|
1082
1160
|
if (tx.isin && !isinToProduct.has(tx.isin)) {
|
|
@@ -1084,13 +1162,21 @@ var Classifier = class {
|
|
|
1084
1162
|
}
|
|
1085
1163
|
}
|
|
1086
1164
|
const confirmed = {};
|
|
1087
|
-
if (
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1165
|
+
if (sidecarPath !== void 0) {
|
|
1166
|
+
if (fs2.existsSync(sidecarPath)) {
|
|
1167
|
+
const existingMap = await this.load(sidecarPath);
|
|
1168
|
+
for (const [isin, entry] of Object.entries(existingMap)) {
|
|
1169
|
+
if (entry.confirmedByUser) {
|
|
1170
|
+
confirmed[isin] = entry;
|
|
1171
|
+
}
|
|
1092
1172
|
}
|
|
1093
1173
|
}
|
|
1174
|
+
} else if (options?.existingClassification) {
|
|
1175
|
+
Object.assign(confirmed, options.existingClassification);
|
|
1176
|
+
}
|
|
1177
|
+
for (const [isin, assetClass] of Object.entries(options?.overrides ?? {})) {
|
|
1178
|
+
const product = isinToProduct.get(isin) ?? isin;
|
|
1179
|
+
confirmed[isin] = buildEntryFromAssetClass(assetClass, product);
|
|
1094
1180
|
}
|
|
1095
1181
|
const toProcess = [];
|
|
1096
1182
|
for (const isin of isinToProduct.keys()) {
|
|
@@ -1100,76 +1186,100 @@ var Classifier = class {
|
|
|
1100
1186
|
}
|
|
1101
1187
|
const warnings = [];
|
|
1102
1188
|
const newEntries = {};
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1189
|
+
if (options?.offline) {
|
|
1190
|
+
for (const isin of toProcess) {
|
|
1191
|
+
const product = isinToProduct.get(isin) ?? isin;
|
|
1192
|
+
warnings.push(
|
|
1193
|
+
`Unrecognized type for ${isin}: unknown. Please classify manually.`
|
|
1194
|
+
);
|
|
1195
|
+
newEntries[isin] = {
|
|
1196
|
+
product,
|
|
1197
|
+
assetClass: "Stock",
|
|
1198
|
+
bucketGain: "B",
|
|
1199
|
+
bucketLoss: "B",
|
|
1200
|
+
taxRate: 0,
|
|
1201
|
+
whiteListed: null,
|
|
1202
|
+
confirmedByUser: false,
|
|
1203
|
+
source: "user"
|
|
1204
|
+
};
|
|
1107
1205
|
}
|
|
1108
|
-
|
|
1109
|
-
const
|
|
1110
|
-
|
|
1111
|
-
)
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1206
|
+
} else {
|
|
1207
|
+
const BATCH_SIZE = 10;
|
|
1208
|
+
const totalBatches = Math.ceil(toProcess.length / BATCH_SIZE);
|
|
1209
|
+
for (let i = 0; i < toProcess.length; i += BATCH_SIZE) {
|
|
1210
|
+
if (i > 0) {
|
|
1211
|
+
await new Promise((resolve) => setTimeout(resolve, 6e3));
|
|
1212
|
+
}
|
|
1213
|
+
const batch = toProcess.slice(i, i + BATCH_SIZE);
|
|
1214
|
+
const requestBody = JSON.stringify(
|
|
1215
|
+
batch.map((isin) => ({ idType: "ID_ISIN", idValue: isin }))
|
|
1216
|
+
);
|
|
1217
|
+
let response = null;
|
|
1218
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1219
|
+
try {
|
|
1220
|
+
const r = await _httpPost(
|
|
1221
|
+
"https://api.openfigi.com/v3/mapping",
|
|
1222
|
+
requestBody,
|
|
1223
|
+
1e4
|
|
1224
|
+
);
|
|
1225
|
+
if (r.status < 500) {
|
|
1226
|
+
response = r;
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
} catch {
|
|
1230
|
+
throw new ClassificationError("NETWORK_ERROR");
|
|
1123
1231
|
}
|
|
1124
|
-
}
|
|
1232
|
+
}
|
|
1233
|
+
if (response === null) {
|
|
1125
1234
|
throw new ClassificationError("NETWORK_ERROR");
|
|
1126
1235
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1236
|
+
const results = JSON.parse(response.data);
|
|
1237
|
+
for (let j = 0; j < batch.length; j++) {
|
|
1238
|
+
const isin = batch[j];
|
|
1239
|
+
const result = results[j];
|
|
1240
|
+
const product = isinToProduct.get(isin) ?? isin;
|
|
1241
|
+
if (!result || result.error || !result.data || result.data.length === 0) {
|
|
1242
|
+
warnings.push(
|
|
1243
|
+
`Unrecognized type for ${isin}: unknown. Please classify manually.`
|
|
1244
|
+
);
|
|
1245
|
+
newEntries[isin] = {
|
|
1246
|
+
product,
|
|
1247
|
+
assetClass: "Stock",
|
|
1248
|
+
bucketGain: "B",
|
|
1249
|
+
bucketLoss: "B",
|
|
1250
|
+
taxRate: 0,
|
|
1251
|
+
whiteListed: null,
|
|
1252
|
+
confirmedByUser: false,
|
|
1253
|
+
source: "user"
|
|
1254
|
+
};
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
const st = result.data[0]?.securityType;
|
|
1258
|
+
const st2 = result.data[0]?.securityType2;
|
|
1259
|
+
const typeToUse = st && isKnownType(st) ? st : st2 && isKnownType(st2) ? st2 : st ?? st2 ?? "Unknown";
|
|
1260
|
+
newEntries[isin] = classifyByType(isin, typeToUse, product, warnings);
|
|
1151
1261
|
}
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
const typeToUse = st && isKnownType(st) ? st : st2 && isKnownType(st2) ? st2 : st ?? st2 ?? "Unknown";
|
|
1155
|
-
newEntries[isin] = classifyByType(isin, typeToUse, product, warnings);
|
|
1262
|
+
const batchIndex = i / BATCH_SIZE;
|
|
1263
|
+
options?.onBatchProgress?.(batchIndex + 1, totalBatches);
|
|
1156
1264
|
}
|
|
1157
1265
|
}
|
|
1158
1266
|
const mergedMap = { ...newEntries, ...confirmed };
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1267
|
+
if (sidecarPath !== void 0) {
|
|
1268
|
+
const sidecarContent = JSON.stringify(
|
|
1269
|
+
{
|
|
1270
|
+
version: 1,
|
|
1271
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1272
|
+
classifications: mergedMap,
|
|
1273
|
+
warnings
|
|
1274
|
+
},
|
|
1275
|
+
null,
|
|
1276
|
+
2
|
|
1277
|
+
);
|
|
1278
|
+
try {
|
|
1279
|
+
fs2.writeFileSync(sidecarPath, sidecarContent, "utf-8");
|
|
1280
|
+
} catch {
|
|
1281
|
+
throw new ClassificationError("WRITE_ERROR");
|
|
1282
|
+
}
|
|
1173
1283
|
}
|
|
1174
1284
|
return mergedMap;
|
|
1175
1285
|
}
|