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
1 change: 0 additions & 1 deletion .claude/worktrees/elegant-johnson-846da4
Submodule elegant-johnson-846da4 deleted from 67eea2
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ dmypy.json
# AI - Claude Code
.claude/settings.local.json
.claude/scheduled_tasks.lock
.claude/worktrees/
CLAUDE.local.md

.gemini/
Expand Down
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"christian-kohler.npm-intellisense",
"christian-kohler.path-intellisense",
"dbaeumer.vscode-eslint",
"detachhead.basedpyright",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"fill-labs.dependi",
Expand All @@ -14,7 +15,6 @@
"kddejong.vscode-cfn-lint",
"ms-azuretools.vscode-containers",
"ms-python.python",
"ms-python.vscode-pylance",
"nefrob.vscode-just-syntax",
"rvest.vs-code-prettier-eslint",
"vitest.explorer",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ See RoboSystems in action with runnable demos that create graphs, load data, and

```bash
just demo-sec # Loads NVIDIA's SEC XBRL data via Dagster pipeline
just demo-close # Entity accounting month close demo
just demo-roboledger # End-to-end RoboLedger demo: bulk OLTP, schedules, FY 2025 filed report, AI close
just demo-custom-graph # Builds custom graph schema with relationship networks
```

Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ just start
just demo

# Or run individual demos
just demo-close
just demo-roboledger
just demo-custom-graph
just demo-sec NVDA 2025
```
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AI Month-End Close Demo
# RoboLedger End-to-End Demo

Demonstrates the full AI-assisted month-end close workflow using synthetic data for a boutique consulting firm (Cascade Advisory Group LLC).
Demonstrates the full RoboLedger workflow — bulk OLTP import, taxonomy & schedule blocks, fiscal calendar, a filed FY 2025 annual report, and an AI-driven month-end close using synthetic data for a boutique consulting firm (Cascade Advisory Group LLC).

Data is generated on a **rolling 16-month window ending at the current month**, so the demo always covers "recent history" no matter when it's run. The OLTP load path is the same `OLTPLoader` the QuickBooks pipeline uses in production — synthetic data is written to a DuckDB file in the exact shape dbt produces, then handed off to the loader.

Expand All @@ -13,13 +13,13 @@ The demo also initializes a **fiscal calendar** with `closed_through = month_bef
just start

# Run the demo setup (creates graph, loads data, creates schedules, uploads policies)
uv run python -m examples.close_demo.main
uv run python -m examples.roboledger_demo.main

# Or load into an existing graph
uv run python -m examples.close_demo.main <graph_id>
uv run python -m examples.roboledger_demo.main <graph_id>

