@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.js CHANGED
@@ -1000,7 +1000,7 @@ function classifyByType(isin, securityType, product, warnings) {
1000
1000
  };
1001
1001
  }
1002
1002
  warnings.push(
1003
- `Unrecognized type: ${securityType}. Please classify manually.`
1003
+ `Unrecognized type for ${isin}: ${securityType}. Please classify manually.`
1004
1004
  );
1005
1005
  return {
1006
1006
  product,
@@ -1013,6 +1013,84 @@ function classifyByType(isin, securityType, product, warnings) {
1013
1013
  source: "user"
1014
1014
  };
1015
1015
  }
1016
+ var ASSET_CLASS_DEFAULTS = {
1017
+ ETF: {
1018
+ assetClass: "ETF",
1019
+ bucketGain: "A",
1020
+ bucketLoss: "B",
1021
+ taxRate: 0.26,
1022
+ whiteListed: null
1023
+ },
1024
+ Stock: {
1025
+ assetClass: "Stock",
1026
+ bucketGain: "B",
1027
+ bucketLoss: "B",
1028
+ taxRate: 0,
1029
+ whiteListed: null
1030
+ },
1031
+ ETC: {
1032
+ assetClass: "ETC",
1033
+ bucketGain: "B",
1034
+ bucketLoss: "B",
1035
+ taxRate: 0,
1036
+ whiteListed: null
1037
+ },
1038
+ GovtBondWL: {
1039
+ assetClass: "GovtBondWL",
1040
+ bucketGain: "A",
1041
+ bucketLoss: "B",
1042
+ taxRate: 0.125,
1043
+ whiteListed: true
1044
+ },
1045
+ GovtBondOther: {
1046
+ assetClass: "GovtBondOther",
1047
+ bucketGain: "A",
1048
+ bucketLoss: "B",
1049
+ taxRate: 0.26,
1050
+ whiteListed: false
1051
+ },
1052
+ CorpBond: {
1053
+ assetClass: "CorpBond",
1054
+ bucketGain: "B",
1055
+ bucketLoss: "B",
1056
+ taxRate: 0,
1057
+ whiteListed: null
1058
+ },
1059
+ Derivative: {
1060
+ assetClass: "Derivative",
1061
+ bucketGain: "B",
1062
+ bucketLoss: "B",
1063
+ taxRate: 0,
1064
+ whiteListed: null
1065
+ },
1066
+ LeverageCert: {
1067
+ assetClass: "LeverageCert",
1068
+ bucketGain: "B",
1069
+ bucketLoss: "B",
1070
+ taxRate: 0,
1071
+ whiteListed: null
1072
+ },
1073
+ CapProtectedCert: {
1074
+ assetClass: "CapProtectedCert",
1075
+ bucketGain: "A",
1076
+ bucketLoss: "B",
1077
+ taxRate: 0.26,
1078
+ whiteListed: null
1079
+ }
1080
+ };
1081
+ function buildEntryFromAssetClass(assetClass, product) {
1082
+ const mapped = ASSET_CLASS_DEFAULTS[assetClass];
1083
+ return {
1084
+ product,
1085
+ assetClass: mapped.assetClass,
1086
+ bucketGain: mapped.bucketGain,
1087
+ bucketLoss: mapped.bucketLoss,
1088
+ taxRate: mapped.taxRate,
1089
+ whiteListed: mapped.whiteListed,
1090
+ confirmedByUser: true,
1091
+ source: "user"
1092
+ };
1093
+ }
1016
1094
  var Classifier = class {
1017
1095
  interactive;
1018
1096
  constructor(options) {
@@ -1034,7 +1112,7 @@ var Classifier = class {
1034
1112
  }
1035
1113
  return parsed.classifications;
1036
1114
  }
1037
- async classify(transactions, sidecarPath, _httpPost = httpsPost) {
1115
+ async classify(transactions, sidecarPath, options, _httpPost = httpsPost) {
1038
1116
  const isinToProduct = /* @__PURE__ */ new Map();
1039
1117
  for (const tx of transactions) {
1040
1118
  if (tx.isin && !isinToProduct.has(tx.isin)) {
@@ -1042,13 +1120,21 @@ var Classifier = class {
1042
1120
  }
1043
1121
  }
1044
1122
  const confirmed = {};
1045
- if (fs2.existsSync(sidecarPath)) {
1046
- const existingMap = await this.load(sidecarPath);
1047
- for (const [isin, entry] of Object.entries(existingMap)) {
1048
- if (entry.confirmedByUser) {
1049
- confirmed[isin] = entry;
1123
+ if (sidecarPath !== void 0) {
1124
+ if (fs2.existsSync(sidecarPath)) {
1125
+ const existingMap = await this.load(sidecarPath);
1126
+ for (const [isin, entry] of Object.entries(existingMap)) {
1127
+ if (entry.confirmedByUser) {
1128
+ confirmed[isin] = entry;
1129
+ }
1050
1130
  }
1051
1131
  }
1132
+ } else if (options?.existingClassification) {
1133
+ Object.assign(confirmed, options.existingClassification);
1134
+ }
1135
+ for (const [isin, assetClass] of Object.entries(options?.overrides ?? {})) {
1136
+ const product = isinToProduct.get(isin) ?? isin;
1137
+ confirmed[isin] = buildEntryFromAssetClass(assetClass, product);
1052
1138
  }
1053
1139
  const toProcess = [];
1054
1140
  for (const isin of isinToProduct.keys()) {
@@ -1058,76 +1144,100 @@ var Classifier = class {
1058
1144
  }
1059
1145
  const warnings = [];
1060
1146
  const newEntries = {};
1061
- const BATCH_SIZE = 10;
1062
- for (let i = 0; i < toProcess.length; i += BATCH_SIZE) {
1063
- if (i > 0) {
1064
- await new Promise((resolve) => setTimeout(resolve, 6e3));
1147
+ if (options?.offline) {
1148
+ for (const isin of toProcess) {
1149
+ const product = isinToProduct.get(isin) ?? isin;
1150
+ warnings.push(
1151
+ `Unrecognized type for ${isin}: unknown. Please classify manually.`
1152
+ );
1153
+ newEntries[isin] = {
1154
+ product,
1155
+ assetClass: "Stock",
1156
+ bucketGain: "B",
1157
+ bucketLoss: "B",
1158
+ taxRate: 0,
1159
+ whiteListed: null,
1160
+ confirmedByUser: false,
1161
+ source: "user"
1162
+ };
1065
1163
  }
1066
- const batch = toProcess.slice(i, i + BATCH_SIZE);
1067
- const requestBody = JSON.stringify(
1068
- batch.map((isin) => ({ idType: "ID_ISIN", idValue: isin }))
1069
- );
1070
- let response = null;
1071
- for (let attempt = 0; attempt < 2; attempt++) {
1072
- try {
1073
- const r = await _httpPost(
1074
- "https://api.openfigi.com/v3/mapping",
1075
- requestBody,
1076
- 1e4
1077
- );
1078
- if (r.status < 500) {
1079
- response = r;
1080
- break;
1164
+ } else {
1165
+ const BATCH_SIZE = 10;
1166
+ const totalBatches = Math.ceil(toProcess.length / BATCH_SIZE);
1167
+ for (let i = 0; i < toProcess.length; i += BATCH_SIZE) {
1168
+ if (i > 0) {
1169
+ await new Promise((resolve) => setTimeout(resolve, 6e3));
1170
+ }
1171
+ const batch = toProcess.slice(i, i + BATCH_SIZE);
1172
+ const requestBody = JSON.stringify(
1173
+ batch.map((isin) => ({ idType: "ID_ISIN", idValue: isin }))
1174
+ );
1175
+ let response = null;
1176
+ for (let attempt = 0; attempt < 2; attempt++) {
1177
+ try {
1178
+ const r = await _httpPost(
1179
+ "https://api.openfigi.com/v3/mapping",
1180
+ requestBody,
1181
+ 1e4
1182
+ );
1183
+ if (r.status < 500) {
1184
+ response = r;
1185
+ break;
1186
+ }
1187
+ } catch {
1188
+ throw new ClassificationError("NETWORK_ERROR");
1081
1189
  }
1082
- } catch {
1190
+ }
1191
+ if (response === null) {
1083
1192
  throw new ClassificationError("NETWORK_ERROR");
1084
1193
  }
1085
- }
1086
- if (response === null) {
1087
- throw new ClassificationError("NETWORK_ERROR");
1088
- }
1089
- const results = JSON.parse(response.data);
1090
- for (let j = 0; j < batch.length; j++) {
1091
- const isin = batch[j];
1092
- const result = results[j];
1093
- const product = isinToProduct.get(isin) ?? isin;
1094
- if (!result || result.error || !result.data || result.data.length === 0) {
1095
- warnings.push(
1096
- `Unrecognized type: unknown. Please classify manually.`
1097
- );
1098
- newEntries[isin] = {
1099
- product,
1100
- assetClass: "Stock",
1101
- bucketGain: "B",
1102
- bucketLoss: "B",
1103
- taxRate: 0,
1104
- whiteListed: null,
1105
- confirmedByUser: false,
1106
- source: "user"
1107
- };
1108
- continue;
1194
+ const results = JSON.parse(response.data);
1195
+ for (let j = 0; j < batch.length; j++) {
1196
+ const isin = batch[j];
1197
+ const result = results[j];
1198
+ const product = isinToProduct.get(isin) ?? isin;
1199
+ if (!result || result.error || !result.data || result.data.length === 0) {
1200
+ warnings.push(
1201
+ `Unrecognized type for ${isin}: unknown. Please classify manually.`
1202
+ );
1203
+ newEntries[isin] = {
1204
+ product,
1205
+ assetClass: "Stock",
1206
+ bucketGain: "B",
1207
+ bucketLoss: "B",
1208
+ taxRate: 0,
1209
+ whiteListed: null,
1210
+ confirmedByUser: false,
1211
+ source: "user"
1212
+ };
1213
+ continue;
1214
+ }
1215
+ const st = result.data[0]?.securityType;
1216
+ const st2 = result.data[0]?.securityType2;
1217
+ const typeToUse = st && isKnownType(st) ? st : st2 && isKnownType(st2) ? st2 : st ?? st2 ?? "Unknown";
1218
+ newEntries[isin] = classifyByType(isin, typeToUse, product, warnings);
1109
1219
  }
1110
- const st = result.data[0]?.securityType;
1111
- const st2 = result.data[0]?.securityType2;
1112
- const typeToUse = st && isKnownType(st) ? st : st2 && isKnownType(st2) ? st2 : st ?? st2 ?? "Unknown";
1113
- newEntries[isin] = classifyByType(isin, typeToUse, product, warnings);
1220
+ const batchIndex = i / BATCH_SIZE;
1221
+ options?.onBatchProgress?.(batchIndex + 1, totalBatches);
1114
1222
  }
1115
1223
  }
1116
1224
  const mergedMap = { ...newEntries, ...confirmed };
1117
- const sidecarContent = JSON.stringify(
1118
- {
1119
- version: 1,
1120
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1121
- classifications: mergedMap,
1122
- warnings
1123
- },
1124
- null,
1125
- 2
1126
- );
1127
- try {
1128
- fs2.writeFileSync(sidecarPath, sidecarContent, "utf-8");
1129
- } catch {
1130
- throw new ClassificationError("WRITE_ERROR");
1225
+ if (sidecarPath !== void 0) {
1226
+ const sidecarContent = JSON.stringify(
1227
+ {
1228
+ version: 1,
1229
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1230
+ classifications: mergedMap,
1231
+ warnings
1232
+ },
1233
+ null,
1234
+ 2
1235
+ );
1236
+ try {
1237
+ fs2.writeFileSync(sidecarPath, sidecarContent, "utf-8");
1238
+ } catch {
1239
+ throw new ClassificationError("WRITE_ERROR");
1240
+ }
1131
1241
  }
1132
1242
  return mergedMap;
1133
1243
  }