Skip to content

Add a bulk tool for clearing the needs_review flag - #150

Closed
rlorenzo wants to merge 1 commit into
robcerda:mainfrom
rlorenzo:feat/bulk-mark-transactions-reviewed
Closed

rlorenzo wants to merge 1 commit into
robcerda:mainfrom
rlorenzo:feat/bulk-mark-transactions-reviewed

Conversation

@rlorenzo

Copy link
Copy Markdown

Why

mark_transaction_reviewed handles one transaction at a time. bulk_categorize_transactions can clear the flag, but it requires a category and rewrites it on every transaction, so reaching for it just to mark things reviewed risks flattening categorization worth keeping.

Both fan out to one request per transaction. On a real account that meant about 8,000 mutations.

What

Monarch has a bulk endpoint. The web client calls Common_BulkUpdateTransactionsMutation when you select transactions and change Review status, so a whole queue costs one round trip.

monarchmoneycommunity==1.5.2 does not wrap it, and none of its 23 mutations is a bulk transaction update. It does expose gql_call, so the library needs no fork.

bulk_mark_transactions_reviewed(transaction_ids, reviewed=True, dry_run=False)

Where the shape came from

Monarch disables introspection for non-admin users and masks GraphQL validation errors, so probing field names tells you nothing. The mutation document and the lowercase reviewStatus enum (needs_review / reviewed) came out of the deployed web bundle. Both directions then ran against the live API on three transactions:

needs_review -> {"success": true, "affectedCount": 3, "errors": null}
reviewed     -> {"success": true, "affectedCount": 3, "errors": null}

Safety

The tool hardcodes allSelected to False. Set it True and Monarch updates everything matching filters instead of the listed ids, which is the difference between marking three transactions and marking an entire account. A test pins it.

The tool sends expectedAffectedTransactionCount as the id count, and reports back Monarch's affectedCount rather than the number requested.

Monarch refuses a write inside an HTTP 200, and this mutation can return success: false with a null errors object. So the tool checks the payload success flag on its own rather than inferring it from absent errors.

read_only.MUTATING_TOOLS lists the tool, so MONARCH_MCP_READ_ONLY hides it.

Working down a review queue had no good option. mark_transaction_reviewed
handles one transaction, and bulk_categorize_transactions can clear the flag
but requires a category and rewrites it on every transaction, so using it to
mark things reviewed risks flattening categorization worth keeping. Both fan
out to one request per transaction, which does not scale to a queue of
thousands.

Monarch has a real bulk endpoint. The web client calls
Common_BulkUpdateTransactionsMutation when you select transactions and change
Review status, so a whole queue costs one round trip. The client library does
not wrap it, but it exposes gql_call, so no fork of the library is needed.

The mutation and the lowercase reviewStatus enum (needs_review / reviewed)
were read out of the deployed web bundle, then confirmed against the live API
in both directions on three transactions.

allSelected is hardcoded False. Set True, Monarch updates everything matching
filters rather than the listed ids, which is the difference between marking
three transactions and marking an entire account; a test pins it.

Monarch refuses a write inside an HTTP 200, and this mutation can report
success: false with a null errors object, so the payload success flag is
checked explicitly rather than inferring it from the absence of errors.
@rlorenzo

Copy link
Copy Markdown
Author

Closing in favor of #151, which generalizes the same endpoint.

Both PRs defined BULK_UPDATE_TRANSACTIONS_MUTATION from the identical GraphQL document, but at different points in transactions.py. Merging both produces no conflict and no test failure -- just the same 30-line document defined twice, with the second definition winning. Not worth leaving for you to untangle.

bulk_update_transactions(transaction_ids, needs_review=False) does what this tool did. If you would rather keep the narrower name as well, it is a five-line wrapper over the general tool and I will add it to #151 on request.

@rlorenzo rlorenzo closed this Sep 18, 2026
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