Mortgage Amortization Calculator
COBOLJCLCGIShellz/OS
A full-stack mainframe integration project bridging modern web technologies with enterprise COBOL. A CGI shell script receives POST data from the web form, URL-decodes the loan parameters, and dynamically writes a complete JCL job that compiles and executes a COBOL batch program on z/OS. The script submits the job, polls the HFS output file for changes, and streams the resulting amortization schedule back to the browser as HTML all in a single request cycle.
- CGI POST parsing and URL-decoding in pure shell
- Runtime JCL generation - compiles and runs COBOL per request
- Async job polling loop with HFS file change detection
- End-to-end web - z/OS - browser pipeline
# Decode POST body into loan parameters
POSTDATA=$(dd bs=1 count="$CONTENT_LENGTH" 2>/dev/null)
loan_title=$(urldecode \
"$(echo "$POSTDATA" | sed -n \
's/.*loan_title=\([^&]*\).*/\1/p')")
loan_total=$(urldecode \
"$(echo "$POSTDATA" | sed -n \
's/.*loan_total=\([^&]*\).*/\1/p')")
annual_rate=$(urldecode \
"$(echo "$POSTDATA" | sed -n \
's/.*annual_interest=\([^&]*\).*/\1/p')")
input_line="${loan_title},${loan_total},\
${annual_rate},${loan_years},${accel_pay},\
${payments_yr},${start_date},"
# Dynamically generate JCL - compile and run COBOL
cat > "$RUNJCL" <<EOF
//WEB524 JOB (ACCT),'WEB RUN',CLASS=A,MSGCLASS=X
//COBRUN EXEC IGYWCL
//COBOL.SYSIN DD DSN=S990085.COBOL.CBL(TESTFILE),DISP=SHR
//LKED.SYSLMOD DD DSN=S990085.COBOL.LOAD(TESTFILE),DISP=SHR
//RUN EXEC PGM=TESTFILE,PARM='S990085'
//LOANFILE DD *
${input_line}
/*
//RESULT DD DSN=S990085.COBOL.RESULT,DISP=OLD
EOF
submit "$RUNJCL"
# Poll HFS output file until job result lands
old_ts=$(stat -c %Y "$OUT_HFS" 2>/dev/null || echo 0)
WAITED=0
while [ $WAITED -lt $MAX_WAIT ]; do
new_ts=$(stat -c %Y "$OUT_HFS" 2>/dev/null || echo 0)
new_size=$(stat -c %s "$OUT_HFS" 2>/dev/null || echo 0)
if [ "$new_ts" -gt "$old_ts" ] \
|| [ "$new_size" -gt "$old_size" ]; then
break
fi
sleep $SLEEP
WAITED=$((WAITED + SLEEP))
done
# Stream result back to browser
if [ -s "$OUT_HFS" ]; then
cat "$OUT_HFS" | escape_html
else
echo "No result yet. (Waited ${WAITED}s)"
fi
SIC Assembler
CSystems ProgrammingAssemblySIC Architecture
A two-pass assembler for the Simplified Instructional Computer (SIC) architecture, written in C. Pass 1 tokenizes source lines, builds a symbol table with addresses, and validates directives and operands. Pass 2 resolves symbols, generates object code records (H, T, M, E) and writes a complete linkable object file. Supports ASCII and EBCDIC character encoding modes, indexed addressing via the ,X modifier, and all standard SIC directives including BYTE, WORD, RESB, RESW, START, and END.
- Two-pass design: symbol table construction then object code generation
- Full SIC directive support: BYTE, WORD, RESB, RESW, START, END
- H/T/M/E record output compatible with SIC linking conventions
- Indexed addressing, ASCII/EBCDIC modes, and robust error handling
/* Pass 1 - tokenize line, assign address, build symbol table */
token = strtok(line, search);
if (-1 != SymbolExists(MySymbolTable, token)) {
printf("Duplicate Symbol Detected\n"
"Terminating Project\n");
fclose(fp);
return -1;
}
if (isalpha((unsigned char)token[0])) {
tempSymbol = token;
tempDirective = strtok(NULL, search);
token = strtok(NULL, search);
if (!strcmp(tempDirective, "WORD")) {
int val = strtol(token, NULL, 10);
if (val >= 8388607 || val <= -8388608) {
printf("Word exceeds 24-bit limit\n");
fclose(fp); return -1;
}
MySymbolTable = InsertSymbol(
MySymbolTable, tempSymbol,
byteCount, fileLineCount + 1);
byteCount += 3;
}
else if (!strcmp(tempDirective, "RESW")) {
MySymbolTable = InsertSymbol(
MySymbolTable, tempSymbol,
byteCount, fileLineCount + 1);
byteCount += strtol(token, NULL, 10) * 3;
}
else if (!strcmp(tempDirective, "RESB")) {
MySymbolTable = InsertSymbol(
MySymbolTable, tempSymbol,
byteCount, fileLineCount + 1);
byteCount += strtol(token, NULL, 10);
}
else { /* standard instruction */
MySymbolTable = InsertSymbol(
MySymbolTable, tempSymbol,
byteCount, fileLineCount + 1);
byteCount += 3;
}
}
/* Pass 2 - emit T records with resolved symbol addresses */
char *opCode = FindOpCode(records[i][1]);
int address = SymbolExists(
MySymbolTable, records[i][2]);
if (address == -1) {
printf("Symbol Doesn't Exist\n"
"Terminating Program\n");
return -1;
}
sprintf(tRecords[tCount++],
"T%06x%02x%s%04x\n",
startCount, 3, opCode, address);
sprintf(mRecords[mCount++],
"M%06x%02x+%s\n",
startCount + 1, 4, records[0][0]);
startCount += 3;
Climate Risk Dashboard
PythonGeocodingFEMA DataData Pipeline
An end-to-end geospatial risk analytics platform currently in active development. The goal is to translate raw building addresses into composite hazard scores by geocoding to county-level FIPS codes and joining against FEMA National Risk Index datasets. Per-hazard scores across flood, wildfire, heat, and severe storm categories are normalized and combined into a weighted composite risk rating for display on an interactive dashboard.
- Address geocoding to FIPS county codes via API
- ETL pipeline joining user input to FEMA NRI datasets
- Per-hazard risk scoring: flood, wildfire, heat, storms
- Composite weighted risk score with interactive output