Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
schema_id: ledgerloom.chart_of_accounts.v1
accounts:
# Assets
- code: Assets:Cash
name: Cash
type: asset
- code: Assets:AccountsReceivable
name: Accounts Receivable
type: asset
- code: Assets:Supplies
name: Supplies
type: asset
- code: Assets:Equipment
name: Equipment
type: asset

# Liabilities
- code: Liabilities:AccountsPayable
name: Accounts Payable
type: liability
- code: Liabilities:NotesPayable
name: Notes Payable
type: liability

# Equity
- code: Equity:OwnerCapital
name: Owner Capital
type: equity
- code: Equity:RetainedEarnings
name: Retained Earnings
type: equity
- code: Equity:Dividends
name: Owner Draws / Dividends
type: equity

# Revenue
- code: Revenue:ServiceRevenue
name: Service Revenue
type: revenue

# Expenses
- code: Expenses:Rent
name: Rent Expense
type: expense
- code: Expenses:Wages
name: Wages Expense
type: expense
- code: Expenses:Supplies
name: Supplies Expense
type: expense
- code: Expenses:Utilities
name: Utilities Expense
type: expense
- code: Expenses:Depreciation
name: Depreciation Expense
type: expense
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
entry_id,date,narration,account,debit,credit
A001,2026-01-31,Adjust supplies used,Expenses:Supplies,150.00,
A001,2026-01-31,Adjust supplies used,Assets:Supplies,,150.00
A002,2026-01-31,Record monthly depreciation,Expenses:Depreciation,50.00,
A002,2026-01-31,Record monthly depreciation,Assets:Equipment,,50.00
A003,2026-01-31,Accrue utilities expense,Expenses:Utilities,75.00,
A003,2026-01-31,Accrue utilities expense,Liabilities:AccountsPayable,,75.00
A004,2026-01-31,Accrue earned service revenue,Assets:AccountsReceivable,200.00,
A004,2026-01-31,Accrue earned service revenue,Revenue:ServiceRevenue,,200.00
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
entry_id,date,narration,account,debit,credit
T001,2026-01-02,Owner investment,Assets:Cash,10000,
T001,2026-01-02,Owner investment,Equity:OwnerCapital,,10000
T002,2026-01-03,Buy supplies (cash),Assets:Supplies,800,
T002,2026-01-03,Buy supplies (cash),Assets:Cash,,800
T003,2026-01-05,Buy equipment on account,Assets:Equipment,5000,
T003,2026-01-05,Buy equipment on account,Liabilities:AccountsPayable,,5000
T004,2026-01-10,Service revenue (cash),Assets:Cash,2500,
T004,2026-01-10,Service revenue (cash),Revenue:ServiceRevenue,,2500
T005,2026-01-12,Service revenue (on account),Assets:AccountsReceivable,1200,
T005,2026-01-12,Service revenue (on account),Revenue:ServiceRevenue,,1200
T006,2026-01-15,Pay rent,Expenses:Rent,600,
T006,2026-01-15,Pay rent,Assets:Cash,,600
T007,2026-01-20,Pay accounts payable,Liabilities:AccountsPayable,1000,
T007,2026-01-20,Pay accounts payable,Assets:Cash,,1000
T008,2026-01-25,Collect accounts receivable,Assets:Cash,700,
T008,2026-01-25,Collect accounts receivable,Assets:AccountsReceivable,,700
T009,2026-01-28,Owner withdrawal (dividends),Equity:Dividends,300.00,
T009,2026-01-28,Owner withdrawal (dividends),Assets:Cash,,300.00
20 changes: 20 additions & 0 deletions examples/workbook/ch04_closing_and_post_close/ledgerloom.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
schema_id: ledgerloom.project_config.v2

project:
name: Workbook Ch04 Closing and Post-Close
period: 2026-01
currency: USD

chart_of_accounts: config/chart_of_accounts.yaml
build_profile: workbook

sources:
- source_type: journal_entries.v1
name: Transactions
file_pattern: inputs/{period}/transactions.csv
entry_kind: transaction

- source_type: journal_entries.v1
name: Adjustments
file_pattern: inputs/{period}/adjustments.csv
entry_kind: adjustment
81 changes: 81 additions & 0 deletions tests/test_example_workbook_ch04_closing_and_post_close.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from __future__ import annotations

import hashlib
import os
import shutil
import subprocess
import sys
from pathlib import Path

import pandas as pd


def _sha256_file(path: Path) -> str:
h = hashlib.sha256()
h.update(path.read_bytes())
return h.hexdigest()


def _run(cmd: list[str], *, env: dict[str, str]) -> None:
res = subprocess.run(cmd, capture_output=True, text=True, env=env)
if res.returncode != 0:
raise AssertionError(
f"Command failed: {' '.join(cmd)}\n"
f"--- stdout ---\n{res.stdout}\n"
f"--- stderr ---\n{res.stderr}\n"
)


def test_example_workbook_ch04_closing_and_post_close_cli_is_runnable_and_deterministic(tmp_path: Path) -> None:
repo_root = Path(__file__).resolve().parents[1]
src = repo_root / "examples" / "workbook" / "ch04_closing_and_post_close"
assert src.exists(), "Expected examples/workbook/ch04_closing_and_post_close to exist in the repo"

project_root = tmp_path / "ch04_closing_and_post_close"
shutil.copytree(src, project_root)

# Ensure subprocess sees the src-layout package even if tests are running without an installed wheel.
env = dict(os.environ)
env["PYTHONPATH"] = str(repo_root / "src") + os.pathsep + env.get("PYTHONPATH", "")

# 1) check (gatekeeper) should be runnable via the CLI
_run([sys.executable, "-m", "ledgerloom", "check", "--project", str(project_root)], env=env)

# 2) build twice with different run IDs; manifests should be byte-identical.
for run_id in ("r1", "r2"):
_run(
[
sys.executable,
"-m",
"ledgerloom",
"build",
"--project",
str(project_root),
"--run-id",
run_id,
],
env=env,
)

run_root = project_root / "outputs" / run_id
assert (run_root / "artifacts" / "entries.csv").exists()
assert (run_root / "artifacts" / "trial_balance_unadjusted.csv").exists()
assert (run_root / "artifacts" / "trial_balance_adjusted.csv").exists()
assert (run_root / "artifacts" / "closing_entries.csv").exists()
assert (run_root / "artifacts" / "trial_balance_post_close.csv").exists()

# Post-close TB should be Balance-Sheet-only
tb_pc = pd.read_csv(run_root / "artifacts" / "trial_balance_post_close.csv", dtype=str)
roots = set(tb_pc["root"].tolist()) if not tb_pc.empty else set()
assert roots.issubset({"Assets", "Liabilities", "Equity"})

# Trust manifest exists and tracks artifacts
manifest_path = run_root / "trust" / "manifest.json"
assert manifest_path.exists()
manifest = manifest_path.read_text(encoding="utf-8")
assert "artifacts/entries.csv" in manifest
assert "artifacts/trial_balance_post_close.csv" in manifest

assert _sha256_file(project_root / "outputs" / "r1" / "trust" / "manifest.json") == _sha256_file(
project_root / "outputs" / "r2" / "trust" / "manifest.json"
)