Today (deployed)
Flask 3.x + Gunicorn + GnuCOBOL baked into a single Docker image, running on EC2 at 18.217.121.166:5002. Business logic lives in six compiled COBOL programs.
Full description of the deployed legacy CardDemo Account module plus the modernization contract. Code-derived from the source-of-truth repo so AI modernization runs and human readers see the same picture.
Flask 3.x + Gunicorn + GnuCOBOL baked into a single Docker image, running on EC2 at 18.217.121.166:5002. Business logic lives in six compiled COBOL programs.
React 19 + Vite SPA with legacy-shaped mock data, unit tests, and a PR preview. Backend parity is not claimed; the UI is API-ready.
Legacy ASCII flat files: acctdata.txt (300 B), carddata.txt (150 B), cardxref.txt (50 B), custdata.txt (500 B).
Jinja2 templates styled to mimic CICS/BMS green-screens. English labels with a few Spanish COBOL error hints.
Flask never re-implements a COBOL rule. It maps HTTP to environment variables, invokes COBOL binaries via subprocess.run, parses their machine-readable stream output, and re-pads LINE SEQUENTIAL writes back to their fixed record length.
| Method + Path | Handler | COBOL invocations | Notes |
|---|---|---|---|
GET /health | health() | CBACT01C, CBACT02C, CBACT03C, CBCUS01C; binary-presence checks for CACTUPC / CCUSTUPC. | Returns JSON; 200 when every reader exits 0 and both updater binaries exist, otherwise 503 with per-program status. |
GET / | account_list() | CBACT01C (CB_STREAM=1). | Renders accounts.html with every record in acctdata.txt. No pagination. |
GET /crossref | cross_reference() | CBACT03C. | Renders crossref.html with the full xref table. |
GET /account/<acct_id> | account_detail() | CBACT01C + CBACT02C + CBACT03C + CBCUS01C. | Renders account_detail.html. 404 if the id is not in acctdata.txt. Shows xref ambiguity warning when multiple rows map to different CUST-IDs. |
GET | POST /account/<acct_id>/update | account_update() | Readers for prefill; on POST CACTUPC and/or CCUSTUPC only when their form section differs from the loaded record. | Implements the CAUP "no net change" guard. Flashes success / error / info according to outcome. |
GET /dev/bridge-log | bridge_debug_log() | — | Debug-only view of the last 30 subprocess events. 404 unless BRIDGE_DEBUG_UI=1. |
| Program | Role | Stream output | Exit codes |
|---|---|---|---|
CBACT01C.cbl | Sequential account reader (300-byte records). | ACCT01|... | 0 ok; stops with message on open/read error. |
CBACT02C.cbl | Sequential card reader (150-byte records). | CARD01|... | Same as CBACT01C. |
CBACT03C.cbl | Sequential xref reader (50-byte records). | XREF03|card|cust|acct | Same as CBACT01C. |
CBCUS01C.cbl | Sequential customer reader (500-byte records). | CUST01|... | 0 ok, 2 I/O error. |
CACTUPC.cbl | Account update with validation. Rewrites acctdata.txt through ACCTOUTPF temp file. | Human DISPLAY lines + CACTUPC_ERR|CODE| on failure. | 0 ok, 1 not found, 2 I/O, 3 validation. |
CCUSTUPC.cbl | Customer update with validation (uses CUSVALUS.cpy US tables). | Human DISPLAY lines + CCUSTUPC_ERR|CODE| on failure. | 0 ok, 1 not found, 2 I/O, 3 validation. |
bridge.py sets CB_STREAM=1 on the child process env to request pipe-delimited output. Readers emit one line per record; the bridge splits by |, trims whitespace, and decodes COBOL overpunch signs for PIC S9(10)V99 money fields.
ACCT01|<ACCT-ID>|<STATUS>|<CURR-BAL>|<CREDIT>|<CASH>|<OPEN>|<EXPIRY>|<REISSUE>|<CYC-CR>|<CYC-DB>|<ZIP>|<GROUP> CARD01|<NUM>|<ACCT-ID>|<CVV>|<EMBOSSED>|<EXPIRY>|<STATUS> XREF03|<CARD>|<CUST-ID>|<ACCT-ID> CUST01|<CUST-ID>|<FIRST>|<MIDDLE>|<LAST>|<ADDR1>|<ADDR2>|<ADDR3>|<STATE>|<COUNTRY>|<ZIP>|<PHONE1>|<PHONE2>|<SSN>|<GOVT-ID>|<DOB>|<EFT>|<PRI>|<FICO>
Update programs emit CACTUPC_ERR|CODE| / CCUSTUPC_ERR|CODE| lines on validation failure. The bridge keeps the last code and formats a Spanish hint from _CACTUPC_ERR_HINTS_ES / _CCUSTUPC_ERR_HINTS_ES. After a successful update, Python re-pads the temporary output file back to the 300 / 500 byte fixed record length.
| Record | Bytes | Key fields |
|---|---|---|
ACCOUNT-RECORD (CVACT01Y) | 300 | ACCT-ID (11), ACCT-ACTIVE-STATUS, three money fields, three dates (open/expiry/reissue), two cycle money fields, ACCT-ADDR-ZIP, ACCT-GROUP-ID. Trailing FILLER 178 bytes. Note: ACCT-EXPIRAION-DATE is the COBOL spelling. |
CARD-RECORD (CVACT02Y) | 150 | CARD-NUM, CARD-ACCT-ID, CARD-CVV-CD, CARD-EMBOSSED-NAME, CARD-EXPIRAION-DATE, CARD-ACTIVE-STATUS. Trailing FILLER 59 bytes. |
CARD-XREF-RECORD (CVACT03Y) | 50 | XREF-CARD-NUM, XREF-CUST-ID (9), XREF-ACCT-ID (11). Multiple xref rows may map to different CUST-IDs; bridge surfaces an ambiguity warning. |
CUSTOMER-RECORD (CVCUS01Y) | 500 | Three name fields (25 each), three address lines (50 each), state (2), country (3), ZIP (10), two phone fields (15 each), SSN (9, plaintext — tech debt), govt id (20), DOB (10), EFT (10), primary holder indicator (1), FICO (3). Trailing FILLER 168 bytes. |
system-overview.md lists every copybook field with PIC clause and byte offset; the CUSVALUS.cpy US validation tables (NANP area codes, state codes, state+ZIP combos) are also described there.
| Code | Condition | Spanish hint (bridge.py) |
|---|---|---|
ACCT_ID | Account id is not exactly 11 numeric digits, or is zero. | Número de cuenta: exactamente 11 dígitos numéricos, distinto de cero. |
STATUS_YN | Active status provided and not Y/N. | Estado activo debe ser Y o N. |
ZIP_DIG | ZIP present and numeric-character count is not 5 or 9. | ZIP de cuenta: 5 o 9 dígitos. |
GROUP_ALNUM | Group id not blank/* and contains anything outside A–Z, a–z, 0–9, space. | Grupo de cuenta: letras, dígitos o espacio; use blanco o * para no cambiar. |
DATE_LEN / DATE_DASH / DATE_NUM / DATE_CAL | Any date must be 10 chars, dashes at positions 5 and 8, numeric components, and pass INTEGER-OF-DATE. | Fecha: 10 caracteres YYYY-MM-DD; día/mes/año válidos. |
DATE_ORDER | open > expiry, reissue > expiry, or reissue < open. | Apertura ≤ caducidad, reemisión entre apertura y caducidad. |
AMT_CHAR | Amount contains characters other than digits, one ., or a leading -. | Importe: sólo dígitos, un punto decimal y signo opcional. |
CASH_GT_CREDIT | Both credit and cash amounts supplied and cash > credit. | Límite en efectivo no puede superar el límite de crédito. |
Codes below are emitted as CCUSTUPC_ERR|CODE|. The complete list mirrors COACTUPC 12xx edits plus US-specific rules from CUSVALUS.cpy.
| Code | Condition |
|---|---|
CUST_ID | CUST_UPD_ID not exactly 9 numeric digits. |
FIRST_LAST | First or last name blank. |
NAME_ALPHA | First / middle / last name contains anything other than letters and space. |
ADR1_REQD / ADR1_ALNUM | Address line 1 missing or contains characters outside alphanumeric + space. |
CITY_REQD / CITY_ALPHA | City (address line 3) missing or contains non-letter / non-space characters. |
GOVT_ALNUM | Government id present but contains non-alphanumeric / non-space characters. |
STATE_LEN / STATE_ALPHA / STATE_LIST | State must be exactly 2 letters, alphabetic, and in VALID-US-STATE-CODE. |
CTRY_LEN / CTRY_ALPHA | Country present must be 3 letters, alphabetic. |
ZIP_DIG / ZIP_STATE | ZIP numeric-count is 5 or 9; state + first 2 ZIP digits must match VALID-US-STATE-ZIP-CD2-COMBO. |
PHONE_DMAX / PHONE_D10 / PHONE_AREA / PHONE_NANP / PHONE_PFX / PHONE_LINE | Each phone is exactly 10 digits; area in NANP (VALID-GENERAL-PURP-CODE, not 000 nor 666); middle 3 and last 4 digits cannot be zero. |
SSN_FMT / SSN_AREA / SSN_MID / SSN_LAST | SSN 9 digits; area 001–665 and 667–899 (not 000/666/≥900); middle 01–99; last 0001–9999. |
FICO_FMT / FICO_RANGE | FICO is 3 digits between 300 and 850. |
PRI | Primary holder indicator must be Y or N. |
EFT_LEN / EFT_NUM / EFT_ZERO | EFT id, when provided, must be exactly 10 numeric digits and numerically non-zero. |
DOB_LEN / DOB_DASH / DOB_NUM | DOB must be 10 chars, dashes at 5 and 8, with numeric YYYY/MM/DD components. |
| Module | Legacy implementation | Modern target (frontend demo) |
|---|---|---|
account-listing | GET / → get_all_accounts() → CBACT01C → accounts.html. | Modern /account-listing with search, status filter, pagination, and legacy handoff actions for detail and update. |
account-details | GET /account/<id> → CBACT01C + CBACT02C + CBACT03C + CBCUS01C → account_detail.html. Shows xref ambiguity warning. | Modern /account-details/:id with account info panel, cards table, and masked customer profile. |
account-update | GET | POST /account/<id>/update runs CACTUPC and/or CCUSTUPC only when the matching form section differs from the loaded record. | Modern /account-update/:id with two sections (account + customer). Minimal validation; fake-but-believable success. |
card-crossref | GET /crossref → CBACT03C → crossref.html. | Modern /card-crossref with searchable table; each row jumps to account details. |
customer-management | Embedded inside account update; CBCUS01C for reads, CCUSTUPC for writes. | Modern /customer-management list + /:customerId detail/update. |
| Variable | Purpose |
|---|---|
ACCTFILE / CARDFILE / XREFFILE / CUSTFILE | Paths to the four legacy data files (bind-mounted at /data/). |
COBOL_BIN_DIR | Directory with compiled COBOL binaries (default /app/bin). |
CB_STREAM | Set to 1 by the bridge to request pipe-delimited output from readers. |
ACCT_UPD_* / CUST_UPD_* | Padded update inputs handed to CACTUPC / CCUSTUPC. See Markdown for each field's length. |
ACCTOUTPF / CUSTOUTPF | Temp output paths used by the updaters before the bridge re-pads records. |
BRIDGE_DEBUG_UI | Enables /dev/bridge-log and the in-memory event buffer (demo only). |
SECRET_KEY / PORT / PYTHONUNBUFFERED | Flask secret (demo default), Gunicorn port (5000), and unbuffered logs. |
The template branch ships four modernized modules with unit tests; each demo PR modernizes only the remaining
slot-in target. Today that target is customer-management.
| Id | Description |
|---|---|
| DEBT-1 | CUST-SSN stored as plain 9-digit number — always mask in any modern UI. |
| DEBT-2 | ACCT-EXPIRAION-DATE typo is baked into the copybook and all derivatives — keep consuming the typo name but rename in any new domain model. |
| DEBT-3 | LINE SEQUENTIAL trailing-space stripping forces Python re-pad after every update. |
| RISK-1 | Xref may have multiple rows per account with different CUST-IDs; the legacy app shows a warning. |
| RISK-2 | Spanish validation messages include legacy codepoints; ensure UTF-8 rendering. |
| RISK-3 | Bridge subprocess timeout is 120 s; large files may time out. |