Data Requirement · Risk & Data Eng

EasyCash Credit-Application Audit

What we need to report, per day, how many applications were submitted for review, how many were approved or rejected, and why the rejected ones failed, at the application grain.

Source of truth  silver.credit_t_cs_credit Product  EasyCash (source = 'easy_cash') Grain  one row per application (id) Figures verified  last 30 days, live query
Submitted / 30d
355,022
decided + in-review
Approved
17,935
status = 1
Rejected
337,070
status = −1
Approval rate
5.1%
approved ÷ submitted
Reject reasons
0
not captured anywhere

The report we want

Three questions, at the application grain, sliceable by day. The counts are already answerable today; the reason breakdown is not.

MetricDefinitionAnswerable today?
Submitted for review / dayCOUNT(*) by submitted_at::dateYes
Approved / Rejected / daysplit by review_statusYes
Approval rateapproved ÷ submittedYes
Rejection reason breakdownCOUNT(*) by reject_reason_codeNo — reason not stored

What exists today

silver.credit_t_cs_credit is already at the right grain, one row per credit application (id), a user can have many. Submit time, decision time, and outcome are all present. Outcome is a clean, terminal 4-value enum (verified over the last 30 days for source='easy_cash'):

statusRows / 30dfinish_timeamountMeaning (inferred, needs sign-off)
−1337,070all set0.00Rejected — terminal, no credit granted
117,935all set100.00Approved — terminal, credit line (fixed 100)
216none0.00In processing (transient)
01none0.00Initialised (transient)

Terminal rows (−1 / 1) always carry a finish_time; approved rows carry a fixed amount = 100.00. That is enough to compute every count above — but the enum meaning is inferred from the data, not from a spec, and needs an official mapping (see REQ-2).

The gap

Blocking · reason not captured

~95% of applications are rejected, and nothing records why.

Every candidate field on credit_t_cs_credit that might hold a reason is empty or constant — checked over the same 30-day window:

  • ext — empty for rejected rows (no JSON payload)
  • type — constant 101 across all 355,044 rows
  • is_pass_general — constant 0 across all rows

The rejection reason lives in the risk / credit-decision engine and is not landed in Fabric today. Reporting "未通过原因 / why rejected" is impossible until it is. This is the one real ask of this document.

Target table — fact_easycash_application

No brand-new grain is needed, credit_t_cs_credit already is application-level. The ask is to expose it as a clean, typed audit table (or a view) with the three decision-reason fields added. Red rows are the fields that don't exist yet.

FieldSource todayStatusNote
application_ididpresentPrimary key; one row per application
user_iduser_idpresentBorrower; id-space vs loan book to be verified
productsourcepresent'easy_cash'
submitted_atcreated_timepresentSubmit-for-review timestamp
decided_atfinish_timepresentNull while in review
review_statusstatusmapNormalise int → APPROVED / REJECTED / IN_REVIEW (REQ-2)
approved_amountamountfixCurrently varchar ('100.00'); cast numeric + confirm currency
reject_reason_codemissingRequired. Machine code from the decision engine
reject_reason_descmissingRequired. Human-readable reason
reject_stagemissingRecommended. Which step declined: anti-fraud / bureau / limit=0 / blacklist / KYC
risk_scoreoptionalDecision score, if the engine exposes one

Requirements

In priority order. REQ-1 is the only true blocker; the rest are hygiene on an otherwise-usable table.

REQ-1

Land the rejection reason at application grain

Add reject_reason_code + reject_reason_desc (and ideally reject_stage) from the risk / credit-decision engine, keyed by application_id. Without this, the reason breakdown cannot be built at all.

REQ-2

Publish the official status enum

Confirm the mapping we inferred (−1 = rejected, 1 = approved, 0 / 2 = transient) and document any other values that can occur, so review_status is derived from a spec, not from observed data.

REQ-3

Type the amount field

Expose amount as a numeric column and state the currency. Today it is a varchar and approved rows are a fixed '100.00'; confirm whether that is a real credit limit or a placeholder.

Metric definitions

What the reporting queries look like once the table above exists.

-- daily: submitted / approved / rejected / approval rate
SELECT CAST(submitted_at AS date) d,
       COUNT(*)                                                    submitted,
       SUM(CASE WHEN review_status='APPROVED' THEN 1 ELSE 0 END) approved,
       SUM(CASE WHEN review_status='REJECTED' THEN 1 ELSE 0 END) rejected
FROM   fact_easycash_application
GROUP BY CAST(submitted_at AS date);

-- rejection reason breakdown  (needs REQ-1)
SELECT reject_reason_code, reject_stage, COUNT(*) n
FROM   fact_easycash_application
WHERE  review_status='REJECTED'
GROUP BY reject_reason_code, reject_stage
ORDER BY n DESC;

Open questions for Risk / Data

Prepared for the Risk & Data Engineering team · figures from a live 30-day query on silver.credit_t_cs_credit (source='easy_cash'). Counts are current as of the query date and will drift.