Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/client/categories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Transaction } from "./types.js";

/**
* RiseUp uses `trackingCategory` for two distinct purposes:
*
* 1. A user-chosen category override (e.g. "Eating Out" instead of the
* auto "Leisure"). This is what we want to surface.
* 2. Internal tracking flags like "blacklist" — used to exclude certain
* transactions (e.g. recurring BIT transfers) from the budget total.
* These are not real categories and should not be shown as such.
*
* Known internal flag names that are NOT user-chosen categories.
*/
const INTERNAL_TRACKING_FLAGS = new Set(["blacklist"]);

/**
* Returns the effective category for a transaction.
*
* When the user manually reclassifies a transaction in the RiseUp app
* (via the "Change category" action), the override is stored on
* `Transaction.trackingCategory`. The auto-classification remains on
* `Transaction.expense`.
*
* The CLI prefers the user's override when it's a real category.
* Falls back to `expense` when no override is set or when the
* override is an internal RiseUp flag (e.g. "blacklist").
*/
export function getEffectiveCategory(tx: Transaction): string {
const name = tx.trackingCategory?.name;
if (name && !INTERNAL_TRACKING_FLAGS.has(name)) return name;
return tx.expense ?? "";
}
7 changes: 4 additions & 3 deletions src/commands/income.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import chalk from "chalk";
import type { Command } from "commander";
import { getEffectiveCategory } from "../client/categories.js";
import { parseMonth } from "../utils/dates.js";
import { formatNIS } from "../formatters/currency.js";
import { createTable, printTable } from "../formatters/table.js";
Expand Down Expand Up @@ -29,7 +30,7 @@ export async function incomeAction(

// Apply --salary-only filter.
const filtered = salaryOnly
? allTransactions.filter((tx) => tx.expense === "\u05DE\u05E9\u05DB\u05D5\u05E8\u05EA")
? allTransactions.filter((tx) => getEffectiveCategory(tx) === "\u05DE\u05E9\u05DB\u05D5\u05E8\u05EA")
: allTransactions;

// Sort by date.
Expand All @@ -45,7 +46,7 @@ export async function incomeAction(
date: tx.transactionDate,
amount: tx.incomeAmount,
businessName: tx.businessName,
category: tx.expense,
category: getEffectiveCategory(tx),
})),
);
return;
Expand All @@ -61,7 +62,7 @@ export async function incomeAction(
tx.transactionDate,
formatNIS(tx.incomeAmount ?? 0),
tx.businessName,
tx.expense,
getEffectiveCategory(tx),
]);
}

Expand Down
3 changes: 2 additions & 1 deletion src/commands/manage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import chalk from "chalk";
import type { Command } from "commander";
import type { Transaction } from "../client/types.js";
import type { RiseUpClient } from "../client/RiseUpClient.js";
import { getEffectiveCategory } from "../client/categories.js";
import { parseMonth } from "../utils/dates.js";
import { formatNIS } from "../formatters/currency.js";
import { createTable, printTable } from "../formatters/table.js";
Expand Down Expand Up @@ -239,7 +240,7 @@ export async function unclassifiedAction(
date: tx.transactionDate,
amount: Math.abs(tx.billingAmount ?? 0),
businessName: tx.businessName,
category: tx.expense,
category: getEffectiveCategory(tx),
source: tx.source,
})),
);
Expand Down
5 changes: 3 additions & 2 deletions src/commands/spending.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import chalk from "chalk";
import type { Command } from "commander";
import { getEffectiveCategory } from "../client/categories.js";
import { parseMonth } from "../utils/dates.js";
import { formatNIS } from "../formatters/currency.js";
import { createTable, printTable } from "../formatters/table.js";
Expand Down Expand Up @@ -40,7 +41,7 @@ export async function spendingAction(
// Apply --category filter.
const filtered = category
? expenses.filter(
(tx) => tx.expense.toLowerCase() === category.toLowerCase(),
(tx) => getEffectiveCategory(tx).toLowerCase() === category.toLowerCase(),
)
: expenses;

Expand All @@ -57,7 +58,7 @@ export async function spendingAction(
break;
case "category":
default:
key = tx.expense || "(uncategorized)";
key = getEffectiveCategory(tx) || "(uncategorized)";
break;
}

Expand Down
7 changes: 4 additions & 3 deletions src/commands/transactions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import chalk from "chalk";
import type { Command } from "commander";
import type { Transaction } from "../client/types.js";
import { getEffectiveCategory } from "../client/categories.js";
import { parseMonth } from "../utils/dates.js";
import { formatNIS } from "../formatters/currency.js";
import { createTable, printTable } from "../formatters/table.js";
Expand Down Expand Up @@ -49,7 +50,7 @@ export async function transactionsAction(
if (category) {
const lowerCategory = category.toLowerCase();
transactions = transactions.filter(
(tx) => tx.expense.toLowerCase() === lowerCategory,
(tx) => getEffectiveCategory(tx).toLowerCase() === lowerCategory,
);
}
if (min != null) {
Expand Down Expand Up @@ -86,7 +87,7 @@ export async function transactionsAction(
date: tx.transactionDate,
amount: getDisplayAmount(tx),
businessName: tx.businessName,
category: tx.expense,
category: getEffectiveCategory(tx),
source: tx.source,
isIncome: tx.isIncome,
...(tx.customerComment ? { comment: tx.customerComment } : {}),
Expand All @@ -107,7 +108,7 @@ export async function transactionsAction(
tx.transactionDate,
`${prefix}${formatNIS(amount)}`,
tx.businessName,
tx.expense,
getEffectiveCategory(tx),
tx.source,
]);
}
Expand Down
3 changes: 2 additions & 1 deletion src/commands/trends.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import chalk from "chalk";
import type { Command } from "commander";
import type { Budget, CashflowTrends } from "../client/types.js";
import { getEffectiveCategory } from "../client/categories.js";
import { formatNIS } from "../formatters/currency.js";
import { createTable, printTable } from "../formatters/table.js";
import { printJson } from "../formatters/json.js";
Expand Down Expand Up @@ -131,7 +132,7 @@ function showByCategory(

const categories = new Map<string, number>();
for (const tx of expenses) {
const cat = tx.expense || "(uncategorized)";
const cat = getEffectiveCategory(tx) || "(uncategorized)";
const amount = Math.abs(tx.billingAmount ?? 0);
categories.set(cat, (categories.get(cat) ?? 0) + amount);
globalCategoryTotals.set(cat, (globalCategoryTotals.get(cat) ?? 0) + amount);
Expand Down