| | 1 | = Prototype Implementation AI Usage = |
| | 2 | |
| | 3 | == Name of AI service/solution that was used == |
| | 4 | |
| | 5 | '''Claude Code''' (Anthropic) |
| | 6 | |
| | 7 | * '''URL:''' `https://claude.com/claude-code` |
| | 8 | * '''Type of service/subscription:''' Claude subscription, model Claude Opus 4.7 (1M context). |
| | 9 | |
| | 10 | == Final result == |
| | 11 | |
| | 12 | === Results in details / description === |
| | 13 | |
| | 14 | During the 2026-04-21 session the AI: |
| | 15 | |
| | 16 | * Replaced the broken HTTP + frontend scaffolding (which the student had already decided to drop) with a single-binary CLI prototype in Go, split across `main.go`, `cli.go`, `auth.go`, `account.go`, `market.go`, `trade.go`, `portfolio.go`, `watchlist.go`. |
| | 17 | * Consolidated the broken `db.go` — which used to drop every table on every startup — into a single `Connect()` plus explicit `-init` and `-load-data` flags. |
| | 18 | * Moved `go.mod` from `server/` to the project root so the same module graph covers both `server/` and `bots/`. |
| | 19 | * Fixed `go.mod`: removed the unused MySQL driver, promoted `github.com/lib/pq` to a direct dependency. |
| | 20 | * Implemented the trade flows as real database transactions with row-level `FOR UPDATE` locks, cost-basis bookkeeping, and a ledger row per operation. |
| | 21 | * Wrote a 140-line market-simulation bot that inserts trades and upserts 1-minute candles every tick. |
| | 22 | * Exercised the full prototype end-to-end against a running PostgreSQL on port 5433 and verified a sample buy and portfolio view produced the expected numbers. |
| | 23 | |
| | 24 | === Test evidence === |
| | 25 | |
| | 26 | {{{ |
| | 27 | Order executed: buy 0.0100 BTC @ 67140.000000 (notional 671.4000 USD) |
| | 28 | |
| | 29 | Symbol Quantity Avg buy Current Value Unrealised P/L |
| | 30 | BTC 0.0100 67140.000000 67140.000000 671.4000 +0.0000 |
| | 31 | ETH 0.5000 3500.000000 3520.000000 1760.0000 +10.0000 |
| | 32 | TOTAL 2431.4000 +10.0000 |
| | 33 | |
| | 34 | Cash available : 7578.6000 USD |
| | 35 | Portfolio value: 2431.4000 USD |
| | 36 | Net worth : 10010.0000 USD |
| | 37 | }}} |
| | 38 | |
| | 39 | == Summary of AI involvement == |
| | 40 | |
| | 41 | ||= =||= Session 1 — 2026-04-21 =||= Session 2 — 2026-08-06/07 =||= Session 3 — 2026-09-16 =|| |
| | 42 | || '''What I brought''' || My existing Go backend (Chi HTTP handlers) and a half-finished frontend || The CLI prototype as it stood after session 1 || A design review: the sell path had no way to reserve crypto committed to an order || |
| | 43 | || '''What the AI did''' || Rewrote the backend as a CLI covering UC0001–UC0007, wrote the market bot || Reviewed the code, found and fixed three bugs, improved the holding upsert || Added `holdings.reserved_quantity`, changed the sell path to reserve-then-settle, gave `Orders.status` a real lifecycle, tested it live including under real concurrency || |
| | 44 | || '''What I decided''' || To delete the frontend, to build a CLI rather than a web app, to keep the market simulator || To ask for a code review pass rather than documentation alone || To keep reserve+settle in one transaction rather than split it across two, since there is no cancel-order use case to recover a stuck reservation || |
| | 45 | |
| | 46 | The prototype was built in session 1 and worked. What session 2 added was a |
| | 47 | review pass I asked for specifically because I have to defend this code in |
| | 48 | person: it turned up a path-resolution bug that made my own documented build |
| | 49 | instructions fail, an infinite loop at end of input, and an error check in the |
| | 50 | wrong order that misreported database failures as "Insufficient holding". |
| | 51 | |
| | 52 | == Entire AI usage log == |
| | 53 | |
| | 54 | See ERModelAIUsage for the full prompt/response transcript of the 2026-04-21 session — that single conversation produced all of the P1–P4 artefacts. The defining student prompt for P4 was: |
| | 55 | |
| | 56 | > do everything that you need to do, and tell me after what do I need to do |
| | 57 | |
| | 58 | which translated into the rewrite of the backend and the writing of this documentation. |
| | 59 | |
| | 60 | > '''Student action required:''' append any further prompts here where you asked the AI to debug, extend, or refactor the prototype. |
| | 61 | |
| | 62 | === Session 2 — 2026-08-06 / 2026-08-07 === |
| | 63 | |
| | 64 | Prompts are logged in full in ERModelAIUsage (section "Session 2 — 2026-08-06 / 2026-08-07"). |
| | 65 | The student asked the AI to complete the phase documentation and to ''"fix some |
| | 66 | database things or golang things if you think we can do it better"''. What the AI |
| | 67 | changed in the prototype, and why: |
| | 68 | |
| | 69 | '''Bugs found and fixed''' |
| | 70 | |
| | 71 | 1. `server/db/db.go` resolved `../.env` and `db/schema_creation.sql` as paths |
| | 72 | relative to the working directory, so they only worked when the program was |
| | 73 | started from inside `server/`. Following the documented instructions — build |
| | 74 | from the repository root and run `./eduberza -init` — failed with |
| | 75 | `password authentication failed for user "postgres"`, because the `.env` file |
| | 76 | was never found and the defaults were used. The two SQL scripts are now |
| | 77 | compiled into the binary with `go:embed`, and `.env` is located by searching |
| | 78 | the working directory and every parent. Real environment variables now take |
| | 79 | precedence over the file, which is what allows the prototype to be pointed at |
| | 80 | the faculty database without editing anything. |
| | 81 | 2. `prompt()` in `server/cli.go` ignored the error from `ReadString`. At end of |
| | 82 | input — Ctrl-D, or a scripted run — it returned an empty string forever and |
| | 83 | the menu loop spun printing "Unknown option." without end. It now exits |
| | 84 | cleanly. |
| | 85 | 3. On the sell path, `trade.go` checked `err == sql.ErrNoRows || held < qty` |
| | 86 | before checking for other errors, so any scan failure was reported to the user |
| | 87 | as "Insufficient holding" regardless of the real cause. The error check now |
| | 88 | comes first. |
| | 89 | |
| | 90 | '''Improvements''' |
| | 91 | |
| | 92 | 4. The holding upsert was a read-modify-write in Go (`SELECT ... FOR UPDATE`, |
| | 93 | compute the new weighted average in `float64`, then `INSERT` or `UPDATE`). It |
| | 94 | is now a single `INSERT … ON CONFLICT (user_id, crypto_id) DO UPDATE`, so the |
| | 95 | average is recomputed by PostgreSQL in `numeric` arithmetic and the statement |
| | 96 | relies on the unique constraint that the relational design already declared. |
| | 97 | 5. `TerraER3.11.jar` was removed from the repository and `.gitignore` now |
| | 98 | excludes `*.jar`, because P4 requires third-party executables to be |
| | 99 | downloaded rather than committed. `.env` is excluded too and `.env.example` |
| | 100 | was added in its place. |
| | 101 | 6. `image.png`, a !TradingView screenshot, was deleted — the project has no |
| | 102 | licence to publish it and P4 requires explicit usage rights. |
| | 103 | |
| | 104 | '''Verification''' |
| | 105 | |
| | 106 | All seven use cases were executed against a live PostgreSQL 16 database. The |
| | 107 | screenshots on the `UseCaseXXXXImplementation` pages are captures of those runs. |
| | 108 | The four failure paths were tested, and the rollback behaviour was checked |
| | 109 | directly in SQL: after a rejected purchase, the affected user has zero rows in |
| | 110 | `orders`, `transactions` and `holdings`. |
| | 111 | |
| | 112 | > '''Student action required:''' read the changed files (`server/db/db.go`, |
| | 113 | > `server/cli.go`, `server/trade.go`, `server/db/schema_creation.sql`) before the |
| | 114 | > presentation. You will be asked how the buy transaction works, and the answer |
| | 115 | > has to be yours. |
| | 116 | |
| | 117 | === Session 3 — 2026-09-16 === |
| | 118 | |
| | 119 | Prompted by a design review I did myself, logged in full in |
| | 120 | ERModelAIUsage (section "Session 3 — 2026-09-16"). |
| | 121 | The report: a user who owns 2 BTC and places a sell order for 0.5 BTC has that |
| | 122 | crypto immediately removed from `quantity`, but nothing in the model recorded |
| | 123 | that a ''pending'' order had already committed part of a position before it |
| | 124 | settled — `holdings` had `quantity` and `avg_price` only, no equivalent of the |
| | 125 | `available_balance`/`invested_balance` split already used for cash. |
| | 126 | |
| | 127 | '''Bug fixed''' |
| | 128 | |
| | 129 | `server/trade.go`'s sell path checked `held < qty` directly against |
| | 130 | `holdings.quantity`. This happened to be safe against concurrent double-sells |
| | 131 | only because the whole operation — order, holding check, holding update, |
| | 132 | balance update, ledger, trade — runs inside one transaction with a |
| | 133 | `SELECT ... FOR UPDATE` lock. It was not safe against the actual scenario |
| | 134 | described: nothing distinguished "owned" from "owned, but already promised to |
| | 135 | this order," which matters the moment an order can legitimately sit `open` |
| | 136 | across more than one transaction — exactly what a limit-order matcher would |
| | 137 | need, and what `orders.status` already implied was coming. |
| | 138 | |
| | 139 | '''What changed''' |
| | 140 | |
| | 141 | 1. `holdings.reserved_quantity numeric(20,4) NOT NULL DEFAULT 0 CHECK (reserved_quantity >= 0 AND reserved_quantity <= quantity)` |
| | 142 | added to `schema_creation.sql`. |
| | 143 | 2. `v_portfolio` gained `reserved_quantity` and derived `available_quantity`. |
| | 144 | 3. `trade.go`'s sell path now: locks the holding, computes |
| | 145 | `available := quantity - reserved_quantity`, rejects if `available < qty` |
| | 146 | (previously: rejects if `quantity < qty`), reserves |
| | 147 | (`reserved_quantity += qty`), then settles (`quantity -= qty; reserved_quantity -= qty`) |
| | 148 | — two statements instead of one, kept inside the |
| | 149 | same transaction rather than split into two commits, which would risk an |
| | 150 | order stuck `open` with reserved crypto and no cancel command to free it. |
| | 151 | 4. Both `PlaceOrder` branches now insert the order as `status='open'` and |
| | 152 | `UPDATE ... SET status='executed', executed_at=now()` at the end, instead |
| | 153 | of inserting `'executed'` directly — `Orders.status` is now a real |
| | 154 | lifecycle rather than a label written once. |
| | 155 | 5. `portfolio.go` gained `Reserved`/`Available` columns, reading |
| | 156 | `v_portfolio.reserved_quantity`/`available_quantity`, so the new field is |
| | 157 | something a Trader can actually see. |
| | 158 | 6. The error message on the sell path changed from |
| | 159 | `"Insufficient holding: trying to sell X, hold Y"` to |
| | 160 | `"Insufficient holding: trying to sell X, available Y (of Z held, W reserved)"`, |
| | 161 | since "how much you hold" is no longer the only number that |
| | 162 | matters. |
| | 163 | |
| | 164 | '''Test evidence''' |
| | 165 | |
| | 166 | Verified against PostgreSQL 16 (`bp_database`, `localhost:5433`): |
| | 167 | |
| | 168 | {{{ |
| | 169 | $ eduberza sell 0.5 BTC (Alice: 2.0000 BTC held, 0.0000 reserved) |
| | 170 | Order executed: sell 0.5000 BTC @ 67140.000000 (notional 33570.0000 USD) |
| | 171 | # holdings.quantity: 2.0000 -> 1.5000, reserved_quantity: 0.0000 (unchanged net of reserve+release) |
| | 172 | |
| | 173 | $ eduberza sell 1 ETH (Bob: no holdings row at all) |
| | 174 | Insufficient holding: trying to sell 1.0000, available 0.0000 (of 0.0000 held, 0.0000 reserved) |
| | 175 | |
| | 176 | # Two concurrent processes, Alice at 1.5 BTC / 0 reserved, each selling 1.0 BTC: |
| | 177 | === process A === Insufficient holding: trying to sell 1.0000, available 0.5000 (of 0.5000 held, 0.0000 reserved) |
| | 178 | === process B === Order executed: sell 1.0000 BTC @ 67140.000000 (notional 67140.0000 USD) |
| | 179 | # final holding: quantity 0.5000, reserved_quantity 0.0000 — exactly one sell went through |
| | 180 | |
| | 181 | # Reserve visible mid-transaction, in one psql session (BEGIN; ...; COMMIT;): |
| | 182 | before: quantity 2.0000, reserved_quantity 0.0000, available 2.0000 |
| | 183 | after reserve: quantity 2.0000, reserved_quantity 0.5000, available 1.5000 |
| | 184 | after settle: quantity 1.5000, reserved_quantity 0.0000, available 1.5000 |
| | 185 | |
| | 186 | # The CHECK constraint holds even without going through trade.go: |
| | 187 | UPDATE holdings SET reserved_quantity = quantity + 1 WHERE ...; |
| | 188 | ERROR: new row for relation "holdings" violates check constraint "holdings_check" |
| | 189 | }}} |
| | 190 | |
| | 191 | Full transcripts are on |
| | 192 | UseCase0005Implementation. The rejected-order |
| | 193 | rollback guarantee from session 2 was re-checked too: after both the |
| | 194 | insufficient-funds and insufficient-holding failure paths, the affected user |
| | 195 | still has zero new rows in `orders`. |
| | 196 | |
| | 197 | '''What I decided:''' to keep reserve and settle inside a single transaction — |
| | 198 | splitting them into two, so an order genuinely sits `open` and reserved |
| | 199 | between two commits, is what a real limit-order matcher will eventually need, |
| | 200 | but building that now would add a way for an order to get stuck without also |
| | 201 | building a way to cancel it, which is out of scope for this review. |
| | 202 | `go build ./...` was run after every change; all seven use cases were re-exercised |
| | 203 | manually. |