InfraForge Docs

InfraNotes Accounting Engine · v0

Welcome

Select a document from the sidebar to read it.

Smart Engine — Frontend Explanation Contract (Release 2)

  • Status: Frozen for Release 2 accounting workspace MVP
  • Date: 2026-05-13
  • Audience: infranotes-front consumers of the accounting workspace (Accounting and Close > Ledger / Reconciliation / Period Close)
  • Related: infranotes-accounting-engine/docs/adr/0001-workload-identity.md (auth contract); INFT-76

The Smart Engine never speaks to the frontend directly — it is a backend-only gRPC service. The accounting stack (infranotes-core-ledger, infranotes-recon, infranotes-accounting-reports) consumes the engine's outputs and re-shapes them into HTTP read models that the FE renders. This doc names the engine fields the FE should surface, what they mean, and which downstream service exposes them.

Why the FE cares about engine explanations

Per INFRANOTES_PRIMAVERA_SURPASS_ROADMAP.md §11 (infranotes-accounting-engine role) and the cross-cutting recommendation §4 "Explainability and Auditability", the engine's differentiator is making automated decisions visible to accountants:

  • Why a posting was generatedClassifyResponse
  • Why a match succeeded or failedMatchResponse
  • Why an anomaly was flaggedAnomalyResponse
  • Why a close is blockedCloseReadinessResponse

Each of those proto responses carries one human-readable explanation string plus structured sidecar fields the FE composes with.

Explanation contract per RPC

1. ClassifyResponse — produced by classification (e.g. expense → account code)

Proto field Type Always present FE usage
status ClassificationStatus enum (CLASSIFIED, CLASSIFICATION_FAILED) yes Pill colour: success / "manual review"
account_code optional string when classified The account the engine chose
dimensions map<string, string> when classified Cost-centre / project / department dimensions; render as key/value chips
matched_rule_id optional string (UUID) when classified Used for "view rule" deep link to rule governance UI
matched_rule_version optional string when classified Show alongside rule name so reviewers see exactly what fired
explanation string always The primary human-readable line under the chosen account
confidence double 0.0..=1.0 always Render as a percentage badge with severity colour: green ≥0.85, amber ≥0.5, red <0.5
confidence_method string (rule_priority, specificity, feedback_weighted, match_gap) always Tooltip on the confidence badge so accountants understand the scoring method
evaluation_trace[] array of EvaluationTrace (rule_id, rule_name, rule_version, matched, condition_results, evaluation_time_ms) always "Why this rule, not the others" drill-down panel — show the matched rule first, then the runners-up
total_evaluation_time_ms uint64 always Diagnostic field; show only in advanced/debug mode
previous_account_code / previous_rule_id / previous_rule_version optional only in batch dry-run with include_diff "This used to classify to X" indicator in batch review UI
classification_changed bool always (false in non-diff mode) Highlight changed rows during batch re-classification reviews
diff_computed bool always (false in non-diff mode) Distinguishes "no prior evaluation to compare" from "compared and unchanged"

2. MatchResponse — produced by reconciliation matching

Proto field Type Always present FE usage
matched_pairs[] array of MatchedPair (source_item_ids, target_item_ids, match_type, score, max_score, matched_rule_id) yes Render as joined pairs in the auto-match queue with a score bar (score / max_score)
unmatched_sources[] array of NearMiss (item_id, optional closest_match_id, failure_reason, optional amount_difference, optional date_difference_days) yes Render in the "needs review" queue with the near-miss as a one-click resolve candidate
unmatched_targets[] array of NearMiss yes Same as unmatched sources, from the GL side
explanation string always Header text above the matching results table (e.g. "Matched 12 of 15 bank lines using rule 'Vendor reference v2.1'")

3. AnomalyResponse — produced by anomaly detection

Proto field Type Always present FE usage
anomalies[] array of Anomaly (rule_id, rule_name, severity, anomaly_type, target_type, target_id, details, context) yes (may be empty) One row per detected anomaly. severity drives the badge colour (INFO/WARNING/CRITICAL). context is a map of values that triggered the rule — render as key/value chips
has_blocking_anomalies bool always Drives the "Period not ready to close" banner
explanation string always Header text summarising the detection run (e.g. "3 anomalies detected; 1 critical")

