@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/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 (fs2.existsSync(sidecarPath)) {
1088
- const existingMap = await this.load(sidecarPath);
1089
- for (const [isin, entry] of Object.entries(existingMap)) {
1090
- if (entry.confirmedByUser) {
1091
- confirmed[isin] = entry;
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
- const BATCH_SIZE = 10;
1104
- for (let i = 0; i < toProcess.length; i += BATCH_SIZE) {
1105
- if (i > 0) {
1106
- await new Promise((resolve) => setTimeout(resolve, 6e3));
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
- const batch = toProcess.slice(i, i + BATCH_SIZE);
1109
- const requestBody = JSON.stringify(
1110
- batch.map((isin) => ({ idType: "ID_ISIN", idValue: isin }))
1111
- );
1112
- let response = null;
1113
- for (let attempt = 0; attempt < 2; attempt++) {
1114
- try {
1115
- const r = await _httpPost(
1116
- "https://api.openfigi.com/v3/mapping",
1117
- requestBody,
1118
- 1e4
1119
- );
1120
- if (r.status < 500) {
1121
- response = r;
1122
- break;
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
- } catch {
1232
+ }
1233
+ if (response === null) {
1125
1234
  throw new ClassificationError("NETWORK_ERROR");
1126
1235
  }
1127
- }
1128
- if (response === null) {
1129
- throw new ClassificationError("NETWORK_ERROR");
1130
- }
1131
- const results = JSON.parse(response.data);
1132
- for (let j = 0; j < batch.length; j++) {
1133
- const isin = batch[j];
1134
- const result = results[j];
1135
- const product = isinToProduct.get(isin) ?? isin;
1136
- if (!result || result.error || !result.data || result.data.length === 0) {
1137
- warnings.push(
1138
- `Unrecognized type: unknown. Please classify manually.`
1139
- );
1140
- newEntries[isin] = {
1141
- product,
1142
- assetClass: "Stock",
1143
- bucketGain: "B",
1144
- bucketLoss: "B",
1145
- taxRate: 0,
1146
- whiteListed: null,
1147
- confirmedByUser: false,
1148
- source: "user"
1149
- };
1150
- continue;
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 st = result.data[0]?.securityType;
1153
- const st2 = result.data[0]?.securityType2;
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
- const sidecarContent = JSON.stringify(
1160
- {
1161
- version: 1,
1162
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1163
- classifications: mergedMap,
1164
- warnings
1165
- },
1166
- null,
1167
- 2
1168
- );
1169
- try {
1170
- fs2.writeFileSync(sidecarPath, sidecarContent, "utf-8");
1171
- } catch {
1172
- throw new ClassificationError("WRITE_ERROR");
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
  }