---
title: "GenAI-Logic vs. Traditional Stack: A Worked Example"
subtitle: "Cascade Allocation + AI-Driven Matching System — Spring Boot Equivalent Estimate"
prepared_by: Claude Code
methodology: Estimate generated by Claude Code from stated per-unit formulas applied against the public schema in database/models.py. All formulas reproducible — see Part 1.
version: 1.0
date: 2026-06-13
status: Directional estimate for technical review. All formulas stated explicitly — reproducible against the public repo.
---

# GenAI-Logic vs. Traditional Stack: A Worked Example

*Cascade Allocation + AI-Driven Matching System — Spring Boot Equivalent Estimate*

---

## TL;DR

A complete, governed cost-allocation system — 13-table schema, two-level cascade allocation (Charge → Department → GL Account), AI-driven project matching, full JSON:API REST + admin UI — built with GenAI-Logic requires **527 lines of non-boilerplate code**.

The Spring Boot equivalent: **approximately 4,000 lines** for the backend alone, **~4,900 lines** including the admin UI.

That's **~8x** on the backend, **~9x** full stack.

**This is noticeably lower than the 21x / 27x measured for `demo_customs_surtax`** — and the reason is informative, not a weakness: **~235 of the 527 lines (45%) is an AI-integration handler** (OpenAI-based fuzzy project matching) that ports to Java at roughly **1:1**. It's real code either way. The platform's declarative-rule leverage is concentrated in the *other* ~292 lines, which includes a two-level cascade-allocation pattern that LogicBank expresses as 2 `Rule.allocate` declarations + 2 recipient-lookup functions — but which requires a ~280-line child-row-lifecycle/proration algorithm in hand-written Java.

---

## Executive Summary

This document provides a worked, reproducible estimate of what it would take to build the GenAI-Logic `allocate_dept_account_demo` system using a conventional Spring Boot stack. It is a companion to `demo_customs_surtax_traditional.md`, using the same formulas (Part 1) applied to a larger, more varied schema — explicitly to test whether the surtax demo's ratio generalizes.

**The system in question** implements departmental cost allocation: charges flow from a `Charge` to departments (`ChargeDeptAllocation`) based on project funding splits, then from departments to GL accounts (`ChargeGlAllocation`) based on departmental charge-definition splits — a two-level percent-based cascade. A separate AI-driven handler matches incoming charges to projects by fuzzy name lookup (Request Pattern, `SysProjectReq` audit trail).

It covers:

- 13 tables (10 domain + Contractor, Department, SysConfig)
- 16 rule declarations: 2 copy, 4 formula, 5 sum, **2 cascade `Rule.allocate`**, 3 events
- Two-level cascade allocation with automatic child-row lifecycle management
- AI-driven project matching (OpenAI fuzzy match, Request Pattern)
- Full REST API with filtering, sorting, pagination, and relationship navigation
- Generated admin application

**The GenAI-Logic baseline:** 527 lines (per the project governance report).

**The Spring Boot equivalent:** ~4,000 lines backend, ~4,900 lines full stack. All formulas stated below — challenge them.

---

## What the Numbers Mean

Before reading the detail, two framing notes — one general, one specific to this project.

**General (same as the surtax companion doc):** the Spring Boot side counts everything — entities, repositories, DTOs, mappers, controllers, filter/pagination/locking infrastructure, admin screens — because in a hand-built Spring project, your team owns all of it. The GenAI-Logic side counts only what your team writes.

**Specific to this project: not all 527 lines are the same kind of thing.**

| Component | LOC | Nature |
|---|---|---|
| `charge_distribution.py` + `definition_rules.py` + template growth | ~292 | Declarative rules (copy/formula/sum), 2-level cascade `Rule.allocate`, recipient-lookup functions, cross-table funding-check |
| `ai_requests/project_identification.py` | ~235 | OpenAI-based fuzzy project matching, registered as an `early_row_event` (Request Pattern) |

The 292-line portion is where LogicBank's dependency engine does the heavy lifting — particularly the cascade `Rule.allocate` declarations, each of which replaces a multi-path child-row create/update/delete pattern (see Part 2).

