2026-05-30 14:33:55 -05:00
|
|
|
'use strict';
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
import type { Dollars } from '../utils/money.mts';
|
|
|
|
|
|
refactor(server): migrate middleware, workers, and db layer to TypeScript (.cts)
- middleware/ (5): requireAuth, securityHeaders, errorFormatter, csrf,
rateLimiter — fully typed against the shared http Req/Res/Next types.
- workers/dailyWorker — fully typed.
- db/ (4): database, subscriptionCatalogSeed, migrations/versionedMigrations,
migrations/legacyReconcileMigrations — large dynamic schema/migration infra,
converted with @ts-nocheck (typing deferred). All requires updated to .cts.
Behavior-preserving. Verified: typecheck:server 0, check:server 0, suite 226/226.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 11:34:08 -05:00
|
|
|
const { getDb } = require('../db/database.cts');
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
const { getCycleRange } = require('./statusService.cts');
|
|
|
|
|
const { accountingActiveSql } = require('./paymentAccountingService.cts');
|
2026-07-06 11:00:01 -05:00
|
|
|
const { getUserSettings } = require('./userSettings.cts');
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
const { localDateString } = require('../utils/dates.mts') as typeof import('../utils/dates.mts');
|
|
|
|
|
const { roundMoney, fromCents } = require('../utils/money.mts') as typeof import('../utils/money.mts');
|
2026-05-30 14:33:55 -05:00
|
|
|
|
|
|
|
|
const MONTHS_BACK = 3;
|
|
|
|
|
const MIN_PAID_MONTHS = 2;
|
|
|
|
|
const MIN_ABS_DELTA = 1.00;
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
interface DriftBillRow {
|
|
|
|
|
id: any;
|
|
|
|
|
name: any;
|
|
|
|
|
category_name: any;
|
|
|
|
|
expected_amount: Dollars;
|
|
|
|
|
recent_amount: Dollars;
|
|
|
|
|
drift_pct: number;
|
|
|
|
|
direction: 'up' | 'down';
|
|
|
|
|
months_sampled: number;
|
|
|
|
|
drift_snoozed_until: any;
|
|
|
|
|
}
|
|
|
|
|
interface DriftReport { bills: DriftBillRow[]; threshold_pct: number; error?: string }
|
|
|
|
|
|
|
|
|
|
function median(arr: number[]): number {
|
2026-05-30 14:33:55 -05:00
|
|
|
if (!arr.length) return 0;
|
|
|
|
|
const sorted = [...arr].sort((a, b) => a - b);
|
|
|
|
|
const mid = Math.floor(sorted.length / 2);
|
|
|
|
|
return sorted.length % 2 !== 0
|
|
|
|
|
? sorted[mid]
|
|
|
|
|
: (sorted[mid - 1] + sorted[mid]) / 2;
|
|
|
|
|
}
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
function monthEnd(year: number, month: number): number {
|
2026-05-30 14:33:55 -05:00
|
|
|
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
|
|
|
}
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
function getDriftReport(userId: number, now: Date = new Date()): DriftReport {
|
2026-05-30 14:33:55 -05:00
|
|
|
try {
|
|
|
|
|
const db = getDb();
|
|
|
|
|
const settings = getUserSettings(userId);
|
|
|
|
|
const thresholdPct = Math.max(1, Math.min(25,
|
|
|
|
|
parseFloat(settings.drift_threshold_pct ?? '5') || 5
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
const bills = db.prepare(`
|
|
|
|
|
SELECT b.*, c.name AS category_name
|
|
|
|
|
FROM bills b
|
|
|
|
|
LEFT JOIN categories c ON c.id = b.category_id
|
|
|
|
|
WHERE b.user_id = ? AND b.active = 1 AND b.deleted_at IS NULL
|
|
|
|
|
`).all(userId);
|
|
|
|
|
|
2026-06-10 19:42:51 -05:00
|
|
|
const todayStr = localDateString(now);
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
const drifted: DriftBillRow[] = [];
|
2026-05-30 14:33:55 -05:00
|
|
|
|
|
|
|
|
const mbsStmt = db.prepare(
|
|
|
|
|
'SELECT is_skipped FROM monthly_bill_state WHERE bill_id = ? AND year = ? AND month = ?'
|
|
|
|
|
);
|
|
|
|
|
const payStmt = db.prepare(`
|
|
|
|
|
SELECT COALESCE(SUM(amount), 0) AS total
|
|
|
|
|
FROM payments
|
|
|
|
|
WHERE bill_id = ? AND paid_date BETWEEN ? AND ? AND deleted_at IS NULL
|
2026-06-07 01:05:48 -05:00
|
|
|
AND ${accountingActiveSql()}
|
2026-05-30 14:33:55 -05:00
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
for (const bill of bills) {
|
2026-06-11 20:12:31 -05:00
|
|
|
const expectedAmount = fromCents(bill.expected_amount);
|
|
|
|
|
if (!expectedAmount || expectedAmount <= 0) continue;
|
2026-05-30 14:33:55 -05:00
|
|
|
if (bill.drift_snoozed_until && bill.drift_snoozed_until > todayStr) continue;
|
|
|
|
|
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
const monthTotals: number[] = [];
|
2026-05-30 14:33:55 -05:00
|
|
|
|
|
|
|
|
for (let i = 1; i <= MONTHS_BACK; i++) {
|
|
|
|
|
const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - i, 1));
|
|
|
|
|
const yr = d.getUTCFullYear();
|
|
|
|
|
const mo = d.getUTCMonth() + 1;
|
|
|
|
|
|
|
|
|
|
// Skip if bill was created after this month ended
|
|
|
|
|
const monthEndStr = `${yr}-${String(mo).padStart(2,'0')}-${String(monthEnd(yr, mo)).padStart(2,'0')}`;
|
|
|
|
|
if (bill.created_at && bill.created_at.slice(0, 10) > monthEndStr) continue;
|
|
|
|
|
|
|
|
|
|
const mbs = mbsStmt.get(bill.id, yr, mo);
|
|
|
|
|
if (mbs?.is_skipped) continue;
|
|
|
|
|
|
|
|
|
|
const range = getCycleRange(yr, mo, bill);
|
|
|
|
|
if (!range) continue;
|
|
|
|
|
|
|
|
|
|
const { total } = payStmt.get(bill.id, range.start, range.end);
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
if (total > 0) monthTotals.push(fromCents(total)!); // total>0 ⇒ fromCents non-null
|
2026-05-30 14:33:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (monthTotals.length < MIN_PAID_MONTHS) continue;
|
|
|
|
|
|
|
|
|
|
const recentAmount = median(monthTotals);
|
2026-06-11 20:12:31 -05:00
|
|
|
const delta = recentAmount - expectedAmount;
|
2026-05-30 14:33:55 -05:00
|
|
|
const absDelta = Math.abs(delta);
|
2026-06-11 20:12:31 -05:00
|
|
|
const driftPct = (delta / expectedAmount) * 100;
|
2026-05-30 14:33:55 -05:00
|
|
|
|
|
|
|
|
if (absDelta < MIN_ABS_DELTA) continue;
|
|
|
|
|
if (Math.abs(driftPct) < thresholdPct) continue;
|
|
|
|
|
|
|
|
|
|
drifted.push({
|
|
|
|
|
id: bill.id,
|
|
|
|
|
name: bill.name,
|
|
|
|
|
category_name: bill.category_name ?? null,
|
2026-06-11 20:12:31 -05:00
|
|
|
expected_amount: expectedAmount,
|
2026-06-10 20:14:13 -05:00
|
|
|
recent_amount: roundMoney(recentAmount),
|
2026-05-30 14:33:55 -05:00
|
|
|
drift_pct: Math.round(driftPct * 10) / 10,
|
|
|
|
|
direction: delta > 0 ? 'up' : 'down',
|
|
|
|
|
months_sampled: monthTotals.length,
|
|
|
|
|
drift_snoozed_until: bill.drift_snoozed_until ?? null,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { bills: drifted, threshold_pct: thresholdPct };
|
refactor(server): migrate 10 services to TypeScript (.cts)
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:35:29 -05:00
|
|
|
} catch (err: any) {
|
2026-05-30 14:33:55 -05:00
|
|
|
console.error('[driftService] getDriftReport error:', err.message);
|
|
|
|
|
return { bills: [], threshold_pct: 5, error: err.message };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { getDriftReport };
|