Skip to content

feat: Enhance reporting and scheduling with rule evaluation improvements#635

Merged
jfrench9 merged 3 commits into
mainfrom
feature/schedule-integrity
Apr 23, 2026
Merged

feat: Enhance reporting and scheduling with rule evaluation improvements#635
jfrench9 merged 3 commits into
mainfrom
feature/schedule-integrity

Conversation

@jfrench9
Copy link
Copy Markdown
Member

Summary

This PR introduces significant enhancements to the reporting and scheduling functionalities across the platform, including expanded rule evaluation capabilities, new schedule management APIs, and comprehensive report command improvements.

Key Accomplishments

Reporting Enhancements

  • Substantially expanded the report commands module (+238 lines), adding new reporting capabilities and workflows within the roboledger subsystem
  • Added new fields/properties to the reports API model to support richer report data
  • Exposed a new ledger GraphQL type field to support downstream reporting queries

Scheduling Improvements

  • Extended the schedules API model (+70 lines) with new scheduling constructs and data structures
  • Built out new schedule command operations (+161 lines) for managing schedule lifecycle
  • Enhanced the schedule service layer (+73 lines) with additional business logic and integrity checks, aligning with the branch goal of schedule integrity

Rule Engine & Evaluation

  • Improved the information block rule engine (+55 lines) with expanded evaluation logic
  • Added new rule evaluators (+30 lines) to support additional rule types or conditions
  • Minor refinement to the rule model definition

API & Routing

  • Added new router endpoints (+24 lines) for roboledger operations, exposing the new scheduling and reporting functionality via the API surface

Infrastructure & Migrations

  • Minor migration adjustment in the taxonomy library version file

Breaking Changes

  • The migration file 0002_taxonomy_library.py has been modified — ensure that environments already past this migration are assessed for compatibility or that the change is backward-compatible (e.g., a cosmetic/comment fix).
  • New fields on API models and GraphQL types may require schema-aware clients to update their queries or integrations.

Testing

  • New test suite added for report commands (test_reports.py, +190 lines) providing comprehensive coverage of the new reporting logic
  • Extended existing tests for the schedule service (test_service.py, +37 lines) to cover the new scheduling integrity features
  • All new and modified business logic paths in the rule engine, evaluators, schedules, and reports should be validated through both unit tests and integration testing against a representative dataset

Infrastructure Considerations

  • Database migrations should be run in staging before production deployment to validate the taxonomy library migration change
  • New API endpoints should be reviewed for proper authentication/authorization middleware coverage
  • GraphQL schema changes should be validated against any code-generated client SDKs to prevent desynchronization
  • The expanded schedule service logic may warrant monitoring for performance characteristics under load, particularly if schedule integrity checks involve cross-record validation

🤖 Generated with Claude Code

Branch Info:

  • Source: feature/schedule-integrity
  • Target: main
  • Type: feature

Co-Authored-By: Claude noreply@anthropic.com

- Updated README to reflect changes in PostgreSQL usage for IAM and extensions.
- Added 'SumEquals' rule pattern to taxonomy library and rule evaluation logic.
- Introduced rule summary in report and schedule responses to aggregate rule evaluation results.
- Implemented disposal of schedules with a new DisposeScheduleRequest and response model.
- Enhanced ScheduleService to create FactSet rows and auto-generate SumEquals rules during schedule creation.
- Added tests for report command helpers and schedule service to ensure correct FactSet handling and rule evaluations.
@jfrench9
Copy link
Copy Markdown
Member Author

@claude please review this PR

@claude
Copy link
Copy Markdown

claude Bot commented Apr 23, 2026

Claude finished @jfrench9's task in 3m 40s —— View job


PR Review — feat: Enhance reporting and scheduling with rule evaluation improvements

  • Gather context and understand request
  • Read changed files
  • Analyze code for bugs, security, performance, and style issues
  • Post comprehensive review feedback

This is a solid, well-structured PR. The architecture is consistent with the existing CQRS/OperationSpec patterns, the new tests are comprehensive and well-organized, and the accounting logic (dispose, truncate, manual entries) is careful about invariants. A few issues worth fixing before merge are listed below.


Bugs

1. Dead computation in dispose_schedule — result discarded

robosystems/operations/roboledger/commands/schedules.py:385