The 235-line portion is **integration code that exists regardless of stack** — calling an LLM, parsing the response, persisting an audit record. Porting it to Java/Spring AI doesn't get smaller; if anything it gets slightly larger (~280 lines, estimated) due to Java's HTTP/JSON/event-listener verbosity. This is not a place where declarative rules compress anything — it's a place where the platform's *Request Pattern* gives you a clean, auditable slot to put AI-driven logic, which is a different (and real) value proposition than LOC reduction.

**Net effect:** because nearly half of this project's "effective LOC" is in the second category, the overall ratio (~8-9x) is well below the surtax demo's 21-27x. That's expected, and it's the more representative number for projects that combine declarative business rules with AI-driven integration logic — which is most of what GenAI-Logic projects actually look like in practice.

---

## Schema Profile

*Source of truth: `database/models.py` in the public repo.*

| Table | Non-meta columns | Relationships |
|---|---|---|
| Contractor | 3 | 2 |
| Department | 3 | 4 |
| ProjectFundingDefinition | 5 | 2 |
| SysConfig | 5 | 0 |
| DeptChargeDefinition | 6 | 4 |
| GlAccount | 6 | 3 |
| Project | 5 | 3 |
| Charge | 8 | 3 |
| DeptChargeDefinitionLine | 5 | 3 |
| ProjectFundingLine | 6 | 4 |
| SysProjectReq | 8 | 2 |
| ChargeDeptAllocation | 7 | 5 |
| ChargeGlAllocation | 6 | 3 |
| **Total** | **73** | **38** |

Compared to `demo_customs_surtax` (6 tables / 56 cols / 10 rels): more than double the tables, ~30% more columns, and **3.8x the relationships** — this schema is dominated by junction/allocation tables with multiple FKs.

---

## Part 1 — Scaffold Layer

Same formulas as the surtax companion doc, applied to all 13 tables.

### Per-table formulas

| Layer | Formula | Rationale |
|---|---|---|
| JPA `@Entity` | `10 + 1×cols + 2×rels` | Lombok-annotated class, `@Column`/`@ManyToOne`/`@OneToMany` |
| `Repository` | `8 + 2×rels` | `JpaRepository<T,Long>` + navigation finder methods |
| DTOs (request + response) | `10 + 2×cols` | Java records for create/update + response shape |
| Mapper (MapStruct) | `6 + 1×cols` | Entity↔DTO field mapping |
| Service | `25 + 5×rels` | `@Service` CRUD + cascade handling |
| `@RestController` | `40 + 12×rels + 0.5×cols` | CRUD endpoints + pagination/sort + relationship endpoints |

### Per-table totals

| Table | Entity | Repo | DTOs | Mapper | Service | Controller | **Total** |
|---|---|---|---|---|---|---|---|
| Contractor | 17 | 12 | 16 | 9 | 35 | 66 | **155** |
| Department | 21 | 16 | 16 | 9 | 45 | 90 | **197** |
| ProjectFundingDefinition | 19 | 12 | 20 | 11 | 35 | 67 | **164** |
| SysConfig | 15 | 8 | 20 | 11 | 25 | 43 | **122** |
| DeptChargeDefinition | 24 | 16 | 22 | 12 | 45 | 91 | **210** |
| GlAccount | 22 | 14 | 22 | 12 | 40 | 79 | **189** |
| Project | 21 | 14 | 20 | 11 | 40 | 79 | **185** |
| Charge | 24 | 14 | 26 | 14 | 40 | 80 | **198** |
| DeptChargeDefinitionLine | 21 | 14 | 20 | 11 | 40 | 79 | **185** |
| ProjectFundingLine | 24 | 16 | 22 | 12 | 45 | 91 | **210** |
| SysProjectReq | 22 | 12 | 26 | 14 | 35 | 68 | **177** |
| ChargeDeptAllocation | 27 | 18 | 24 | 13 | 50 | 104 | **236** |
| ChargeGlAllocation | 22 | 14 | 22 | 12 | 40 | 79 | **189** |
| **Subtotal** | 279 | 180 | 276 | 151 | 515 | 1016 | **2,417** |

### Cross-cutting infrastructure

Same flat, platform-level items as the surtax estimate — these don't scale meaningfully with table count.

