Reconcile CIPC BizPortal against the Workflow database for every
director ID number we hold, and report only the two kinds of
difference that need a decision:
1. new appointment - CIPC lists the person as a director of company
X, the database does not.
2. no longer a director - the database lists the person at company X,
CIPC no longer shows them there.
Everything that is in sync is not reported, so the list stays short
enough to work through by hand.
- New page /cipc-report with an explorer that runs the check, polls
the run and lists the exceptions.
- New API route /api/cipc/report: POST starts a background batch run
of the existing "Bizportal Directopr id no search.py" (invoked
exactly as it already behaves, including its own login rotation),
GET returns run status and the exception lists. ?demo=1 returns a
fixed sample so the screen can be shown without a live run.
- New action route /api/cipc/report/action for the per-exception
follow-up actions.
- scripts/cipc-report/build_report.py renders the run into a PDF with
the exceptions marked, plus the sample report used by ?demo=1.
The BizPortal tooling only exists on the Windows dev machine, so like
the other CIPC routes this tab cannot run the live search from the
Coolify Linux image.
|
||
|---|---|---|
| .. | ||
| cipc-report | ||
| __test_capture.py | ||
| _add_field.py | ||
| _capture_periods.py | ||
| _check_apostrophe.py | ||
| _check_deems.py | ||
| _check_signature_images.py | ||
| _cipc_service.js | ||
| _clear_ufc_documents.py | ||
| _compare_doc_text.py | ||
| _compare_docs.py | ||
| _create_vat_collection.js | ||
| _create_vat_v2.js | ||
| _date_test.py | ||
| _dump_doc_text.py | ||
| _inspect_collection.py | ||
| _inspect_tesh.py | ||
| _null_test.py | ||
| _patch_vat_fields.js | ||
| _patch_vat_schema.js | ||
| _probe_res_docs.py | ||
| _probe_res_docs2.py | ||
| _regex_test.py | ||
| _schema_check.py | ||
| _schema_inspect.py | ||
| _schema_test.py | ||
| _show_table.py | ||
| _upload_signed_resolution.py | ||
| _verify.py | ||
| _verify_pb.py | ||
| _verify_signed_now.py | ||
| backfill_client_status.mjs | ||
| BACKUP_SARS login Company spesific_20261008-181711.py | ||
| Bizportal Directopr id no search.py | ||
| Bizportal Enterprise No. Search.py | ||
| Bizportal Search.py | ||
| bizportal_service.py | ||
| build_resolution_master.py | ||
| build_shareholder_master.py | ||
| check-mail.ps1 | ||
| cleanup_ab.ps1 | ||
| init_pb_schema.py | ||
| manage_sidecar.sh | ||
| mark_otp_required.py | ||
| otp_fill.py | ||
| pb_migration_shareholder_certificates.mjs | ||
| pb_migration_shareholder_change.mjs | ||
| README.md | ||
| requirements.txt | ||
| SARS login Company spesific.py | ||
| SARS retun EMP201 push to PB.py | ||
| SARS Retun EMP201 single or all pty.py | ||
| SARS Retun History Period.py | ||
| SARS retun VAT201 push to PB.py | ||
| SARS Retun VAT201 single or all pty.py | ||
| SARS Return EMP201 data.py | ||
| sars_login.py | ||
| SARS_login_Select_Company.py | ||
| sars_login_service.py | ||
| SARS_retun_History_select.py | ||
| SARS_Return_Search.py | ||
| screenshot_dashboard.py | ||
| smoke_phase6.py | ||
| subst.py | ||
| subst_shareholder.py | ||
| task_worker.py | ||
BizPortal 2.0 — Python Playwright service
Sidecar that drives https://www.bizportal.gov.za via a real headless
Chromium. The Next.js side talks to it over HTTP — no LLM agent, no
file queue, no polling. Sits next to the existing /bizportal page
without touching it.
Why this exists
The existing /bizportal page queues searches to /tmp/bizportal_queue/
and an LLM agent reads the page snapshot, decides what to click, and
writes the result back. Fast enough when it works, but the agent is
the brittle bit — it misreads the page, times out, or clicks the
wrong button. This service replaces that with a deterministic Python
script.
Architecture
Browser ─► /bizportal-2 ─► /api/bizportal-2/* ─► this service ─► BizPortal
│
└─► PocketBase (via service)
The Python service holds a persistent storage_state.json so the
user is logged in once and every subsequent request reuses the
session.
Setup
cd scripts
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
playwright install chromium
cp .env.example .env # fill in real credentials
Run
# Dev
uvicorn bizportal_service:app --host 127.0.0.1 --port 7001 --reload
# Production (Coolify / systemd)
uvicorn bizportal_service:app --host 127.0.0.1 --port 7001 --workers 1
Keep --workers 1 — the script uses a single shared browser context
to preserve the logged-in session.
Endpoints
GET /health
{
"ok": true,
"service": "bizportal-2",
"playwright_ready": true,
"logged_in_cached": true,
"headless": true
}
POST /search
{ "type": "ck", "query": "2022/808158/08" }
{
"ok": true,
"query": "2022/808158/08",
"type": "ck",
"results": [
{
"enterprise_no": "2022/808158/08",
"enterprise_name": "AMPLEFORTH TRADING (PTY) LTD",
"enterprise_type": "Private Company",
"enterprise_status": "Business is in Business",
"directors": [
{
"id_number": "870922XXXX08X",
"names": "DAVID",
"surname": "ELS",
"status": "Active",
"type": "Director"
}
]
}
],
"log": ["Navigating to login", "..."]
}
POST /reserve
{ "names": ["AMPLEFORTH PTY LTD", "AMPLEFORTH TRADING", "AMPLEFORTH SA", "AMPLEFORTH HOLDINGS"], "dry_run": false }
{
"ok": true,
"balance_before": 412.50,
"status": "bizportal_done",
"log": ["..."],
"message": "Submitted via Bizportal. The R50 fee will be debited later."
}
Tunable selectors
BizPortal uses ASP.NET WebForms (cntMain_* IDs). The known-stable
IDs for login and name reservation are baked in. The search field
IDs are not 100% verified — try a search after starting the service
once and tune the DIRECTOR_SEARCH_FIELD_CANDIDATES /
ENTERPRISE_SEARCH_FIELD_CANDIDATES lists at the top of
bizportal_service.py if you get a "Could not find …" error.
The page URL is logged in the error so you can inspect the live page in your browser and grab the right field ID.
Resetting the session
rm scripts/storage_state.json
# Restart the service — it will log in once and re-cache the session.
Running as a systemd service
[Unit]
Description=BizPortal 2.0 Python service
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/workflow/scripts
EnvironmentFile=/var/www/workflow/scripts/.env
ExecStart=/var/www/workflow/scripts/.venv/bin/uvicorn bizportal_service:app --host 127.0.0.1 --port 7001 --workers 1
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
What this does NOT replace
- The existing
/bizportalpage (kept untouched) - The
/api/bizportal/search/*queue routes (kept untouched) - The
/api/register/[id]/apply-bizportalroute (kept untouched) - The PocketBase collections and bulk-add / link logic
If you decide to retire the old flow, see
scripts/README.rollback.md (TODO if you want one).