Add Phase 6 self improving ledger selection engine

This commit is contained in:
A R R R Associates
2026-08-22 13:30:38 +05:30
parent ba0d638dfb
commit 7e1b9c24ef
7 changed files with 1096 additions and 0 deletions
@@ -0,0 +1,229 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-6">
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">Tools · Accounting Intelligence</p>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Self-improving Ledger Selection</h1>
<p class="mt-1 text-sm text-slate-500">Phase 6 ranks accounting natures and existing client Tally ledgers from confirmed learning, Phase 5 history, business context and conservative deterministic rules. It never posts to Tally.</p>
</div>
<div class="flex flex-wrap gap-2">
<a href="/tools/accounting/historical-learning{% 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 text-slate-700 hover:bg-slate-50">Historical Learning</a>
<a href="/tools/accounting/taxonomy" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Accounting Taxonomy</a>
<a href="/tools/tally{% if selected_client %}?client_id={{ selected_client.id }}{% endif %}" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Back to Tally</a>
</div>
</div>
{% if reviewed %}
<div class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-800">Review saved. The confirmed choice has been added to this client's learning memory.</div>
{% endif %}
{% if error %}
<div class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">{{ error }}</div>
{% endif %}
<section class="rounded-2xl bg-white p-5 shadow-soft">
<form method="get" action="/tools/accounting/ledger-learning" class="grid gap-4 lg:grid-cols-6">
<label class="text-sm font-medium text-slate-700 lg:col-span-2">
ERP Client
<select name="client_id" required class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">
<option value="">Select client</option>
{% for client in clients %}
<option value="{{ client.id }}" {% if selected_client and selected_client.id == client.id %}selected{% endif %}>{{ client.client_name }}</option>
{% endfor %}
</select>
</label>
{% if selected_client %}
<label class="text-sm font-medium text-slate-700 lg:col-span-2">
Tally Company
<select name="tally_guid" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">
<option value="">All mapped companies</option>
{% for guid, company in tally_companies %}
<option value="{{ guid }}" {% if query.tally_guid == guid %}selected{% endif %}>{{ company or guid }} · {{ guid }}</option>
{% endfor %}
</select>
</label>
<div class="lg:col-span-2 flex items-end">
<button type="submit" class="w-full rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Load Client</button>
</div>
<label class="text-sm font-medium text-slate-700 lg:col-span-2">
Supplier / Party Name
<input name="supplier_name" value="{{ query.supplier_name }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="e.g. ABC Tyres Pvt Ltd" />
</label>
<label class="text-sm font-medium text-slate-700">
Supplier GSTIN
<input name="supplier_gstin" value="{{ query.supplier_gstin }}" maxlength="15" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm uppercase" />
</label>
<label class="text-sm font-medium text-slate-700">
HSN
<input name="hsn_code" value="{{ query.hsn_code }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
</label>
<label class="text-sm font-medium text-slate-700">
Amount
<input name="amount" value="{{ query.amount }}" inputmode="decimal" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" />
</label>
<div class="flex items-end">
<button type="submit" class="w-full rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Get Suggestions</button>
</div>
<label class="text-sm font-medium text-slate-700 lg:col-span-6">
Item / Invoice Description
<textarea name="description" rows="2" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="Product/service description, narration or invoice particulars">{{ query.description }}</textarea>
</label>
{% endif %}
</form>
</section>
{% if selected_client %}
<section class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-2xl bg-white p-4 shadow-soft">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Business Model</div>
<div class="mt-2 font-semibold text-slate-900">{{ profile.business_model|replace('_',' ')|title if profile and profile.business_model else 'Not profiled' }}</div>
</div>
<div class="rounded-2xl bg-white p-4 shadow-soft">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Industry</div>
<div class="mt-2 font-semibold text-slate-900">{{ profile.primary_industry if profile and profile.primary_industry else '-' }}</div>
</div>
<div class="rounded-2xl bg-white p-4 shadow-soft">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Profile Status</div>
<div class="mt-2 font-semibold {% if profile and profile.profile_status == 'confirmed' %}text-emerald-700{% else %}text-amber-700{% endif %}">{{ profile.profile_status|title if profile else 'Missing' }}</div>
</div>
<div class="rounded-2xl bg-white p-4 shadow-soft">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Learned Rules</div>
<div class="mt-2 text-2xl font-semibold text-slate-900">{{ learning_summary|length }}</div>
</div>
</section>
{% if suggestions %}
<section class="space-y-4">
<div>
<h2 class="text-lg font-semibold text-slate-900">Ranked Suggestions</h2>
<p class="mt-1 text-sm text-slate-500">Confirm the proposed treatment or correct it. The final human choice becomes the learning label.</p>
</div>
{% for item in suggestions %}
<article class="rounded-2xl bg-white p-5 shadow-soft">
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<div class="flex flex-wrap items-center gap-2">
<h3 class="text-lg font-semibold text-slate-900">{{ item.nature.name }}</h3>
<span class="rounded-full px-2.5 py-1 text-xs font-semibold {% if item.confidence >= 85 %}bg-emerald-50 text-emerald-700{% elif item.confidence >= 60 %}bg-blue-50 text-blue-700{% else %}bg-amber-50 text-amber-700{% endif %}">
{{ item.confidence }}% confidence
</span>
</div>
<p class="mt-1 text-xs text-slate-500">{{ item.nature.code }} · {{ item.nature.capital_revenue|title }}</p>
</div>
{% if item.suggested_ledger %}
<div class="rounded-xl bg-slate-50 px-3 py-2 text-sm">
<span class="text-slate-500">Suggested Tally ledger:</span>
<span class="ml-1 font-semibold text-slate-900">{{ item.suggested_ledger }}</span>
</div>
{% endif %}
</div>
<div class="mt-4 rounded-xl border border-slate-200 bg-slate-50 p-3">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Why</div>
<ul class="mt-2 space-y-1 text-sm text-slate-700">
{% for reason in item.reasons %}
<li>• {{ reason }}</li>
{% endfor %}
</ul>
</div>
<form method="post" action="/tools/accounting/ledger-learning/review" class="mt-4 grid gap-3 lg:grid-cols-5">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="client_id" value="{{ selected_client.id }}" />
<input type="hidden" name="tally_guid" value="{{ query.tally_guid }}" />
<input type="hidden" name="supplier_name" value="{{ query.supplier_name }}" />
<input type="hidden" name="supplier_gstin" value="{{ query.supplier_gstin }}" />
<input type="hidden" name="hsn_code" value="{{ query.hsn_code }}" />
<input type="hidden" name="description" value="{{ query.description }}" />
<input type="hidden" name="amount" value="{{ query.amount }}" />
<input type="hidden" name="suggested_nature_id" value="{{ item.nature.id }}" />
<input type="hidden" name="suggested_ledger_name" value="{{ item.suggested_ledger }}" />
<input type="hidden" name="suggested_confidence" value="{{ item.confidence }}" />
<input type="hidden" name="explanation" value="{{ item.reasons|join('||') }}" />
<label class="text-sm font-medium text-slate-700 lg:col-span-2">
Final Accounting Nature
<select name="final_nature_id" required class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">
{% for nature in natures %}
<option value="{{ nature.id }}" {% if nature.id == item.nature.id %}selected{% endif %}>{{ nature.name }}</option>
{% endfor %}
</select>
</label>
<label class="text-sm font-medium text-slate-700 lg:col-span-2">
Final Tally Ledger
<select name="final_ledger_name" class="mt-1 w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">
<option value="">No ledger selected yet</option>
{% for mapping in mappings %}
<option value="{{ mapping.ledger_name }}" {% if mapping.ledger_name == item.suggested_ledger %}selected{% endif %}>{{ mapping.ledger_name }}</option>
{% endfor %}
</select>
<span class="mt-1 block text-xs font-normal text-slate-500">Only Phase 5 mapped client ledgers are offered. Create/change mappings from Historical Learning.</span>
</label>
<div class="flex items-end">
<button type="submit" class="w-full rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white hover:bg-slate-800">Confirm / Correct</button>
</div>
</form>
</article>
{% endfor %}
</section>
{% elif query.supplier_name or query.supplier_gstin or query.hsn_code or query.description %}
<div class="rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">No usable suggestion was produced. Map historical ledgers in Phase 5 or manually select a review-required nature after adding more transaction context.</div>
{% endif %}
<section class="rounded-2xl bg-white p-5 shadow-soft">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h2 class="font-semibold text-slate-900">Learning Memory</h2>
<p class="mt-1 text-sm text-slate-500">Client-specific rules learned from reviewed selections. A correction lowers confidence in the rejected treatment and strengthens the confirmed one.</p>
</div>
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600">Top {{ learning_summary|length }}</span>
</div>
<div class="mt-4 overflow-x-auto">
<table class="min-w-full text-sm">
<thead><tr class="border-b border-slate-200 text-left text-xs uppercase tracking-wide text-slate-500"><th class="px-2 py-2">Rule</th><th class="px-2 py-2">Value</th><th class="px-2 py-2">Nature</th><th class="px-2 py-2">Ledger</th><th class="px-2 py-2 text-right">Confirmed</th><th class="px-2 py-2 text-right">Corrected</th><th class="px-2 py-2 text-right">Confidence</th></tr></thead>
<tbody>
{% for item in learning_summary %}
<tr class="border-b border-slate-100">
<td class="px-2 py-2 font-medium text-slate-700">{{ item.rule.rule_type|replace('_',' ')|title }}</td>
<td class="px-2 py-2 text-slate-600">{{ item.rule.display_value }}</td>
<td class="px-2 py-2 text-slate-900">{{ item.nature.name }}</td>
<td class="px-2 py-2 text-slate-600">{{ item.rule.ledger_name or '-' }}</td>
<td class="px-2 py-2 text-right">{{ item.rule.confirmation_count }}</td>
<td class="px-2 py-2 text-right">{{ item.rule.rejection_count }}</td>
<td class="px-2 py-2 text-right font-semibold">{{ item.rule.confidence_percent }}%</td>
</tr>
{% else %}
<tr><td colspan="7" class="px-2 py-8 text-center text-slate-500">No reviewed selections yet.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
<section class="rounded-2xl bg-white p-5 shadow-soft">
<h2 class="font-semibold text-slate-900">Recent Review Feedback</h2>
<div class="mt-4 overflow-x-auto">
<table class="min-w-full text-sm">
<thead><tr class="border-b border-slate-200 text-left text-xs uppercase tracking-wide text-slate-500"><th class="px-2 py-2">When</th><th class="px-2 py-2">Supplier</th><th class="px-2 py-2">Accepted</th><th class="px-2 py-2">Suggested</th><th class="px-2 py-2">Final Ledger</th></tr></thead>
<tbody>
{% for event in events %}
<tr class="border-b border-slate-100">
<td class="px-2 py-2 text-slate-600">{{ event.created_at_utc }}</td>
<td class="px-2 py-2 text-slate-900">{{ event.supplier_name or event.supplier_gstin or '-' }}</td>
<td class="px-2 py-2"><span class="rounded-full px-2 py-1 text-xs font-semibold {% if event.accepted_suggestion %}bg-emerald-50 text-emerald-700{% else %}bg-amber-50 text-amber-700{% endif %}">{{ 'Yes' if event.accepted_suggestion else 'Corrected' }}</span></td>
<td class="px-2 py-2 text-slate-600">{{ event.suggested_ledger_name or '-' }} · {{ event.suggested_confidence }}%</td>
<td class="px-2 py-2 font-medium text-slate-900">{{ event.final_ledger_name or '-' }}</td>
</tr>
{% else %}
<tr><td colspan="5" class="px-2 py-8 text-center text-slate-500">No review feedback yet.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endif %}
</div>
{% endblock %}
@@ -15,6 +15,7 @@
<div class="flex flex-wrap gap-2">
<a href="/tools/accounting/taxonomy" class="rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Accounting Taxonomy</a>
{% if selected_client %}<a href="/tools/accounting/historical-learning?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">Historical Learning</a>{% endif %}
{% if selected_client %}<a href="/tools/accounting/ledger-learning?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">Ledger Learning</a>{% endif %}
{% 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>