用于 TallyPrime的实例 A 技能(提取器),通过 Telegram/WhatsApp 解析 PDF/图片中的发票/账单,提取结构化数据(交易方、GSTIN、商品、税额等...
Extract invoice/bill data from PDFs and images received via Telegram or WhatsApp, convert to a canonical JSON payload, and POST to the bridge service on Instance B for Tally entry.
tally-skill on Instance B.reference/extraction-schema.md).Goal: zero manual entry for CAs handling many clients.
/v1/post-voucher).Scope note: This skill is for Instance A (Extractor). It does not post to Tally — that responsibility belongs to tally-skill on Instance B.
Use when:
Do NOT use this skill for:
tallyca runs on Instance B)^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$. If invalid, flag it.taxable_amount × tax_rate / 100 ≈ tax_amount (within ±0.01). If mismatch, flag it.reference/extraction-schema.md state code table.YYYY-MM-DD. Common formats on invoices: DD/MM/YYYY, DD-MM-YYYY, DD.MM.YYYY, MMM DD, YYYY.{companyShort}-{voucherType}-{invoiceNumber}-{date}.User sends PDF or image via Telegram. The document is available for OCR/vision processing.
Before extraction, verify Instance B is reachable:
bash
curl -s -X GET \
-H "Authorization: Bearer $BRIDGE_BEARER" \
"$BRIDGE_URL/v1/health"
Expected response:
json
{
"tally": "ok",
"company_default": "ABC Company",
"version": "1.0.0"
}
If tally: "down", inform user: "Tally is not running on the client machine. Please ask them to open TallyPrime."
Parse the document and extract:
FieldSourceValidationParty nameInvoice headerRequiredParty GSTINNear party name or GST section15-char formatCompany GSTINNear company name or letterhead15-char formatInvoice numberInvoice headerRequiredInvoice dateInvoice headerParse to YYYY-MM-DDItemsLine items tableAt least oneItem descriptionLine itemRequired per itemHSN/SACLine item4-8 digitsQuantityLine itemNumericUnitLine itemOptionalRateLine itemNumericTax rateLine item or GST sectionPercentageCGST/SGST/IGST amountsGST sectionNumericTotalInvoice footerRequired, must balanceNarrationNotes sectionOptional
Document typeVoucher typePurchase invoice (we are buyer)PurchaseSales invoice (we are seller)SalesPayment receiptReceiptPayment voucherPaymentCredit noteCreditNoteDebit noteDebitNote
Clues:
If ambiguous, ask user: "Is this a purchase (you bought) or sales (you sold)?"
Run these checks before sending to bridge:
CheckRuleAction if failsRequired fieldsAll required fields presentList missing fields, ask userGSTIN format15-char regex matchFlag invalid, ask userHSN format4-8 digitsFlag invalid, ask userTax mathtaxable × rate / 100 ≈ taxFlag mismatch, ask userTotal balanceitems + taxes = total (±1)Flag mismatch, ask userDate parseableValid dateAsk user for correct date
Construct the JSON payload per reference/extraction-schema.md:
json
{
"schema_version": "1.0",
"request_id": "uuid-v4",
"idempotency_key": "abc-purchase-xyz-186-20260518",
"company": "ABC Company",
"voucher": {
"type": "Purchase",
"date": "2026-05-18",
"number": "186",
"is_invoice_mode": true,
"voucher_class": null,
"narration": "Against Invoice 186",
"party": {
"name": "XYZ Party",
"gstin": "27AABCU9603R1ZM",
"place_of_supply": "Maharashtra",
"registration_type": "Regular"
},
"company_gstin": "27AABCU9603R1ZN",
"items": [...],
"taxes": {...},
"total": 46199.83,
"bill_allocations": [...]
},
"source": {
"kind": "pdf",
"filename": "invoice_186.pdf",
"extracted_at": "2026-05-18T10:30:00Z"
},
"confidence": {
"overall": 0.93,
"fields": {...}
}
}
Send the JSON to Instance B:
bash
# Compute HMAC
BODY='{"schema_version":"1.0",...}'
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$BRIDGE_HMAC_SECRET" | cut -d' ' -f2)
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BRIDGE_BEARER" \
-H "X-Signature: hmac-sha256=$SIGNATURE" \
-H "Idempotency-Key: abc-purchase-xyz-186-20260518" \
-d "$BODY" \
"$BRIDGE_URL/v1/post-voucher"
Full HTTP contract in reference/bridge.md.
json
{
"status": "posted",
"guid": "abc-purchase-xyz-186-20260518",
"voucher_number": "186",
"company": "ABC Company",
"summary": "Purchase voucher posted: XYZ Party, ₹46,199.83",
"masters_created": ["XYZ Party"]
}
Reply to user (see reference/prompts.md):
Entry posted to Tally.
Company: ABC Company
Type: Purchase
Party: XYZ Party
Invoice No: 186
Date: 18 May 2026
Amount: ₹46,199.83 (Taxable: ₹39,152.40 + IGST: ₹7,047.43)
New ledger created: XYZ Party
json
{
"status": "needs_clarification",
"missing_fields": ["voucher.voucher_class"],
"message": "Please confirm the voucher class name (e.g., 'Purchase @ 18 %')."
}
Forward the question to the user.
json
{
"status": "error",
"error_code": "TALLY_UNREACHABLE",
"message": "Could not connect to Tally."
}
Reply to user: "Tally is not responding. Please check that TallyPrime is open and try again."
The company name must match exactly in TallyPrime. Strategies:
/v1/health returns company_defaultSome Tally companies use voucher classes for automatic GST splitting. This skill does NOT know which class to use — that's Instance B's job.
voucher_class: null and let Instance B handle itneeds_clarification for class, forward to userAssign confidence scores based on:
Extraction qualityConfidenceClear text, high contrast, exact match0.95 - 1.0Readable but some ambiguity0.7 - 0.94Blurry, low contrast, guessed0.4 - 0.69Highly uncertain0.0 - 0.39
Fields with confidence < 0.7 should be flagged for user confirmation before posting.
First 2 digits of GSTIN → Place of Supply:
CodeState01Jammu and Kashmir02Himachal Pradesh03Punjab04Chandigarh05Uttarakhand06Haryana07Delhi08Rajasthan09Uttar Pradesh10Bihar11Sikkim12Arunachal Pradesh13Nagaland14Manipur15Mizoram16Tripura17Meghalaya18Assam19West Bengal20Jharkhand21Odisha22Chhattisgarh23Madhya Pradesh24Gujarat25Daman and Diu26Dadra and Nagar Haveli27Maharashtra28Andhra Pradesh (Old)29Karnataka30Goa31Lakshadweep32Kerala33Tamil Nadu34Puducherry35Andaman and Nicobar Islands36Telangana37Andhra Pradesh38Ladakh
There is exactly one chat interface (Telegram on Instance A). Instance B has no Telegram/WhatsApp — only the bridge HTTP endpoint.
Rendering diagram...
EnvironmentInstance AInstance BTally accessProductionYour EC2 (Telegram, Codex Plus)Client mini-PC beside TallyB → http://localhost:9000 (no ngrok for Tally)Dev / testingSame Ubuntu EC2Second OpenClaw on same EC2B uses ngrok URL to remote Tally
Instance A reaches B via an inbound tunnel on the client box ( ngrok http 8787 or Cloudflare Tunnel). Share BRIDGE_URL, BRIDGE_BEARER, and BRIDGE_HMAC_SECRET with the team running A.
StepSettingValue1HostUbuntu EC2 (or dev server)2InstallNode.js, OpenClaw3LLMOpenAI ChatGPT Codex Plus session (subscription)4Skill loadedtally-extractor-skill/ only — do not load tally-skill/5ChannelTelegram bot token on A only6BRIDGE_URL``https://<client-tunnel>.ngrok.app (no trailing slash)7BRIDGE_BEARERShared secret from client team8BRIDGE_HMAC_SECRETShared HMAC secret from client team9Do not setTALLY_URL on Instance A10Preflightcurl -H "Authorization: Bearer $BRIDGE_BEARER" $BRIDGE_URL/v1/health → tally: ok
reference/extraction-schema.md — Full JSON schema, examplesreference/bridge.md — Endpoints, auth, errorsreference/prompts.md — User-facing messages