@fuzzle/opencode-accountant 0.13.13-next.1 → 0.13.13

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.
Files changed (2) hide show
  1. package/dist/index.js +40 -159
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25416,47 +25416,6 @@ function generateRightsDistributionEntry(action, logger) {
25416
25416
  `;
25417
25417
  return entry;
25418
25418
  }
25419
- function generatePendingMergerWorthlessEntry(date5, orderNum, outgoingSymbols, outgoingIsins, outgoingQuantities, totalCostBasis, currency, logger) {
25420
- const formattedDate = formatDate(date5);
25421
- const symbols = outgoingSymbols.join(" + ");
25422
- const description = escapeDescription(`Worthless: ${symbols} (unresolved merger)`);
25423
- logger?.debug(`Generating pending merger worthless entry: ${symbols}, loss: ${totalCostBasis.toFixed(2)} ${currency}`);
25424
- const postings = [];
25425
- if (outgoingQuantities.length > 0) {
25426
- const totalQty = outgoingQuantities.reduce((a, b) => a + b, 0);
25427
- for (let i2 = 0;i2 < outgoingSymbols.length; i2++) {
25428
- const symbol2 = outgoingSymbols[i2];
25429
- const quantity = outgoingQuantities[i2] || 0;
25430
- if (quantity <= 0)
25431
- continue;
25432
- const proportion = totalQty > 0 ? quantity / totalQty : 0;
25433
- const costForSymbol = totalCostBasis * proportion;
25434
- const costBasisPerUnit = costForSymbol / quantity;
25435
- const commodity = formatCommodity(symbol2);
25436
- postings.push({
25437
- account: "equity:conversion",
25438
- amount: `-${formatQuantity(quantity)} ${commodity} @ ${formatPrice(costBasisPerUnit)} ${currency}`
25439
- });
25440
- }
25441
- }
25442
- postings.push({
25443
- account: "expenses:losses:capital",
25444
- amount: formatAmount2(totalCostBasis, currency)
25445
- });
25446
- let entry = `${formattedDate} ${description}
25447
- `;
25448
- entry += ` ; swissquote:order:${orderNum}
25449
- `;
25450
- entry += ` ; Pending merger resolved as worthless - total loss: ${totalCostBasis.toFixed(2)} ${currency}
25451
- `;
25452
- if (outgoingIsins.length > 0) {
25453
- entry += ` ; Original ISINs: ${outgoingIsins.join(", ")}
25454
- `;
25455
- }
25456
- entry += formatPostings(postings) + `
25457
- `;
25458
- return entry;
25459
- }
25460
25419
  function formatJournalFile(entries, year, currency) {
25461
25420
  const header = `; Swissquote ${currency.toUpperCase()} investment transactions for ${year}
25462
25421
  ; Generated by opencode-accountant
@@ -25614,44 +25573,6 @@ var MERGER_LIKE_TYPES = new Set([
25614
25573
  "Exchange of securities",
25615
25574
  "Corporate Action"
25616
25575
  ]);
25617
- function processNonMergerAction(action, inventory, entries, logger) {
25618
- switch (action.type) {
25619
- case "Reverse Split": {
25620
- const qty = action.quantity;
25621
- if (qty < 0) {
25622
- return;
25623
- }
25624
- const oldQty = getHeldQuantity(inventory, action.symbol);
25625
- const newQty = Math.abs(qty);
25626
- if (oldQty > 0) {
25627
- const ratio = newQty / oldQty;
25628
- adjustLotsForSplit(inventory, action.symbol, ratio, logger);
25629
- const entry = generateSplitEntry(action, oldQty, newQty, logger);
25630
- entries.push(entry);
25631
- } else {
25632
- logger?.warn(`Reverse split for ${action.symbol} but no lots found`);
25633
- }
25634
- break;
25635
- }
25636
- case "Worthless Liquidation": {
25637
- const removedLots = removeLots(inventory, action.symbol, logger);
25638
- if (removedLots.length > 0) {
25639
- const entry = generateWorthlessEntry(action, removedLots, logger);
25640
- entries.push(entry);
25641
- } else {
25642
- logger?.warn(`Worthless liquidation for ${action.symbol} but no lots found`);
25643
- }
25644
- break;
25645
- }
25646
- case "Rights Distribution": {
25647
- if (action.quantity > 0) {
25648
- const entry = generateRightsDistributionEntry(action, logger);
25649
- entries.push(entry);
25650
- }
25651
- break;
25652
- }
25653
- }
25654
- }
25655
25576
  function processCorporateActions(actions, inventory, lotInventoryPath, projectDir, logger) {
25656
25577
  const entries = [];
25657
25578
  actions.sort((a, b) => formatDate(a.date).localeCompare(formatDate(b.date)));
@@ -25665,19 +25586,46 @@ function processCorporateActions(actions, inventory, lotInventoryPath, projectDi
25665
25586
  }
25666
25587
  }
25667
25588
  const mergerGroups = groupMergerActions(mergerActions);
25668
- mergerGroups.sort((a, b) => formatDate(a.date).localeCompare(formatDate(b.date)));
25669
- let mi = 0;
25670
- let oi = 0;
25671
- while (mi < mergerGroups.length || oi < otherActions.length) {
25672
- const mergerDate = mi < mergerGroups.length ? formatDate(mergerGroups[mi].date) : "\uFFFF";
25673
- const otherDate = oi < otherActions.length ? formatDate(otherActions[oi].date) : "\uFFFF";
25674
- if (mergerDate <= otherDate) {
25675
- const groupEntries = processMultiWayMerger(mergerGroups[mi], inventory, lotInventoryPath, projectDir, logger);
25676
- entries.push(...groupEntries);
25677
- mi++;
25678
- } else {
25679
- processNonMergerAction(otherActions[oi], inventory, entries, logger);
25680
- oi++;
25589
+ for (const group of mergerGroups) {
25590
+ const groupEntries = processMultiWayMerger(group, inventory, lotInventoryPath, projectDir, logger);
25591
+ entries.push(...groupEntries);
25592
+ }
25593
+ for (const action of otherActions) {
25594
+ switch (action.type) {
25595
+ case "Reverse Split": {
25596
+ const qty = action.quantity;
25597
+ if (qty < 0) {
25598
+ continue;
25599
+ }
25600
+ const oldQty = getHeldQuantity(inventory, action.symbol);
25601
+ const newQty = Math.abs(qty);
25602
+ if (oldQty > 0) {
25603
+ const ratio = newQty / oldQty;
25604
+ adjustLotsForSplit(inventory, action.symbol, ratio, logger);
25605
+ const entry = generateSplitEntry(action, oldQty, newQty, logger);
25606
+ entries.push(entry);
25607
+ } else {
25608
+ logger?.warn(`Reverse split for ${action.symbol} but no lots found`);
25609
+ }
25610
+ break;
25611
+ }
25612
+ case "Worthless Liquidation": {
25613
+ const removedLots = removeLots(inventory, action.symbol, logger);
25614
+ if (removedLots.length > 0) {
25615
+ const entry = generateWorthlessEntry(action, removedLots, logger);
25616
+ entries.push(entry);
25617
+ } else {
25618
+ logger?.warn(`Worthless liquidation for ${action.symbol} but no lots found`);
25619
+ }
25620
+ break;
25621
+ }
25622
+ case "Rights Distribution": {
25623
+ if (action.quantity > 0) {
25624
+ const entry = generateRightsDistributionEntry(action, logger);
25625
+ entries.push(entry);
25626
+ }
25627
+ break;
25628
+ }
25681
25629
  }
25682
25630
  }
25683
25631
  return entries;
@@ -25764,7 +25712,6 @@ function processMultiWayMerger(group, inventory, lotInventoryPath, projectDir, l
25764
25712
  orderNum: group.orderNum,
25765
25713
  outgoingSymbols,
25766
25714
  outgoingIsins: group.outgoing.map((a) => a.isin),
25767
- outgoingQuantities: group.outgoing.map((a) => Math.abs(a.quantity)),
25768
25715
  totalCostBasis,
25769
25716
  currency: outgoingCurrency || "CAD"
25770
25717
  }, logger);
@@ -25838,68 +25785,6 @@ function removePendingMerger(projectDir, lotInventoryPath, key, logger) {
25838
25785
  logger?.debug(`Removed pending merger state: ${key}`);
25839
25786
  }
25840
25787
  }
25841
- function resolveRemainingPendingMergers(projectDir, lotInventoryPath, logger) {
25842
- const pendingDir = getPendingMergerDir(projectDir, lotInventoryPath);
25843
- if (!fs18.existsSync(pendingDir)) {
25844
- return { resolved: 0, journalFilesUpdated: [] };
25845
- }
25846
- const files = fs18.readdirSync(pendingDir).filter((f) => f.endsWith(".json"));
25847
- if (files.length === 0) {
25848
- return { resolved: 0, journalFilesUpdated: [] };
25849
- }
25850
- const entriesByJournal = new Map;
25851
- const resolvedFiles = [];
25852
- for (const file2 of files) {
25853
- const filePath = path13.join(pendingDir, file2);
25854
- try {
25855
- const content = fs18.readFileSync(filePath, "utf-8");
25856
- const state = JSON.parse(content);
25857
- const dateMatch = state.date.match(/\d{2}-\d{2}-(\d{4})/);
25858
- if (!dateMatch) {
25859
- logger?.warn(`Invalid date in pending merger ${file2}: ${state.date}`);
25860
- continue;
25861
- }
25862
- const year = parseInt(dateMatch[1], 10);
25863
- const entry = generatePendingMergerWorthlessEntry(state.date, state.orderNum, state.outgoingSymbols, state.outgoingIsins, state.outgoingQuantities || [], state.totalCostBasis, state.currency, logger);
25864
- const journalFile = path13.join(projectDir, "ledger", "investments", `${year}-${state.currency.toLowerCase()}.journal`);
25865
- if (!entriesByJournal.has(journalFile)) {
25866
- entriesByJournal.set(journalFile, []);
25867
- }
25868
- entriesByJournal.get(journalFile).push(entry);
25869
- fs18.unlinkSync(filePath);
25870
- resolvedFiles.push(file2);
25871
- logger?.info(`Resolved pending merger ${file2} as worthless: ${state.outgoingSymbols.join(", ")} (${state.totalCostBasis.toFixed(2)} ${state.currency})`);
25872
- } catch (error45) {
25873
- const message = error45 instanceof Error ? error45.message : String(error45);
25874
- logger?.warn(`Failed to resolve pending merger ${file2}: ${message}`);
25875
- }
25876
- }
25877
- const journalFilesUpdated = [];
25878
- for (const [journalFile, journalEntries] of entriesByJournal) {
25879
- if (fs18.existsSync(journalFile)) {
25880
- fs18.appendFileSync(journalFile, `
25881
- ` + journalEntries.join(`
25882
- `));
25883
- } else {
25884
- const basename5 = path13.basename(journalFile, ".journal");
25885
- const parts = basename5.split("-");
25886
- const yearStr = parts[0];
25887
- const currency = parts.slice(1).join("-");
25888
- const header = formatJournalFile(journalEntries, parseInt(yearStr, 10), currency);
25889
- fs18.writeFileSync(journalFile, header);
25890
- }
25891
- journalFilesUpdated.push(journalFile);
25892
- logger?.info(`Updated ${path13.basename(journalFile)} with worthless resolution entries`);
25893
- }
25894
- const remaining = fs18.readdirSync(pendingDir).filter((f) => f.endsWith(".json"));
25895
- if (remaining.length === 0) {
25896
- try {
25897
- fs18.rmdirSync(pendingDir);
25898
- logger?.debug("Removed empty pending-mergers directory");
25899
- } catch {}
25900
- }
25901
- return { resolved: resolvedFiles.length, journalFilesUpdated };
25902
- }
25903
25788
  async function preprocessSwissquote(csvPath, projectDir, currency, year, lotInventoryPath = DEFAULT_LOT_INVENTORY_PATH, symbolMap = {}, logger) {
25904
25789
  logger?.startSection(`Swissquote Preprocessing: ${path13.basename(csvPath)}`);
25905
25790
  const stats = {
@@ -26845,10 +26730,6 @@ async function executeSwissquotePreprocessStep(context, contextIds, logger) {
26845
26730
  }
26846
26731
  logger?.logStep("Swissquote Preprocess", "success", `Processed: ${result.stats.trades} trades, ${result.stats.dividends} dividends, ${result.stats.corporateActions} corporate actions, ${result.stats.forexTransactions} forex`);
26847
26732
  }
26848
- const pendingResolution = resolveRemainingPendingMergers(context.directory, lotInventoryPath, logger);
26849
- if (pendingResolution.resolved > 0) {
26850
- logger?.logStep("Swissquote Pending Mergers", "success", `Resolved ${pendingResolution.resolved} pending merger(s) as worthless`);
26851
- }
26852
26733
  if (allForexRows.length > 0) {
26853
26734
  const firstCtx = swissquoteContexts[0];
26854
26735
  const yearJournalPath = path16.join(context.directory, "ledger", `${firstCtx.year}.journal`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzzle/opencode-accountant",
3
- "version": "0.13.13-next.1",
3
+ "version": "0.13.13",
4
4
  "description": "An OpenCode accounting agent, specialized in double-entry-bookkepping with hledger",
5
5
  "author": {
6
6
  "name": "ali bengali",