# CA Automator

A practice desk for a chartered accountant. It runs in a browser, works with no
connection, saves what you type to the device within a second and to the server
every five minutes, and produces every working paper as either Excel or Word.

---

## Read this first

**Every regulatory figure in this package is an unverified placeholder.** The rate
master ships with 52 rows — tax slabs, pool rates, penalty fees, SSF percentages —
each marked `UNVERIFIED`, seeded only so the engines run on day one. They are not a
legal source and were not copied from a verified reading of the Act.

Before this touches a client file, open **Rate master**, check each row against the
Income Tax Act 2058, the current Finance Act and the relevant IRD circulars, correct
the value, record the clause, and mark it verified. Until you do, every computation
shows a red count and every exported Excel and Word file carries a warning line
naming the specific unverified rates it relied on.

This is the same convention as your IPO advisory system and the due diligence room:
nothing regulatory is written into code, so a Finance Act change is a data edit, not
a rebuild.

---

## Running it in five minutes

```bash
pip install -r requirements.txt

cd server
python3 run.py init                              # create database, load seed data
python3 run.py adduser "Your Name" you@firm.com  # prompts for a password
python3 run.py serve                             # http://localhost:8000
```

In a second terminal, start the background worker:

```bash
cd server && python3 run.py worker
```

Docker instead:

```bash
docker compose up -d
docker compose exec app python run.py adduser "Your Name" you@firm.com
```

---

## What is in the box

```
server/     schema.sql   22-table database schema (SQLite now, Postgres notes included)
            seed.sql     rate master, obligations, report templates, blog categories
            db.py        data layer, password hashing, autosave and conflict logic
            engines.py   six calculation engines, all rate-master driven
            reports.py   Excel (openpyxl) and Word (python-docx) builders
            app.py       WSGI application — the whole API
            run.py       dev server, admin CLI, night worker, backups

web/        index.html   app shell
            style.css    the house style
            engines.js   the same six engines, in the browser, for offline work
            app.js       offline store, autosave heartbeat, sync, module views
            sw.js        service worker — the app opens with no connection

content/    blog/        six finished articles, ready to publish
            SEO-PLAN.md  the strategy plus the next forty article titles
            import_blog.py

deploy/     nginx, systemd units;  Dockerfile and docker-compose.yml at the root
```

---

## The four things you asked for, and how each works

### Online and offline

The front end is a progressive web app. The service worker caches the shell on first
visit, so the app opens with no connection at all — including installed to a phone
home screen or a desktop dock.

The calculation engines exist twice: `server/engines.py` and `web/engines.js`. They
are line-for-line equivalents and are verified to produce identical output on the
same inputs. That is what makes offline work real rather than degraded — you get the
same numbers on a plane that you get at your desk.

What genuinely needs the server: adding a client, verifying a rate, and the formatted
`.xlsx` / `.docx` files. Everything else runs locally.

### Autosave every five minutes

Two layers, because one is not enough:

- **Local, ~1 second after you stop typing.** Written to IndexedDB. Nothing waits on
  the network, so a dropped connection or a closed laptop never costs you work.
- **Server, every 5 minutes.** The heartbeat pushes everything dirty. Offline, the
  beat is queued and the dial shows it hollow instead of filled. On reconnect the
  whole queue goes up in one batch, in sequence order.

Every beat writes a row to `autosave_snapshot`, so you can restore any earlier state
from the history endpoint. Idle beats are detected by checksum and do not write a
snapshot, so the table does not fill with identical copies overnight.

If two devices edited the same workfile, the server refuses to overwrite: the
incoming version is parked in `sync_conflict` and you are told to compare, rather
than one machine silently winning.

The **shift dial** in the header is the readout — a 24-hour arc, one pip per beat
today, filled when synced and hollow when queued, with the count of files still
waiting in the middle.

### Excel and Word for every report

Six modules × two formats = twelve builders, all working. Excel output uses real
number formats, frozen panes, banded rows and a totals row; Word output uses your
firm heading, a proper table and a prepared-by / reviewed-by block.

Offline, the export buttons still work — Excel becomes CSV and Word becomes a
Word-compatible HTML file that opens directly in Word. Reconnect and export again for
the fully formatted version. The button never simply fails.

### Ready to host, with a database

`app.py` is a WSGI application using only the standard library, so it runs under
gunicorn, waitress, mod_wsgi or the bundled dev server, with no framework to keep up
with. Sample nginx config and systemd units are in `deploy/`.

It runs on SQLite as shipped — that's the default, zero-config path, and needs no
separate database server at all. This is already inexpensive to host: a small VPS
(no special requirements — plain Python 3, no compiled dependencies) runs it fine.

