[{"kind":"term","slug":"business-rule","title":"Business rule","answer":"A constraint the business enforces regardless of which application is being used, such as an approval threshold or an eligibility condition.","domain":["governance"],"order":6,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#business-rule"},{"kind":"term","slug":"cross-department-process","title":"Cross-department process","answer":"A workflow that spans more than one team, such as sales handing off to fulfillment, modeled as one continuous process rather than as separate steps in separate tools.","domain":["truth"],"order":11,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#cross-department-process"},{"kind":"term","slug":"data","title":"Data","answer":"The recorded facts a business has about its entities and events, structured according to the model rather than scattered across tools.","domain":["truth"],"order":8,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#data"},{"kind":"term","slug":"decision","title":"Decision","answer":"A choice made by a person or a system that changes what happens next, tracked as its own record rather than only its downstream effect.","domain":["truth"],"order":9,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#decision"},{"kind":"term","slug":"dependency","title":"Dependency","answer":"A requirement that one step, entity, or workflow places on another before it can proceed.","domain":["structure"],"order":4,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#dependency"},{"kind":"term","slug":"entity","title":"Entity","answer":"A distinct thing the business tracks: a customer, an order, an employee, an asset. Entities are the nouns the rest of the system refers to.","domain":["structure"],"order":1,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#entity"},{"kind":"term","slug":"evidence","title":"Evidence","answer":"The data and events that justified a decision, kept attached to that decision so the reasoning can be reviewed later.","domain":["truth"],"order":10,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#evidence"},{"kind":"term","slug":"permission","title":"Permission","answer":"A rule defining who can view, change, or act on a given entity or workflow step, defined once and enforced everywhere that entity appears.","domain":["governance"],"order":5,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#permission"},{"kind":"term","slug":"relationship","title":"Relationship","answer":"A defined connection between two entities, such as which customer placed which order. Relationships are modeled directly, not inferred from matching IDs across separate tables.","domain":["structure"],"order":2,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#relationship"},{"kind":"term","slug":"state-transition","title":"State transition","answer":"A recorded change in an entity's status, such as an order moving from placed to fulfilled, along with what caused the change.","domain":["governance"],"order":7,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#state-transition"},{"kind":"term","slug":"workflow","title":"Workflow","answer":"A sequence of steps that moves an entity from one state to another, with a defined owner at each step.","domain":["structure"],"order":3,"published":"2026-08-29","updated":"2026-08-29","body":"","url":"https://ahromlabs.com/systems#workflow"},{"kind":"note","slug":"accounting-inside-the-manufacturing-erp","title":"GST, TDS and the general ledger inside a manufacturing ERP, with Tally optional","answer":"Shanti Boilers' operations system is also its book of record: chart of accounts, journal posting, GSTR-1, GSTR-3B, ITC reconciliation, TDS, reverse charge, fixed assets and bank reconciliation, with 23 reports and documents generated from the same data. Tally is an optional sync target, not the books. Statutory rates arrive daily from a human-verified registry.","domain":["accounting-integration","compliance","manufacturing"],"patterns":["auto-match-only-when-mutually-unique","human-verified-statutory-rates","compute-once-render-many","derive-balances-dont-store-them"],"evidence":[{"metric":"reports and documents","value":"23 — 19 catalog reports plus 4 per-record PDFs"},{"metric":"GST returns","value":"GSTR-1 (B2B and HSN, matching the GST portal's split) and GSTR-3B"},{"metric":"bank reconciliation auto-match","value":"exact amount within ±3 days, and only when the match is mutually unique"},{"metric":"statutory rate updates","value":"pulled daily from a human-approved registry; no scraping"},{"metric":"books lock","value":"enforced at the single point every journal posting passes through"}],"published":"2026-09-24","updated":"2026-09-24","body":"Most Indian manufacturers run operations in one place and accounts in Tally, and spend the month reconciling the two. At Shanti Boilers & Pressure Vessels, the original plan was the same: operations in the new system, accounting left to a separate package. That was reversed in August 2026. Here's what the system does now, and the case for building it this way.\n\n## Why put the ledger inside the operations system?\n\nEvery accounting entry in a manufacturer starts as an operational event: a purchase order, a goods receipt, a vendor bill, a dispatch, a sales invoice. When operations and accounts live in different systems, each event is entered twice and the two copies drift. With the ledger in the same system, a vendor bill posts its own journal entry from the purchase it belongs to, and a dispatch carries its freight cost and invoice link into the books directly. Tally remains available as a sync target for the accountant who wants it — it just isn't where the truth lives.\n\n## What compliance is covered?\n\nPer legal entity: a chart of accounts and a posting engine; sales invoices, credit notes, vendor bills and debit notes; GSTR-1 (split into B2B and HSN tables the way the GST portal expects) and GSTR-3B; input-tax-credit reconciliation; TDS deduction with a TDS register; reverse charge on both the purchase and sales side; a fixed-asset register with depreciation; payroll exported into accounting; and trial balance, profit and loss, balance sheet and cash flow. Changes are written to an audit log with a searchable viewer, and a books lock stops postings into a closed period at the one function every journal entry passes through — so no document type can slip around it.\n\n## Where do GST and TDS rates come from?\n\nFrom a separate registry of Indian statutory rates — GST, TDS, PF, ESI, income-tax and professional-tax slabs — where a person enters and approves each change, and every deployment pulls approved changes on a daily schedule. Nothing scrapes government websites: no reliable official rates API exists, and a wrong compliance rate applied unattended costs more than a person checking around Budget day and GST Council meetings.\n\n## How does bank reconciliation decide what to match?\n\nA bank statement is imported from the bank's own export, and each line is matched against unreconciled ledger entries by exact amount within three days either side. A match is applied automatically only when it's mutually unique — exactly one candidate on each side. Anything ambiguous is shown as a suggestion for a person to confirm, and a statement line with no ledger entry at all — a bank charge, interest — becomes a one-click journal entry instead of a dead end.\n\n## How was it tested?\n\nWith real transactions, not only unit tests. A reverse-charge test that posted real documents through the ledger found and fixed two posting bugs. A single order was then run through every department end to end, which found that sales invoices weren't carrying their project link — fixed before any real invoice depended on it.\n\n## Boundary\n\nE-invoicing has been researched and deliberately deferred. The system keeps one set of books per legal entity; consolidation across entities isn't built.","url":"https://ahromlabs.com/notes/accounting-inside-the-manufacturing-erp"},{"kind":"note","slug":"ai-extraction-human-in-the-loop","title":"AI extraction with a human in the loop, five document types","answer":"Five document types — purchase invoices, freight invoices, bills of entry, purchase orders, bank statements — get extracted by an LLM reading the PDF directly, no separate OCR step. Zero-correction rates run 88-95% depending on type, purchase orders highest, bills of entry lowest. Every extraction still goes through a human review before anything is approved or posted.","domain":["ai-extraction","accounting-integration","trading"],"systems":["ls-crm"],"patterns":["human-confirmed-extraction"],"evidence":[{"metric":"purchase invoice zero-correction rate","value":"94%"},{"metric":"freight invoice zero-correction rate","value":"91%"},{"metric":"bill of entry zero-correction rate","value":"88%"},{"metric":"purchase order zero-correction rate","value":"95%"},{"metric":"bank statement zero-correction rate","value":"89%"}],"published":"2026-08-29","updated":"2026-09-24","body":"## What gets extracted\n\nLS Technologies, an electronics-components import/export trading business, runs five kinds of documents through AI extraction before anything reaches Tally: purchase invoices, freight invoices, bills of entry, purchase orders, and bank statements. Each has its own prompt tailored to that document's fields — a purchase invoice and a customs bill of entry don't share a structure, and treating them as one generic \"document\" would lose accuracy on both.\n\n## How it reads a PDF\n\nThere's no separate OCR step. The PDF is sent straight to the model — an LLM with a file-parsing capability reads it directly, rather than running text extraction first and handing the model plain text. Bank statements get special handling: because they can run to many pages, the document is processed in chunks and the results are stitched back together and de-duplicated, rather than trying to reason over one long document in a single pass.\n\n## How accurate it actually is\n\nReviewed against real usage, here's the share of each document type that needs zero correction before it's approved:\n\n- Purchase order: 95%\n- Purchase invoice: 94%\n- Bank statement: 89%\n- Freight invoice: 91%\n- Bill of entry: 88%\n\nBills of entry come in lowest — customs documentation is the least standardized of the five, which tracks with what you'd expect from the least uniform paperwork. Every number here still means a meaningful share of documents need a correction, which is exactly why extraction alone was never the plan.\n\n## How do these numbers compare with advertised accuracy?\n\nInvoice-automation tools commonly advertise 95–99% accuracy. A 2026 benchmark review found those claims have converged in that range while being measured under conditions each vendor chooses, with no independent benchmark behind them ([Parseur](https://parseur.com/blog/ai-invoice-processing-benchmarks)). The figures above are lower, and deliberately so: they're per document type, from real usage, and they count any document a person had to correct — including customs paperwork such as bills of entry. A lower number measured honestly is more useful for planning than a higher one measured on a vendor's own samples.\n\n## What happens to the rest\n\nNothing gets extracted and posted in one step. Every document — regardless of how confident the extraction looked — sits in a review state until a person checks it and approves it. That's not a hedge against a bad model; it's the same decision this practice makes anywhere a model's output could turn into a financial record: draft with AI, confirm with a person, post only after that.","url":"https://ahromlabs.com/notes/ai-extraction-human-in-the-loop"},{"kind":"note","slug":"ibr-statutory-folder-from-bom","title":"Generating a boiler's IBR statutory folder from its BOM and test certificates","answer":"Shanti Boilers ships every boiler with an IBR statutory folder for the Directorate of Boilers. The system builds it — cover letter, Forms II(1), III, III A and IV A — from the project's bill-of-materials tree and a bank of material test certificates, each unique on cert, cast and plate number and reused across about 3.2 boilers in the sample.","domain":["compliance","manufacturing","quality-control"],"patterns":["unconfirmed-inferences-stay-read-only","human-confirmed-extraction"],"evidence":[{"metric":"test certificate identity","value":"cert no. + cast no. + plate no. together — one cert number covered 4 casts in the sample"},{"metric":"certificate reuse","value":"about 3.2 boilers per certificate in the real sample"},{"metric":"form set","value":"chosen by boiler model — 8 models, each with its own set"},{"metric":"Form IV A sections","value":"derived from the BOM tree, no hardcoded section names"},{"metric":"certificate-to-part linking","value":"suggested by confidence, never applied without a person"}],"published":"2026-09-24","updated":"2026-09-24","body":"Shanti Boilers & Pressure Vessels manufactures boilers, pressure-reducing stations and steam headers. Every unit it ships needs a statutory folder for the Directorate of Boilers, under the Indian Boiler Regulations (IBR): the forms that prove each plate, tube and forging came from certified material. Ahrom Labs built the operations system that now assembles that folder from data the plant already records.\n\n## What goes into the folder?\n\nA complete folder is filed in a fixed order: a covering letter listing its contents, a documentation label, the forms, the list of mountings and fittings, a stage-wise inspection report, and copies of the material test certificates themselves. Which forms are needed depends on the boiler model — a standard shell boiler needs Form II(1), III, III A and IV A; a small industrial boiler swaps II(1) and III for Form XVII; a pressure-reducing station or steam header needs only III and IV A. The system picks the form set from the model set on the project, not from a person remembering which applies.\n\n## Why is a test certificate harder to key than it looks?\n\nA certificate number alone isn't unique — in the client's own sample, one certificate number covered four different casts. So each certificate in the bank is identified by certificate number, cast number and plate number together. And one certificate isn't used once: a single plate gets cut into parts for several boilers, and the sample showed each certificate reused across about 3.2 of them. Certificates and projects are therefore many-to-many — a certificate can be uploaded before any project claims it, and using it on a project's folder is what allocates it.\n\n## How does the BOM become Form IV A?\n\nForm IV A lists every material part with its certificate, grouped into lettered sections — A. Shell, B. Water Wall Assembly, and so on. The system doesn't store those section names. It walks the project's bill-of-materials tree, takes each part's ancestor one level below the root as its section, and letters only the sections that actually contain parts. A boiler with a different structure gets different sections automatically; anything not yet placed in the tree lands on a clearly labelled \"Ungrouped Materials\" page rather than disappearing.\n\n## Where does AI help, and where does it stop?\n\nCertificate PDFs are read by AI on upload, so the certificate's fields don't have to be typed. Matching a certificate to the BOM line it belongs to is suggested by a confidence score learned from past approvals — but on this document, a suggestion never links itself. A person clicks to use it, because a wrong link here ends up in paperwork a boiler inspector relies on. The [same-confidence note](/notes/same-confidence-different-autonomy) explains why the identical score is allowed to act on its own elsewhere in the same system.\n\n## Boundary\n\nThe generated folder has been compared against real filed documents the client provided. That comparison found formatting gaps in individual forms — wording and field-layout differences — which are tracked and being closed form by form. Bulk import of historical certificates from spreadsheets isn't built yet; certificates are added one at a time, with AI filling the fields.","url":"https://ahromlabs.com/notes/ibr-statutory-folder-from-bom"},{"kind":"note","slug":"outgrown-tally-signs","title":"Seven signs your business has outgrown Tally — and what fixed each one","answer":"You've outgrown Tally when the work happens outside it: invoices typed twice, a parallel Excel for production, sales asking accounts for outstanding balances, stock Tally can't describe. Replacing Tally is rarely the fix — in one system we built, Tally stays as the books and approved invoices post into it within 30 seconds.","domain":["erp-selection","accounting-integration","trading","manufacturing"],"patterns":["local-agent-cloud-db","role-scoped-finance-views","human-confirmed-extraction"],"evidence":[{"metric":"Approved invoice to Tally voucher","value":"within 30 seconds, through a local agent"},{"metric":"Outstanding balances back to the sales side","value":"every 15 minutes"},{"metric":"Import documents read by AI instead of typed","value":"5 types, 88–95% needing no correction"}],"published":"2026-09-24","updated":"2026-09-24","body":"TallyPrime is excellent at what it's for: the books. Businesses outgrow it not because it gets worse, but because more of their work happens somewhere Tally can't see. Here are the seven signs we see most — each one drawn from a business we've built for, with what fixed it.\n\n## 1. The same invoice is typed twice\n\nOnce into a spreadsheet or web app for operations, once into Tally for accounts. At [LS Technologies](/industries/electronics-component-trading), an electronics importer, approved invoices now post into Tally as vouchers within 30 seconds through a small agent on the Tally PC — no second typing. See [Posting vouchers into TallyPrime from a cloud app](/notes/tally-voucher-posting).\n\n## 2. Someone reads every PDF and keys it in\n\nPurchase invoices, freight bills, bills of entry, bank statements. At the same business, AI reads five document types directly from the PDF, and 88–95% need no correction depending on type — but every one is still approved by a person before it posts. See [AI extraction with a human in the loop](/notes/ai-extraction-human-in-the-loop).\n\n## 3. Sales keeps asking accounts what a customer owes\n\nOutstanding balances live in Tally; the sales team doesn't. The fix is syncing them back: in the same system, voucher status and outstanding bills flow from Tally to the sales side every 15 minutes.\n\n## 4. Production runs on a parallel Excel\n\nWork orders, job cards, material issues, what's waiting on what — none of it fits vouchers. At [Shanti Boilers](/industries/boiler-pressure-vessel-manufacturing), each boiler is a project with its own bill of materials and milestones across design, procurement, stores, production, QC and dispatch, and the system shows what each step is waiting on.\n\n## 5. Your stock isn't really a count\n\nSteel plate gets cut; the offcut is a specific piece, not \"one more plate\". Reels of components move between locations. Tally stores quantities. At Shanti Boilers, every plate is a piece with dimensions and every cut is recorded, so a 15.70 kg remnant goes back into stock instead of being scrapped. See [Putting steel plate offcuts back into stock](/notes/plate-remnants-back-into-stock).\n\n## 6. Two companies, one owner, one office\n\nA design firm and its furniture workshop; a trading firm and its sister concern. Two sets of books, one client list, and staff who need to record cash without seeing the bank. At [Savistar and Saag](/industries/interior-design-and-furniture), both run on one system: each invoice carries its own company's GSTIN, and staff see only their own cash entries. See [Two companies, one book](/notes/two-companies-one-book).\n\n## 7. Industry paperwork is assembled by hand\n\nA boiler's statutory folder, a test-certificate trail, a customs file. If a person compiles it from records that already exist in your systems, it can be generated instead — as Shanti Boilers' IBR folder now is. See [Generating a boiler's IBR statutory folder](/notes/ibr-statutory-folder-from-bom).\n\n## Do you have to replace Tally?\n\nUsually not. There are two sound paths. Keep Tally as the books and connect an operations system to it, as LS Technologies did — the accountant's workflow doesn't change. Or move the books into the operations system and keep Tally as an optional sync, as Shanti Boilers did. Which one fits depends on how much of your accounting starts as operational events. The [comparison of Tally, ERPNext, Odoo and custom](/notes/tally-vs-erpnext-vs-custom-erp) covers the choice in full.\n\n## Boundary\n\nThese signs come from three businesses — trading, manufacturing, interior design. If only one or two apply, a smaller fix (an integration, a report) may be enough; that's worth finding out before building anything.","url":"https://ahromlabs.com/notes/outgrown-tally-signs"},{"kind":"note","slug":"plate-remnants-back-into-stock","title":"Putting steel plate offcuts back into stock, by weight derived from geometry","answer":"At Shanti Boilers, every cut of a plate or section records what was used and what usable remnant was kept; the system derives every weight from dimensions and density, returns the remnant to stock for a later project, and books the rest as scrap. A 157.00 kg plate became 127.17 kg used, 15.70 kg remnant and 14.13 kg scrap — conserved exactly.","domain":["manufacturing","inventory","cost-accounting"],"patterns":["derive-balances-dont-store-them"],"evidence":[{"metric":"live cut test","value":"157.00 kg source → 127.17 kg used + 15.70 kg remnant + 14.13 kg scrap"},{"metric":"weights typed by the operator","value":"none — plate L×W×T×density, sections length×kg/m"},{"metric":"piece traceability","value":"every cut output chained to its source piece (used, remnant, scrap)"},{"metric":"cost of a used piece","value":"by weight fraction — 100 kg used of a 157 kg, ₹1,000 plate → ₹636.94"}],"published":"2026-09-24","updated":"2026-09-24","body":"A boiler maker buys steel plate in standard sizes and cuts it to the parts a design needs. The leftover is often big enough to use on the next job — if anyone knows it exists. At Shanti Boilers & Pressure Vessels, it used to be re-bought or scrapped blindly. This is how the operations system Ahrom Labs built now keeps it.\n\n## Why couldn't the old stock record hold an offcut?\n\nInventory was a single number per item — how many plates of a grade and size were on hand. An offcut isn't one of those plates; it's a specific piece with its own dimensions. With nowhere to record a piece, a remnant sitting in the stores could never be matched to a future bill of materials, even when it was exactly the right size.\n\n## What gets recorded at the moment of cutting?\n\nEach physical piece carries its own dimensions: length, width and thickness for a plate; length and weight-per-metre for an angle, channel, beam, pipe or bar. At the cut, the operator enters only what was used and what usable remnant they kept. The source piece is closed, each used piece is linked to the project it went into, each remnant goes back into stock as an available piece, and whatever weight is left over is booked as scrap automatically. Every output is chained to the piece it came from, so a part on a finished boiler traces back to the plate it was cut from.\n\n## Why derive weight instead of weighing or typing it?\n\nWeight is computed from geometry and density — never typed. That makes the arithmetic self-checking: used plus remnant plus scrap must equal the source. In the live test, a 2000 × 1000 × 10 mm plate (157.00 kg) was cut into a 127.17 kg part and a 15.70 kg remnant, leaving 14.13 kg of scrap — summing exactly back to the source. A typed weight can't be held to that standard; a derived one can't fail it.\n\n## How does a remnant get reused?\n\nBecause a remnant is stored as a real piece with real dimensions, it's matched against bill-of-materials lines still waiting for material, exactly like a newly purchased plate. When a BOM is released, a fitting piece is reserved automatically — the least-waste piece first, a thickness mismatch rejected, rotation allowed so an offcut that fits sideways still counts — and only the shortfall goes to Procurement to buy. In the live test, a line needing two pieces with one matching remnant in stock reserved that remnant and sent only the second piece to purchasing.\n\n## What does a cut piece cost?\n\nCost follows weight. A used piece carries the fraction of its source's cost that its weight represents: 100 kg used from a 157 kg plate that cost ₹1,000 is booked at ₹636.94. Building this surfaced a real, pre-existing error in how average cost was being recalculated for piece-tracked stock — fixed at the root rather than patched in the report.\n\n## Boundary\n\nPiece tracking is switched on per item and applies to plate and section stock that's actually cut. Matching looks for one sufficient piece per requirement; nesting — planning the most economical 2D layout of several parts on one plate before cutting — is not part of this system.","url":"https://ahromlabs.com/notes/plate-remnants-back-into-stock"},{"kind":"note","slug":"same-confidence-different-autonomy","title":"The same confidence score, two different autonomy rules","answer":"Two match-suggestion systems in the same manufacturing app score candidates identically — a Laplace-smoothed approval ratio, promoted at 75% confidence with at least 3 prior approvals. One auto-applies its top match. The other never does, no matter how confident the score, because it decides what goes on a statutory quality-control document.","domain":["ai-extraction","human-in-the-loop","manufacturing"],"patterns":["human-confirmed-extraction","unconfirmed-inferences-stay-read-only"],"evidence":[{"metric":"promotion threshold","value":"(approvals+1) / (approvals+rejections+2) ≥ 0.75, minimum 3 approvals"},{"metric":"certificate-matcher autonomy","value":"never auto-applies — a person always clicks \"use this certificate\""},{"metric":"stock-matcher autonomy","value":"auto-reserves its top match, same confidence math, same threshold"}],"published":"2026-08-31","updated":"2026-08-31","body":"## Two systems, the same scoring math\n\nA manufacturing operation runs two independent match-suggestion systems that solve the same shape of problem: given a new record, rank the best existing candidates to link it to. Both score confidence the same way — a Laplace-smoothed approval ratio, `(approvals + 1) / (approvals + rejections + 2)`, promoted to \"high confidence\" once it clears 0.75 with at least 3 recorded approvals.\n\n## What each one is actually matching\n\nOne system matches leftover cut material back to bill-of-materials lines still waiting on stock — reserving whichever offcut fits, rotation allowed, so usable material doesn't get set aside as scrap. The other matches incoming test certificates to the bill-of-materials line they belong to, the record that ends up inside a statutory quality-control document.\n\n## Same score, different action\n\nAt the identical confidence threshold, the stock-matching system auto-reserves its top candidate the moment it clears the bar — a database write happens with no person involved. The certificate-matching system never does that. It always returns a ranked list; nothing gets linked until a person clicks \"use this certificate.\" A promoted, high-confidence match just moves to the top of that list — it doesn't get to act on its own.\n\n## Why the same math earns different trust\n\nA wrong stock reservation is a wasted click — someone notices the piece doesn't fit and picks another from the list. A wrong match on a quality certificate becomes part of the paperwork proving a part meets its material specification. The statistics can't tell those two mistakes apart; the decision about which one gets to act unsupervised has to be made separately, by someone who understands what's actually at stake on each side.","url":"https://ahromlabs.com/notes/same-confidence-different-autonomy"},{"kind":"note","slug":"tally-voucher-posting","title":"Posting vouchers into TallyPrime from a cloud app","answer":"A local agent, running on the same PC as TallyPrime, polls a cloud app's database every 30 seconds and posts approved invoices into Tally's local XML gateway, syncing voucher status every 15 minutes and master data nightly. Missing ledgers and real rejections are held for a person; connection failures retry automatically until Tally is reachable again.","domain":["accounting-integration","trading"],"systems":["ls-crm"],"patterns":["local-agent-cloud-db","human-confirmed-extraction"],"evidence":[{"metric":"push poll cadence","value":"30 seconds"},{"metric":"voucher/outstanding sync","value":"15 minutes (30 cycles x 30s)"},{"metric":"master data sync (ledgers, stock, voucher types)","value":"24 hours"},{"metric":"ledger-missing handling","value":"flagged for manual review, not auto-created; retries stop until fixed"}],"published":"2026-08-29","updated":"2026-08-29","body":"Ahrom Labs built the CRM and Tally-accounting layer running underneath LS Technologies, an electronics-components import/export trading business. Every purchase invoice, freight bill, and customs document gets extracted, reviewed, and — once approved — has to land as a real voucher inside TallyPrime. Here's how that actually works.\n\n## Why doesn't the app talk to Tally directly?\n\nTallyPrime's own integration surface is a local HTTP XML gateway that only listens on the machine running Tally itself — there's no cloud endpoint to call. A small agent runs on that same PC and bridges the two systems: the cloud app writes an approved invoice to its own database, and the local agent reads from there and posts it into Tally's gateway.\n\n## How fast does data move between the two systems?\n\nThree different cadences, not one. The agent polls for newly approved invoices every 30 seconds and pushes them into Tally immediately. It syncs voucher status and outstanding-bill data back to the cloud app every 15 minutes. Ledger, stock-item, and voucher-type master data — the slower-moving reference data — syncs once every 24 hours.\n\n## What happens when a ledger doesn't exist yet?\n\nBefore an invoice is even approved, the system checks every ledger name its Tally posting would need against a locally cached snapshot of Tally's own ledgers, and flags anything unrecognized for the reviewer. Nothing gets auto-created in Tally on the system's own authority — a missing ledger is a decision for a person, not the software.\n\n## How are Tally-offline errors handled differently from real rejections?\n\nEvery push failure gets classified before anything else happens. A connection failure — Tally is offline, the network hiccups — is transient: the invoice stays queued, and the same 30-second poll picks it back up automatically on the next cycle. A rejection from Tally itself — a ledger that truly doesn't exist, a duplicate voucher — is structural: the invoice is held with the exact reason attached, and the automatic retry stops until a person fixes it.\n\n## How does the system know a voucher actually landed in Tally?\n\nPosting isn't the end of the story. Each 15-minute sync compares what the app believes it posted against what Tally's own data currently shows. A voucher is marked confirmed only once it's actually visible inside Tally; if a confirmed voucher is later deleted inside Tally itself, the next sync notices and rolls the record back to unconfirmed.\n\n## Boundary\n\nNone of this works as a cloud-to-cloud integration, because it isn't one — it depends on TallyPrime and its local gateway being reachable on the same machine as the agent. If that PC is off, nothing posts until it's back on; the 30-second retry picks up wherever it left off, with no manual restart needed.\n\n## Reference implementation\n\nThe XML-building and error-classification logic described above is open-sourced as [`tally-voucher-xml`](https://github.com/ahromlabs/tally-voucher-xml) — the protocol-correct parts, stripped of this business's ledger names and database coupling.","url":"https://ahromlabs.com/notes/tally-voucher-posting"},{"kind":"note","slug":"tally-vs-erpnext-vs-custom-erp","title":"Tally, ERPNext, Odoo or a custom ERP: how a manufacturer should choose","answer":"Stay on TallyPrime if the need is accounting with simple stock. Choose ERPNext or Odoo when your processes match what they ship. Build custom when the workflow is the business — IBR paperwork, piece-level plate traceability, two companies on one book. Shanti Boilers planned an ERPNext integration for accounting, then built its own ledger instead.","domain":["erp-selection","manufacturing","accounting-integration"],"patterns":["local-agent-cloud-db","human-verified-statutory-rates"],"evidence":[{"metric":"Tally is enough when","value":"the need is accounting, GST and simple stock"},{"metric":"ERPNext or Odoo fit when","value":"your processes match what the package ships"},{"metric":"Custom fits when","value":"the workflow or the regulation is specific to your business"},{"metric":"Shanti Boilers' accounting decision","value":"ERPNext integration planned → own ledger built, Tally kept as optional sync"}],"published":"2026-09-24","updated":"2026-09-24","body":"Ahrom Labs builds custom systems, so this isn't a neutral comparison — which is exactly why it starts with when you shouldn't hire us. The choice is rarely \"which software is best\"; it's which one matches how much of your business is standard.\n\n## When is TallyPrime enough?\n\nWhen what you need is accounting: double-entry books, vouchers, GST returns, and stock as quantities on invoices. Every CA in India knows it, and for a small company whose problem is the books, it's hard to beat on cost. It stops being enough when work starts happening outside it — the widely quoted sign is a production manager keeping a parallel Excel for work orders, because in Tally stock is an attribute of a voucher rather than something that moves through a factory ([ERPDrive](https://erpdrive.in/blog/erp-vs-tally-for-manufacturing.html)). Multi-level bills of materials — assemblies inside assemblies — are where most manufacturers feel it first.\n\n## When are ERPNext or Odoo the better choice?\n\nWhen your processes look like the ones they ship. ERPNext is open source, built in India, with no per-user licence fees and strong GST, e-way bill and e-invoicing support; Odoo's community edition is free, with enterprise features and many modules paid ([Ksolves](https://www.ksolves.com/blog/odoo/compare-odoo-vs-erpnext), [Cudio](https://www.cudio.com/blog/erpnext-vs-odoo)). If an implementation partner can configure one to fit your business without writing much code, it will be faster and cheaper than a custom build — take that option.\n\n## When does a custom system make sense?\n\nWhen the part that's specific to you is the part that matters. Three examples from systems we've built: a boiler maker's IBR statutory folder generated from its bill of materials and test certificates; steel plate tracked as individual pieces so offcuts go back into stock by weight; and two sister companies sharing one client list and one ledger while staff see only their own cash entries. Each of those would be heavy customisation on a packaged ERP — and heavy customisation is code you maintain through every upgrade of someone else's product. At that point, owning the system outright is often the simpler path.\n\n## What did a boiler manufacturer actually decide?\n\n[Shanti Boilers & Pressure Vessels](/industries/boiler-pressure-vessel-manufacturing) had its operations system built custom from the start — projects, BOM, procurement, stores, production, QC — with CRM, selling and HR built natively to the feature depth ERPNext offers. Regulated accounting — the ledger, GST, TDS and statutory payroll — was originally planned as an integration with ERPNext. In August 2026 that was reversed: the operations system became the book of record, ERPNext was dropped, and Tally was kept as an optional sync target rather than the books. The result is described in [GST, TDS and the general ledger inside a manufacturing ERP](/notes/accounting-inside-the-manufacturing-erp).\n\nThe cost is real, and worth stating: owning the ledger meant building GST returns, TDS, reverse charge, fixed assets and bank reconciliation, and testing them with real transactions — a reverse-charge test found two posting bugs before any real books depended on them. Keeping statutory rates correct is handled by a human-approved rate registry rather than by each system separately.\n\n## A quick way to decide\n\n- Is your problem mostly the books and GST? **Stay on TallyPrime**, and connect your other tools to it if needed.\n- Do your processes match a standard manufacturing ERP, with little customisation? **ERPNext or Odoo**, through a good implementation partner.\n- Is there industry paperwork, traceability or a business structure no package models well? **Custom** — and decide separately whether the books move in or Tally stays as the ledger.\n- Not sure? That's what a modeling phase is for: [map the business first](/engagement#pricing), then choose.\n\n## Boundary\n\nThe package descriptions above summarise the cited comparisons as of September 2026; licences and editions change, so check current terms before deciding. We have not implemented ERPNext or Odoo for a client — this guide compares them from the outside.","url":"https://ahromlabs.com/notes/tally-vs-erpnext-vs-custom-erp"},{"kind":"note","slug":"two-companies-one-book","title":"Two companies, one book","answer":"Savistar and Saag — sister companies, same owners — share one client list and one combined finance ledger in a single app, not a multi-tenant system. The real structure is in who can see what: staff see only their own cash entries, and bank data is rejected outright for any staff request, enforced at the API, not just hidden in the UI.","domain":["access-control","finance"],"systems":["savistar-ops"],"patterns":["role-scoped-finance-views","graceful-degradation-by-env-var"],"evidence":[{"metric":"shared structure","value":"one client list, one combined finance ledger, no tenant/company table"},{"metric":"staff cash visibility","value":"own entries only"},{"metric":"staff bank visibility","value":"none, every bank route rejects staff"}],"published":"2026-08-29","updated":"2026-08-31","body":"## Why one book?\n\nSavistar (interior design) and Saag (furniture manufacturing) are sister companies — a sister concern arrangement, in Indian accounting terms — under the same owners, sharing a single client list. Rather than running two separate systems that need to be kept in sync, both businesses operate out of one app with one combined finance ledger.\n\n## How \"two companies\" actually works\n\nThere's no tenant or company table underneath this — it's a single shared database with one client table. What distinguishes a Savistar job from a Saag order is mostly a matter of which tab it lives in (Projects for Savistar's design work, Orders for Saag's furniture orders), plus a single field on each invoice that picks the right letterhead, logo, GSTIN, and registration details when a PDF is generated. It's a shared single-tenant app modeling two business lines, not a multi-tenant system — simpler than \"two companies, one book\" might suggest, and that simplicity is the point: nobody has to maintain a heavier structure than the business actually needs.\n\n## Where the real structure is: who can see what\n\nThe part that isn't simple is financial visibility. Staff log day-to-day cash transactions, but their view of the cash ledger is scoped to only the entries they personally created — enforced at the point the data is fetched, not just hidden in the interface. Bank data goes further: it isn't scoped, it's excluded entirely. Every request touching bank information from a staff account is rejected before any query even runs. Owners see everything; staff see their own cash activity and nothing from the bank side.\n\n## Why it's built this way\n\nNone of this depends on every integration being perfectly configured. The database, file storage, and AI extraction the app uses elsewhere are each optional at the infrastructure level — the app runs regardless, and each feature behaves according to whether its own configuration is present, rather than the whole system depending on all of them being wired up.\n\n---\n\n> \"They really took the time to understand our concerns and requirements, and the system was built the way we had envisioned it. It has brought much more structure, visibility, and control to the way we operate.\"\n>\n> — Sachi & Haripriya, owners of Savistar and Saag","url":"https://ahromlabs.com/notes/two-companies-one-book"},{"kind":"pattern","slug":"auto-match-only-when-mutually-unique","title":"Auto-apply a match only when it's mutually unique — everything else goes to a person","answer":"When matching two lists — bank statement lines to ledger entries — apply a match automatically only if exactly one candidate exists on each side within tolerance (exact amount, ±3 days). Any ambiguity becomes a suggestion for a person, and an unmatched line gets a one-click fix. Fewer auto-matches, but none of them are guesses.","domain":["reliability","finance","human-in-the-loop"],"evidence":[{"metric":"tolerance","value":"exact amount, ±3 days for clearing lag"},{"metric":"auto-applied when","value":"exactly one eligible candidate on each side"},{"metric":"self-check assertions","value":"8, including \"ambiguous candidates stay low confidence\""}],"published":"2026-09-24","updated":"2026-09-24","body":"## Context\n\nBank reconciliation pairs each bank statement line with the ledger entry it corresponds to. Most pairs are obvious. Some aren't: two payments of the same amount a day apart, or a cheque that cleared three days after it was booked. A matcher that picks \"the closest\" in those cases will sometimes pick wrong — and a wrong reconciliation is harder to find than an unreconciled line.\n\n## Decision\n\nA match is applied automatically only when it is mutually unique: the statement line has exactly one eligible ledger candidate within tolerance, and that ledger entry has exactly one eligible statement line. Tolerance is the exact amount within a few days either side. Anything else is shown as a low-confidence suggestion — closest by date — for a person to confirm. Only unreconciled entries are ever candidates. A statement line with no ledger entry at all (a bank charge, interest) offers a one-click journal entry.\n\n## Why\n\nMutual uniqueness is a condition the matcher can prove, not a score it has to trust. When it holds, there is no second interpretation to get wrong. When it doesn't, the ambiguity is real information a person should see, not noise to be resolved by a tie-break rule.\n\n## Trade-off\n\nA month with many identical amounts — standing instructions, equal instalments — auto-matches less and leaves more for a person to click through. That's the intended cost: the manual queue grows exactly where a guess would have been riskiest.","url":"https://ahromlabs.com/patterns/auto-match-only-when-mutually-unique"},{"kind":"pattern","slug":"compute-blockers-on-read","title":"Compute \"what's blocking this\" on every read, and observe before you enforce","answer":"To show why a workflow step is blocked and by what, compute the blocker fresh on every read from the step's declared predecessor and live signals — never store it. Ship it read-only first: surface the signal everywhere the step appears, enforce nothing, and flag steps marked done while their predecessor isn't. Enforcement comes after the signal is trusted.","domain":["operational-intelligence","architecture","workflow"],"evidence":[{"metric":"blocker storage","value":"none — computed on every read, so it can't go stale"},{"metric":"v1 enforcement","value":"none — observational signal only"},{"metric":"consistency check","value":"flags a step marked done while its own predecessor isn't"}],"published":"2026-09-24","updated":"2026-09-24","body":"## Context\n\nA manufacturing order moves through dozens of milestones across departments — design, procurement, production, QC, dispatch. When one stalls, the useful question is not \"is it late\" but \"what is it waiting for, and who can unblock it\". Answering that with a hand-written check inside each feature produces as many definitions of \"blocked\" as there are screens.\n\n## Decision\n\nEach milestone declares its structural predecessor, configurable system-wide. One pure function takes a milestone and returns whether it's ready and, if not, what it's blocked by — an unfinished predecessor, or a live signal such as materials not yet received. The result is computed on every read and attached to the milestone wherever it renders; it is never written to the database. The first version enforces nothing: it only shows the signal, alongside — not merged into — the existing late/at-risk status. It also flags the reverse inconsistency: a milestone marked done while the thing it depends on isn't.\n\n## Why\n\nA stored \"blocked\" flag is a second copy of facts that already exist, and it goes stale the moment one of them changes. Computing on read removes that failure mode entirely. Shipping read-only first means the signal is checked against how the plant actually works before it's allowed to stop anyone — an enforced rule that's wrong halts real work; an observed one that's wrong just gets corrected.\n\n## Trade-off\n\nEvery read pays for the computation, and a signal nobody is forced to act on can be ignored. Both are accepted for the first version: the computation is small, and the point of observing first is to learn which blockers deserve enforcement before any of them get it.","url":"https://ahromlabs.com/patterns/compute-blockers-on-read"},{"kind":"pattern","slug":"compute-once-render-many","title":"One computed result, several renderers — never recalculate a report per format","answer":"A report shown on screen, returned as JSON and exported as PDF should come from one computation, with each format only rendering its result. Register each report once with the exact function that computes it; on-screen table, API and PDF all call that function. Across 23 reports and documents, the three formats cannot disagree.","domain":["architecture","reporting"],"evidence":[{"metric":"reports built this way","value":"19 catalog reports + 4 per-record PDFs"},{"metric":"calculations per report","value":"1, shared by the JSON route, the screen and the PDF"}],"published":"2026-09-24","updated":"2026-09-24","body":"## Context\n\nBusiness reports get asked for in more than one form: a table on screen, a PDF to email to the auditor, sometimes raw data for another system. The quick way to add a PDF is to write a PDF generator that queries the data again. Now there are two calculations of the same trial balance, and the day one of them changes, the screen and the printout disagree.\n\n## Decision\n\nEach report is registered once in a catalog, and its entry points at the same `compute` function the report's JSON route already uses. The screen and the PDF don't compute anything; they take that result and render it, with a small hand-written function per report to shape it into a table. A shared PDF frame supplies the identity header, page numbers and a table header that repeats across pages, so every report looks like it came from the same company.\n\n## Why\n\nWhen there is only one calculation, \"the numbers don't match\" stops being a possible bug class. It also makes adding a format cheap: a new renderer reuses every report's existing, already-tested computation.\n\n## Trade-off\n\nThe per-report shaping functions are hand-written rather than generated from a generic report definition. Report results differ too much — a trial balance is a flat table, GSTR-1 is two tables matching the GST portal's split, GSTR-3B is only totals — for one generic template to render them all well, so a little repetition was chosen over a configuration language nobody would want to maintain.","url":"https://ahromlabs.com/patterns/compute-once-render-many"},{"kind":"pattern","slug":"derive-balances-dont-store-them","title":"Compute a balance from source transactions at read time, don't store a running total","answer":"An outstanding balance — partial deliveries, payroll advances — can be tracked as a running total updated on each transaction, or computed fresh from source records every time. Compute it at read time instead of storing it separately, trading recomputation cost for avoiding drift between the stored total and the transactions that actually produced it.","domain":["architecture","finance"],"systems":["savistar-ops"],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nA figure like \"outstanding balance\" or \"amount still owed\" can be maintained two ways: as a stored value updated whenever a related transaction happens, or computed from those transactions whenever it's needed.\n\n## Decision\n\nCompute it at read time from the underlying transactions — orders, deliveries, returns, advances — rather than maintaining a separately-updated stored total.\n\n## Why\n\nA stored running total can drift from the transactions that produced it the moment an update is missed, applied twice, or applied out of order. A derived value can't drift, because it's recalculated from the same source every time.\n\n## Trade-off\n\nRecomputation cost on every read instead of an O(1) stored read. Fine at the transaction volumes this was built for; a system with a much higher read rate on this figure would need to revisit the trade.","url":"https://ahromlabs.com/patterns/derive-balances-dont-store-them"},{"kind":"pattern","slug":"fail-closed-preserve-visibility","title":"Fail-closed doesn't have to mean fail-invisible — pick the blocking mechanism deliberately","answer":"A security-critical control needs default-deny, but the naive way to enforce \"blocked\" — disabling the underlying service entirely — can also destroy the ability to detect the thing being blocked. Default to blocked, but enforce it through a mechanism that blocks use without blocking detection, so a blocked resource can still be found and requested.","domain":["reliability","access-control"],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nA security-critical control needs a default-deny posture. The obvious way to enforce \"blocked\" is to disable the underlying service or device entirely — but that can also destroy the ability to even detect or discover the thing being blocked.\n\n## Decision\n\nDefault to blocked, but enforce it through a mechanism that blocks *use* of the resource without blocking *detection* of it — so a blocked resource can still be found and its access requested, rather than disappearing entirely.\n\n## Why\n\nFail-closed doesn't have to mean fail-invisible. Picking the specific blocking mechanism — not just deciding \"blocked vs. not\" — is the actual decision that determines whether a legitimate access request can even happen afterward.\n\n## Trade-off\n\nRequires a more specific, less obvious enforcement mechanism than the blunt \"turn it off\" option, and more care to confirm it genuinely blocks use while it does.","url":"https://ahromlabs.com/patterns/fail-closed-preserve-visibility"},{"kind":"pattern","slug":"failures-flagged-not-lost","title":"Classify failures before retrying — transient errors retry, real rejections wait for a person","answer":"A voucher push can fail because the target system is briefly unreachable, or because the voucher is genuinely invalid — two different problems needing different responses. Classify the failure first: transient errors retry automatically, structural ones are held with the reason attached for a person to fix.","domain":["reliability","accounting-integration"],"systems":["ls-crm"],"evidence":[{"metric":"retry behavior for transient errors","value":"automatic, next 30-second poll cycle"},{"metric":"retry behavior for structural errors","value":"none — held with reason attached until a person resolves it"}],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nA voucher push into Tally can fail for two very different reasons: Tally being briefly unreachable, or the voucher itself being genuinely invalid — a ledger that doesn't exist, a duplicate entry.\n\n## Decision\n\nClassify every failure before deciding what happens next. A connection problem is transient — the invoice stays queued and gets picked up automatically on the next poll cycle. A rejection from Tally itself is structural — the invoice is held, with the exact reason attached, and the automatic retry stops until a person fixes the underlying issue.\n\n## Why\n\nTreating every failure the same way breaks in one of two directions: retry forever on a voucher that will never succeed, or give up on a connection blip that would have resolved itself on its own thirty seconds later.\n\n## Trade-off\n\nThe classification depends on parsing the target system's own error messages, which is simple to reason about but brittle if that wording ever changes — a workaround built against one system's specific quirks, not a general error-handling framework.\n\n## As code\n\nThe transient-vs-structural classifier is open-sourced as `classifyNetworkError()`/`classifyResponseError()` in [`tally-voucher-xml`](https://github.com/ahromlabs/tally-voucher-xml).","url":"https://ahromlabs.com/patterns/failures-flagged-not-lost"},{"kind":"pattern","slug":"graceful-degradation-by-env-var","title":"Optional integrations should degrade the specific feature, not the app","answer":"Optional integrations shouldn't be a hard requirement at startup — check each one's config where it's actually used, and decide what happens per feature. Here that means three different behaviors for three integrations: a full local fallback, a silent skip, and a hard reject. The trade-off is holding three separate rules in mind instead of one simple story.","domain":["reliability","configuration"],"systems":["savistar-ops"],"evidence":[{"metric":"behavior when the database URL is missing","value":"falls back fully to a local database file"},{"metric":"behavior when object-storage vars are missing","value":"inconsistent by design — one endpoint skips the upload silently, another rejects the request"},{"metric":"behavior when the AI extraction key is missing","value":"always rejects just the extraction call"}],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nThree integrations — a hosted database, object storage, and an LLM API — each unlock a feature, but none of them are required for the app to run at all.\n\n## Decision\n\nEach integration's configuration is checked independently, right where it's used, rather than as one hard requirement at startup. What happens when it's missing is decided per feature rather than by one shared rule: the database falls back to a local file entirely; object storage either skips the upload silently or rejects just that request, depending on which endpoint; the AI integration always rejects just the extraction call.\n\n## Why\n\nA single hard requirement on every optional integration at boot would make local development and partial deployments unnecessarily brittle — the whole app would refuse to start over a feature nobody's using yet.\n\n## Trade-off\n\nThis is deliberately inconsistent, not one uniform mechanism — three integrations, three different behaviors when missing. That's a real cost to hold in your head; it's not something you can describe as \"the app degrades gracefully\" and leave at that. Each integration point needs its own specific answer.","url":"https://ahromlabs.com/patterns/graceful-degradation-by-env-var"},{"kind":"pattern","slug":"human-confirmed-extraction","title":"AI drafts, a person confirms — never auto-post extracted data","answer":"AI-based document extraction is fast but not perfect — even a good system leaves some share of documents needing correction. Post nothing automatically: every extraction sits in a review state until a person approves it. The cost is a manual step on every document; the alternative is trusting unreviewed numbers with real money.","domain":["ai-extraction","human-in-the-loop"],"systems":["ls-crm"],"evidence":[{"metric":"purchase invoice zero-correction rate","value":"94%"},{"metric":"bank statement zero-correction rate (hardest document type)","value":"89%"}],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nLLM-based document extraction is fast — a purchase invoice comes back parsed in seconds — but it isn't perfect. Even a well-tuned pipeline leaves a meaningful share of documents needing a correction before the numbers are right.\n\n## Decision\n\nEvery extracted document sits in a review state. Nothing downstream — a posting, a saved record, anything else — happens until a person looks at it and approves it. The model drafts; it never commits on its own.\n\n## Why\n\nA wrong number in a financial posting is expensive to unwind after the fact — tracing it, reversing it, re-entering it correctly. A short review before it goes anywhere is cheaper than a correction after it's already downstream. Even at a 94% zero-correction rate on the best-performing document type, a meaningful share still needs a human to catch it.\n\n## Trade-off\n\nEvery document gets a manual step and some latency it wouldn't have with straight auto-posting. That's the deliberate cost — the alternative is trusting an unreviewed model with money.\n\nThe same decision has been made independently a second time, in a separate engagement, for a completely different kind of document — not an accounting record at all. Same reasoning, same conclusion: AI drafts, a person confirms.","url":"https://ahromlabs.com/patterns/human-confirmed-extraction"},{"kind":"pattern","slug":"human-verified-statutory-rates","title":"Keep statutory rates in one human-approved registry that deployments pull from — don't scrape them","answer":"GST, TDS, PF, ESI, income-tax and professional-tax rates change a few times a year and must be right. Keep them in one central registry where a person enters and approves each change, and have every deployment pull approved changes daily through the same validation as a hand-entered rate. No scraping of government sites.","domain":["compliance","architecture","reliability"],"evidence":[{"metric":"rate categories","value":"GST, TDS, PF, ESI, income-tax slabs, professional-tax slabs"},{"metric":"update path","value":"draft → approved in the registry → pulled by each deployment on a daily cron"},{"metric":"automated scraping","value":"none — no reliable official rates API exists"}],"published":"2026-09-24","updated":"2026-09-24","body":"## Context\n\nEvery Indian business system that computes tax needs current statutory rates, and they change on known occasions — the Union Budget, GST Council meetings, state notifications. Typing them into each client's system separately means the same change is made several times, with several chances to get it wrong. Automating it by scraping government websites fails silently when a page changes shape.\n\n## Decision\n\nRun one registry of statutory rates, separate from any client system. A change is entered as a draft and becomes visible only once a person approves it, with its effective dates. Each deployment pulls approved changes on a daily schedule using a cursor, and applies them through exactly the same insert and validation functions its own admin screens use — a pulled rate and a hand-typed rate cannot behave differently. The registry is multi-tenant from the start, with a key per deployment.\n\n## Why\n\nA compliance rate is the kind of number where being wrong unattended costs more than being late. A person checking around known trigger dates is cheap and reliable; a scraper is cheap until the day it quietly isn't. Centralising the approval means that person does the work once for every client, and routing the pull through the existing validation means the sync adds no second set of rules to trust.\n\n## Trade-off\n\nRates are only as current as the person maintaining the registry. The design relies on the change dates being predictable — which they mostly are — and accepts a short lag after an unscheduled notification in exchange for never applying an unverified rate.","url":"https://ahromlabs.com/patterns/human-verified-statutory-rates"},{"kind":"pattern","slug":"local-agent-cloud-db","title":"Route through a local agent, not a direct API, when the target system is local-only","answer":"When the system you need to integrate with only exposes a local interface — like TallyPrime's local-only XML gateway — route through a small agent running on the same machine instead of building toward a cloud API that doesn't exist. The cost is lag: minutes to a day, depending on how fast that data actually needs to move.","domain":["architecture","accounting-integration"],"systems":["ls-crm"],"evidence":[{"metric":"push poll cadence","value":"30 seconds"},{"metric":"voucher/outstanding sync","value":"15 minutes"},{"metric":"master data sync","value":"24 hours"}],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nTallyPrime's own integration surface is a local HTTP XML gateway — it only listens on the machine running Tally itself. There's no cloud-reachable endpoint to call from a hosted web app.\n\n## Decision\n\nRun a small agent on the same machine as Tally. The cloud app never talks to Tally directly — it writes to its own database, and the local agent reads from there and posts into Tally's gateway.\n\n## Why\n\nThis isolates everything Tally-specific — the local-only connection, Tally's own XML quirks, the possibility that the PC is simply switched off — inside one small, replaceable process. The cloud app stays a normal web app that only ever talks to its own database.\n\n## Trade-off\n\nThe integration only works while that PC and its agent are running. Data isn't instantly consistent between the two systems — pushes go out every 30 seconds, voucher status syncs back every 15 minutes, and slower-moving reference data (ledgers, stock items, voucher types) syncs once every 24 hours. That lag is the cost of not needing the target system to expose anything it doesn't already have.\n\n## As code\n\nThe Tally-facing half of this agent — building and posting voucher XML against the local gateway — is open-sourced as [`tally-voucher-xml`](https://github.com/ahromlabs/tally-voucher-xml).","url":"https://ahromlabs.com/patterns/local-agent-cloud-db"},{"kind":"pattern","slug":"one-confirmation-teaches-the-system","title":"Let one deliberate human correction teach the system — but only for narrowly trusted error shapes","answer":"When imported data carries typos — TINNER for THINNER, PALTE for PLATE — suggest a correction only for two trusted shapes, one-character edits and adjacent swaps, and remember the answer after a single human confirmation. A looser \"edit distance ≤ 2\" rule matched PALTE to VALVE as readily as to PLATE, and was rejected.","domain":["human-in-the-loop","data-quality","manufacturing"],"evidence":[{"metric":"trusted typo shapes","value":"edit distance exactly 1, or a single adjacent transposition"},{"metric":"confirmations needed to learn a word","value":1},{"metric":"false positive caught before shipping","value":"PALTE ≈ VALVE at the same plain edit distance as PALTE ≈ PLATE"}],"published":"2026-09-24","updated":"2026-09-24","body":"## Context\n\nBills of materials arrive as spreadsheets typed by people, and the system categorises each line from its description. Misspellings — a missing letter, two letters swapped — land lines in the wrong category or none. Hand-adding each known misspelling as a rule fixes yesterday's typos, never tomorrow's.\n\n## Decision\n\nDuring the import review a person already does, the system suggests a spelling correction next to the category dropdown — never instead of it. It trusts only two typo shapes against a short, curated list of real keywords: a single inserted, deleted or substituted character, or a genuine swap of two adjacent letters. When a person confirms a suggestion, that word is stored and applied automatically on every future import. One confirmation is enough, because this is a direct yes-or-no answer from someone looking at the line, not an inference that needs repeated evidence.\n\n## Why\n\nA looser rule looked simpler: accept anything within an edit distance of two, which a transposition costs. Checked before shipping, it matched PALTE to VALVE exactly as closely as to PLATE — two unrelated substitutions scoring the same as one swap — which would have made the right suggestion ambiguous. Narrow shapes keep suggestions rare and right, which is what makes a single confirmation safe to learn from.\n\n## Trade-off\n\nTypos outside the two shapes — two unrelated wrong letters, a missing syllable — get no suggestion and still need a person to pick the category by hand. The system learns less per import, in exchange for almost never proposing a wrong word.","url":"https://ahromlabs.com/patterns/one-confirmation-teaches-the-system"},{"kind":"pattern","slug":"reconcile-against-source-of-truth","title":"After writing to an external system, verify against it — don't trust your own write","answer":"After writing to an external system of record, don't trust your own write as fact — read back from the authoritative system and update your own copy to match what's actually there. The external system stays the real source of truth; your copy is a cache of it, verified after every write.","domain":["reliability","accounting-integration"],"systems":["ls-crm"],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nA write to an external system of record can succeed on the wire but still not mean what you think — the request could be accepted and then rejected downstream, or applied differently than expected.\n\n## Decision\n\nAfter posting, read back from the authoritative system and update your own copy to match what's actually there, not what you believe you sent.\n\n## Why\n\nThe external system is the real source of truth. Your own record of \"what happened\" is only correct once it's been checked against that system, not the moment the write request returns success.\n\n## Trade-off\n\nAdds a read-back step, and a window where your copy is provisionally \"pending confirmation\" rather than settled — cheaper than the alternative of silently drifting from what the source of truth actually holds.","url":"https://ahromlabs.com/patterns/reconcile-against-source-of-truth"},{"kind":"pattern","slug":"reference-rate-anomaly-detection","title":"Flag deviations from a known reference rate, don't manually audit every charge","answer":"Incoming billed amounts can't all be manually audited line by line. Compare each charge against a known reference rate and automatically flag deviations, instead of trusting every incoming number or requiring full manual review. Catches overcharges without a person checking every line — only as good as how current the reference data stays.","domain":["reliability","finance"],"systems":["savistar-ops"],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nVendor and freight charges arrive as numbers a system has no independent way to verify — trusting them outright risks paying overcharges; auditing every line manually doesn't scale.\n\n## Decision\n\nMaintain a reference rate card for the relevant charge type, and compare each incoming charge against it automatically, flagging anything that deviates.\n\n## Why\n\nThis catches overcharges without requiring a person to check every line, while still surfacing exactly the cases that need a human look — the ones that don't match what's expected.\n\n## Trade-off\n\nThe check is only as good as the reference data. A stale rate card either produces false flags or misses real overcharges — someone has to keep it current for the pattern to keep working.","url":"https://ahromlabs.com/patterns/reference-rate-anomaly-detection"},{"kind":"pattern","slug":"replay-queued-payload-through-existing-handler","title":"Queue the original request, replay it through the same handler on approval","answer":"Some actions need approval before taking effect, but duplicating business logic for \"pending\" vs. \"approved\" versions of the same action doubles the surface area for bugs. Queue non-approved actions as the original request payload, and on approval, replay it through the exact same handler real-time requests use — one code path instead of two.","domain":["architecture","reliability"],"systems":["pcb-inventory"],"evidence":[{"metric":"replay mechanism","value":"queued request payload replayed through the same handler function real-time requests use, not a separate approval-path implementation"}],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nSome actions need a manager's approval before they take effect, but writing a separate \"approved version\" of that action's logic means every change to the real handler has to be mirrored in the approval path, or the two quietly drift apart.\n\n## Decision\n\nStore a non-approved action as a generic request record holding the original request payload as-is. On approval, replay that payload through the exact same handler function that processes real-time, non-gated requests.\n\n## Why\n\nThere's one code path for the actual business logic, used whether the action happened immediately or after approval. Nothing to keep in sync between two implementations of the same operation.\n\n## Trade-off\n\nA queued payload is only as valid as it was at queue time — if something else changes state between queuing and approval, the replay can act on stale assumptions. That case needs its own handling; this pattern doesn't solve it by itself.","url":"https://ahromlabs.com/patterns/replay-queued-payload-through-existing-handler"},{"kind":"pattern","slug":"role-scoped-finance-views","title":"Scope financial visibility by role at the query layer, not just the UI","answer":"Don't rely on hiding UI elements to protect financial data from the wrong role — enforce the boundary where the data is fetched. Staff cash visibility is filtered to their own entries at the query level; bank data is rejected outright for staff before any query runs. The trade-off: it's a per-endpoint discipline, not one central gate.","domain":["access-control","finance"],"systems":["savistar-ops"],"evidence":[{"metric":"cash visibility scope for staff","value":"own entries only, enforced in the API query"},{"metric":"bank visibility for staff","value":"none — every bank API route rejects staff requests"}],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nStaff need to log day-to-day cash transactions, but shouldn't see the business's full financial picture. Bank data is for owners only.\n\n## Decision\n\nThe boundary is enforced where the data is fetched, not just in what the interface shows. A staff member's view of cash entries is filtered to only the entries they created, at the point the data is queried. Bank data isn't filtered at all — every bank-related request from a staff account is rejected outright, before any query runs.\n\n## Why\n\nHiding a UI element doesn't stop someone from calling the underlying endpoint directly. Enforcing the boundary at the point requests reach the server means the restriction holds regardless of what client is asking.\n\n## Trade-off\n\nThere's no single central gate doing this for the whole app — no row-level security at the database, just a rule applied at each relevant endpoint. Every new finance-adjacent feature has to remember to apply the same check. It's a discipline the team has to keep, not a guarantee the system enforces on its own.","url":"https://ahromlabs.com/patterns/role-scoped-finance-views"},{"kind":"pattern","slug":"seeded-credentials-forced-rotation","title":"Zero-config first run — seed default logins, force rotation through the UI","answer":"A new client instance needs to be usable immediately, not blocked on manual account provisioning. Auto-create the schema and seed default logins on first run, then force rotation through a UI visible to everyone, rather than leaving default credentials to quietly persist. Zero-friction setup, with a real but narrow exposure window.","domain":["reliability","configuration"],"systems":["savistar-ops"],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nA new client instance needs to be usable immediately. Requiring an admin to manually provision the first real account before anyone can log in adds friction to every new deployment.\n\n## Decision\n\nOn first run, auto-create the schema and seed a small set of default logins. Rotation isn't optional or hidden — a \"Change password\" action is visible to everyone in the navigation, right after first login.\n\n## Why\n\nZero manual setup gets a new instance usable the moment it's deployed, without leaving default credentials live indefinitely — the rotation path is impossible to overlook because it's not buried in a settings page.\n\n## Trade-off\n\nThere's a real, if narrow, window where seeded credentials are live before someone actually rotates them. The trade is accepted deliberately: friction-free first run, in exchange for trusting that rotation actually happens promptly.","url":"https://ahromlabs.com/patterns/seeded-credentials-forced-rotation"},{"kind":"pattern","slug":"self-healing-sequence-counters","title":"Re-derive a sequence counter from the data itself before trusting it","answer":"A sequential ID scheme backed by a mutable counter table can desync from manual edits or partial writes. Before incrementing, re-derive the counter as the max of its stored value and the highest numeric suffix already in the table — it heals itself from drift instead of needing a manual fix, at the cost of an extra scan on each allocation.","domain":["reliability","architecture"],"systems":["pcb-inventory"],"evidence":[{"metric":"resync check before each allocation","value":"max(stored counter, highest numeric suffix already in the table)"}],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nA sequential, human-readable ID scheme (like a reel or box number with an incrementing suffix) is usually backed by a counter stored separately from the records it numbers. Manual edits, partial writes, or a restored backup can leave that counter out of sync with what's actually in the table.\n\n## Decision\n\nBefore incrementing the counter to allocate a new ID, re-derive it as the maximum of its currently stored value and the highest numeric suffix actually present in the table.\n\n## Why\n\nThe scheme heals itself from drift automatically. Nobody has to notice a desync and manually reset a counter — the next allocation just corrects for it.\n\n## Trade-off\n\nEvery allocation now does a scan to find the current maximum, instead of a pure increment. Worth it for the guarantee that IDs never collide even after the counter itself gets out of sync.","url":"https://ahromlabs.com/patterns/self-healing-sequence-counters"},{"kind":"pattern","slug":"separate-machine-from-human-identity","title":"Make a machine credential structurally unable to pass as a human one","answer":"A system with both human users and machine/service clients needs a leaked machine credential to be unable to impersonate a human. Machine identity is carried in a token claim that can only come from the token itself, never a request body, and the human-session check explicitly excludes that credential type — structurally impossible, not just policy.","domain":["access-control","architecture"],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nA system that serves both human users and machine or service clients needs a way to tell the two apart — and a leaked machine credential shouldn't be usable to impersonate a human, even by accident.\n\n## Decision\n\nMachine identity is carried in a token claim that can only originate from the token itself — never accepted from a request body — and the code path that checks for a valid human session explicitly excludes that credential type, rather than treating \"authenticated\" as one undifferentiated bucket.\n\n## Why\n\nA leaked machine token literally cannot be pasted somewhere and pass as a human login. That's not a policy someone has to remember to enforce — it's structurally impossible given how the identity is carried and checked.\n\n## Trade-off\n\nTwo distinct credential-checking code paths to maintain instead of one unified \"is this request authenticated\" check.","url":"https://ahromlabs.com/patterns/separate-machine-from-human-identity"},{"kind":"pattern","slug":"unconfirmed-inferences-stay-read-only","title":"Mark inferred rules unconfirmed, and keep them read-only until a person confirms them","answer":"A system can infer relationships from existing data — implicit orderings baked into config — that look plausible but were never confirmed by a domain expert. Mark such inferences explicitly unconfirmed and keep any automation that would act on them read-only until a domain expert actually confirms them, rather than letting the inference stand in.","domain":["reliability","data-modeling"],"published":"2026-08-29","updated":"2026-08-29","body":"## Context\n\nA system can infer relationships or rules from existing data — implicit orderings baked into configuration, patterns that look consistent across records — without those relationships ever having been confirmed by someone who actually knows the business.\n\n## Decision\n\nExplicitly mark such inferences as unconfirmed, and keep any automation that would act on them in a read-only or advisory state until a domain expert actually confirms them. Building the enforcing version of that automation is deliberately deferred.\n\n## Why\n\nAn inferred rule that turns out wrong, and is already gating real users' actions, is worse than having no rule at all — it fails confidently instead of failing obviously.\n\n## Trade-off\n\nSlower to get automated enforcement live, and it requires someone to actually do the confirmation step rather than letting the inference quietly stand in for it indefinitely.","url":"https://ahromlabs.com/patterns/unconfirmed-inferences-stay-read-only"},{"kind":"industry","slug":"boiler-pressure-vessel-manufacturing","title":"Software for boiler and pressure-vessel manufacturers","audience":"Boiler and pressure-vessel manufacturers","answer":"Ahrom Labs built the operations system Shanti Boilers & Pressure Vessels runs on — BOM, procurement, stores, plate cutting, production, QC, IBR statutory folders, GST and TDS accounting, and a customer portal, in one system. Each boiler's IBR folder is generated from its bill of materials and a bank of material test certificates.","domain":["manufacturing","compliance","quality-control"],"clients":["shanti-boilers"],"services":["custom-erp-crm","compliance-accounting","operational-intelligence","document-extraction"],"notes":["ibr-statutory-folder-from-bom","plate-remnants-back-into-stock","accounting-inside-the-manufacturing-erp","same-confidence-different-autonomy","tally-vs-erpnext-vs-custom-erp"],"patterns":["compute-blockers-on-read","one-confirmation-teaches-the-system","unconfirmed-inferences-stay-read-only","human-verified-statutory-rates","auto-match-only-when-mutually-unique","compute-once-render-many"],"evidence":[{"metric":"IBR forms generated","value":"Cover letter, Forms II(1), III, III A and IV A"},{"metric":"Boiler models, each with its own form set","value":"8"},{"metric":"Reports and documents","value":"23, one computation each"},{"metric":"Live plate-cut test","value":"157.00 kg → 127.17 used + 15.70 remnant + 14.13 scrap"}],"published":"2026-09-24","updated":"2026-09-24","body":"## What does a boiler manufacturer need that a generic ERP doesn't give?\n\nFour things generic ERPs treat as edge cases are the core of this business. Every boiler is its own project, with its own bill of materials and a chain of milestones across design, procurement, stores, production, QC, dispatch and commissioning. Every plate and tube has to trace back to the material test certificate it arrived with. Every unit ships with statutory paperwork for the Directorate of Boilers under the Indian Boiler Regulations. And steel plate is bought in standard sizes and cut, so stock is pieces with dimensions, not a count. The system Ahrom Labs built for [Shanti Boilers & Pressure Vessels](/work#shanti-boilers) is designed around those four facts rather than bolted onto a generic stock-and-invoice model.\n\n## Can the IBR statutory folder be generated instead of typed?\n\nYes. The folder — covering letter, Forms II(1), III, III A and IV A — is generated from the project's bill-of-materials tree and a bank of material test certificates. The form set is chosen by boiler model, Form IV A's lettered sections come from the BOM's own structure, and certificate PDFs are read by AI on upload. Matching a certificate to the part it covers is suggested by confidence but always confirmed by a person, because it ends up in paperwork an inspector relies on. The details are in [Generating a boiler's IBR statutory folder](/notes/ibr-statutory-folder-from-bom).\n\n## How is material traced from test certificate to finished boiler?\n\nEach test certificate is identified by certificate, cast and plate number together — one certificate number covered four casts in the client's own sample — and one certificate is typically used across several boilers. When a plate is cut, every used piece, remnant and scrap piece is chained to the plate it came from. QC records heat and lot numbers, non-conformance reports with a disposition and a verification step before closing, and hold points that stop a job until inspection clears it.\n\n## How does work move between departments without chasing people?\n\nEach project's milestones declare what they depend on, and the system shows what each one is waiting for, computed fresh every time the page is opened. Procurement runs as a visible pipeline — requests, sourcing, supplier selection, purchase order issued, closed — with new-item requests from engineering accepted into procurement rather than appearing unannounced. On the shop floor, work orders and job cards carry production; engineering has its own calculation sheets linked to the drawings they substantiate.\n\n## What happens to plate offcuts?\n\nThey go back into stock as real pieces with dimensions, and every weight is derived from geometry rather than typed. In a live test, a 157.00 kg plate became 127.17 kg of parts, a 15.70 kg remnant returned to stock, and 14.13 kg of scrap — summing exactly to the source. When a BOM is released, a fitting remnant is reserved before anything is bought. See [Putting steel plate offcuts back into stock](/notes/plate-remnants-back-into-stock).\n\n## Can GST, TDS and accounts live in the same system?\n\nThey do here. The system is the book of record — ledger, GSTR-1 and GSTR-3B, input-tax-credit reconciliation, TDS, reverse charge, fixed assets, bank reconciliation, a books lock and an audit log — with Tally available as an optional sync target. The original plan left regulated accounting to a separate ERP package; in August 2026 that was reversed, and the operations system became the book of record. See [GST, TDS and the general ledger inside a manufacturing ERP](/notes/accounting-inside-the-manufacturing-erp).\n\n## What does the customer see?\n\nA read-only portal: each order as a stepper from order received to commissioning, with overall progress, estimated dispatch, and the QC certificates and packing lists filed under the stage they belong to. A customer only ever sees their own orders.\n\n## Can it stop drawings leaving the office on a USB drive?\n\nThe same system runs a device-control layer on office PCs. USB drives, CDs and DVDs, phones and chosen websites are blocked by default; an employee's attempt to use one files a request, a manager approves it with a one-time code, and access opens for a time-boxed window — 15 minutes by default. Every step is audited.\n\n## Boundary\n\nNesting — planning the layout of several parts on one plate — is not part of the system. E-invoicing is researched and deferred. The generated IBR forms have been checked against the client's real filed documents; the remaining gaps are formatting, and are being closed form by form.","url":"https://ahromlabs.com/industries/boiler-pressure-vessel-manufacturing"},{"kind":"industry","slug":"electronics-component-trading","title":"Software for electronics-component importers and traders","audience":"Electronics-component importers, traders and distributors","answer":"Ahrom Labs built LS Technologies' ERP and CRM with TallyPrime automation, AI extraction of purchase and import paperwork, and a separate inventory system for PCB components and reels. Approved invoices post into Tally within 30 seconds, and five document types — bills of entry included — are extracted at 88–95% zero-correction, each approved by a person.","domain":["trading","accounting-integration","ai-extraction","inventory"],"clients":["ls-technologies"],"services":["tally-integration","document-extraction","custom-erp-crm"],"notes":["tally-voucher-posting","ai-extraction-human-in-the-loop","outgrown-tally-signs"],"patterns":["local-agent-cloud-db","failures-flagged-not-lost","reconcile-against-source-of-truth","human-confirmed-extraction","replay-queued-payload-through-existing-handler","self-healing-sequence-counters"],"evidence":[{"metric":"Approved invoice to Tally voucher","value":"within 30 seconds"},{"metric":"Document types extracted by AI","value":"5, including bills of entry"},{"metric":"Zero-correction rate","value":"88–95% depending on document type"},{"metric":"Documents posted without a person's approval","value":"None"}],"published":"2026-09-24","updated":"2026-09-24","body":"## Why does an import-trading business end up typing the same invoice twice?\n\nBecause operations and accounts live in different places. Purchase orders, supplier invoices, freight bills and customs paperwork arrive as PDFs; the team reads them, works in spreadsheets or a web app, and then someone types the same figures into TallyPrime. [LS Technologies](/work#ls-technologies), an electronics-components import and export business, had exactly that shape. The system Ahrom Labs built reads the documents, keeps the operational record, and posts the accounting entry into Tally itself.\n\n## Can a cloud app post directly into TallyPrime?\n\nNot directly — TallyPrime's integration gateway only listens on the machine that runs Tally. A small agent on that PC bridges the two: it picks up approved invoices every 30 seconds and posts them as vouchers, syncs voucher status and outstanding bills back every 15 minutes, and refreshes ledgers, stock items and voucher types nightly. A ledger that doesn't exist yet in Tally is flagged for a person, never created by the software. See [Posting vouchers into TallyPrime from a cloud app](/notes/tally-voucher-posting); the protocol code is open-sourced as [tally-voucher-xml](https://github.com/ahromlabs/tally-voucher-xml).\n\n## How accurate is AI extraction on import documents?\n\nMeasured on real usage, the share of documents needing no correction is 95% for purchase orders, 94% for purchase invoices, 91% for freight invoices, 89% for bank statements and 88% for bills of entry. Those are lower than the 95–99% many invoice-automation tools advertise — but a 2026 benchmark review found those claims are measured under vendor-chosen conditions, with no independent benchmark ([Parseur](https://parseur.com/blog/ai-invoice-processing-benchmarks)). These are per-document-type numbers from a live system, and every document still waits for a person's approval before anything reaches the books. See [AI extraction with a human in the loop](/notes/ai-extraction-human-in-the-loop).\n\n## What happens when Tally rejects a voucher?\n\nThe failure is classified before anything else happens. If Tally is simply offline, the voucher stays queued and posts on the next 30-second cycle, with no restart. If Tally rejects it — a missing ledger, a duplicate — it's held with the exact reason attached until a person fixes it. After every sync, the system checks what Tally actually holds; a voucher deleted inside Tally is marked unconfirmed again.\n\n## How are components and reels tracked?\n\nIn a separate inventory system built for PCB components and reels. Actions that need approval are queued as the original request and replayed through exactly the same code path once approved, so a pending action can't behave differently from an immediate one. Sequential IDs re-derive their counter from the data before each allocation, healing any drift from manual edits. Moving a reel is written so that two people moving the same reel at the same moment can't both succeed — verified by forcing exactly that race.\n\n## Boundary\n\nThe Tally link depends on the office PC running Tally being switched on; if it's off, nothing posts until it's back, and the queue resumes on its own. This is not a cloud-to-cloud integration, because Tally doesn't offer one.","url":"https://ahromlabs.com/industries/electronics-component-trading"},{"kind":"industry","slug":"interior-design-and-furniture","title":"Software for interior design firms and furniture workshops","audience":"Interior design firms, design-and-build studios and furniture manufacturers","answer":"Ahrom Labs built the backend Savistar (interior design) and Saag (furniture manufacturing) run on: workers, clients, vendors and freight, projects and site visits, workshop orders, and one finance ledger for two sister companies. Each invoice carries the right company's GSTIN, and staff see only their own cash entries — enforced at the server, not hidden in the screen.","domain":["interior-design","manufacturing","finance","access-control"],"clients":["savistar-saag"],"services":["custom-erp-crm","multi-company-finance","operational-intelligence"],"notes":["two-companies-one-book","outgrown-tally-signs"],"patterns":["role-scoped-finance-views","reference-rate-anomaly-detection","derive-balances-dont-store-them","graceful-degradation-by-env-var"],"evidence":[{"metric":"Companies on one book","value":"2 sister concerns, one client list"},{"metric":"Staff view of the cash ledger","value":"Own entries only"},{"metric":"Staff access to bank data","value":"None, refused at the server"},{"metric":"Vendor and freight charges","value":"Checked against reference rates; deviations flagged"}],"published":"2026-09-24","updated":"2026-09-24","body":"## Why do design firms outgrow spreadsheets and chat?\n\nA design-and-build job touches clients, site visits, vendors, labour, freight and payments at once, usually spread across a spreadsheet per project and a chat group per site. The owners know how it all connects; nobody else does. [Savistar and Saag](/work#savistar-saag) — an interior design firm and a furniture workshop under the same owners — moved all of it into one system: workers, clients, vendors and freight, projects and site visits on the design side, workshop orders on the furniture side.\n\n## Can a design studio and its furniture workshop share one system?\n\nYes, and without the weight of a multi-company ERP. Both businesses share one client list and one combined ledger; a design job lives under Projects and a furniture order under Orders, and each invoice carries its own company's letterhead, logo and GSTIN when the PDF is generated. It's a single app modeling two business lines — a sister-concern arrangement, in Indian accounting terms — which is exactly as much structure as the business needs. See [Two companies, one book](/notes/two-companies-one-book).\n\n## How do you stop staff seeing the whole cash book?\n\nBy filtering at the point the data is fetched, not by hiding buttons. Staff log day-to-day cash, but their view of the cash ledger contains only the entries they created. Bank data isn't scoped for staff at all — every request for it from a staff account is refused before any query runs. Owners see everything.\n\n## How are vendor and freight overcharges caught?\n\nEvery incoming charge is compared against a known reference rate, and anything that deviates is flagged automatically — instead of trusting every bill or asking someone to audit each line by hand. It catches overcharges as long as the reference rates are kept current.\n\n## How are partial deliveries and advances tracked?\n\nOutstanding amounts — a partial delivery, an advance against pay — are computed from the underlying transactions every time they're shown, rather than kept as a running total that can drift from the records that produced it.\n\n## What did the owners say?\n\n> \"They really took the time to understand our concerns and requirements, and the system was built the way we had envisioned it. It has brought much more structure, visibility, and control to the way we operate.\"\n>\n> — Sachi & Haripriya, owners of Savistar and Saag\n\n## Boundary\n\nThis is one shared app for two related businesses, not a multi-tenant platform: adding an unrelated third company would mean a different design, not a new setting.","url":"https://ahromlabs.com/industries/interior-design-and-furniture"},{"kind":"service","slug":"custom-erp-crm","title":"Custom ERP, CRM and operations systems","answer":"We build the operational system a business runs on — clients, vendors, workers, orders, projects, purchasing, inventory, production, freight and finance — on one data model instead of a stack of disconnected tools. We model the business first; that model becomes the specification. Built so far for boiler manufacturing, PCB and electronics trading, and interior design.","layer":"records","forWhom":"Any business that has outgrown spreadsheets and off-the-shelf software — where a few people are the only ones who know how everything connects.","evidence":[{"metric":"Systems built","value":"4, for 4 client businesses"},{"metric":"Industries","value":"Boiler and pressure-vessel manufacturing, PCB and electronics trading, interior design, furniture"},{"metric":"Outstanding balances","value":"Computed from source transactions at read time, not stored as a running total"}],"proof":["note/tally-vs-erpnext-vs-custom-erp","note/plate-remnants-back-into-stock","note/two-companies-one-book","pattern/derive-balances-dont-store-them","pattern/replay-queued-payload-through-existing-handler","pattern/self-healing-sequence-counters"],"url":"https://ahromlabs.com/services#custom-erp-crm"},{"kind":"service","slug":"compliance-accounting","title":"GST, TDS and statutory compliance, built in","answer":"We build accounting and statutory compliance into the operations system itself: ledger, GSTR-1, GSTR-3B, ITC reconciliation, TDS, reverse charge, fixed assets, bank reconciliation, audit log and books lock — and industry filings such as a boiler's IBR forms, generated from the BOM. Tally becomes optional. Rates come from a human-verified registry.","layer":"records","forWhom":"Manufacturers and traders who enter every purchase and sale twice — once in operations, once in Tally — and reconcile the two every month.","evidence":[{"metric":"Reports and documents from one computation each","value":"23"},{"metric":"GST returns","value":"GSTR-1 (B2B and HSN) and GSTR-3B"},{"metric":"Bank reconciliation","value":"Auto-matched only when mutually unique; the rest go to a person"},{"metric":"Industry filings","value":"IBR Forms II(1), III, III A and IV A, from the BOM and test certificates"}],"proof":["note/accounting-inside-the-manufacturing-erp","note/ibr-statutory-folder-from-bom","pattern/human-verified-statutory-rates","pattern/auto-match-only-when-mutually-unique","pattern/compute-once-render-many"],"url":"https://ahromlabs.com/services#compliance-accounting"},{"kind":"service","slug":"tally-integration","title":"TallyPrime integration for cloud apps","answer":"We connect cloud business apps to TallyPrime through a small agent on the PC that runs Tally, because Tally's XML gateway only listens locally. Approved invoices post as vouchers within 30 seconds, status syncs every 15 minutes, and masters nightly. Missing ledgers and Tally rejections are held for a person, never auto-created.","layer":"records","forWhom":"Indian businesses whose accounts live in TallyPrime but whose sales, purchase or operations work has moved to a web app — and who are tired of re-keying the same invoice twice.","evidence":[{"metric":"Push cadence","value":"30 seconds"},{"metric":"Voucher and outstanding sync","value":"15 minutes"},{"metric":"Ledger, stock-item and voucher-type masters","value":"24 hours"},{"metric":"Open-source reference library","value":"tally-voucher-xml, 32 tests"}],"proof":["note/tally-voucher-posting","note/outgrown-tally-signs","pattern/local-agent-cloud-db","pattern/failures-flagged-not-lost","pattern/reconcile-against-source-of-truth"],"url":"https://ahromlabs.com/services#tally-integration"},{"kind":"service","slug":"multi-company-finance","title":"Multi-company finance and role-scoped access","answer":"We build finance systems for sister concerns and group companies: one shared client list and ledger, with each invoice carrying the right company's letterhead and GSTIN. Who can see what is enforced where the data is fetched, not hidden in the screen — staff see only their own cash entries, and bank data is refused to them outright.","layer":"operations","forWhom":"Families and partners running two or more related businesses out of one office, who need one view of the money without every employee seeing all of it.","evidence":[{"metric":"Staff view of the cash ledger","value":"Own entries only, enforced in the API query"},{"metric":"Staff access to bank data","value":"None — every bank route rejects staff"},{"metric":"Companies per invoice","value":"Chosen per invoice: letterhead, logo, GSTIN"}],"proof":["note/two-companies-one-book","pattern/role-scoped-finance-views","pattern/separate-machine-from-human-identity"],"url":"https://ahromlabs.com/services#multi-company-finance"},{"kind":"service","slug":"operational-intelligence","title":"Analytics and operational intelligence","answer":"Once the business runs on one model, analytics stops being a spreadsheet export. We build reporting through to advanced analytics on the operational data itself: charges flagged automatically against reference rates, match suggestions ranked by a Laplace-smoothed confidence score, and reports computed from the transactions themselves, so every figure can be traced back.","layer":"analytics","forWhom":"Owners who already have the data but decide on gut feel, because every report means someone stitching exports together by hand.","evidence":[{"metric":"Overcharge detection","value":"Every vendor and freight charge compared against a reference rate; deviations flagged"},{"metric":"Confidence scoring","value":"(approvals+1) / (approvals+rejections+2) ≥ 0.75, minimum 3 approvals"},{"metric":"Workflow blockers","value":"What each milestone is waiting on, computed fresh on every read"},{"metric":"Material yield","value":"Every cut conserved: 157.00 kg → 127.17 used + 15.70 remnant + 14.13 scrap"},{"metric":"Built for","value":"Boiler manufacturing (Shanti Boilers), interior design and furniture (Savistar & Saag)"}],"proof":["note/plate-remnants-back-into-stock","note/same-confidence-different-autonomy","pattern/compute-blockers-on-read","pattern/reference-rate-anomaly-detection","pattern/derive-balances-dont-store-them"],"url":"https://ahromlabs.com/services#operational-intelligence"},{"kind":"service","slug":"document-extraction","title":"AI document extraction with human review","answer":"We build AI extraction for purchase invoices, freight invoices, bills of entry, purchase orders and bank statements. An LLM reads the PDF directly, with a prompt per document type. 88–95% of documents need zero correction, and every one still waits for a person to approve it before anything is posted to the books.","layer":"ai","forWhom":"Trading, import-export and manufacturing businesses whose accounts team spends its day typing GST invoices, customs paperwork and bank statements into Tally.","evidence":[{"metric":"Purchase order, zero-correction rate","value":"95%"},{"metric":"Purchase invoice, zero-correction rate","value":"94%"},{"metric":"Freight invoice, zero-correction rate","value":"91%"},{"metric":"Bank statement, zero-correction rate","value":"89%"},{"metric":"Bill of entry, zero-correction rate","value":"88%"},{"metric":"Documents posted without human approval","value":"None"}],"proof":["note/ai-extraction-human-in-the-loop","note/same-confidence-different-autonomy","note/ibr-statutory-folder-from-bom","pattern/human-confirmed-extraction","pattern/one-confirmation-teaches-the-system"],"url":"https://ahromlabs.com/services#document-extraction"},{"kind":"service","slug":"rag-knowledge-graphs","title":"RAG and knowledge graphs over your business","answer":"Every system we build starts as a model of entities, relationships and decisions — which is already the schema of a knowledge graph. We build retrieval (RAG) and knowledge-graph layers on top, so people and AI agents can ask questions of your records, documents and past decisions and get answers that cite where they came from.","layer":"ai","forWhom":"Businesses whose know-how lives in documents, inboxes and a few senior people's heads, and who want an assistant that answers from their own data rather than the internet.","evidence":[{"metric":"Running on this site","value":"Search and chat over every published page (Ctrl/Cmd+K), and the same index as an MCP server for AI agents"},{"metric":"Running today","value":"Ahrom Labs' own decision corpus, which the AI coding agents in 4 client codebases are instructed to check before any new design"},{"metric":"Inferred relationships","value":"Marked unconfirmed and kept read-only until a person confirms them"},{"metric":"Answers","value":"Cite the record or document they came from"}],"proof":["pattern/unconfirmed-inferences-stay-read-only","pattern/human-confirmed-extraction"],"url":"https://ahromlabs.com/services#rag-knowledge-graphs"},{"kind":"client","slug":"ls-technologies","title":"LS Technologies","answer":"An ERP and CRM with TallyPrime accounting automation and AI document extraction, plus a separate inventory management system for PCB components and reels.","industry":"Electronics-components import/export trading","services":["tally-integration","document-extraction","custom-erp-crm"],"highlights":["Approved invoices post into TallyPrime as vouchers within 30 seconds, through a local agent on the Tally PC.","Five document types — purchase and freight invoices, bills of entry, purchase orders, bank statements — extracted by AI at 88–95% zero-correction, every one approved by a person before posting.","Every posted voucher is checked against Tally's own data; one deleted inside Tally is marked unconfirmed again on the next sync."],"proof":["note/tally-voucher-posting","note/ai-extraction-human-in-the-loop","pattern/replay-queued-payload-through-existing-handler"],"url":"https://ahromlabs.com/work#ls-technologies"},{"kind":"client","slug":"savistar-saag","title":"Savistar & Saag","answer":"The backend both businesses run on: workers, clients, vendors and freight, projects and site visits for Savistar, workshop orders for Saag, and one combined finance ledger.","industry":"Interior design and furniture manufacturing — sister companies","services":["custom-erp-crm","multi-company-finance","operational-intelligence"],"highlights":["Each invoice picks its own company's letterhead, logo and GSTIN from a single shared app.","Staff see only their own cash entries; bank data is refused to staff at the API.","Vendor and freight charges are checked against reference rates and deviations flagged automatically."],"proof":["note/two-companies-one-book","pattern/role-scoped-finance-views","pattern/reference-rate-anomaly-detection"],"url":"https://ahromlabs.com/work#savistar-saag"},{"kind":"client","slug":"shanti-boilers","title":"Shanti Boilers & Pressure Vessels","answer":"A custom ERP, CRM and operational-intelligence system for a boiler and pressure-vessel manufacturer — from bills of materials and material stock to the test certificates that go into statutory quality-control documents.","industry":"Boiler and pressure-vessel manufacturing","services":["custom-erp-crm","compliance-accounting","operational-intelligence","document-extraction"],"highlights":["Each boiler's IBR statutory folder — cover letter and Forms II(1), III, III A and IV A — generated from its bill of materials and a bank of material test certificates.","Plate and section offcuts go back into stock with weight derived from geometry: a 157.00 kg plate became 127.17 kg used, 15.70 kg remnant and 14.13 kg scrap, conserved exactly.","The system is also the book of record: GSTR-1, GSTR-3B, TDS, reverse charge, fixed assets and bank reconciliation, with 23 reports — Tally optional.","Test certificates are matched to the lines they belong to by confidence, but never linked without a person confirming, because they end up in statutory paperwork."],"proof":["note/ibr-statutory-folder-from-bom","note/plate-remnants-back-into-stock","note/accounting-inside-the-manufacturing-erp","note/same-confidence-different-autonomy","pattern/compute-blockers-on-read"],"url":"https://ahromlabs.com/work#shanti-boilers"},{"kind":"question","slug":"cost","title":"How much does a custom ERP cost in India?","answer":"Published 2026 guides put a custom ERP for a small Indian manufacturer at roughly ₹3–15 lakh, and a fuller build for a mid-sized manufacturer at ₹15–40 lakh. Where a project lands depends on scope, and Ahrom Labs quotes a fixed price for each phase once the business has been modeled — never an open-ended hourly bill.","detail":["Those ranges are market figures from other firms' published guides, not our price list. We don't publish a rate card because two businesses that both want \"an ERP\" can need very different systems — the modeling phase is what makes a fixed quote honest."],"sources":[{"label":"ProftCode — custom ERP for Indian manufacturers","href":"https://www.proftcode.com/blog/why-indian-manufacturers-switching-custom-erp/"},{"label":"NGD Technolab — custom ERP for manufacturing companies in India","href":"https://ngendevtech.com/blog/custom-erp-development-for-manufacturing-companies-in-india/"}],"url":"https://ahromlabs.com/engagement#cost"},{"kind":"question","slug":"pricing","title":"How do you price a custom software project?","answer":"In fixed-price phases. Every engagement starts with a short, fixed-fee modeling phase that maps how the business works and ends with a phased plan and a fixed quote for each phase.","detail":["You keep the model and the plan whether or not you continue with us. If you do, the modeling fee is credited against the first build phase.","Each build phase is quoted before it starts, goes live, and is in use before the next one begins — so you never pay for months of work you can't see."],"sources":[],"url":"https://ahromlabs.com/engagement#pricing"},{"kind":"question","slug":"cost-drivers","title":"What makes ERP software cost more or less?","answer":"Mostly how many departments and workflows it covers, what it has to integrate with, how much old data moves in, and whether we've solved the problem before.","detail":["More departments and more hand-offs between them mean more to model and build.","Integrations add work: TallyPrime, e-way bill, bank statements, government portals, software you want to keep.","Migrating years of data out of spreadsheets or an old system is its own piece of work, quoted on its own.","Anything already in our pattern library — Tally posting, document extraction, role-scoped finance, piece-level inventory — costs less, because it's reuse rather than invention."],"sources":[],"url":"https://ahromlabs.com/engagement#cost-drivers"},{"kind":"question","slug":"timeline","title":"How long does a custom ERP take to go live?","answer":"It depends on how new the problem is: something we've already built on another engagement can ship in days or weeks, while genuinely new ground takes a couple of months, delivered in phases so part of it is in use early.","detail":["Every decision from past engagements is written down in our public pattern library, and the developers and AI agents working on a new system check it before designing anything. That's why a repeat problem is fast.","The modeling phase ends with a dated plan per phase, so you know the timeline before you commit to the build."],"sources":[],"url":"https://ahromlabs.com/engagement#timeline"},{"kind":"question","slug":"tally","title":"Will it work with Tally, or do we have to replace Tally?","answer":"Either way works: Tally can stay as your books, with approved invoices posted into it automatically within 30 seconds, or the books can move into the new system with Tally kept as an optional sync.","detail":["LS Technologies kept Tally as the books. Shanti Boilers moved the ledger, GST and TDS into its operations system and kept Tally optional. Which fits depends on how much of your accounting starts as operational events."],"sources":[],"url":"https://ahromlabs.com/engagement#tally"},{"kind":"question","slug":"ownership","title":"Who owns the source code and the data?","answer":"You do. Once the project is paid in full, the custom code we wrote for you and all of your data are yours, transferred in writing.","detail":["We keep ownership of pre-existing, reusable components — internal libraries and tools we use across clients — and give you a permanent licence to use them in your system.","Your data is always yours, at every stage, paid or not."],"sources":[],"url":"https://ahromlabs.com/engagement#ownership"},{"kind":"question","slug":"hosting","title":"Is it hosted on the cloud or on our own server?","answer":"Wherever suits you: during development we host it on our own server at no charge, and at go-live we set up a production cloud server in your name, recommending a provider based on the first interview.","detail":["Server, storage and domain charges are paid by you directly to the provider, so there's no mark-up and no dependency on us to keep it running.","If you'd rather use your own cloud account or your own premises, we deploy there instead."],"sources":[],"url":"https://ahromlabs.com/engagement#hosting"},{"kind":"question","slug":"support","title":"What does the AMC cover, and what does it cost?","answer":"Support after go-live runs under an annual maintenance contract (AMC) of 15–21% of the project value per year — nearer 21% for smaller projects, nearer 15% for larger ones.","detail":["The AMC covers fixes and keeping the system and its server running, plus small changes. A new module is quoted as its own phase.","Support requests come in by email today, answered within 48 hours at most. A ticketing system for urgent work is on its way."],"sources":[],"url":"https://ahromlabs.com/engagement#support"},{"kind":"question","slug":"scope-changes","title":"What happens when requirements change mid-project?","answer":"A change is written down, priced and agreed before it's built — never billed after the fact. Because the model is agreed first, most changes show up as a clear difference from it rather than as a surprise.","detail":["A small change inside the current phase is absorbed into it; a change to how the business is modeled becomes part of a later phase, quoted before it starts."],"sources":[],"url":"https://ahromlabs.com/engagement#scope-changes"},{"kind":"question","slug":"what-goes-wrong","title":"What goes wrong on your projects, and how do you catch it?","answer":"Real bugs, found by testing with real transactions before anyone depended on them — and they're written up in the engineering notes rather than hidden.","detail":["A reverse-charge test that posted real documents through a new ledger found two posting bugs. Running a single order through every department end to end found that sales invoices weren't carrying their project link. Building per-piece plate costing uncovered an existing error in how average cost was recalculated for cut stock — fixed at the root, not patched in the report.","An AI suggestion rule that looked reasonable matched the misspelling PALTE to VALVE as readily as to PLATE; it was caught and narrowed before it shipped."],"sources":[],"url":"https://ahromlabs.com/engagement#what-goes-wrong"},{"kind":"question","slug":"why-erp-fails","title":"Why do ERP implementations fail in Indian SMEs, and what's different here?","answer":"Mostly because people go back to Excel and WhatsApp: the system doesn't match how the business actually runs, and old data turns out messier than anyone planned for. Modeling the business first, scoping data migration up front, and delivering in phases that are each in use before the next target exactly those failures.","detail":["The modeling phase maps your entities, workflows and decisions as they really are, so the system is built around them rather than around a template.","Data migration is scoped and quoted as its own piece of work instead of being discovered halfway through."],"sources":[{"label":"Manufacturing Today India — why ERP projects fail","href":"https://www.manufacturingtodayindia.com/why-erp-projects-fail"},{"label":"Indian Printer Publisher — why traditional ERP systems fail in India","href":"https://indianprinterpublisher.com/blog/2026/05/traditional-erp-systems-india/"},{"label":"Ultra Consultants — causes of ERP project failure","href":"https://ultraconsultants.com/erp-software-blog/15-causes-of-erp-project-failure/"}],"url":"https://ahromlabs.com/engagement#why-erp-fails"},{"kind":"question","slug":"references","title":"Can we see your work or talk to a past client?","answer":"Every client on our work page is named with their permission, and each system is written up in detail with real numbers. Ask, and we'll check whether a past client is willing to talk to you.","detail":["The owners of Savistar and Saag have said publicly: \"It has brought much more structure, visibility, and control to the way we operate.\""],"sources":[],"url":"https://ahromlabs.com/engagement#references"},{"kind":"question","slug":"continuity","title":"You're a small firm — what if you're unavailable?","answer":"The work doesn't depend on one person: a team of developers and AI agents carries the delivery load, and every system ships with a canonical system document that lets any developer or agent pick it up cold.","detail":["That document records what exists, how it fits together, and every known gap — the operations system for Shanti Boilers has one running to thousands of lines, updated as each piece of work lands.","Combined with owning your code and your server, it means you're never locked in: another team could take over from the document alone."],"sources":[],"url":"https://ahromlabs.com/engagement#continuity"},{"kind":"question","slug":"fit","title":"Is my business a fit for a custom system?","answer":"Probably, if your business has outgrown spreadsheets and off-the-shelf software and a few people are the only ones who know how everything connects — industry matters less than the shape of the problem. If a standard package fits your processes, we'll say so.","detail":[],"sources":[],"url":"https://ahromlabs.com/engagement#fit"}]