AO = Affordability Online
AO is one of several application channels that feed credit scoring requests into the HomeChoice
HCScore system. It represents online affordability assessments where the applicant submits only
their ID number and cell number — no full name is provided up front.
| Channel | Webhook Code | Source Table | Hook File | Response Format |
|---|---|---|---|---|
| AO (Affordability Online) | HCSCOREAO |
bloom_hcscoreao_payload |
ObjHookHCScoreAO.py |
Simplified (AO_* fields) |
| CR (Credico) | HCSCORECR |
bloom_hcscorecr_payload |
ObjHookHCScoreCR.py |
Full scoring response |
| WA (WhatsApp) | HCSCOREWA |
bloom_hcscorewa_payload |
ObjHookHCScoreWA.py |
Full scoring response |
All channels normalize into bloom_hcscore and run the same HCSCORE workflow.
The _guid column has been renamed to descriptive named columns across all webhook hooks:
| File | GUID column name | Used in |
|---|---|---|
ObjHookHCScoreCR.py |
hcscorecr_guid |
forward_to_gateway WHERE |
ObjHookHCScoreWA.py |
hcscore_guid (INSERT), hcscorewa_guid (SELECT/WHERE) |
|
ObjHookHCScoreAO.py |
hcscore_guid (INSERT), hcscoreao_guid (SELECT/WHERE) |
|
ObjHookHCScoreCR_UAT.py |
hcscore_guid (INSERT), hcscorecr_uat_guid (SELECT/WHERE) |
https://api.hcapi.co.za/
All webhook calls (AO, CR, WA) are routed through this endpoint.
| Server | Role | IP | State |
|---|---|---|---|
| HCAPI-LIVE | Production API | 13.246.105.193 (EC2 af-south-1) | running |
| Homechoice AO Live - api.hcapi.co.za | AO Live (older) | 172.31.32.234 (internal) | stopped |
| Homechoice AO - UAT 2025-09 | UAT (Sep 2025) | 13.246.200.113 | running |
| Homechoice_AO_UAT | UAT | 13.244.97.211 | running |
UAT image tag: HC-AO-20250916
| Environment | Host | Schema |
|---|---|---|
| Development | db.ashpool.co.za |
hcapi.live |
| Production (RDS) | 13.245.52.247 |
hcapi.live |
Development credentials (local dev only):
db.ashpool.co.zaaxion1@Lemmings1206hcapi.livedef_webhook.HCSCOREAO)| Field | Value |
|---|---|
WebhookCode |
HCSCOREAO |
Package |
HOMECHOICE |
Description |
HomeChoice scoring proxy |
Active |
N (currently inactive in hcapi.live) |
Direction |
IN |
BaseUrl |
https://api.hcapi.co.za/ |
Httpmethod |
POST |
DataKey |
Guid |
ServiceCode |
HOMECHOICE |
Runs before the hook normalizes data. Inserts an initial staging record:
INSERT INTO data_hcscore(guid, scorecategory, calldate)
SELECT hcscore_ AS guid, scorecategory, now()
FROM bloom_hcscore_payload
WHERE Hcscore_ LIKE '$guid$'
NOTE: References bloom_hcscore_payload, not bloom_hcscoreao_payload. This appears to be
a legacy/shared staging table name.
Runs after the hook completes. Resolves Leadorigin on bloom_hcscore by matching the
applicant against homechoice.axion.data_external_leads:
UPDATE bloom_hcscore AS B
LEFT JOIN (
SELECT B.hcscore_guid, SmsLocation, InsertDate
FROM bloom_hcscore AS B
LEFT JOIN `homechoice.axion`.data_external_leads AS L
ON B.Idnumber = L.Idnumber OR RIGHT(B.Cellno, 9) = L.Cellshort
WHERE B.hcscore_guid = '$guid$'
AND DATEDIFF(NOW(), L.InsertDate) < 14
ORDER BY L.InsertDate DESC
LIMIT 1
) AS L ON B.hcscore_guid = L.hcscore_guid
SET B.Leadorigin = CASE
WHEN smslocation IS NULL THEN '4'
WHEN smslocation = '' THEN '4'
WHEN smslocation BETWEEN '30000' AND '90000' THEN '2'
WHEN smslocation IN ('99991') THEN '1'
WHEN smslocation IN ('99994') THEN '3'
WHEN smslocation IN ('99995','1000007') THEN '5'
WHEN smslocation IN ('99996') THEN '6'
WHEN smslocation IN ('99997') THEN '7'
WHEN smslocation IN ('99998') THEN '8'
WHEN smslocation IN ('1000001','1000002','1000003','1000004','1000005','1000006') THEN '3'
ELSE '99'
END
WHERE B.hcscore_guid = '$guid$'
Key points:
IDNumber OR last-9-digits of cell number (Cellshort)'99' (unknown, AO-specific — not '10' like other channels)Leadorigin is NOT statically hardcoded for AO; it is dynamically resolved'99' as the fallback when no lead match is found.| SmsLocation | Leadorigin | Meaning |
|---|---|---|
| NULL or empty | 4 |
No source |
| 30000–90000 | 2 |
|
| 99991 | 1 |
|
| 99994 | 3 |
|
| 99995, 1000007 | 5 |
|
| 99996 | 6 |
|
| 99997 | 7 |
|
| 99998 | 8 |
|
| 1000001–1000006 | 3 |
|
| No lead match | 99 |
AO default — unresolved |
The AO response is not the same as the main HCSCORE response. It returns a simplified
scoring result with AO_-prefixed fields:
SELECT
InF1.Guid AS AO_Guid,
InF2.Idnumber AS IdNumber,
MAX(Inf3.updatedate) AS AO_ScoreDate,
InF1.Score AS AO_Score,
InF1.StrikethroughIndicator AS AO_Strikethrough,
InF2.cst_fraud AS AO_CST_Fraud,
InF2.cst_deceased AS AO_CST_Deceased,
InF2.cst_emigrated AS AO_CST_Emigrated
FROM data_scorestaging AS InF1
LEFT JOIN bloom_donormalenquiry AS Inf2 ON InF1.guid = Inf2.guid
LEFT JOIN bloom_donormalenquiry AS Inf3
ON InF2.IDnumber = Inf3.IDNumber AND Inf3.updatedate > 0
WHERE InF1.Guid = '$guid$'
Source tables:
data_scorestaging — scorecard output (score, strikethrough)bloom_donormalenquiry — bureau response (CST fraud/deceased/emigrated flags, latestIDNumber)| Parameter | Required | Description |
|---|---|---|
Guid |
Yes | External reference GUID from the AO system |
IDNumber |
Yes | South African 13-digit ID number |
Cellno |
Yes | Applicant mobile number |
| Parameter | Direction | Source | Description |
|---|---|---|---|
AO_Guid |
OUT | data_scorestaging.Guid |
Echo of the request GUID |
IdNumber |
OUT | bloom_donormalenquiry.Idnumber |
ID number used for scoring |
AO_ScoreDate |
OUT | MAX(bloom_donormalenquiry.updatedate) |
Most recent bureau date for this ID |
AO_Score |
OUT | data_scorestaging.Score |
Numeric score |
AO_Strikethrough |
OUT | data_scorestaging.StrikethroughIndicator |
Approval/decline indicator |
AO_CST_Fraud |
OUT | bloom_donormalenquiry.cst_fraud |
Fraud flag from bureau |
AO_CST_Deceased |
OUT | bloom_donormalenquiry.cst_deceased |
Deceased flag from bureau |
AO_CST_Emigrated |
OUT | bloom_donormalenquiry.cst_emigrated |
Emigrated flag from bureau |
The AO response is deliberately simplified compared to the full HCSCORE response. It does
not return credit limit, scorecard category, pinpoint variables, or lead wording.
The Python hook (ObjHookHCScoreAO.py) reads from bloom_hcscoreao_payload and inserts a
normalized record into bloom_hcscore before triggering the HCSCORE workflow.
| bloom_hcscore column | AO value / logic |
|---|---|
hcscore_guid |
From payload (previously _guid) |
Guid |
From payload (Guid) — external AO reference |
Acctclass |
'0' (default) |
Acctclasstype |
'0' (default) |
Applicationchannel |
'1' (WebApp) |
Cellno |
From payload (CellNo) |
Cellotherno |
'' (not provided) |
Ctryno |
'1' (South Africa) |
Deceasedflag |
'False' |
Dob |
Derived from SA ID (see below) |
Firstname |
'x' (placeholder — AO does not provide names) |
Surname |
'x' (placeholder) |
Gender |
Derived from SA ID digit 7 (see below) |
Homeno |
'' (not provided) |
Idnumber |
From payload (IDNumber) |
Bloom_idnumber |
Same as Idnumber |
Idtypeno |
'1' (RSA ID) |
Msca |
'99999' (new account placeholder) |
Passportflag |
'N' |
Rtprofile |
'0' |
Fcprofile |
'0' |
Scorecardno |
'1' (new scorecard) |
Scorecategory |
'N' (new category) |
Workno |
'' (not provided) |
Department |
'46' (fixed AO department code) |
Leadorigin |
NOT set at insert time — resolved later by PostSql |
Date of Birth:
IF(LENGTH(IDNumber) = 13,
CONCAT(IF(LEFT(IDNumber, 2) < '23', '20', '19'), LEFT(IDNumber, 6)),
''
)
Century prefix: 20xx if first two digits < 23, else 19xx. Produces YYYYMMDD string.
Gender:
IF(LENGTH(IDNumber) = 13,
IF(MID(IDNumber, 7, 1) < 5, 'F', 'M'),
'F'
)
Digit 7 of SA ID: 0–4 = Female, 5–9 = Male. Defaults to 'F' if ID not 13 digits.
The HCSCORE workflow in def_workflows has the following steps:
| Rank | Name | Type | Notes |
|---|---|---|---|
| 0 | HCSCORE | API | Entrypoint — receives guid |
| 10 | HC_Prescore | CALC | Pre-scoring validation and preparation |
| 20 | Table | CALC | Table-based lookup (country routing) |
| 30 | HCEXPERIAN | SERVICE | Experian SA bureau call |
| 40 | HCEXPERIAN_LES | SERVICE | Experian Lesotho |
| 50 | HCEXPERIAN_NAM | SERVICE | Experian Namibia |
| 60 | HCEXPERIAN_ESW | SERVICE | Experian Eswatini |
| 70 | ScorecardSelection | CALC | Selects which scorecard to apply |
| 80 | PayingSeg | SCORECARD | Paying segment scorecard |
| 90 | NonPayingSeg | SCORECARD | Non-paying segment scorecard |
| 100 | ThinFile | SCORECARD | Thin-file scorecard |
| 110 | NoReturn | CALC | Handles no-return cases |
| 120 | HC_Postscorecard | CALC | Post-scorecard calculations |
| 130–170 | Postnoscore / PostNoScore | CALC | Various no-score paths |
| 180 | HC_Postscorecard | CALC | Second post-scorecard pass |
| 190 | DC_RecentScore | CALC | Recent score duplicate check |
| 200 | PostError | CALC | Error handling |
| 210 | END NODE | DONE | Terminal state |
| 220 | DC_InvalidCountryCode | CALC | Invalid country code handling |
| 230 | HC_Postscorecard_b | CALC | Additional postscorecard step |
| 240 | seg_limits | SEGMENT | Segment limit checks |
Routing (from Flow diagram):
HC_Prescore routes to one of: RSA bureau, LES/NAM/ESW bureaus, DC_InvalidCountryCode,ScorecardSelection; others go to PostnoscoreScorecardSelection routes to one of: PayingSeg, NonPayingSeg, ThinFile, or NoReturn| Aspect | AO | CR (Credico) | WA (WhatsApp) |
|---|---|---|---|
| Source table | bloom_hcscoreao_payload |
bloom_hcscorecr_payload |
bloom_hcscorewa_payload |
Leadorigin at insert |
NOT set | '14' hardcoded |
'14' hardcoded |
Leadorigin via PostSql |
From data_external_leads |
Not present | Not present |
Leadorigin default (no match) |
'99' |
N/A | N/A |
Department |
'46' |
'46' |
'46' |
| Post-score callback | POST to hcawsgateway.technocore.co.za |
POST to hcawsgateway.technocore.co.za |
POST to hcawsgateway.technocore.co.za |
| ShortCode usage | Not used | '100003' (SMS location) |
Used for smsLocation |
| Response format | Simplified AO_* fields |
Full scoring response incl. lead wording | Full scoring response incl. lead wording |
ClientMessage / LeadType |
Not returned | Derived from lead wording tables | Derived from lead wording tables |
| ATB/marketable flag check | No | No | Yes (leads_hc_data.marketableflag) |
| Table | Schema | Purpose |
|---|---|---|
bloom_hcscoreao_payload |
hcapi.live | Raw AO webhook payloads — staging before normalization |
bloom_hcscore |
hcapi.live | Normalized scoring requests — all channels |
bloom_hcscore_payload |
hcapi.live | Shared legacy staging (referenced in PreSql) |
data_scorestaging |
hcapi.live | Scorecard output — score, strikethrough, maxterm |
bloom_donormalenquiry |
hcapi.live | Bureau response buffer — CST flags, payload, date |
data_hcscore |
hcapi.live | Score tracking table (guid, scorecategory, calldate) |
data_external_leads |
homechoice.axion | External lead source — used for Leadorigin resolution |
data_client_rdg |
hcapi.live | Persistent random digit groups per ID number |
data_dim_scorecat |
hcapi.live | Score category dimension table |
| Column | Description |
|---|---|
guid |
Links to data_scorestaging.guid |
IDnumber |
Applicant ID — used for self-join to get latest bureau date |
updatedate |
Bureau call timestamp — AO response returns MAX(updatedate) as AO_ScoreDate |
cst_fraud |
Fraud flag from bureau |
cst_deceased |
Deceased flag from bureau |
cst_emigrated |
Emigrated flag from bureau |
payload |
Pinpoint variables (used by other channels, not AO) |
envelopepayload |
Full JSON response from bureau (used by other channels, not AO) |
API key authentication is configured in def_webhook.ApiKey on the HCSCORE webhook.
Two keys are stored comma-separated. The AO webhook shares authentication via the
HCSCORE service code. Do not hardcode keys — always retrieve from def_webhook.
To retrieve the active API key:
SELECT SUBSTRING_INDEX(ApiKey, ',', -1) AS ApiKey
FROM def_webhook
WHERE WebhookCode = 'HCSCORE';
External AO system POST (IDNumber, Guid, Cellno)
|
v
HCSCOREAO webhook (api.hcapi.co.za)
|
+--> PreSql: INSERT INTO data_hcscore (staging record)
|
+--> ObjHookHCScoreAO.Process(param_1 = hcscoreao_guid)
| |
| +--> INSERT INTO bloom_hcscore
| | (normalized from bloom_hcscoreao_payload,
| | name='x', dept='46', Leadorigin NOT set)
| |
| +--> ObjWorkflow.Run("HCSCORE", hcscoreao_guid)
| |
| +--> HC_Prescore CALC (validate, country routing)
| +--> HCEXPERIAN SERVICE (bureau call SA/LES/NAM/ESW)
| +--> ScorecardSelection CALC
| +--> PayingSeg / NonPayingSeg / ThinFile SCORECARD
| +--> HC_Postscorecard CALC
| +--> END NODE
|
+--> PostSql: UPDATE bloom_hcscore SET Leadorigin
| (from data_external_leads match on IDNumber/cell, 14-day window)
| Default → '99' if no lead found
|
+--> HookLookupSql: BUILD response
| SELECT AO_Guid, IdNumber, AO_ScoreDate, AO_Score,
| AO_Strikethrough, AO_CST_Fraud, AO_CST_Deceased, AO_CST_Emigrated
| FROM data_scorestaging JOIN bloom_donormalenquiry
|
v
Response JSON returned to AO system
| File | Purpose |
|---|---|
factory.webhook/package.homechoice/ObjHookHCScoreAO.py |
AO webhook handler (normalization) |
factory.webhook/package.homechoice/ObjHookHCScoreCR.py |
Credico webhook handler |
factory.webhook/package.homechoice/ObjHookHCScoreWA.py |
WhatsApp webhook handler |
factory.webhook/package.homechoice/ObjHookHCScore.py |
Base/direct HCScore hook |
factory.service/package.homechoice/ObjServiceHCExperian.py |
Experian SA SOAP integration |
factory.service/package.homechoice/ObjServiceHCExperian_LES.py |
Experian Lesotho |
factory.service/package.homechoice/ObjServiceHCExperian_NAM.py |
Experian Namibia |
factory.service/package.homechoice/ObjServiceHCExperian_ESW.py |
Experian Eswatini |
factory.service/package.homechoice/ObjServiceBureauRouter.py |
Bureau routing orchestrator |
resource.notes/package.homechoice/BUREAU_FRAMEWORK.md |
Bureau routing documentation |
resource.notes/package.homechoice/servers.yaml |
Server inventory with IPs |