**MySQL is also fully supported**, for hosts (typically cPanel/shared hosting) that
offer MySQL but not a persistent-disk SQLite-friendly setup:

```bash
export CA_DB_ENGINE=mysql
export CA_MYSQL_HOST=localhost      # CA_MYSQL_PORT/USER/PASSWORD/DATABASE too
python3 server/run.py init          # creates the schema from schema_mysql.sql
```

To move an existing SQLite installation's data across (safe to re-run; only ever
reads the SQLite file, never writes to it):

```bash
cd server
python3 migrate_sqlite_to_mysql.py --sqlite-path ../data/ca_automator.db \
    --mysql-host localhost --mysql-user root --mysql-password '' \
    --mysql-database ca_automator
```

`server/schema_mysql.sql` is generated from `server/schema.sql` — the single source
of truth stays the SQLite file; regenerate the MySQL one from it, don't hand-edit
either independently. **cPanel specifically**: this needs a host whose control panel
offers "Setup Python App" (CloudLinux + Passenger) — most modern cPanel hosts do, but
budget plans sometimes don't, so confirm before committing. `server/app.py` already
exposes the plain WSGI `application(environ, start_response)` callable that feature
expects; point it at `server/app.py` and set the `CA_DB_ENGINE`/`CA_MYSQL_*`
environment variables in the app's config, same as any other WSGI deployment.

---

## The modules

| Code | Module | What it does |
|---|---|---|
| DP | Depreciation | Schedule 2 pools, absorption tranche from the Nepali purchase month, section 16 repair ceiling, de-minimis write-off, negative-base gain, special industry uplift |
| TD | Salary & TDS | Employee-wise annual computation, both retirement deduction limbs, insurance caps, monthly deduction for remaining months |
| BT | Business income tax | Book profit to taxable income line by line, disallowances, depreciation swap, loss utilisation, credits |
| PN | Fees & interest | Sections 117 / 118 / 119 and VAT charges, each with its basis of computation spelled out |
| LN | Loan & EMI | EMI or equal-principal, moratorium with capitalised interest, cost of borrowing, journal entries |
| LS | Lease (NFRS 16) | Liability at present value, ROU asset, both payment timings, period-wise unwinding |

Adding a seventh is a registry entry in `web/app.js` plus an engine function and two
report builders. Your existing tools — the FS generator, the projection model, the
due diligence room, the FDI automator, the grant and IPO systems — plug in the same
way, and this shell gives them shared clients, autosave, sync and report output
instead of each carrying its own.

---

## The blog engine

Six finished articles are in `content/blog/`, loaded into the database with:

```bash
python3 content/import_blog.py --publish
```

Each carries its own slug, title, meta description, focus keyword, category, schema
type and a FAQ block that becomes FAQPage structured data — the thing that wins the
expandable answers in search results.

They deliberately target **confusion queries** rather than fact lookups. Someone
searching "VAT rate Nepal" wants a number and will leave; someone searching "why
doesn't my VAT return match my books" has a problem and is one step from calling you.
`content/SEO-PLAN.md` explains the strategy and lists the next forty titles in
publishing order.

One rule runs through all of them: **no article states a current rate or due date as
settled fact.** They explain mechanisms, which stay true for years, and flag that
figures change each Finance Act. Every post carries a `review_due_on` field. A stale
number published under your name does more reputational damage than no article at all.

Rendering the posts as public pages is the piece not built here — the content, the
schema and the loader are ready, and they need dropping into whatever your firm's
website runs on.

---

## What is not built

Being straight about the edges:

- **No public blog front end.** Content and database are ready; the rendering layer is not.
- **No document upload UI.** The `document` table and storage directory exist; the screen does not.
- **The conflict merge screen** is an API endpoint, not yet a UI. Conflicts are detected, recorded and reported, but resolving one currently means an API call.
- **Deadlines are populated by API, not by a screen**, and the obligation rules that would generate them automatically are seeded but unverified.
- **Single firm per installation.** The schema supports several; the API scopes to firm 1.
- **No password reset flow.** Use `run.py adduser` for now.

---

## Operations

```bash
python3 run.py backup     # timestamped copy, 30-day retention
python3 run.py worker     # reminders, queued reports, pruning, nightly backup
curl localhost:8000/api/health
```

Autosave snapshots are pruned to 120 days and 200 per file by the worker; both are
configurable in the `setting` table.

**Before you go live:** serve over HTTPS (service workers require a secure origin),
run the app as a non-root user, keep `CA_DATA_DIR` outside the web root, and take the
backups somewhere other than the server they came from.