The Anomaly.details field is a free-form string from the rule's action template — render as the primary description. The Anomaly.context map is the "evidence" pane (vendor name, amount, period total, etc.).

4. CloseReadinessResponse — produced by close-readiness evaluation

Proto field Type Always present FE usage
is_ready bool always The big traffic-light at the top of the period-close page
readiness_score int32 (0–100) always Score badge / progress ring
blocking_items[] array of BlockingItem (rule_id, rule_name, description, resolution_action) yes (may be empty) The "what's blocking close" checklist; resolution_action is the call-to-action text on the row's button
checklist_summary[] array of ChecklistItem (task_name, status, passed, optional failure_reason) yes The full close-readiness checklist; mark passed items green, failed items red with failure_reason
explanation string always Header summary above the checklist (e.g. "Ready to close — 12 of 12 tasks complete")

What the FE does NOT consume

These fields exist on engine RPCs but are not part of the FE handoff:

  • FeedbackRequest / FeedbackResponse — internal to the engine; rule-tuning analytics surface, not user-facing.
  • RuleVersionRequest / RuleVersionResponse — rule-governance UI concern, exposed via the rule-management surface (out of scope for the accounting workspace).
  • ValidateRulesRequest / ValidateRulesResponse — rule-authoring tool concern.
  • RuleStatisticsRequest / RuleStatisticsResponse — rule-tuning dashboard concern.

Which downstream service exposes each RPC's output

The FE does not call the engine directly. The mapping:

Engine RPC Consumed by FE surface area
ClassifyTransaction / ClassifyBatch infranotes-core-ledger (journal-posting pipeline) Accounting > Ledger > Journal Entries (classification panel on each line)
MatchReconciliation infranotes-recon (auto-match workbench) Accounting > Reconciliation > Auto-Match / Match Review
DetectAnomalies infranotes-core-ledger (anomaly flags on journal posting), infranotes-recon (close anomalies) Anomaly badges on journal lines; "Open anomalies" panel on the close-readiness page
EvaluateCloseReadiness infranotes-recon (close cockpit) Accounting > Period Close > Readiness
RecordAnomalyDismissal infranotes-recon (user clicks "dismiss" on a flagged anomaly) Anomaly review modal
RecordFeedback Internal — admin/tuning surfaces only

Each downstream service has its own read-model shape; the engine's explanation fields propagate through into those HTTP responses verbatim. The FE consumer should look at the downstream service's API contract (infranotes-core-ledger/docs/openapi/core-ledger-api.yaml, infranotes-recon/docs/openapi/recon-service-api.yaml) for the actual JSON field paths the FE binds against.

Internationalisation note

All explanation strings the engine emits are currently English-only. Per the platform localisation roadmap, country-pack work owns translating these in Release 4. For Release 2, the FE may display them as-is or wrap them in <EnglishOnly> markers per the front-end i18n convention. The structured sidecar fields (rule_name, severity, etc.) are programmatic identifiers and never translated.

Versioning

The explanation text is not a stable contract — wording may change without a proto bump. The FE must never === compare on it. The structured fields (enum values, field names, UUIDs) are the stable contract; any change there will be communicated via the engine's proto changelog and a coordinated downstream-service update.

Open follow-ups (handoff to FE lane)

  • Empty-state copy for each RPC's response — what to show when classification status is CLASSIFICATION_FAILED, when matched_pairs is empty, when anomalies is empty. Owned by Product Design.
  • Rule-detail deep links — the FE renders matched_rule_id and Anomaly.rule_id as link-outs; the target rule-governance UI is INFT-76 Phase 2 scope (rule authoring/testing surface), not built yet. Stub as a tooltip until then.
  • Confidence threshold UI — the engine has a LOW_CONFIDENCE_THRESHOLD env var; FE could surface a tenant-configurable threshold setting on the rule-tuning page once that surface exists.