round(original_amount / 100.0, 2)   # ← result is computed but never assigned
net_book_value = original_amount - accumulated_depreciation

The round(...) call computes a dollar-denominated value but immediately discards it. original_amount, accumulated_depreciation, and net_book_value are all used in subsequent arithmetic and in the response as cents, so the conversion has no effect. This is dead code that suggests a unit-of-measure confusion — was this meant to convert or validate something? At minimum it should be removed; if it was meant to be a dollars display value, it needs to be assigned.

Fix this →


2. update_schedule and delete_schedule never commit

robosystems/operations/roboledger/commands/schedules.py:315 and :523

Every other command in this file — create_schedule, truncate_schedule, create_closing_entry, create_manual_closing_entry, dispose_schedule — calls session.commit() before returning. update_schedule ends with session.flush() only, and delete_schedule ends with session.flush() only. If the callers (via the IB dispatch path) don't commit, changes will be silently dropped.

If the intent is that execute_operation / the OperationSpec wrapper owns the commit for these two, the other commands' explicit session.commit() calls contradict that (and would produce harmless but surprising double-commits). The pattern should be consistent throughout the file.

Fix this →


Logic Issue

3. SumEquals sum includes historical facts — triggers false failures on truncated schedules

robosystems/operations/information_block/rules/engine.py:172-180

row = session.execute(
    text(
        "SELECT ROUND(SUM(value)::numeric, 2) AS total "
        "FROM facts "
        "WHERE element_id = :eid AND structure_id = :sid AND period_type = 'duration'"
    ),
    {"eid": element_id, "sid": structure_id},
).fetchone()

_bind_sum_variables sums all duration facts regardless of fact_scope. When a schedule is created with closed_through or later truncated, historical facts remain in the DB. The auto-generated SumEquals rule's expected_total equals original_amount (the full depreciation), but after truncation the remaining facts sum to less than that — the check will fail even though the schedule is intentionally shortened.

Consider either filtering to fact_scope = 'in_scope' and adjusting the expected total at truncation time, or skipping/marking-stale the SumEquals rule when the schedule is truncated.

Fix this →


Security / Observability

4. Internal exception details leaked to clients in _share_to_target

robosystems/operations/roboledger/commands/reports.py:601-605

except Exception as e:
    return ShareResultItem(
        target_graph_id=target_graph_id,
        status="error",
        error=f"Failed to validate target graph: {e!s}",   # ← raw exception string
    )

The inner platform_session block returns str(e) directly to the caller with no logging. This could expose internal connection details, SQL error messages, or stack information. The second except block (line ~659) at least calls logger.error first; this first block silently discards the log. It should log the full exception and return a sanitized message.

Fix this →


Minor Issues

5. _rule_summary defined twice

robosystems/operations/roboledger/commands/reports.py:87 and commands/schedules.py:45

Identical helper. Should live in a shared location (e.g., a _utils.py in commands/) and be imported by both.

6. Router module docstring is stale

robosystems/routers/extensions/roboledger/operations.py:13

The header comment still says Registered (41) and omits dispose-schedule and evaluate-rules from the schedule section list. Should be updated to 43.

7. _bind_variables could receive None element IDs for schedules

robosystems/operations/information_block/rules/engine.py:215-216

element_ids = {a.from_element_id for a in associations} | {a.to_element_id for a in associations}

If any association has from_element_id = None (valid for the anchor pattern used by schedule associations), None enters element_ids and is passed to load_rules_for_structure. This is unlikely to cause visible failures since the IN-clause handles NULLs gracefully, but it's worth filtering: {x for x in (...) if x is not None}.


What's Good

  • Migration sync: Adding SumEquals to both rule.py (model CHECK constraint) and 0002_taxonomy_library.py (DDL) is correct and consistent — they were in sync before, and they're in sync now. The migration comment explains the change clearly.
  • truncate_schedule month-end enforcement: Requiring new_end_date to be the last day of a month with a helpful error message is exactly the right UX for full-month schedule facts.
  • Idempotent create_closing_entry: The five-outcome model (created/unchanged/regenerated/removed/skipped) is well-designed and the test coverage mirrors it directly.
  • _assert_period_not_closed guard: Preventing manual entry creation in closed periods is the right integrity check, and the "no-op if no fiscal period row" fallback is a thoughtful way to handle fresh tenants.
  • Test quality: Both test files are well-structured, cover edge cases (leap year Feb 29, boundary closed_through, rounding drift over 84 months), and use clear mock sequences.
  • dispose_schedule: The atomicity of truncate + disposal entry in one call is a good UX choice that avoids partial state.
  • GraphQL rule_summary as JSON: Correct workaround for Strawberry's inability to auto-derive dict[str, int] — and the in-code comment explains why.

