Projects

A closer look at some of the things I've built.

<- Back to Portfolio
Project 01

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
View Live ->
mortgage.cgi
# 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
Project 02

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
main.c
/* 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;
Project 03
Work in Progress

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
dashboard-preview.png
Climate Risk Dashboard screenshot