Add Phase 5 historical Tally learning foundation

This commit is contained in:
A R R R Associates
2026-08-22 13:01:17 +05:30
parent 45fedd0d96
commit a9a5effcd0
15 changed files with 546 additions and 6 deletions
@@ -1,2 +1 @@
__version__ = "1.11.0"
AGENT_NAME = "ERP Local Agent"
__version__ = "1.12.0"
@@ -1077,6 +1077,54 @@ class LocalAccountingStore:
).fetchall()
return [dict(row) for row in rows]
def historical_learning_evidence(self, client_id: int, *, tally_guid: str, date_from: str = "", date_to: str = "", voucher_scope: str = "purchase") -> dict:
"""Return neutral aggregated historical ledger facts from the local .act store.
No accounting classification is performed locally. The ERP server maps
these facts to its canonical taxonomy.
"""
if not self.exists(client_id):
raise ValueError("Accounting storage is not initialized for this client.")
guid = str(tally_guid or "").strip()
if not guid:
raise ValueError("Tally company GUID is required.")
mapping = self.get_active_mapping_by_guid(client_id, guid)
params: list = [guid]
where = ["v.tally_guid=?", "TRIM(COALESCE(v.party_ledger_name,''))<>''", "LOWER(TRIM(COALESCE(v.is_cancelled,''))) NOT IN ('yes','true','1')", "LOWER(TRIM(e.ledger_name))<>LOWER(TRIM(v.party_ledger_name))"]
if str(voucher_scope or "purchase").strip().lower() == "purchase":
where.append("LOWER(v.voucher_type_name) LIKE '%purchase%'")
if date_from:
where.append("v.voucher_date>=?"); params.append(str(date_from))
if date_to:
where.append("v.voucher_date<=?"); params.append(str(date_to))
sql = f"""
SELECT v.party_ledger_name AS party_ledger_name,
COALESCE(pl.parent,'') AS party_parent_group,
e.ledger_name AS counter_ledger_name,
COALESCE(cl.parent,'') AS counter_parent_group,
v.voucher_type_name AS voucher_type_name,
COUNT(DISTINCT v.id) AS voucher_count,
COALESCE(SUM(ABS(e.amount)),0) AS absolute_amount_total,
MIN(NULLIF(v.voucher_date,'')) AS first_voucher_date,
MAX(NULLIF(v.voucher_date,'')) AS last_voucher_date,
MIN(NULLIF(v.narration,'')) AS sample_narration
FROM tally_vouchers v
JOIN tally_voucher_ledger_entries e ON e.voucher_id=v.id
LEFT JOIN tally_ledgers pl ON pl.tally_guid=v.tally_guid AND LOWER(TRIM(pl.name))=LOWER(TRIM(v.party_ledger_name))
LEFT JOIN tally_ledgers cl ON cl.tally_guid=v.tally_guid AND LOWER(TRIM(cl.name))=LOWER(TRIM(e.ledger_name))
WHERE {' AND '.join(where)}
GROUP BY v.party_ledger_name, COALESCE(pl.parent,''), e.ledger_name, COALESCE(cl.parent,''), v.voucher_type_name
ORDER BY voucher_count DESC, v.party_ledger_name COLLATE NOCASE, e.ledger_name COLLATE NOCASE
LIMIT 5000
"""
with self.connect(client_id) as db:
rows = [dict(r) for r in db.execute(sql, tuple(params)).fetchall()]
return {
"client_id": int(client_id), "tally_guid": guid, "company_name": str(mapping.get("company_name") or ""),
"date_from": str(date_from or ""), "date_to": str(date_to or ""), "voucher_scope": str(voucher_scope or "purchase"),
"evidence": rows, "evidence_count": len(rows),
}
def snapshot(self, client_id: int) -> dict:
path = self.db_path(client_id)
if not path.is_file():
@@ -36,6 +36,8 @@ class AgentCommandProcessor:
result = self._sync_masters(payload)
elif action == "accounting_sync_transactions":
result = self._sync_transactions(payload)
elif action == "accounting_historical_evidence":
result = self._historical_evidence(payload)
elif action == "accounting_depreciation_preview":
result = self._depreciation_preview(payload)
elif action == "accounting_calculate_it_depreciation":
@@ -67,6 +69,7 @@ class AgentCommandProcessor:
"tally_capability": True, "accounting_act_capability": True,
"tally_mapping_capability": True, "tally_master_sync_capability": True,
"tally_transaction_sync_capability": True,
"historical_learning_read_capability": True,
"it_depreciation_capability": True,
"tally_writeback_capability": True,
}
@@ -201,6 +204,16 @@ class AgentCommandProcessor:
self.logger.info("Tally transaction sync completed client_id=%s company=%s period=%s..%s rows=%s", client_id, company_name, date_from, date_to, sync.get("rows_processed"))
return {"synced": True, "sync": sync, "accounting": self.store.snapshot(client_id), "tally": tally_status, "agent": self._agent_info()}
def _historical_evidence(self, payload: dict[str, Any]) -> dict[str, Any]:
client_id = int(payload.get("client_id"))
return self.store.historical_learning_evidence(
client_id,
tally_guid=str(payload.get("tally_guid") or ""),
date_from=str(payload.get("date_from") or ""),
date_to=str(payload.get("date_to") or ""),
voucher_scope=str(payload.get("voucher_scope") or "purchase"),
)
def _depreciation_preview(self, payload: dict[str, Any]) -> dict[str, Any]:
client_id = int(payload.get("client_id"))
tally_guid = str(payload.get("tally_guid") or "").strip()
@@ -49,7 +49,7 @@ class StorageAgent:
"free_bytes": free,
"agent_version": __version__,
"agent_name": "ERP Local Agent",
"capabilities": ["storage", "tally", "accounting_act", "tally_master_sync", "tally_transaction_sync", "local_dashboard", "manual_updates"],
"capabilities": ["storage", "tally", "accounting_act", "tally_master_sync", "tally_transaction_sync", "local_dashboard", "manual_updates", "historical_learning_read"],
"agent_time_utc": datetime.now(timezone.utc).isoformat(),
}
self.client.heartbeat(payload)
@@ -190,7 +190,7 @@ class StorageAgentTunnel:
def _capabilities(self) -> list[str]:
return [
"storage", "local_dashboard", "manual_updates", "accounting_act", "agent.jobs.v2",
"storage", "local_dashboard", "manual_updates", "historical_learning_read", "accounting_act", "agent.jobs.v2",
"tally.status", "tally.company_identity", "tally.read_groups",
"tally.read_ledgers", "tally.read_stock_items", "tally.read_vouchers",
"tally.read_trial_balance", "tally.master_sync", "tally.transaction_sync",