feat: tax reporting engine — Schedule E, Form 1065, K-1 allocations (2024/2025)#73
Conversation
…ted K-1 allocations Add per-property Schedule E reports grouped by IRS line items (Lines 3-19), Form 1065 partnership data with COA-coded income/deductions, and time-weighted K-1 member allocations supporting mid-year ownership changes. Key features: - Schedule E: groups transactions by property × IRS line via COA mapping - Form 1065: partnership P&L with income/deduction breakdown by COA code - K-1 engine: time-weighted allocation periods for mid-year member changes (Luisa Arias exit Oct 14 2024, Sharon Jones entry Mar 15 2024, ARIBIA→ITCB restructure Oct 29 2024, Nick→JAV assignment Dec 16 2024) - Year-specific member arrays (members_2024 vs members) in tenant metadata - CPA export: BOM-prefixed CSV organized by form/schedule/line - Client: Schedule E and Form 1065/K-1 tabs on Reports page with tax year selector Verified member ownership from Notion, ChittyEvidence, and Gmail: - ARIBIA LLC: individual members → ITCB 100% sole member (Amendment B, Oct 29 2024) - IT CAN BE LLC: Nick 85%/Sharon 15% → JAV 85%/Sharon 15% (Dec 16 2024) - JAV LLC: registered in Florida Dec 13 2024 (filing #500439906755) New files: - server/lib/tax-reporting.ts — pure-function engine (all types + computation) - server/routes/tax.ts — 3 endpoints (schedule-e, form-1065, export) - server/__tests__/tax-reporting.test.ts — 22 tests (all passing) Modified: - server/storage/system.ts — added getPropertiesForTenants() - server/app.ts — mounted taxRoutes - client/src/hooks/use-reports.ts — 3 new hooks + types - client/src/pages/Reports.tsx — tab UI (Consolidated/Schedule E/Form 1065) - database/seeds/it-can-be-llc.ts — verified member ownership data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@coderabbitai review Please evaluate:
|
📝 WalkthroughWalkthroughThis PR introduces comprehensive tax reporting functionality with client-side hooks and UI components for Schedule E and Form 1065 reports, a server-side tax reporting library implementing IRS calculation logic, new HTTP endpoints for report generation and export, and enhanced database seeding with membership structures. Changes
Sequence DiagramsequenceDiagram
actor User
participant Client as Client<br/>(React App)
participant Server as Server<br/>(Hono Routes)
participant Storage as Storage<br/>(Database)
participant TaxLib as Tax Library<br/>(Calculations)
User->>Client: Request Schedule E Report
Client->>Server: GET /api/reports/tax/schedule-e<br/>(taxYear, includeDescendants)
Server->>Storage: loadTaxData()<br/>- Get tenants (± descendants)<br/>- Get transactions (tax year)<br/>- Get properties
Storage-->>Server: tenants, transactions, properties
Server->>TaxLib: buildScheduleEReport()<br/>(transactions, properties, tenants)
TaxLib->>TaxLib: resolveScheduleELine()<br/>per transaction
TaxLib->>TaxLib: Aggregate by property<br/>& line item
TaxLib-->>Server: ScheduleEReport<br/>(per-property columns,<br/>entity-level totals)
Server-->>Client: JSON Report
Client->>User: Display Schedule E Table
User->>Client: Export as CSV
Client->>Server: POST /api/reports/tax/export<br/>(taxYear, format=csv)
Server->>TaxLib: buildScheduleEReport()<br/>+ buildForm1065Report()
TaxLib->>TaxLib: Calculate member<br/>allocations<br/>(time-weighted)
TaxLib-->>Server: scheduleE, form1065
Server->>TaxLib: serializeScheduleECsv()<br/>+ serializeForm1065Csv()
TaxLib-->>Server: CSV blob
Server->>Storage: ledgerLog()<br/>(audit trail)
Server-->>Client: File Download<br/>(tax-package-2025.csv)
Client->>User: Save File
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
chittyfinance | 7cc9c70 | Mar 28 2026, 08:37 PM |
There was a problem hiding this comment.
Pull request overview
Adds a new tax reporting pipeline to ChittyFinance, exposing Schedule E and Form 1065/K-1 reporting endpoints, a pure-function reporting engine + CSV export, and new UI tabs on the Reports page to view/export these outputs.
Changes:
- Introduces a pure tax reporting engine (
tax-reporting.ts) to build Schedule E, Form 1065/K-1 allocations, and CSV/combined “tax package” exports. - Adds protected API endpoints under
/api/reports/tax/*and wires them into the server app. - Extends the client Reports page with Schedule E + Form 1065/K-1 tabs and a tax package export action; seeds updated with year-specific member timelines.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| server/storage/system.ts | Adds a DB helper to fetch active properties for a tenant scope. |
| server/routes/tax.ts | New API routes to generate Schedule E, Form 1065, and export full tax package. |
| server/lib/tax-reporting.ts | Core tax reporting engine + CSV serializers + allocation logic. |
| server/app.ts | Mounts the new taxRoutes router. |
| server/tests/tax-reporting.test.ts | Unit + route integration tests for tax reporting and export. |
| database/seeds/it-can-be-llc.ts | Adds/updates tenant metadata for member ownership timelines (incl. members_2024). |
| client/src/pages/Reports.tsx | Adds Schedule E and Form 1065/K-1 tabs + export controls in the UI. |
| client/src/hooks/use-reports.ts | Adds tax report query types/hooks + export mutation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
PR Review posted via follow-up comment - see below for full review content |
|
test plain comment no hash lines |
|
updated body test |
uri: chittycanon://docs/ops/policy/chittyfinance-charter
|
| Type | Service | Purpose |
|---|---|---|
| Upstream | ChittyAuth | Token validation |
| Upstream | ChittyID | Identity (planned) |
| Peer | ChittyConnect | Mercury Bank proxy |
| Peer | ChittyChronicle | Audit logging (planned) |
| External | Mercury Bank | Banking integration |
| External | Wave Accounting | Accounting integration |
| External | Stripe | Payment processing |
| External | OpenAI | AI financial advice |
| External | GitHub | Project cost attribution |
| Storage | Neon PostgreSQL | Database |
API Contract
Base URL: https://finance.chitty.cc
Financial Data
| Endpoint | Method | Purpose |
|---|---|---|
/api/financial-summary |
GET | Financial summary |
/api/transactions |
GET | Transaction list |
/api/recurring-charges |
GET | Recurring charges |
/api/recurring-charges/:id/optimizations |
GET | AI optimization |
Integrations
| Endpoint | Method | Purpose |
|---|---|---|
/api/integrations/status |
GET | Integration config status |
/api/integrations/wave/authorize |
GET | Wave OAuth flow |
/api/integrations/stripe/connect |
POST | Stripe customer |
/api/integrations/stripe/webhook |
POST | Stripe webhooks |
/api/mercury/accounts |
GET | Mercury accounts |
AI Services
| Endpoint | Method | Purpose |
|---|---|---|
/api/ai/advice |
POST | Financial advice |
/api/ai/cost-reduction |
POST | Cost reduction plan |
/api/ai/message |
POST | Conversational AI |
Ownership
| Role | Owner |
|---|---|
| Service Owner | ChittyOS |
| Technical Lead | @chittyos-infrastructure |
| Contact | finance@chitty.cc |
Three Aspects (TY VY RY)
Source: chittycanon://gov/governance#three-aspects
| Aspect | Abbrev | Question | ChittyFinance Answer |
|---|---|---|---|
| Identity | TY | What IS it? | Full-stack financial management platform — intelligent tracking, AI-powered advice, recurring charge optimization for the IT CAN BE LLC entity structure |
| Connectivity | VY | How does it ACT? | Mercury Bank via ChittyConnect proxy; Wave Accounting OAuth 2.0 + GraphQL; Stripe payment processing + webhooks; OpenAI GPT-4o financial advice; dual-mode (standalone SQLite / system PostgreSQL multi-tenant) |
| Authority | RY | Where does it SIT? | Tier 3 Service — authoritative for financial data aggregation and AI advice; delegates identity to ChittyID, banking connectivity to ChittyConnect, audit logging to ChittyChronicle |
Document Triad
This charter is part of a synchronized documentation triad. Changes to shared fields must propagate.
| Field | Canonical Source | Also In |
|---|---|---|
| Canonical URI | CHARTER.md (Classification) | CHITTY.md (blockquote) |
| Tier | CHARTER.md (Classification) | CHITTY.md (blockquote) |
| Domain | CHARTER.md (Classification) | CHITTY.md (blockquote), CLAUDE.md (header) |
| Endpoints | CHARTER.md (API Contract) | CHITTY.md (Endpoints table), CLAUDE.md (API section) |
| Dependencies | CHARTER.md (Dependencies) | CHITTY.md (Dependencies table), CLAUDE.md (Architecture) |
| Certification badge | CHITTY.md (Certification) | CHARTER.md frontmatter status |
Related docs: CHITTY.md (badge/one-pager) | CLAUDE.md (developer guide)
Compliance
- Service registered in ChittyRegistry (did:chitty:REG-XE6835, 2026-02-22)
- Health endpoint operational at /health
- CLAUDE.md development guide present
- CHARTER.md present
- CHITTY.md present
- OAuth security (CSRF-protected state tokens)
- Webhook signature verification (Stripe)
- Multi-tenant data isolation
Charter Version: 2.0.0 | Last Updated: 2026-03-22
PR Review — Tax Reporting EngineGood feature addition overall. Clean separation of concerns (pure lib, Hono routes, typed hooks). The time-weighted K-1 logic is well-thought-out and the 22-test suite covers the core algorithms. A few issues worth addressing before merge. Bug — Duplicate member name produces duplicate K-1 rows In database/seeds/it-can-be-llc.ts, ARIBIA LLC members_2024 has two entries for Nicholas Bianchi (pct 90 through 2024-03-14, then pct 85 through 2024-10-28). In buildMemberAllocations in server/lib/tax-reporting.ts, the accumulator is keyed by m.name so the second entry silently overwrites the first. Then return members.map iterates all entries and calls memberTotals.get(m.name) — both entries resolve to the same accumulated value, producing two identical K-1 rows instead of one combined row. Fix: use a composite key (name+startDate) internally and merge by name at output, or deduplicate by grouping on memberName and summing. (tx as any) casts indicate missing type fields server/lib/tax-reporting.ts casts twice: (tx as any).propertyId and (tx as any).description. ReportingTransactionRow is missing these fields. The casts silently return undefined at runtime. Add the fields to the type or an extended interface so the compiler catches mismatches. sanitizeForm1065ForClient is a no-op K1MemberAllocation has no ein field — only MemberOwnership does. The function is a shallow copy that strips nothing. Either remove it and the misleading comment, or add an explicit destructure that drops ein when it gets added to the allocation type. Income double-counted in buildTaxPackage summary totalIncome = scheduleE.properties.reduce(...) + form1065.reduce(...). Schedule E is per-property and Form 1065 is per-entity — the same underlying transactions populate both. The summary double-counts them. Compute from a single pass over the raw transactions array, or flag the totals as estimates. URL.revokeObjectURL may fire before download starts In client/src/hooks/use-reports.ts: a.click() followed immediately by URL.revokeObjectURL(url). The safe pattern is setTimeout(() => URL.revokeObjectURL(url), 100). storage: any loses type safety in routes loadTaxData in server/routes/tax.ts takes storage: any. The three new methods (getTenantDescendantIds, getTenantsByIds, getTransactionsForTenantScope) are not checked at compile time. Type this as SystemStorage or add the methods to IStorage so missing methods fail at build time. Minor notes
What is solid
The duplicate-member bug must be fixed before production — it produces two identical K-1 entries for Nicholas Bianchi on ARIBIA LLC 2024 instead of one combined row. The rest can be follow-up issues if preferred. |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
client/src/pages/Reports.tsx (1)
27-29:⚠️ Potential issue | 🟠 MajorThe tax tabs default to an unsupported year on March 28, 2026.
taxYearstarts fromnew Date().getFullYear(), but the selector only exposes 2024 and 2025. In 2026 this renders the control with no matching option and immediately issues report/export requests for 2026. Initialize from the supported-year list, or derive the options from the same source, so the default and UI cannot drift.Suggested fix
const currentYear = new Date().getFullYear(); +const SUPPORTED_TAX_YEARS = [2024, 2025] as const; +const defaultTaxYear = SUPPORTED_TAX_YEARS.includes( + currentYear as (typeof SUPPORTED_TAX_YEARS)[number], +) + ? currentYear + : SUPPORTED_TAX_YEARS[SUPPORTED_TAX_YEARS.length - 1]; const defaultStart = `${currentYear}-01-01`; const defaultEnd = `${currentYear}-12-31`; @@ - const [taxYear, setTaxYear] = useState(currentYear); + const [taxYear, setTaxYear] = useState<number>(defaultTaxYear); @@ - <option value={2024}>2024</option> - <option value={2025}>2025</option> + {SUPPORTED_TAX_YEARS.map((year) => ( + <option key={year} value={year}> + {year} + </option> + ))}Also applies to: 300-300, 434-440
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@client/src/pages/Reports.tsx` around lines 27 - 29, The default date variables currentYear/defaultStart/defaultEnd are derived from new Date().getFullYear(), which can drift from the selector options (only 2024 and 2025); instead initialize the default taxYear and its defaultStart/defaultEnd from the canonical supported-years source used by the selector (e.g., the array or function that provides the options) so the default always matches an available option—replace new Date().getFullYear() usage with selecting the first/appropriate value from the supportedYears/options provider and build defaultStart/defaultEnd from that chosen tax year (update any references to taxYear, defaultStart, and defaultEnd accordingly).
🧹 Nitpick comments (1)
server/__tests__/tax-reporting.test.ts (1)
168-257: Please add a regression for repeated member names across dated ownership records.The 2024 seed metadata models the same member name across multiple periods, but this suite only exercises distinct names. A case with two
Nicholas Bianchirecords would catch duplicate K-1 rows, and a case with the Oct 15–28 90% window would catch silent renormalization.Possible test case
+ it('collapses repeated member names across periods', () => { + const members: MemberOwnership[] = [ + { name: 'Nicholas Bianchi', pct: 90, endDate: '2024-03-14' }, + { name: 'Nicholas Bianchi', pct: 85, startDate: '2024-03-15', endDate: '2024-10-28' }, + { name: 'Luisa Arias', pct: 10, endDate: '2024-10-14' }, + { name: 'Sharon E Jones', pct: 5, startDate: '2024-03-15', endDate: '2024-10-28' }, + { name: 'IT CAN BE LLC', pct: 100, startDate: '2024-10-29' }, + ]; + const result = buildMemberAllocations(2024, 1000, members); + expect(result.filter((m) => m.memberName === 'Nicholas Bianchi')).toHaveLength(1); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/__tests__/tax-reporting.test.ts` around lines 168 - 257, Add a regression test that covers repeated member names across dated ownership records to ensure buildMemberAllocations and buildForm1065Report correctly merge allocations by memberName instead of producing duplicate K-1 rows or silently renormalizing percentages: create a tenant with two MemberOwnership entries that share the same name (e.g., "Nicholas Bianchi") with different pct and different date ranges (one spanning Oct 15–28 window), call buildMemberAllocations for the tax year and assert the resulting array has a single entry for that memberName with totalAllocated equal to the sum of both periods and that periods are preserved/merged appropriately; also call buildForm1065Report with transactions to assert the member appears once in memberAllocations and totalAllocated matches expected (no unintended renormalization).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@client/src/hooks/use-reports.ts`:
- Around line 204-223: The useExportTaxPackage mutation currently calls fetch()
directly without tenant scoping; update it to either call the shared
apiRequest() helper (which applies withTenant()) instead of fetch, or call
useTenantId() inside useExportTaxPackage and append ?tenantId=${tenantId} to the
request QS (and include tenantId when building URLSearchParams) so the export
endpoint receives tenant context; update imports to bring in apiRequest or
useTenantId and replace the raw fetch call in useExportTaxPackage accordingly.
In `@database/seeds/it-can-be-llc.ts`:
- Around line 84-90: The members_2024 array currently leaves 2024-10-15 through
2024-10-28 unallocated (Nicholas Bianchi 85% + Sharon E Jones 5% = 90%); update
the members_2024 entries to ensure each day sums to 100% by either adding an
entry (e.g., owner with pct: 10 and startDate: '2024-10-15', endDate:
'2024-10-28') or by shifting the surrounding startDate/endDate boundaries
(adjust Nicholas Bianchi and/or IT CAN BE LLC dates) so no gap remains; update
the array entries for members_2024 accordingly so the allocator no longer
normalizes missing ownership for those dates.
In `@server/lib/tax-reporting.ts`:
- Around line 626-630: The package summary currently double-counts transactions
tagged with propertyId because buildScheduleEReport includes them and
buildForm1065Report also includes them when tenantId maps to a
holding/series/management entity; fix this by excluding overlapping
property-tagged partnership activity from one side before summing in
buildTaxPackage — e.g., when computing the form1065 aggregates (the
params.form1065.reduce for ordinaryIncome/totalDeductions) filter out records
that have a propertyId (or deduplicate by transaction id) so that
params.scheduleE.properties totals and params.form1065 totals do not both
include the same property-linked transactions.
- Around line 470-507: The duplicate K-1 rows happen because the final return
maps over the original members array (members) instead of the aggregated map
(memberTotals), so repeated member names produce repeated rows; fix by returning
results derived from memberTotals (e.g., iterate over memberTotals.entries() or
Array.from(memberTotals.values()) to build each row using entry and its key as
memberName) rather than mapping members, keeping use of round2,
entry.allocatedIncome and entry.periods as before.
In `@server/routes/tax.ts`:
- Around line 90-94: The current catch blocks collapse all errors into a 400
response with only { error }, so change the error handling in the Schedule E
report handlers (the catch blocks around the report-generation/storage code in
server/routes/tax.ts and the similar blocks referenced at 118-122 and 185-188)
to distinguish validation errors from internal failures and return a consistent
JSON error body. Concretely: detect validation-type errors (e.g., instances of
your request/input ValidationError or when tax year/params are invalid) and
respond with c.json({ type: 'validation', message: error.message, statusCode:
400 }, 400); for all other errors (storage, generation, unexpected exceptions)
log the full error server-side and respond with c.json({ type: 'internal_error',
message: 'Failed to generate Schedule E report' or a generic internal message,
statusCode: 500 }, 500); apply the same pattern to the other two catch blocks so
all route error responses include type, message, and statusCode and use
appropriate HTTP status codes.
In `@server/storage/system.ts`:
- Around line 652-667: The helper getPropertiesForTenants currently filters out
non-active properties (the eq(schema.properties.isActive, true) predicate),
which causes historical reports to miss property rows; remove that isActive
filter from the .where(...) clause so the query only filters by tenantId
(inArray(schema.properties.tenantId, tenantIds)) and returns all properties for
those tenants; keep the early-return for empty tenantIds and preserve the
selected columns (schema.properties.id, tenantId, name, address, city, state).
---
Outside diff comments:
In `@client/src/pages/Reports.tsx`:
- Around line 27-29: The default date variables
currentYear/defaultStart/defaultEnd are derived from new Date().getFullYear(),
which can drift from the selector options (only 2024 and 2025); instead
initialize the default taxYear and its defaultStart/defaultEnd from the
canonical supported-years source used by the selector (e.g., the array or
function that provides the options) so the default always matches an available
option—replace new Date().getFullYear() usage with selecting the
first/appropriate value from the supportedYears/options provider and build
defaultStart/defaultEnd from that chosen tax year (update any references to
taxYear, defaultStart, and defaultEnd accordingly).
---
Nitpick comments:
In `@server/__tests__/tax-reporting.test.ts`:
- Around line 168-257: Add a regression test that covers repeated member names
across dated ownership records to ensure buildMemberAllocations and
buildForm1065Report correctly merge allocations by memberName instead of
producing duplicate K-1 rows or silently renormalizing percentages: create a
tenant with two MemberOwnership entries that share the same name (e.g.,
"Nicholas Bianchi") with different pct and different date ranges (one spanning
Oct 15–28 window), call buildMemberAllocations for the tax year and assert the
resulting array has a single entry for that memberName with totalAllocated equal
to the sum of both periods and that periods are preserved/merged appropriately;
also call buildForm1065Report with transactions to assert the member appears
once in memberAllocations and totalAllocated matches expected (no unintended
renormalization).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 750fa56f-820a-4e73-adad-68ab06a4958b
📒 Files selected for processing (8)
client/src/hooks/use-reports.tsclient/src/pages/Reports.tsxdatabase/seeds/it-can-be-llc.tsserver/__tests__/tax-reporting.test.tsserver/app.tsserver/lib/tax-reporting.tsserver/routes/tax.tsserver/storage/system.ts
Summary
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests
Chores