| Item | LOC | Rationale |
|---|---|---|
| Filter/`Specification` builder | 150 | Parse query params → JPA `Specification` predicates |
| Pagination/sort envelope + relationship resolution | 200 | Custom response wrapper to match JSON:API document shape |
| `@ControllerAdvice` exception handling | 75 | 404/400/409 mapping, validation error shape |
| OpenAPI/Swagger + CORS/security config | 65 | `springdoc-openapi` config, `WebMvcConfigurer` |
| App bootstrap | 40 | `application.yml` equivalents requiring Java config |
| **Subtotal** | **530** | |

**Scaffold total: 2,417 + 530 = ~2,950 LOC**

---

## Part 2 — Business Logic Layer

The 16 rule declarations (2 copy, 4 formula, 5 sum, 2 cascade allocate, 3 events), implemented as imperative Java.

| # | Rule (LogicBank) | Spring Boot equivalent | LOC |
|---|---|---|---|
| 1–2 | Copy `percent`: `ChargeDeptAllocation` ← `ProjectFundingLine`, `ChargeGlAllocation` ← `DeptChargeDefinitionLine` | Fetch recipient row, assign `percent` field | 20 |
| | **Copy subtotal (2 rules)** | | **20** |
| 3 | `ChargeDeptAllocation.amount = charge.amount × percent / 100` | `BigDecimal` multiply/divide + scale/rounding | 16 |
| 4 | `ChargeGlAllocation.amount = dept_alloc.amount × percent / 100` | Same | 16 |
| 5 | `DeptChargeDefinition.is_active = (total_percent == 100)` | Boolean comparison, recompute on save | 10 |
| 6 | `ProjectFundingDefinition.is_active = (total_percent == 100)` | Boolean comparison, recompute on save | 10 |
| | **Formula subtotal (4 rules)** | | **52** |
| 7–11 | 5× SUM rollups: `Charge.total_distributed_amount`, `Project.total_charges`, `GlAccount.total_allocated`, `DeptChargeDefinition.total_percent`, `ProjectFundingDefinition.total_percent` | Custom `@Query` aggregation + recalc-on-child-change wiring, ×5 | 100 |
| | **Sum subtotal (5 rules)** | | **100** |
| 12 | **Cascade Allocate, Level 1**: `Charge → ChargeDeptAllocation`, recipients = active `ProjectFundingLine` rows, percent-based proration | Diff existing `ChargeDeptAllocation` rows vs. current recipients; create/update/delete per recipient; percent-based `amount` with remainder/rounding so the sum equals `charge.amount` exactly; re-trigger on `Charge.amount` change *or* recipient-list change | 140 |
| 13 | **Cascade Allocate, Level 2**: `ChargeDeptAllocation → ChargeGlAllocation`, recipients = active `DeptChargeDefinitionLine` rows, same pattern one level deeper | Same algorithm, triggered by Level-1 output; must also re-run when `DeptChargeDefinitionLine` recipients change | 140 |
| | **Cascade Allocate subtotal (2 rules)** | | **280** |
| 14 | Event: `Charge` — must reference a project with an active funding definition (cross-table check, 2 lookups) | Validation method, 2 repository lookups, exception on failure | 30 |
| 15 | Event: register AI project-matching handler to run *before* Allocate fires (ordering) | Event-listener ordering / `@Order` annotation wiring | 20 |
| | **Event/ordering subtotal (2 rules)** | | **50** |
| 16 | **AI handler**: fuzzy-match unidentified `Charge` → `Project` via OpenAI (`early_row_event`, Request Pattern with `SysProjectReq` audit trail) — 235 LOC in Python | OpenAI/Spring AI client call, prompt construction, response parsing, `@PrePersist`/`EntityListener` wiring, `SysProjectReq` audit persistence — **near-1:1 port, not a declarative-rule reduction** | 280 |
| — | Cascade orchestration — wire the 2-level allocate + 5 sum/formula dependencies into ~6 write-path handlers (`Charge`, `ChargeDeptAllocation`, `ChargeGlAllocation`, `ProjectFundingLine`, `DeptChargeDefinitionLine`, plus downstream `Project`/`GlAccount`/`DeptChargeDefinition`/`ProjectFundingDefinition` recalcs) | ~45 LOC × 6 handlers — higher per-handler than the surtax demo's 30, because recipient-list changes require **diffing and re-creating child allocation rows**, not just recomputing a value | 280 |
| | **Logic layer total** | | **~1,060** |