# Dry run (validate data only)
uv run python -m examples.close_demo.main --dry-run
uv run python -m examples.roboledger_demo.main --dry-run
```

## What Gets Created
Expand All @@ -36,6 +36,7 @@ uv run python -m examples.close_demo.main --dry-run
| **Schedules** | 6 | 2 depreciation + 4 prepaid amortization schedules (staggered renewals) |
| **Schedule Facts** | mixed | Historical (pre-target) vs in_scope (target onward) — close workflow only acts on in_scope |
| **Documents** | 4 | Close procedures, depreciation policy, prepaid policy, revenue policy |
| **FY 2025 Report** | 1 | Annual report — generated, packaged, and **filed** as a Plan C capstone (Report Block lifecycle end-to-end). The current period stays queued for the AI close workflow. |

## The Company

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Demo-only reset logic for close_demo.
"""Demo-only reset logic for roboledger_demo.

This is NOT a production operation. It selectively wipes demo-generated
state while preserving graph infrastructure (entity, library-seeded
Expand Down Expand Up @@ -50,19 +50,35 @@ def reset_demo_state(graph_id: str) -> None:

with extensions_session(graph_id) as session:
# 1-3. All entries + line items + transactions (tenant-generated;
# the library itself never authors these).
# the library itself never authors these). Dimension tables
# FK back to their parent rows so wipe them first.
session.execute(text("DELETE FROM line_item_dimensions"))
session.execute(text("DELETE FROM line_items"))
session.execute(text("DELETE FROM entry_dimensions"))
session.execute(text("DELETE FROM entries"))
session.execute(text("DELETE FROM transaction_dimensions"))
session.execute(text("DELETE FROM transactions"))

# 3b. Events + their dimensions. Events power journal_entry_recorded
# / asset_disposed / schedule_entry_due — none library-seeded. Must
# delete event_dimensions first (FK), then events (which referenced
# entries above via triggered_by_event_id), then agents (events.agent_id).
session.execute(text("DELETE FROM event_dimensions"))
session.execute(text("DELETE FROM events"))
session.execute(text("DELETE FROM agents"))
session.execute(text("DELETE FROM dimensions"))

# 4. Tenant-origin associations. The library's immutability triggers
# raise on any UPDATE/DELETE of rows with created_by='library-seeder',
# so we filter those out explicitly. CoA mapping arcs created by the
# demo (created_by='coa-classifier' or the demo user) are fair game.
# association_classifications FK associations → wipe first.
session.execute(text("DELETE FROM association_classifications"))
session.execute(
text("DELETE FROM associations WHERE created_by != :seeder"),
{"seeder": _LIBRARY_SEEDER},
)
session.execute(text("DELETE FROM classifications"))

# 5. Facts
session.execute(text("DELETE FROM facts"))
Expand Down Expand Up @@ -107,20 +123,27 @@ def reset_demo_state(graph_id: str) -> None:
),
{"seeder": _LIBRARY_SEEDER},
)
session.execute(
text(
"DELETE FROM fact_sets WHERE structure_id IN "
"(SELECT id FROM structures WHERE created_by != :seeder)"
),
{"seeder": _LIBRARY_SEEDER},
)
# 6c.iii. Reports + dependents. Reports are not library-seeded, so
# wipe everything. report_shares.report_id and publish_list_members
# FK to their parents — delete children first. fact_sets.report_id
# FKs reports, so all fact_sets must go before reports.
session.execute(text("DELETE FROM report_shares"))
session.execute(text("DELETE FROM publish_list_members"))
session.execute(text("DELETE FROM publish_lists"))
# All fact_sets — the structure-id filter above only catches tenant-
# owned structures, but report fact_sets attach to library structures
# (e.g. fac default). Safe to wipe all: no fact_sets are library-seeded.
session.execute(text("DELETE FROM fact_sets"))
session.execute(text("DELETE FROM reports"))

# 7. Tenant-origin structures (any the demo created — anchor
# structure, schedules, report layouts).
session.execute(
text("DELETE FROM structures WHERE created_by != :seeder"),
{"seeder": _LIBRARY_SEEDER},
)
session.execute(text("DELETE FROM structure_templates"))
session.execute(text("DELETE FROM event_handlers"))

# 8. Entity taxonomy linkages (will be re-created by demo setup).
session.query(EntityTaxonomy).delete(synchronize_session=False)
Expand Down
File renamed without changes.
114 changes: 104 additions & 10 deletions examples/close_demo/main.py → examples/roboledger_demo/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#!/usr/bin/env python3
"""Cascade Advisory Group LLC — AI Month-End Close Demo
"""Cascade Advisory Group LLC — RoboLedger End-to-End Demo

Sets up a complete demo environment with synthetic consulting company data,
CoA→GAAP mappings, depreciation/prepaid schedules, and accounting policy
documents. After running, use Claude Desktop or MCP tools to simulate
a month-end close.
CoA→GAAP mappings, depreciation/prepaid schedules, accounting policy
documents, and a filed FY 2025 annual report. After running, use Claude
Desktop or MCP tools to simulate the close workflow on the queued period.

Data is generated for a rolling 16-month window ending at the current month,
so the demo stays evergreen. OLTP load goes through the same `OLTPLoader`
Expand All @@ -21,10 +21,10 @@
access in `_reset.py` — this is intentionally NOT a product operation.

Usage:
uv run python -m examples.close_demo.main # Create new graph + load
uv run python -m examples.close_demo.main <graph_id> # Load into existing graph
uv run python -m examples.close_demo.main --dry-run # Validate data only
uv run python -m examples.close_demo.main --ai # Use MappingAgent instead of hardcoded mappings (requires Bedrock)
uv run python -m examples.roboledger_demo.main # Create new graph + load
uv run python -m examples.roboledger_demo.main <graph_id> # Load into existing graph
uv run python -m examples.roboledger_demo.main --dry-run # Validate data only
uv run python -m examples.roboledger_demo.main --ai # Use MappingAgent instead of hardcoded mappings (requires Bedrock)

Requires: Docker stack running (just start)
"""
Expand Down Expand Up @@ -507,7 +507,7 @@ def initialize_fiscal_calendar(graph_id: str) -> str:
graph_id,
closed_through=closed_through,
earliest_data_period=demo_start_period,
note="close_demo initialization",
note="roboledger_demo initialization",
)

fc = result.get("fiscal_calendar", {})
Expand Down Expand Up @@ -673,6 +673,93 @@ def materialize_graph(graph_id: str) -> None:
print(f" WARNING: Materialization failed: {result.error or result.message}")


# ---------------------------------------------------------------------------
# Step 7: Generate + file FY 2025 annual report (Plan C capstone)
# ---------------------------------------------------------------------------


def generate_fy2025_report(graph_id: str) -> str | None:
"""Create a published, filed FY 2025 annual report.

Exercises the Report Block lifecycle end-to-end: ``create-report`` →
``get-report-package`` → ``file-report``. The result is a frozen,
filed snapshot of the prior year visible at ``/reports/{id}`` in the
package viewer, alongside the queued-for-close current period.

Returns the report_id (or None on failure) so the caller can print
the viewer URL.
"""
# NOTE: ``LedgerClient.create_report`` discards the synchronous result
# (it returns only operation_id + status). The server runs create-report
# synchronously and inlines the report row in the envelope's ``result``,
# so we bypass the wrapper and call the generated API directly to get
# the report_id back. Drop this once the SDK helper is fixed to return
# the full envelope.
from robosystems_client.api.extensions_robo_ledger.op_create_report import (
sync_detailed as api_create_report,
)
from robosystems_client.models import CreateReportRequest

client = _get_ledger_client()

# Find the coa_mapping structure created during the taxonomy seed
structures = client.list_structures(graph_id, structure_type="coa_mapping")
if not structures:
print(" ERROR: No coa_mapping structure — was the CoA created?")
return None
mapping_id = structures[0]["id"]

# Find the FAC presentation taxonomy. This is where the proper
# income_statement / cash_flow_statement structures live (with
# associations to FAC elements). The bare ``fac v1`` reporting_standard
# only has a default placeholder structure with no associations, which
# is why the report would otherwise have zero rendered statements.
taxonomies = client.list_taxonomies(graph_id, taxonomy_type="mapping")
fac_pres = next(
(t for t in taxonomies if t.get("name", "").startswith("fac-presentation")),
None,
)
if not fac_pres:
print(" ERROR: No fac-presentation taxonomy seeded on this graph")
return None
taxonomy_id = fac_pres["id"]

body = CreateReportRequest(
name="FY 2025 Annual Report",
mapping_id=mapping_id,
taxonomy_id=taxonomy_id,
period_start=date(2025, 1, 1),
period_end=date(2025, 12, 31),
period_type="annual",
comparative=False,
)
response = api_create_report(graph_id=graph_id, body=body, client=client._get_client())
envelope = client._call_op("Create report", response)
payload = envelope.result if isinstance(envelope.result, dict) else {}
report_id = payload.get("id") or payload.get("report_id")
if not report_id:
print(f" WARNING: No report_id in result: {payload}")
return None
print(f" Generated: {report_id}")

# Pull the package to confirm it rehydrates
package = client.get_report_package(graph_id, report_id)
if package:
items = package.get("items", []) or []
print(
f" Package: {len(items)} block(s) — {', '.join(i.get('block_type', '?') for i in items)}"
)

# File it — flips filing_status draft → filed
try:
client.file_report(graph_id, report_id)
print(" Filed: ✓")
except Exception as e:
print(f" WARNING: file_report failed: {e}")

return report_id


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
Expand All @@ -692,7 +779,7 @@ def main() -> None:

# Validate data
txns = get_all_transactions()
print(f"\n{COMPANY_NAME} — Close Demo Setup")
print(f"\n{COMPANY_NAME} — RoboLedger Demo Setup")
print("=" * 60)
print(f" Accounts: {len(ACCOUNTS)}")
print(f" Transactions: {len(txns)}")
Expand Down Expand Up @@ -776,6 +863,10 @@ def main() -> None:
print("\nMaterializing to graph...")
materialize_graph(graph_id)

# Generate + file FY 2025 annual report (Plan C capstone)
print("\nGenerating FY 2025 annual report...")
fy2025_report_id = generate_fy2025_report(graph_id)

# Summary
print("\n" + "=" * 60)
print(f" Graph ID: {graph_id}")
Expand All @@ -789,6 +880,9 @@ def main() -> None:

print("\n Ready for AI close workflow!")
print(f"\n Close target: {close_target} ({close_label})")
if fy2025_report_id:
print(f" FY 2025: filed ({fy2025_report_id})")
print(f" Viewer URL: /reports/{fy2025_report_id}?graph={graph_id}")
print("\n Next steps:")
print(" 1. Open Claude Desktop (or MCP client)")
print(f" 2. Switch to workspace: {graph_id}")
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ def write_demo_duckdb(
Args:
duckdb_path: Destination .duckdb file (overwritten if it exists).
accounts: List of (code, name, classification, sub_classification,
balance_type, parent_code) tuples from close_demo.data.ACCOUNTS.
balance_type, parent_code) tuples from roboledger_demo.data.ACCOUNTS.
transactions: List of (date, type, description, merchant, lines) tuples
from close_demo.data.get_all_transactions(), where `lines` is
from roboledger_demo.data.get_all_transactions(), where `lines` is
[(account_code, debit_cents, credit_cents), ...].

Returns:
Expand Down Expand Up @@ -270,7 +270,7 @@ def _insert_line_items(con, rows: list[tuple]) -> None:

def default_duckdb_path() -> Path:
"""Return the default working path for the demo DuckDB file."""
return Path("/tmp/close_demo/close_demo.duckdb")
return Path("/tmp/roboledger_demo/roboledger_demo.duckdb")


if __name__ == "__main__":
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Information Block validation — Step 7 of the close demo.
"""Information Block validation — Step 7 of the roboledger demo.

Runs after schedules are created to verify:
- FactSet rows exist for every schedule
Expand All @@ -9,7 +9,7 @@

Can also be run standalone against any graph:

uv run python -m examples.close_demo.validate <graph_id>
uv run python -m examples.roboledger_demo.validate <graph_id>
"""

from __future__ import annotations
Expand Down Expand Up @@ -481,7 +481,7 @@ def _main() -> None:
else:
graph_id = creds.get("graphs", {}).get("cascade_demo", "")
if not graph_id:
print("Usage: uv run python -m examples.close_demo.validate <graph_id>")
print("Usage: uv run python -m examples.roboledger_demo.validate <graph_id>")
sys.exit(1)

print(f"{_BOLD}Information Block Validation{_RESET} graph={graph_id}")
Expand Down
8 changes: 4 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ migrate-reset db="platform" env=_local_env:

# Run all demos
demo:
@just demo-close
@just demo-roboledger
@just demo-custom-graph
@just demo-sec

Expand All @@ -351,9 +351,9 @@ demo-sec-subscribe plan="sec-starter":
demo-sec-query *args:
uv run examples/sec_demo/query_examples.py {{ args }}

# Run AI close demo — sets up synthetic consulting company with schedules, mappings, and policies
demo-close *args="":
EXTENSIONS_ENABLED=true UV_ENV_FILE={{_local_env}} uv run python -m examples.close_demo.main {{args}}
# Run RoboLedger end-to-end demo — synthetic consulting company, schedules, mappings, policies, FY 2025 filed report, and a queued period for AI close
demo-roboledger *args="":
EXTENSIONS_ENABLED=true UV_ENV_FILE={{_local_env}} uv run python -m examples.roboledger_demo.main {{args}}

# Run custom graph demo end-to-end (flags: new-user,new-graph,skip-queries)
demo-custom-graph flags="new-graph" real_s3="false" base_url="http://localhost:8000":
Expand Down
Loading
Loading