← Back to docs hub

System Overview

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.

Version: 2026-04-15 | Source markdown: docs/system-overview.md | Source of truth: cardDemo-modernization-sai3

Platform at a Glance

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.

Modernization demo target

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.

Data

Legacy ASCII flat files: acctdata.txt (300 B), carddata.txt (150 B), cardxref.txt (50 B), custdata.txt (500 B).

UI style

Jinja2 templates styled to mimic CICS/BMS green-screens. English labels with a few Spanish COBOL error hints.

Deployed Legacy Runtime

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.

Flask routes

Method + Path Handler COBOL invocations Notes
GET /healthhealth()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 /crossrefcross_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>/updateaccount_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-logbridge_debug_log()Debug-only view of the last 30 subprocess events. 404 unless BRIDGE_DEBUG_UI=1.

COBOL programs

Program Role Stream output Exit codes
CBACT01C.cblSequential account reader (300-byte records).ACCT01|...0 ok; stops with message on open/read error.
CBACT02C.cblSequential card reader (150-byte records).CARD01|...Same as CBACT01C.
CBACT03C.cblSequential xref reader (50-byte records).XREF03|card|cust|acctSame as CBACT01C.
CBCUS01C.cblSequential customer reader (500-byte records).CUST01|...0 ok, 2 I/O error.
CACTUPC.cblAccount 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.cblCustomer 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 protocol

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.

Legacy Data Shapes (copybooks)

Record Bytes Key fields
ACCOUNT-RECORD (CVACT01Y)300ACCT-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)150CARD-NUM, CARD-ACCT-ID, CARD-CVV-CD, CARD-EMBOSSED-NAME, CARD-EXPIRAION-DATE, CARD-ACTIVE-STATUS. Trailing FILLER 59 bytes.
CARD-XREF-RECORD (CVACT03Y)50XREF-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)500Three 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.
Source note: The Markdown source 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.

Business Rules (from COBOL)

CACTUPC — Account update

Code Condition Spanish hint (bridge.py)
ACCT_IDAccount id is not exactly 11 numeric digits, or is zero.Número de cuenta: exactamente 11 dígitos numéricos, distinto de cero.
STATUS_YNActive status provided and not Y/N.Estado activo debe ser Y o N.
ZIP_DIGZIP present and numeric-character count is not 5 or 9.ZIP de cuenta: 5 o 9 dígitos.
GROUP_ALNUMGroup 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_CALAny 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_ORDERopen > expiry, reissue > expiry, or reissue < open.Apertura ≤ caducidad, reemisión entre apertura y caducidad.
AMT_CHARAmount contains characters other than digits, one ., or a leading -.Importe: sólo dígitos, un punto decimal y signo opcional.
CASH_GT_CREDITBoth credit and cash amounts supplied and cash > credit.Límite en efectivo no puede superar el límite de crédito.

CCUSTUPC — Customer update

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_IDCUST_UPD_ID not exactly 9 numeric digits.
FIRST_LASTFirst or last name blank.
NAME_ALPHAFirst / middle / last name contains anything other than letters and space.
ADR1_REQD / ADR1_ALNUMAddress line 1 missing or contains characters outside alphanumeric + space.
CITY_REQD / CITY_ALPHACity (address line 3) missing or contains non-letter / non-space characters.
GOVT_ALNUMGovernment id present but contains non-alphanumeric / non-space characters.
STATE_LEN / STATE_ALPHA / STATE_LISTState must be exactly 2 letters, alphabetic, and in VALID-US-STATE-CODE.
CTRY_LEN / CTRY_ALPHACountry present must be 3 letters, alphabetic.
ZIP_DIG / ZIP_STATEZIP 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_LINEEach 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_LASTSSN 9 digits; area 001–665 and 667–899 (not 000/666/≥900); middle 01–99; last 0001–9999.
FICO_FMT / FICO_RANGEFICO is 3 digits between 300 and 850.
PRIPrimary holder indicator must be Y or N.
EFT_LEN / EFT_NUM / EFT_ZEROEFT id, when provided, must be exactly 10 numeric digits and numerically non-zero.
DOB_LEN / DOB_DASH / DOB_NUMDOB must be 10 chars, dashes at 5 and 8, with numeric YYYY/MM/DD components.
No-net-change guard: Flask compares every posted account and customer field against the loaded record. When both sections match, no COBOL is invoked and the user gets an informational flash ("Sin cambios respecto a los datos cargados..."). This mirrors the legacy CAUP behavior and must be preserved in any modernized update flow.

Modules (business-domain view)

Module Legacy implementation Modern target (frontend demo)
account-listingGET /get_all_accounts() → CBACT01C → accounts.html.Modern /account-listing with search, status filter, pagination, and legacy handoff actions for detail and update.
account-detailsGET /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-updateGET | 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-crossrefGET /crossref → CBACT03C → crossref.html.Modern /card-crossref with searchable table; each row jumps to account details.
customer-managementEmbedded inside account update; CBCUS01C for reads, CCUSTUPC for writes.Modern /customer-management list + /:customerId detail/update.

Environment & Runtime

Variable Purpose
ACCTFILE / CARDFILE / XREFFILE / CUSTFILEPaths to the four legacy data files (bind-mounted at /data/).
COBOL_BIN_DIRDirectory with compiled COBOL binaries (default /app/bin).
CB_STREAMSet 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 / CUSTOUTPFTemp output paths used by the updaters before the bridge re-pads records.
BRIDGE_DEBUG_UIEnables /dev/bridge-log and the in-memory event buffer (demo only).
SECRET_KEY / PORT / PYTHONUNBUFFEREDFlask secret (demo default), Gunicorn port (5000), and unbuffered logs.

Modernization Contract (frontend demo)

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.

Risks & Tech Debt

Id Description
DEBT-1CUST-SSN stored as plain 9-digit number — always mask in any modern UI.
DEBT-2ACCT-EXPIRAION-DATE typo is baked into the copybook and all derivatives — keep consuming the typo name but rename in any new domain model.
DEBT-3LINE SEQUENTIAL trailing-space stripping forces Python re-pad after every update.
RISK-1Xref may have multiple rows per account with different CUST-IDs; the legacy app shows a warning.
RISK-2Spanish validation messages include legacy codepoints; ensure UTF-8 rendering.
RISK-3Bridge subprocess timeout is 120 s; large files may time out.