Add Phase 13 internal accounting model

This commit is contained in:
A R R R Associates
2026-08-22 21:52:44 +05:30
parent ef2b0e1f83
commit 0c0457aaca
15 changed files with 1171 additions and 3 deletions
@@ -7,7 +7,7 @@
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Server AI Semantic Layer</h1>
<p class="mt-1 max-w-4xl text-sm text-slate-500">AI is a fallback only. Confirmed mappings, historical evidence and deterministic rules remain primary. AI can select only an existing Accounting Nature; it cannot create ledgers or post to Tally.</p>
</div>
<a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold">Back to Tally</a>
<div class="flex gap-2"><a href="/tools/accounting/internal-model" class="rounded-xl border border-emerald-300 bg-emerald-50 px-4 py-2 text-sm font-semibold text-emerald-800">Internal Model</a><a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold">Back to Tally</a></div>
</div>
<section class="rounded-2xl bg-white p-5 shadow-soft">
@@ -29,6 +29,12 @@
<div class="xl:col-span-3"><div class="text-xs uppercase text-slate-500">Suggestion</div><div class="mt-1 font-semibold">{{ tx.suggested_voucher_type }}</div><div class="text-sm">{{ tx.auto_party or '—' }}</div><div class="text-sm">{{ tx.suggested_ledger_name or tx.analyzer_ledger or 'Review ledger' }}</div><div class="text-xs text-slate-500">Confidence {{ tx.suggested_confidence }}%</div></div>
<div class="xl:col-span-6">
{% if tx.review_status != 'reviewed' %}
{% if not tx.contra_pair_id %}
<form method="post" action="/tools/accounting/bank-posting/{{ tx.id }}/internal-predict" class="mb-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}">
<button class="w-full rounded-lg border border-emerald-300 bg-emerald-50 px-3 py-2 text-sm font-semibold text-emerald-800">Internal Model · Shadow Predict</button>
</form>
{% endif %}
{% if not tx.contra_pair_id and tx.suggested_confidence < 90 %}
<form method="post" action="/tools/accounting/bank-posting/{{ tx.id }}/ai-assist" class="mb-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}">
@@ -0,0 +1,82 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-6">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">Accounting Intelligence · Phase 13</p>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">ARRR Internal Accounting Model</h1>
<p class="mt-1 max-w-4xl text-sm text-slate-500">Tenant-specific model trained only from accountant-reviewed outcomes. No external AI API is required. New models start in shadow mode and never post to Tally.</p>
</div>
<a href="/tools/accounting/ai" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold">Phase 12 AI</a>
</div>
{% if message %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800">{{ message }}</div>{% endif %}
{% if error %}<div class="rounded-xl border border-red-200 bg-red-50 p-3 text-sm text-red-800">{{ error }}</div>{% endif %}
<section class="rounded-2xl bg-white p-5 shadow-soft">
<div class="grid gap-4 md:grid-cols-4">
<div><div class="text-xs uppercase text-slate-500">Runtime</div><div class="mt-1 font-semibold">{{ 'Internal model enabled' if summary.enabled else 'Shadow only' }}</div></div>
<div><div class="text-xs uppercase text-slate-500">Apply threshold</div><div class="mt-1 font-semibold">{{ '%.0f'|format(summary.minimum_apply_probability * 100) }}%</div></div>
<div><div class="text-xs uppercase text-slate-500">Shadow predictions</div><div class="mt-1 font-semibold">{{ summary.predictions }}</div></div>
<div><div class="text-xs uppercase text-slate-500">Reviewed live accuracy</div><div class="mt-1 font-semibold">{{ summary.live_accuracy }}%</div></div>
</div>
<p class="mt-3 text-xs text-slate-500">To allow an active model to update suggestions after shadow validation, set ACCOUNTING_INTERNAL_MODEL_ENABLED=true on the ERP server. Even then it changes only the suggested Accounting Nature; ledger mapping and human review remain unchanged.</p>
</section>
<section class="rounded-2xl bg-white p-5 shadow-soft">
<div class="flex flex-wrap gap-3">
<form method="post" action="/tools/accounting/internal-model/train">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Train New Model from Reviewed Data</button>
</form>
<form method="post" action="/tools/accounting/internal-model/deactivate">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold">Return All Models to Shadow</button>
</form>
</div>
</section>
<section class="rounded-2xl bg-white shadow-soft">
<div class="border-b border-slate-200 px-5 py-4"><h2 class="font-semibold">Model versions</h2></div>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead><tr class="border-b bg-slate-50 text-left text-xs uppercase text-slate-500"><th class="px-3 py-3">Version</th><th class="px-3 py-3">Training</th><th class="px-3 py-3">Classes</th><th class="px-3 py-3">Validation</th><th class="px-3 py-3">Top-2</th><th class="px-3 py-3">Status</th><th class="px-3 py-3"></th></tr></thead>
<tbody>
{% for m in summary.models %}
<tr class="border-b border-slate-100">
<td class="px-3 py-3"><div class="font-semibold">{{ m.version_label }}</div><div class="text-xs text-slate-500">{{ m.algorithm }}</div></td>
<td class="px-3 py-3">{{ m.training_examples }} train / {{ m.validation_examples }} validation</td>
<td class="px-3 py-3">{{ m.class_count }} · {{ m.vocabulary_size }} features</td>
<td class="px-3 py-3">{{ '%.1f'|format(m.validation_accuracy * 100) }}%</td>
<td class="px-3 py-3">{{ '%.1f'|format(m.validation_top2_accuracy * 100) }}%</td>
<td class="px-3 py-3">{{ 'Active' if m.is_active else 'Shadow' }}</td>
<td class="px-3 py-3">{% if not m.is_active %}<form method="post" action="/tools/accounting/internal-model/{{ m.id }}/activate"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><button class="rounded-lg border border-indigo-300 bg-indigo-50 px-3 py-1 text-xs font-semibold text-indigo-800">Mark Active</button></form>{% endif %}</td>
</tr>
{% else %}<tr><td colspan="7" class="px-4 py-10 text-center text-slate-500">No internal model trained yet.</td></tr>{% endfor %}
</tbody>
</table>
</div>
</section>
<section class="rounded-2xl bg-white shadow-soft">
<div class="border-b border-slate-200 px-5 py-4"><h2 class="font-semibold">Recent shadow/live predictions</h2><p class="text-sm text-slate-500">{{ summary.reviewed_predictions }} reviewed · {{ summary.correct_predictions }} correct · {{ summary.applied_predictions }} applied to suggestions</p></div>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead><tr class="border-b bg-slate-50 text-left text-xs uppercase text-slate-500"><th class="px-3 py-3">Source</th><th class="px-3 py-3">Model</th><th class="px-3 py-3">Nature ID</th><th class="px-3 py-3">Probability</th><th class="px-3 py-3">Mode</th><th class="px-3 py-3">Outcome</th></tr></thead>
<tbody>
{% for p in predictions %}
<tr class="border-b border-slate-100">
<td class="px-3 py-3">{{ p.source_type }} #{{ p.source_record_id }}</td>
<td class="px-3 py-3">#{{ p.model_id }}</td>
<td class="px-3 py-3">{{ p.predicted_nature_id }}</td>
<td class="px-3 py-3">{{ '%.1f'|format(p.predicted_probability * 100) }}%</td>
<td class="px-3 py-3">{{ 'Shadow' if p.shadow_mode else 'Live suggestion' }}</td>
<td class="px-3 py-3">{% if p.prediction_correct is sameas true %}<span class="text-emerald-700">Correct</span>{% elif p.prediction_correct is sameas false %}<span class="text-amber-700">Corrected by reviewer</span>{% else %}Awaiting review{% endif %}</td>
</tr>
{% else %}<tr><td colspan="6" class="px-4 py-10 text-center text-slate-500">No model predictions yet.</td></tr>{% endfor %}
</tbody>
</table>
</div>
</section>
</div>
{% endblock %}
@@ -35,6 +35,12 @@
<td class="px-3 py-4 min-w-[250px]">{% if suggested %}<div class="flex gap-2"><span class="font-semibold">{{ suggested.name }}</span><span class="rounded-full px-2 py-1 text-xs font-semibold {% if row.suggested_confidence>=85 %}bg-emerald-50 text-emerald-700{% elif row.suggested_confidence>=60 %}bg-blue-50 text-blue-700{% else %}bg-amber-50 text-amber-700{% endif %}">{{ row.suggested_confidence }}%</span></div><div class="text-sm text-slate-600">{{ row.suggested_ledger_name or 'No mapped Tally ledger' }}</div>{% if explanations.get(row.id) %}<details class="mt-2"><summary class="cursor-pointer text-xs font-semibold text-brand-600">Why?</summary><ul class="mt-1 text-xs text-slate-500">{% for x in explanations.get(row.id)[:5] %}<li>• {{ x }}</li>{% endfor %}</ul></details>{% endif %}{% else %}<span class="rounded-full bg-amber-50 px-2 py-1 text-xs font-semibold text-amber-700">Review required</span>{% endif %}</td>
<td class="px-3 py-4 min-w-[220px]">{% if row.review_status=='reviewed' %}<div class="rounded-xl border border-emerald-200 bg-emerald-50 p-3"><div class="text-xs font-semibold uppercase text-emerald-700">Reviewed</div><div class="font-semibold">{{ final.name if final else '-' }}</div><div class="text-sm text-slate-600">{{ row.final_ledger_name or 'No ledger' }}</div></div>{% else %}<span class="text-xs text-slate-500">Awaiting review</span>{% endif %}</td>
<td class="px-3 py-4 min-w-[330px]">
{% if row.review_status!='reviewed' %}
<form method="post" action="/tools/accounting/purchase-review/purchase/{{ row.id }}/internal-predict" class="mb-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}">{% for k in ['tally_guid','status','confidence','source','supplier','return_period','page','per_page'] %}<input type="hidden" name="{{ k }}" value="{{ filters[k] }}">{% endfor %}
<button class="w-full rounded-lg border border-emerald-300 bg-emerald-50 px-3 py-2 text-sm font-semibold text-emerald-800">Internal Model · Shadow Predict</button>
</form>
{% endif %}
{% if row.review_status!='reviewed' and row.suggested_confidence < 90 %}
<form method="post" action="/tools/accounting/purchase-review/purchase/{{ row.id }}/ai-assist" class="mb-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}">{% for k in ['tally_guid','status','confidence','source','supplier','return_period','page','per_page'] %}<input type="hidden" name="{{ k }}" value="{{ filters[k] }}">{% endfor %}
@@ -22,6 +22,7 @@
{% if selected_client %}<a href="/tools/accounting/purchase-posting?client_id={{ selected_client.id }}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Post to Tally</a>{% endif %}
{% if selected_client %}<a href="/tools/accounting/bank-posting?client_id={{ selected_client.id }}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Bank → Tally</a>{% endif %}
<a href="/tools/accounting/ai{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-xl border border-indigo-300 bg-indigo-50 px-4 py-2 text-sm font-semibold text-indigo-800 hover:bg-indigo-100">Accounting AI</a>
<a href="/tools/accounting/internal-model" class="rounded-xl border border-emerald-300 bg-emerald-50 px-4 py-2 text-sm font-semibold text-emerald-800 hover:bg-emerald-100">Internal Model</a>
{% if selected_client %}<a href="/tools/tally/depreciation?client_id={{ selected_client.id }}" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Depreciation (IT)</a>{% endif %}
<a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}&refresh=1{% else %}?refresh=1{% endif %}" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white shadow-soft hover:bg-brand-700">Refresh Tally Companies</a>
</div>