@fuzzle/opencode-accountant 0.10.6-next.1 → 0.10.6
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 +0 -9
- package/agent/accountant.md +0 -9
- package/dist/index.js +14 -73
- package/docs/tools/import-pipeline.md +1 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -270,15 +270,6 @@ The pipeline automatically manages account declarations:
|
|
|
270
270
|
|
|
271
271
|
**No manual account setup required.** Account declarations are created proactively before import attempts.
|
|
272
272
|
|
|
273
|
-
#### Commodity Declarations
|
|
274
|
-
|
|
275
|
-
For Swissquote investment imports, the pipeline automatically manages commodity declarations:
|
|
276
|
-
|
|
277
|
-
- Collects all stock ticker symbols from trades, dividends, and corporate actions
|
|
278
|
-
- Writes sorted `commodity "SYMBOL"` declarations to `ledger/investments/commodities.journal`
|
|
279
|
-
- Merges with existing declarations (idempotent)
|
|
280
|
-
- Ensures `hledger check --strict` validation passes for quoted commodity symbols
|
|
281
|
-
|
|
282
273
|
#### Unknown Postings
|
|
283
274
|
|
|
284
275
|
When a transaction doesn't match any `if` pattern in the rules file, hledger assigns it to `income:unknown` or `expenses:unknown` depending on the transaction direction. The pipeline will fail at the validation step, reporting the unknown postings so you can add appropriate rules before retrying.
|
package/agent/accountant.md
CHANGED
|
@@ -164,15 +164,6 @@ The import pipeline automatically:
|
|
|
164
164
|
- Prevents `hledger check --strict` failures due to missing account declarations
|
|
165
165
|
- No manual account setup required
|
|
166
166
|
|
|
167
|
-
### Automatic Commodity Declarations
|
|
168
|
-
|
|
169
|
-
For Swissquote investment imports, the pipeline automatically:
|
|
170
|
-
|
|
171
|
-
- Collects all stock ticker symbols from trades, dividends, and corporate actions
|
|
172
|
-
- Writes sorted `commodity "SYMBOL"` declarations to `ledger/investments/commodities.journal`
|
|
173
|
-
- Merges with existing declarations (idempotent)
|
|
174
|
-
- Prevents `hledger check --strict` failures due to missing commodity declarations
|
|
175
|
-
|
|
176
167
|
### Automatic Balance Detection
|
|
177
168
|
|
|
178
169
|
The reconciliation step attempts to extract closing balance from:
|
package/dist/index.js
CHANGED
|
@@ -4533,7 +4533,7 @@ var init_accountSuggester = __esm(() => {
|
|
|
4533
4533
|
|
|
4534
4534
|
// src/index.ts
|
|
4535
4535
|
init_agentLoader();
|
|
4536
|
-
import { dirname as
|
|
4536
|
+
import { dirname as dirname6, join as join15 } from "path";
|
|
4537
4537
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4538
4538
|
|
|
4539
4539
|
// node_modules/zod/v4/classic/external.js
|
|
@@ -17051,38 +17051,6 @@ function ensureYearJournalExists(directory, year) {
|
|
|
17051
17051
|
}
|
|
17052
17052
|
return yearJournalPath;
|
|
17053
17053
|
}
|
|
17054
|
-
function ensureCommodityDeclarations(commodityJournalPath, symbols, logger) {
|
|
17055
|
-
const existing = new Set;
|
|
17056
|
-
if (fs4.existsSync(commodityJournalPath)) {
|
|
17057
|
-
const content2 = fs4.readFileSync(commodityJournalPath, "utf-8");
|
|
17058
|
-
for (const line of content2.split(`
|
|
17059
|
-
`)) {
|
|
17060
|
-
const match = line.trim().match(/^commodity\s+"(.+)"/);
|
|
17061
|
-
if (match) {
|
|
17062
|
-
existing.add(match[1]);
|
|
17063
|
-
}
|
|
17064
|
-
}
|
|
17065
|
-
}
|
|
17066
|
-
const missing = [];
|
|
17067
|
-
for (const symbol2 of symbols) {
|
|
17068
|
-
const upper = symbol2.toUpperCase();
|
|
17069
|
-
if (!existing.has(upper)) {
|
|
17070
|
-
missing.push(upper);
|
|
17071
|
-
existing.add(upper);
|
|
17072
|
-
}
|
|
17073
|
-
}
|
|
17074
|
-
if (missing.length === 0) {
|
|
17075
|
-
return { added: [], updated: false };
|
|
17076
|
-
}
|
|
17077
|
-
const sorted = Array.from(existing).sort((a, b) => a.localeCompare(b));
|
|
17078
|
-
const content = sorted.map((s) => `commodity "${s}"`).join(`
|
|
17079
|
-
`) + `
|
|
17080
|
-
`;
|
|
17081
|
-
ensureDirectory(path3.dirname(commodityJournalPath));
|
|
17082
|
-
fs4.writeFileSync(commodityJournalPath, content);
|
|
17083
|
-
logger?.info(`Commodity declarations: added ${missing.length} (${missing.join(", ")})`);
|
|
17084
|
-
return { added: missing.sort(), updated: true };
|
|
17085
|
-
}
|
|
17086
17054
|
|
|
17087
17055
|
// src/utils/dateUtils.ts
|
|
17088
17056
|
function formatDateISO(date5) {
|
|
@@ -25048,9 +25016,6 @@ function formatDate(dateStr) {
|
|
|
25048
25016
|
function escapeDescription(desc) {
|
|
25049
25017
|
return desc.replace(/[;|]/g, "-").trim();
|
|
25050
25018
|
}
|
|
25051
|
-
function formatCommodity(symbol2) {
|
|
25052
|
-
return `"${symbol2.toUpperCase()}"`;
|
|
25053
|
-
}
|
|
25054
25019
|
function formatQuantity(qty) {
|
|
25055
25020
|
return qty.toFixed(6).replace(/\.?0+$/, "");
|
|
25056
25021
|
}
|
|
@@ -25068,12 +25033,11 @@ function generateBuyEntry(trade, logger) {
|
|
|
25068
25033
|
const fees = trade.costs;
|
|
25069
25034
|
const cashOut = totalCost + fees;
|
|
25070
25035
|
logger?.debug(`Generating Buy entry: ${qty} ${trade.symbol} @ ${price} ${trade.currency}`);
|
|
25071
|
-
const commodity = formatCommodity(trade.symbol);
|
|
25072
25036
|
let entry = `${date5} ${description}
|
|
25073
25037
|
`;
|
|
25074
25038
|
entry += ` ; swissquote:order:${trade.orderNum} isin:${trade.isin}
|
|
25075
25039
|
`;
|
|
25076
|
-
entry += ` assets:investments:stocks:${trade.symbol} ${qty}
|
|
25040
|
+
entry += ` assets:investments:stocks:${trade.symbol} ${qty} @ ${price} ${trade.currency}
|
|
25077
25041
|
`;
|
|
25078
25042
|
if (fees > 0) {
|
|
25079
25043
|
entry += ` expenses:fees:trading:swissquote ${formatAmount2(fees, trade.currency)}
|
|
@@ -25093,7 +25057,6 @@ function generateSellEntry(trade, consumed, logger) {
|
|
|
25093
25057
|
const cashIn = saleProceeds - fees;
|
|
25094
25058
|
const gain = calculateCapitalGain(consumed, salePrice, trade.quantity);
|
|
25095
25059
|
logger?.debug(`Generating Sell entry: ${qty} ${trade.symbol} @ ${salePrice} ${trade.currency}, gain: ${gain.toFixed(2)}`);
|
|
25096
|
-
const commodity = formatCommodity(trade.symbol);
|
|
25097
25060
|
let entry = `${date5} ${description}
|
|
25098
25061
|
`;
|
|
25099
25062
|
entry += ` ; swissquote:order:${trade.orderNum} isin:${trade.isin}
|
|
@@ -25104,7 +25067,7 @@ function generateSellEntry(trade, consumed, logger) {
|
|
|
25104
25067
|
for (const c of consumed) {
|
|
25105
25068
|
const lotQty = formatQuantity(c.quantity);
|
|
25106
25069
|
const lotPrice = formatPrice(c.lot.costBasis);
|
|
25107
|
-
entry += ` assets:investments:stocks:${trade.symbol} -${lotQty}
|
|
25070
|
+
entry += ` assets:investments:stocks:${trade.symbol} -${lotQty} @ ${lotPrice} ${trade.currency}
|
|
25108
25071
|
`;
|
|
25109
25072
|
}
|
|
25110
25073
|
entry += ` assets:broker:swissquote:${trade.currency.toLowerCase()} ${formatAmount2(cashIn, trade.currency)}
|
|
@@ -25152,14 +25115,13 @@ function generateSplitEntry(action, oldQuantity, newQuantity, logger) {
|
|
|
25152
25115
|
`;
|
|
25153
25116
|
entry += ` ; Ratio: ${ratio.toFixed(4)} (${oldQuantity} -> ${newQuantity})
|
|
25154
25117
|
`;
|
|
25155
|
-
|
|
25156
|
-
entry += ` assets:investments:stocks:${action.symbol} -${formatQuantity(oldQuantity)} ${commodity}
|
|
25118
|
+
entry += ` assets:investments:stocks:${action.symbol} -${formatQuantity(oldQuantity)}
|
|
25157
25119
|
`;
|
|
25158
|
-
entry += ` equity:conversion ${formatQuantity(oldQuantity)}
|
|
25120
|
+
entry += ` equity:conversion ${formatQuantity(oldQuantity)}
|
|
25159
25121
|
`;
|
|
25160
|
-
entry += ` equity:conversion -${formatQuantity(newQuantity)}
|
|
25122
|
+
entry += ` equity:conversion -${formatQuantity(newQuantity)}
|
|
25161
25123
|
`;
|
|
25162
|
-
entry += ` assets:investments:stocks:${action.symbol} ${formatQuantity(newQuantity)}
|
|
25124
|
+
entry += ` assets:investments:stocks:${action.symbol} ${formatQuantity(newQuantity)}
|
|
25163
25125
|
`;
|
|
25164
25126
|
return entry;
|
|
25165
25127
|
}
|
|
@@ -25176,11 +25138,10 @@ function generateWorthlessEntry(action, removedLots, logger) {
|
|
|
25176
25138
|
`;
|
|
25177
25139
|
entry += ` ; Total loss: ${totalCost.toFixed(2)} ${currency}
|
|
25178
25140
|
`;
|
|
25179
|
-
const commodity = formatCommodity(action.symbol);
|
|
25180
25141
|
for (const lot of removedLots) {
|
|
25181
25142
|
const qty = formatQuantity(lot.quantity);
|
|
25182
25143
|
const price = formatPrice(lot.costBasis);
|
|
25183
|
-
entry += ` assets:investments:stocks:${action.symbol} -${qty}
|
|
25144
|
+
entry += ` assets:investments:stocks:${action.symbol} -${qty} @ ${price} ${currency}
|
|
25184
25145
|
`;
|
|
25185
25146
|
}
|
|
25186
25147
|
entry += ` expenses:losses:capital ${formatAmount2(totalCost, currency)}
|
|
@@ -25205,18 +25166,16 @@ function generateMultiWayMergerEntry(group, crossCurrencyOutgoingSymbols, logger
|
|
|
25205
25166
|
}
|
|
25206
25167
|
for (const out of group.outgoing) {
|
|
25207
25168
|
const qty = formatQuantity(Math.abs(out.quantity));
|
|
25208
|
-
|
|
25209
|
-
entry += ` assets:investments:stocks:${out.symbol} -${qty} ${commodity}
|
|
25169
|
+
entry += ` assets:investments:stocks:${out.symbol} -${qty}
|
|
25210
25170
|
`;
|
|
25211
|
-
entry += ` equity:conversion ${qty}
|
|
25171
|
+
entry += ` equity:conversion ${qty}
|
|
25212
25172
|
`;
|
|
25213
25173
|
}
|
|
25214
25174
|
for (const inc of group.incoming) {
|
|
25215
25175
|
const qty = formatQuantity(Math.abs(inc.quantity));
|
|
25216
|
-
|
|
25217
|
-
entry += ` equity:conversion -${qty} ${commodity}
|
|
25176
|
+
entry += ` equity:conversion -${qty}
|
|
25218
25177
|
`;
|
|
25219
|
-
entry += ` assets:investments:stocks:${inc.symbol} ${qty}
|
|
25178
|
+
entry += ` assets:investments:stocks:${inc.symbol} ${qty}
|
|
25220
25179
|
`;
|
|
25221
25180
|
}
|
|
25222
25181
|
return entry;
|
|
@@ -25226,12 +25185,11 @@ function generateRightsDistributionEntry(action, logger) {
|
|
|
25226
25185
|
const qty = formatQuantity(Math.abs(action.quantity));
|
|
25227
25186
|
const description = escapeDescription(`Rights Distribution: ${action.symbol} - ${action.name}`);
|
|
25228
25187
|
logger?.debug(`Generating Rights Distribution entry: ${qty} ${action.symbol}`);
|
|
25229
|
-
const commodity = formatCommodity(action.symbol);
|
|
25230
25188
|
let entry = `${date5} ${description}
|
|
25231
25189
|
`;
|
|
25232
25190
|
entry += ` ; swissquote:order:${action.orderNum} isin:${action.isin}
|
|
25233
25191
|
`;
|
|
25234
|
-
entry += ` assets:investments:stocks:${action.symbol} ${qty}
|
|
25192
|
+
entry += ` assets:investments:stocks:${action.symbol} ${qty} @ 0.00 CAD
|
|
25235
25193
|
`;
|
|
25236
25194
|
entry += ` income:capital-gains:rights-distribution 0.00 CAD
|
|
25237
25195
|
`;
|
|
@@ -25799,23 +25757,6 @@ async function preprocessSwissquote(csvPath, projectDir, currency, year, lotInve
|
|
|
25799
25757
|
logger?.logStep("write-journal", "success", `Created ${path13.basename(journalFile)} with ${journalEntries.length} entries`);
|
|
25800
25758
|
}
|
|
25801
25759
|
}
|
|
25802
|
-
const stockSymbols = new Set;
|
|
25803
|
-
for (const trade of trades) {
|
|
25804
|
-
stockSymbols.add(trade.symbol);
|
|
25805
|
-
}
|
|
25806
|
-
for (const dividend of dividends) {
|
|
25807
|
-
stockSymbols.add(dividend.symbol);
|
|
25808
|
-
}
|
|
25809
|
-
for (const action of corporateActions) {
|
|
25810
|
-
stockSymbols.add(action.symbol);
|
|
25811
|
-
if (action.newSymbol) {
|
|
25812
|
-
stockSymbols.add(action.newSymbol);
|
|
25813
|
-
}
|
|
25814
|
-
}
|
|
25815
|
-
if (stockSymbols.size > 0) {
|
|
25816
|
-
const commodityJournalPath = path13.join(projectDir, "ledger", "investments", "commodities.journal");
|
|
25817
|
-
ensureCommodityDeclarations(commodityJournalPath, stockSymbols, logger);
|
|
25818
|
-
}
|
|
25819
25760
|
logger?.logResult({
|
|
25820
25761
|
totalRows: stats.totalRows,
|
|
25821
25762
|
simpleTransactions: stats.simpleTransactions,
|
|
@@ -26744,7 +26685,7 @@ to produce equity conversion entries for Bitcoin purchases.
|
|
|
26744
26685
|
}
|
|
26745
26686
|
});
|
|
26746
26687
|
// src/index.ts
|
|
26747
|
-
var __dirname2 =
|
|
26688
|
+
var __dirname2 = dirname6(fileURLToPath3(import.meta.url));
|
|
26748
26689
|
var AGENT_FILE = join15(__dirname2, "..", "agent", "accountant.md");
|
|
26749
26690
|
var AccountantPlugin = async () => {
|
|
26750
26691
|
const agent = loadAgent(AGENT_FILE);
|
|
@@ -220,8 +220,7 @@ See [classify-statements](classify-statements.md) for details.
|
|
|
220
220
|
- **Rights Distributions**: adds shares at zero cost basis
|
|
221
221
|
- **Cross-currency mergers**: saves outgoing side to pending state, loads when incoming side is processed
|
|
222
222
|
8. Generates per-year investment journal entries (`ledger/investments/{year}-{currency}.journal`) based on the CSV filename's date range
|
|
223
|
-
9.
|
|
224
|
-
10. Outputs filtered CSV (simple transactions only) for hledger rules import
|
|
223
|
+
9. Outputs filtered CSV (simple transactions only) for hledger rules import
|
|
225
224
|
|
|
226
225
|
**Symbol Map**: Maps Swissquote's internal symbol names (e.g., `GOLD ROYALTY RG`, `VIZSLA WT 12.25`) to canonical ticker symbols (e.g., `GROY`, `VROY-WT`). Applied before any processing, so journal entries, lot inventory filenames, and account names all use the mapped symbols. See [Symbol Map Configuration](../configuration/symbol-map.md).
|
|
227
226
|
|