### A note on correctness

The cascade `Rule.allocate` pattern doesn't just compute a value — it **owns the lifecycle of the child rows it creates**. When a `ProjectFundingLine` recipient is added, removed, or has its percent changed, LogicBank's allocate engine automatically creates, updates, or deletes the corresponding `ChargeDeptAllocation` rows — and cascades that change to Level 2 (`ChargeGlAllocation`) and the dependent sums, in the correct order.

A hand-built Java port of this is the kind of code where **dangling-row bugs** are easy to introduce and hard to catch in review: if a recipient is removed and the corresponding allocation row isn't deleted (or its Level-2 children aren't deleted), the sums (`Charge.total_distributed_amount`, `GlAccount.total_allocated`) silently drift from the source data. This is the lifecycle-management sibling of the "stale cross-table value" bug class documented in the surtax companion doc — same root cause (a multi-step cascade that the framework guarantees and a hand-written port has to remember), different symptom (orphaned rows vs. stale fields).

---

## Part 3 — Admin UI (Optional)

13 resources instead of 6 — the React-Admin equivalent scales accordingly.

| Item | Formula | LOC |
|---|---|---|
| Data provider (JSON:API-compatible) | flat | 120 |
| Per-resource List/Edit/Create/Show (×13 tables) | `50 + 1×cols` per table, Σcols=73 | 723 |
| App shell / routing / auth | flat | 80 |
| **Admin UI total** | | **~920** |

---

## Summary

| Scope | LOC | Ratio vs. 527 lines |
|---|---|---|
| **GenAI-Logic** (entire non-boilerplate codebase) | **527** | 1x |
| Spring Boot — scaffold only | ~2,950 | 5.6x |
| Spring Boot — business logic only | ~1,060 | 2.0x |
| **Spring Boot — backend total** | **~4,000** | **~8x** |
| Spring Boot — full stack (incl. admin UI) | ~4,900 | ~9x |

### Portfolio comparison

| | `demo_customs_surtax` | `allocate_dept_account_demo` |
|---|---|---|
| Tables | 6 | 13 |
| Effective LOC | 93 | 527 |
| Composition | 100% declarative rules (copy/formula/sum/constraint) | ~55% declarative rules + cascade allocate, ~45% AI-integration handler |
| Spring Boot backend | ~2,000 | ~4,000 |
| Backend ratio | **21x** | **~8x** |
| Spring Boot full stack | ~2,550 | ~4,900 |
| Full-stack ratio | **27x** | **~9x** |
| Standout construct | Live cross-table formula (rules #12/#14) | 2-level cascade `Rule.allocate` (rules #12/#13) |

**Reading this honestly:** the ratio is not a fixed constant — it depends on what fraction of a project's hand-written logic is "declarative rule that LogicBank compresses" vs. "integration code that exists no matter what" (AI calls, external APIs, file I/O). The surtax demo is close to a best case (100% compressible). This demo is closer to a realistic mix. **Both numbers are real and both are useful** — but a marketing claim that leads with "21-27x" without this context would not survive a technical reviewer building this project themselves.

---

## Verification

This is a directional estimate. All per-unit formulas are stated explicitly in Part 1 (identical to the surtax companion doc) so they can be re-derived, challenged, or adjusted against the actual schema.

The GenAI-Logic baseline (527 lines, 33 weighted rules / 10 domain tables, Coverage 3.3, Integrity 79) is verified against `allocate_dept_account_demo_governance_report.md` and can be confirmed by anyone who installs the open-source sample. The rules are in `logic/logic_discovery/charge_distribution.py`, `logic/logic_discovery/definition_rules.py`, and `logic/logic_discovery/ai_requests/project_identification.py`. Count them yourself.

---

*This estimate was generated by Claude Code, applying the same stated per-unit formulas used in `demo_customs_surtax_traditional.md` to the public schema in `database/models.py`. All formulas are in Part 1 — reproduce or challenge them against the actual repo. This is a directional estimate for evaluation purposes, not a software estimation deliverable.*