@jfrench9
Copy link
Copy Markdown
Member Author

@claude have we fixed all issues?

@claude
Copy link
Copy Markdown

claude Bot commented Apr 23, 2026

Claude finished @jfrench9's task in 1m 35s —— View job


Issue Status Check — PR #635


3 of 7 issues are fixed. 4 remain.


✅ Fixed

Bug #1 — Dead computation in dispose_schedule
The dead round(original_amount / 100.0, 2) expression is gone. The code now correctly converts dollars to cents via accumulated_depreciation = round(accumulated_depreciation_dollars * 100). Clean.

Minor #5_rule_summary defined twice
Both reports.py and schedules.py now import rule_summary from _guards.py (from robosystems.operations.roboledger.commands._guards import rule_summary as _rule_summary). The _guards.py module is the single canonical definition. ✅

Minor #6 — Router docstring stale
The header comment now reads **Registered (43):** and the Schedules section includes dispose-schedule and evaluate-rules. ✅


❌ Not Fixed

Bug #2update_schedule and delete_schedule never commit
robosystems/operations/roboledger/commands/schedules.py:308 and :524

Both functions still end with session.flush() only — no session.commit(). Every other command in the file (create_schedule, truncate_schedule, create_closing_entry, create_manual_closing_entry, dispose_schedule) calls session.commit() before returning. Changes written by update_schedule and delete_schedule will be silently dropped unless the caller commits elsewhere.

Fix this →


Logic #3SumEquals still sums all facts including historical ones
robosystems/operations/information_block/rules/engine.py:172-179

_bind_sum_variables still queries without a fact_scope filter:

SELECT ROUND(SUM(value)::numeric, 2) AS total
FROM facts
WHERE element_id = :eid AND structure_id = :sid AND period_type = 'duration'

This will still fail for truncated schedules — the sum of remaining facts won't equal original_amount. Note: dispose_schedule was updated to delete the SumEquals rule after disposal (good!), but truncate_schedule does not do the same. So a truncated-but-not-disposed schedule will produce a permanent false SumEquals failure.

The fix is either:

  1. Add AND fact_scope = 'in_scope' to the query and update expected_total at truncation time, or
  2. Have truncate_schedule also delete the native SumEquals rule (matching what dispose_schedule now does)

Fix this →


Security #4 — Raw exception leaked in _share_to_target
robosystems/operations/roboledger/commands/reports.py:594-599

The first except Exception as e block (inside the platform_session try) still returns raw exception details to the caller with no logging:

except Exception as e:
    return ShareResultItem(
        target_graph_id=target_graph_id,
        status="error",
        error=f"Failed to validate target graph: {e!s}",  # ← raw str(e), no log
    )

The second except block at line 651 correctly calls logger.error(...) first. This first block still does not. Internal DB connection strings, SQL errors, or stack info could be surfaced to API consumers.

Fix this →


Minor #7 — _bind_variables still accepts None element IDs
robosystems/operations/information_block/rules/engine.py:215-217

Still no None-filtering:

element_ids = {a.from_element_id for a in associations} | {
    a.to_element_id for a in associations
}

If any association has a null from_element_id or to_element_id (valid for anchor-pattern schedule associations), None enters the set and gets passed to load_rules_for_structure. Low-severity but easy to fix:

element_ids = {
    x for x in (
        {a.from_element_id for a in associations} |
        {a.to_element_id for a in associations}
    )
    if x is not None
}

Fix this →


for gid, sid in self._cleanup:
try:
client.delete_schedule(gid, sid)
except Exception:
dr, cr = et.get("debit_element_id", ""), et.get("credit_element_id", "")
if dr and cr:
return dr, cr
except Exception:
@jfrench9 jfrench9 merged commit fb747e4 into main Apr 23, 2026
13 checks passed
@jfrench9 jfrench9 deleted the feature/schedule-integrity branch April 23, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant