Credorafor Sapphire Bank

Pre-approved corporate credit, decided inside Exasol.

An ExaThon India 2026 project, a hands-on try-out of the Exasol Personal Local Starter Kit and Exasol Personal, deployed on AWS. Sapphire Bank is fictional and all data is synthetic.

Anamika · Product Manager @ Equitas Small Finance Bank

Built for ExaThon India 2026 · Powered by Exasol Personal
Sapphire Bank, read in place 3rd party data, via API Core bankingOracle · VS_CBS Customer masterMySQL · VS_CIF Loan originationPostgreSQL · VS_LOS CIBILcibil.credora.link GSTNgst.credora.link Account Aggregatoraa.credora.link Exasol RUN_ANALYSIS one call Pre-approved offeramount · ROI · tenure Negotiation limitfloor ROI · max amount Ranked top 50PREFERRING · to RMs

The bank already holds the data. Offers are still slow and manual.

To make a corporate customer a credit offer, someone has to visit six systems, three in the bank and three 3rd party.

Core banking · Oracleaccounts, ~850,000 transactionsSapphire Bank Customer master · MySQLcompanies, partners, KYCSapphire Bank Loans · PostgreSQLcredit need, 5,000 past loansSapphire Bank CIBIL · credit bureaupartner and company reports3rd party GSTN · tax network2–10 years of GST returns3rd party Account Aggregatorpartners' other-bank accounts3rd party, with consent Credit officer company 1 of 1,000 Today, one company at a time, one system at a time Core banking Customer master Loans CIBIL GSTN Account Aggregator ExasolRUN_ANALYSIS 1,000 companies in , one call With Credora, all six at once, inside Exasol

Analyse the customers we have, then offer them credit first.

1,000 companiesexisting current-account customers, 2 to 5 partners each
Analysed in Exasolbank data, bureau, GST and AA data, risk model, policy
Pre-approved offeramount, ROI (rate) and tenure, plus a negotiation limit
RM calls the customerand negotiates inside the limit, no trip back to credit

PDprobability of default

How likely the company is not to repay, from a model trained on the bank's own past loans. Graded A1 (under 1%) to D (12% and above).

RAROCrisk-adjusted return

(Rate − cost of funds − operating cost − PD × loss given default) ÷ capital held. Every offer must clear the 15% hurdle.

DSCRdebt service coverage

Cash the company generates ÷ what the loan costs it each year. At least 1.25 for the offer, 1.10 at the negotiation limit.

GSTgoods and services tax

Monthly sales each company files with the tax network, GSTN, for 2 to 10 years per company. Late filings and a gap to bank credits are warning signs.

CIBILIndia's credit bureau

A score for every partner and a commercial rank (CMR) for every company. A partner below 600 or a CMR of 9 or worse means no offer.

AAAccount Aggregator

With the customer's consent, the partners' accounts at other banks. The consent expires, so Exasol purges the data on time, with proof.

Everything meets in Exasol, and leaves as a decision.

Bank data is read in place, 3rd party data is fetched via API by UDFs, and every person and the AI read results as their own Exasol user.

Sapphire Bank · private RDS 3rd party data via API · credora.link 1 2 3 4 5 SQL as the role's user MCP, same grants Oracle · core bankingCBS · ACCOUNT, TXN MySQL · customerscif · company, partner PostgreSQL · loanslos · past_loan CIBIL relaycibil.credora.link GSTN relaygst.credora.link Account Aggregatoraa.credora.link Personal 2026.1 · EC2 r6i.xlarge Virtual schemasVS_CBS · VS_CIF · VS_LOS API UDFs, Python SET16 in parallel RUN_ANALYSIS, Lua13 logged steps ML in UDFsTRAIN_PD · PD_SCORE · ANOMALY_FLAG Skyline rankingPREFERRING · top 50 Semantic layerCREDORA_SEM.PORTFOLIO Roles, row securitya user per role · V_RM_* Purge with proofPURGE_EXPIRED · SHA-256 Web appcredora.loan AdminCREDORA_ADMIN Data scientistCREDORA_DATASCI Loan operationsCREDORA_LOANOPS Relationship managerCREDORA_RM Ask CredoraClaude + exasol-mcp-serveras the signed-in role'sown Exasol user CRMPostgreSQL crmoffers, status
StoreCompute in ExasolOrchestration, AISecurity3rd party APIPeople, web

One engine does seven jobs, and the data never leaves it.

Exasol Personal used to the full, with open-source extensions from both GitHub organisations, exasol and exasol-labs.

Exasol FederationVS_CBS · VS_CIF · VS_LOSoracle-virtual-schema · adbc-vs-rs UDFs fetch 3rd party dataCIBIL · GST · AA, 16 at oncescript-languages-release (Python 3) Machine learningTRAIN_PD · PD_SCORE · ANOMALY_FLAGmodels as files in BucketFS Lua orchestrationRUN_ANALYSIS · 13 stepsplus TRAIN_MODEL, PURGE_EXPIRED RankingPREFERRING skylineno hand-tuned weights SemanticsCREDORA_SEM.PORTFOLIOsemantic-views · preprocessor-framework Security, for people and AIa user per role · V_RM_* viewsmcp-server logs in as the role Without it you need an ETL tool, an API gateway, an ML platform, a scheduler, a ranking service, a metrics layer and an access proxy.

Virtual schemas push the work down. Nothing is copied.

Feature step 7 aggregates core banking by account and month. Exasol hands the join, the filter and the GROUP BY to Oracle.

-- RUN_ANALYSIS, step 7 INSERT INTO CREDORA_STAGE.CA_MONTHLY SELECT t.ACCOUNT_NO, a.OWNER_ID, TRUNC(t.TXN_DATE, 'MM') MON, SUM(CASE WHEN t.DR_CR = 'C' THEN t.AMOUNT ELSE 0 END), ... COUNT(*) N FROM VS_CBS.TXN t JOIN VS_CBS.ACCOUNT a ON a.ACCOUNT_NO = t.ACCOUNT_NO WHERE a.ACCT_TYPE = 'CA' GROUP BY t.ACCOUNT_NO, a.OWNER_ID, TRUNC(t.TXN_DATE, 'MM')

Monthly totals per account come back. The ~850,000 transactions stay in Oracle, and the bank keeps one system of record.

RDS, private subnets · read in place VS_CBSvirtual schema · Oracle VS_CIFvirtual schema · MySQL VS_LOSvirtual schema · PostgreSQL Oracle adapter 6.0Java · JDBC · ojdbc17exasol/oracle-virtual-schema adbc-vs-rs 0.7 · one adapter, two enginesRust + Arrow, in the Rust script language container 0.23.1exasol-labs/adbc-vs-rs · exasol-labs/language-container-rs Oracle SE2CBS.TXN~850,000 rows stay here MySQL 8.4cif.company, partner~3,500 partners, KYC PostgreSQL 16los.applicationcredit need on file,5,000 past loans 1 2 JDBC :1521 ADBC :3306 ADBC :5432

Python UDFs fetch 3rd party data via API, 16 at a time.

A SET script groups the keys into 16 buckets by the first hex digit of an MD5 hash, and Exasol runs one UDF instance per bucket, in parallel.

script-languages-release 3rd party data APIs · credora.link SELECT CREDORA.CIBIL_CONSUMER_FETCH( 'MOCK_CIBIL', pan) FROM (SELECT DISTINCT pan, SUBSTR(HASH_MD5(pan), 1, 1) b FROM VS_CIF."partner" ...) GROUP BY b MOCK_CIBILCONNECTION withaddress + token MOCK_GSTNnever writtenin the SQL text MOCK_AAgranted per role cibil.credora.link3,490 consumer reports in 0.7 s gst.credora.link71,217 GST returns in 1.4 s aa.credora.link5,810 accounts in 3.2 s, with consent ids CREDORA_STAGECIBIL_CONSUMER · CIBIL_COMMERCIALGST_FILING · AA_ACCOUNTevery row carries PURGE_AT PURGE_EXPIRED · Luadeletes rows past PURGE_AT,every 15 minutes PURGE_LOGrow count + SHA-256 fingerprintproof without keeping the data

One Lua call runs the whole analysis, step by logged step.

EXECUTE SCRIPT CREDORA.RUN_ANALYSIS('<run>') commits after every step, so the web app can watch the run as it happens.

    Measured on the live deployment for 1,000 companies, on one r6i.xlarge node.

    CREDORA.RUN_LOG
    STEP STATUS ROWS_OUT MS

    A failed step is logged with its error and stops the run. The last published run stays visible in the app.

    The models are trained, stored and scored inside Exasol.

    Past loans are read live from PostgreSQL. Two models train on the same loans and the same hold-out. Only the active one decides.

    AUC, Gini, MODEL_PATH loads the active pickle Past loansVS_LOS."past_loan_training"5,000 loans, ~8% defaultedread live from PostgreSQL TRAIN_PD · championgradient-boosted trees,monotonic credit constraints TRAIN_SCORECARDchallenger, logisticregression scorecard BucketFSmodels/pd_<version>.pklmodels/pd_<version>-lr.pklevery version kept, next tothe Rust SLC, the adapterand the JDBC driver CREDORA.MODEL_REGISTRY MODELAUCGINIIS_ACTIVE boosting scorecard <v><v>-lr FEATURES13 per company,from step 7 PD_SCORE4 parallel instances, ~1.9 sPD and each feature'seffect on it SCORESPD, grade A1 to D,contributions ANOMALY_FLAGIsolation Forest, 200 trees of 1,000 flagged (3%) APPROVE → REFER,none declined on the flag Challenger, shadowsame run, never decides.would change gradesand decisions Admin approvesthe data scientist requestsactivation of a version.Only then it decides.SET IS_ACTIVE =(VERSION = :v)

    PREFERRING ranks on three goals at once, with no weights.

    The skyline is every approved company that no other beats on return, risk and amount together. Peel it off, take the next skyline, until 50 are ranked.

    probability of default · lower is better RAROC at the offer · higher is better dot size = offer amount · illustrative companies
    UPDATE CREDORA.DECISIONS SET SKYLINE_LAYER = :l WHERE APP_ID IN ( SELECT APP_ID FROM CREDORA.DECISIONS WHERE DECISION = 'APPROVE' AND SKYLINE_LAYER IS NULL PREFERRING HIGH RAROC_OFFER PLUS LOW PD PLUS HIGH OFFER_AMOUNT)

    On the measured run the layers held 83 companies by the time 50 were reached, in 0.02 s. The best 50 approved go to the relationship managers.

    One certified definition serves the app, the analyst and the AI.

    CREDORA_SEM.PORTFOLIO holds certified metrics. The SQL preprocessor compiles semantic queries per role, inside Exasol.

    METRIC approval_rate AS approved_companies / NULLIF(companies_analysed, 0) ON ENTITY decision FORMAT 'percentage' DISPLAY 'Approval rate' SYNONYMS ('approval ratio', 'hit rate') RATIO PUBLIC CERTIFIED
    model CREDORA_SEM.PORTFOLIO one row per company ofthe latest published run approved companiesapproval ratepre-approved amountnegotiation headroomexpected lossanomaly-flagged companiessynonyms · verified questions exasol-labs/exasol-semantic-views 0.5.1 Web app screensdashboards per role, the same numbers Ask CredoraClaude answers from the semantic layer first Any SQL clientan analyst's GROUP BY gets the same rule One semantic statement, as the RM exasol-labs/preprocessor-framework 0.9.1 SELECT industry, approval_rateFROM CREDORA_SEM.PORTFOLIO PREPROC_RT.MASTERowns the one system slot Rule for R_RMone rule per role, fail-open SEMANTIC_VIEWS_V1semantic-views 0.5.1 compiler Plain SQL overV_LATEST_* (granted per role) About 4 ms per statement for routed roles. CREDORA_SVC, which runs the jobs, has no rule and pays nothing.

    Exasol, not the app, decides who sees what.

    Four people, four Exasol users. A relationship manager reads only the V_RM_* views, which return the companies assigned to CURRENT_USER.

    Raw 3rd party data · CREDORA_STAGE admin and data scientist only Bank databases and all results · + loan operations read through virtual schemas, or straight from the CREDORA schema CREDORA.DECISIONS CREDORA.FEATURES VS_CBS.TXN The RM's views · V_RM_* … JOIN RM_ASSIGNMENT ON EXA_USER = CURRENT_USER V_RM_DECISIONS V_RM_COMPANY (full PAN) CREDORA_RM CREDORA_ADMIN CREDORA_DATASCI CREDORA_LOANOPS
    -- signed in to Exasol as CREDORA_RM (role R_RM)
    CREDORA_RM> SELECT * FROM CREDORA.DECISIONS;
    insufficient privileges
    CREDORA_RM> SELECT * FROM CREDORA.V_RM_DECISIONS;
    rows, the companies assigned to
    CREDORA_RM, from published runs
    CREATE VIEW CREDORA.V_RM_DECISIONS AS SELECT d.* FROM CREDORA.DECISIONS d JOIN CREDORA.RM_ASSIGNMENT a ON a.CIF_ID = d.CIF_ID AND a.EXA_USER = CURRENT_USER JOIN CREDORA.RUN_PUBLISH p ON p.RUN_ID = d.RUN_ID;

    The view runs with its owner's rights, so R_RM needs no grant on the tables underneath. In production each RM gets an own Exasol user, and the same views work unchanged.

    Ask Credora logs in as you, so the AI cannot read more than you.

    Claude reaches Exasol only through the official Exasol MCP server (exasol/mcp-server), started for each question on the app host as the signed-in role's own Exasol user.

    EC2 app host Personsigned in as rm BackendFastAPI, pyexasol exasol-mcp-server2.3.0, stdio, writes off Exasolchecks R_RM grants ClaudeAnthropic API or Bedrock 1question + login token (role rm) 2topic gate asks if this is about Credora's analysis, IN or OUT, no tools 3IN (OUT is refused here, and Exasol is never queried) 4exasol_login(rm) → CREDORA_RM 5start as CREDORA_RM 6question + MCP tool list (semantic layer first) 7tool call with SQL 8SQL as CREDORA_RM 9rows inside the grants, or insufficient privileges 10result rows 11answer 12answer + every SQL it ran

    From a published run to a disbursed loan, in four roles.

    Each hand-off is a row in Exasol or the CRM, so admin, data scientist and loan operations can track every company.

    Data scientistCREDORA_DATASCI AdminCREDORA_ADMIN Loan operationsCREDORA_LOANOPS RelationshipmanagerCREDORA_RM Loan statuslogged by the RM Train · what-ifproposes model, policy ApprovesIS_ACTIVE · CONFIG official rerun as CREDORA_SVC Run analysisRUN_ANALYSIS Publish the runRUN_PUBLISH Assign to RMsRM_ASSIGNMENT Track every companyadmin and data scientist too Sees own bookV_RM_* · full detail Negotiatesinside the limit, or escalates Contacted Negotiating Loan taken In progress Disbursed

    Started on the starter kit, deployed to AWS with one command.

    AWS · ap-south-1 (Mumbai) VPC 172.30.0.0/16 · created by the Exasol launcher Public subnet 172.30.1.0/24 Private subnets 172.30.2.0/24 + 172.30.3.0/24 · RDS not publicly accessible 1 2 3 4 UDFs → cibil./gst./aa.credora.link, out and back in :8563 Judges, browsersHTTPS :443 Route 53credora.loan@ www docs video pptcredora.link@ www cibil gst aaevery record → the apphost's Elastic IP Admin IPs (/32):22 :2581 :8443 :8563 Anthropic APIor Amazon Bedrock App host · EC2 t3.large172.30.1.20 · Elastic IP · Docker Composecaddy · frontend · backend + exasol-mcp-servergenerator · mock cibil, gstn, aa · mockdbCaddy, automatic HTTPS for both domains Exasol Personal 2026.1.0EC2 r6i.xlarge · 172.30.1.11:8563 SQL (TLS) · :8443 admin UI:2581 BucketFS · :22 SSHexasol/exasol-personal launcher 2.3.0 RDS Oracle SE2:1521 JDBC · CBSACCOUNT · TXN (~850k) RDS MySQL 8.4:3306 ADBC · cifcompany · partner · rm RDS PostgreSQL 16:5432 ADBC · los, crmapplication · past_loan · offer Secrets ManagerDB, API and Exasol users Amazon S3ground truth, model archive IAM instance rolereads the runtime secret Amazon Bedrockoptional, AI_PROVIDER=bedrock
    Before AWS, on a laptopExasol Personal Local Starter Kit (exakit 0.2.3) with local Exasol, MCP setup, exapump, pyexasol
    $ python deploy/credora.py deploy --env <name>
    Exasol on EC2, launcher 2.3.0
    RDS ×3, app host, S3, DNS (OpenTofu)
    App, mocks, sites (Compose)
    1,000 synthetic companies
    SLC, adapters, UDFs, Lua, roles, semantics
    First training and analysis

    Realistic volumes for a mid-size corporate book.

    The data is synthetic, but the volumes are not small. Rows per source on a log scale, and what one run costs.

    1k10k100k1M
    s

    the full analysis of 1,000 companies, 13 steps, one Exasol call

    AUC of the champion. The challenger scorecard reaches

    16

    UDF instances fetching 3rd party data via API in parallel

    companies flagged by the Isolation Forest, and approvals became referrals

    ~75s

    to regenerate all the synthetic data, with 1,000 companies, ~3,500 partners, every source rebuilt consistently. Semantic routing adds ~4 ms per statement.

    Narrow doors, and failures that stay contained.

    Network

    Only 80 and 443 are public. Exasol's ports open to admin IPs only, and RDS sits in private subnets.

    Database grants

    A user per role. A refused query reaches the app as 403 Exasol denied access for your role.

    AI in the same boundary

    MCP writes off, a topic gate before any query, every SQL statement shown with the answer.

    Secrets

    Generated per deployment into Secrets Manager. API tokens live in CONNECTION objects. None in the repo.

    Consent and retention

    3rd party replies carry a purge time. PURGE_EXPIRED deletes them and keeps a SHA-256 proof.

    If this failsWhat happensRecovery
    A bank databaseThat step fails and is logged, and the last published run stays visibleRe-run when it is back
    A 3rd party APIThe fetch fails with the API's error, and nothing half-done is publishedRe-run, the relays are stateless
    A preprocessor moduleThe framework skips it (fail-open), and plain SQL keeps workingFix or remove its rule
    The Exasol nodeThe app shows the last results it can reach, and analysis and AI pauseRestart, and deploy recreates objects
    The app hostThe site is down, but Exasol and the databases are unaffectedcredora.py ship
    The MCP serverAsk Credora returns 503, and the rest of the app worksAsk again for a fresh process
    A question outside grantsExasol refuses, and Claude says the role cannot see itNone, this is access control working

    Every screen in the demo proves an Exasol feature.

    Explain mode shows it live, with an Exasol console showing the SQL behind each screen, an ⓘ glossary, and a control room.

    Built with Exasol open source. Thank you.

    Every project below runs in the live deployment, from both GitHub organisations, exasol and exasol-labs. Thank you to the Exasol team and the contributors.

    Credorafor Sapphire Bank

    Contributions welcome. Want to build on Credora, adapt it, or buy or license it?

    Raise an issue at github.com/anamika-iit-kgp/credora. Credora is an ExaThon India 2026 project, a hands-on try-out of the Exasol Personal Local Starter Kit and Exasol Personal, deployed on AWS.

    Built for ExaThon India 2026 · Powered byExasolPersonal

    Project by Anamika, Product Manager @ Equitas Small Finance Bank. Sapphire Bank is fictional and all data is synthetic.