Ignore:
Timestamp:
09/16/26 23:37:15 (2 weeks ago)
Author:
Stefan <trsunovstefan@…>
Branches:
main
Children:
8b447ef
Parents:
df05838
Message:

add reserved_quantity and modify the phases, add v_03.png and v_03.xml for P1

Location:
docs/P1-ConceptualModel
Files:
2 added
3 edited

Legend:

Unmodified
Added
Removed
  • docs/P1-ConceptualModel/ERModel.md

    rdf05838 r9577c79  
    1 # Entity-Relationship Model v.02
     1# Entity-Relationship Model v.03
    22
    33## Diagram
    44
    5 ![ERModel_v02](ERModel_v02.png)
    6 
     5![ERModel_v03](ERModel_v03.png)
    76
    87Notation: Chen. Rectangles are entity sets, diamonds are relationships, ellipses
    … …  
    2322## Data requirements
    2423
     24Each entity set is given as a short rationale for why it exists as its own set,
     25its keys, and its attributes as a table. Each relationship is given as its
     26cardinality and participation, a short rationale, and — where it carries data —
     27an attribute table.
     28
    2529### Entity sets
    2630
    … …  
    2832Registered participants of the platform. Every action in the simulation is
    2933attributed to a user, and the two balance attributes are what makes the
    30 simulation work: cash that is free to trade is tracked separately from cash that
    31 is currently committed to open positions, so the platform can refuse a purchase
    32 without having to recompute the whole portfolio first.
    33 
    34 - **Candidate keys:** `{id}`, `{username}`, `{email}`. Primary key: **`id`**.
    35   A surrogate UUID was chosen because it is opaque and stable — `username` and
    36   `email` are both things a user may legitimately want to change later, and
    37   every relationship in the diagram points at `Users`, so a mutable key would
    38   propagate changes across the whole database.
    39 - **Attributes:**
    40   - `id` — UUID, required, primary key.
    41   - `username` — text, max 50, required, unique.
    42   - `email` — text, max 255, required, unique, must contain `@`.
    43   - `full_name` — text, max 200, optional.
    44   - `password_hash` — text, max 255, required. Never the password itself; the
    45     prototype stores a SHA-256 hex digest.
    46   - `available_balance` — numeric(18,4), required, default 0, must be ≥ 0.
    47   - `invested_balance` — numeric(18,4), required, default 0, must be ≥ 0.
    48   - `created_at` — timestamp with time zone, required, defaults to now.
    49   - `updated_at` — timestamp with time zone, optional (null until first change).
     34simulation work: cash that is free to trade is tracked separately from cash
     35that is currently committed to open positions, so the platform can refuse a
     36purchase without having to recompute the whole portfolio first.
     37
     38**Keys:** candidates `{id}`, `{username}`, `{email}`; primary key **`id`**. A
     39surrogate UUID was chosen because it is opaque and stable — `username` and
     40`email` are both things a user may legitimately want to change later, and
     41every relationship in the diagram points at `Users`, so a mutable key would
     42propagate changes across the whole database.
     43
     44| Attribute | Type | Constraints |
     45|---|---|---|
     46| `id` | UUID | PK, required |
     47| `username` | text(50) | required, unique |
     48| `email` | text(255) | required, unique, contains `@` |
     49| `full_name` | text(200) | optional |
     50| `password_hash` | text(255) | required — never the password itself; the prototype stores a SHA-256 hex digest |
     51| `available_balance` | numeric(18,4) | required, default 0, ≥ 0 |
     52| `invested_balance` | numeric(18,4) | required, default 0, ≥ 0 |
     53| `created_at` | timestamptz | required, defaults to now |
     54| `updated_at` | timestamptz | optional (null until first change) |
    5055
    5156#### Cryptos
    … …  
    5560in the *asset*, not in a particular pair.
    5661
    57 - **Candidate keys:** `{id}`, `{symbol}`. Primary key: **`id`**, for the same
    58   reason as in `Users`; `symbol` is kept as a unique natural key because that is
    59   what users type and see.
    60 - **Attributes:**
    61   - `id` — UUID, required, primary key.
    62   - `symbol` — text, max 20, required, unique (e.g. `BTC`).
    63   - `name` — text, max 255, required (e.g. `Bitcoin`).
    64   - `created_at` — timestamptz, required, defaults to now.
     62**Keys:** candidates `{id}`, `{symbol}`; primary key **`id`**, for the same
     63reason as in `Users`. `symbol` is kept as a unique natural key because that is
     64what users type and see.
     65
     66| Attribute | Type | Constraints |
     67|---|---|---|
     68| `id` | UUID | PK, required |
     69| `symbol` | text(20) | required, unique (e.g. `BTC`) |
     70| `name` | text(255) | required (e.g. `Bitcoin`) |
     71| `created_at` | timestamptz | required, defaults to now |
    6572
    6673#### Markets
    … …  
    7178trades, candles and orders all reference the pair, not the asset.
    7279
    73 - **Candidate keys:** `{id}`, `{crypto_id, quote_currency}` — that pair is
    74   unique by definition, since a given asset can only be quoted once per
    75   currency. Primary key: **`id`**, so that the many entity sets referencing a
    76   market carry one narrow column instead of a composite key.
    77 - **Attributes:**
    78   - `id` — UUID, required, primary key.
    79   - `quote_currency` — text, exactly 3 characters, required, default `USD`.
    80   - `is_active` — boolean, required, default true. Inactive markets are hidden
    81     from the trading menus but keep their history.
    82   - `created_at` — timestamptz, required, defaults to now.
     80**Keys:** candidates `{id}`, `{crypto_id, quote_currency}` — that pair is
     81unique by definition, since a given asset can only be quoted once per
     82currency; primary key **`id`**, so that the many entity sets referencing a
     83market carry one narrow column instead of a composite key.
     84
     85| Attribute | Type | Constraints |
     86|---|---|---|
     87| `id` | UUID | PK, required |
     88| `quote_currency` | text(3) | required, default `USD` |
     89| `is_active` | boolean | required, default true — inactive markets are hidden from the trading menus but keep their history |
     90| `created_at` | timestamptz | required, defaults to now |
    8391
    8492#### Orders
    … …  
    8896makes the ledger auditable.
    8997
    90 - **Candidate keys:** `{id}` only. There is no natural key — the same user can
    91   place two identical orders on the same market in the same second, and both are
    92   legitimately distinct. Primary key: **`id`**.
    93 - **Attributes:**
    94   - `id` — UUID, required, primary key.
    95   - `side` — text, required, restricted to `buy` or `sell`.
    96   - `type` — text, required, restricted to `market` or `limit`. The prototype
    97     executes only `market` orders; `limit` exists so the model does not have to
    98     change when limit orders are implemented.
    99   - `status` — text, required, restricted to `open`, `executed`, `cancelled`.
    100   - `quantity` — numeric(20,4), required, must be > 0.
    101   - `price` — numeric(18,6), optional — null for a market order until it fills,
    102     then the fill price.
    103   - `placed_at` — timestamptz, required, defaults to now.
    104   - `executed_at` — timestamptz, optional, set when the order fills.
     98Placing an order is what triggers a **reservation** of whatever it commits —
     99the crypto being sold (`Holds.reserved_quantity`, below) on a sell, cash
     100already handled the same way on a buy via `available_balance` /
     101`invested_balance`. `status` therefore has real meaning as a lifecycle, not
     102just a label: `open` means reserved but not yet settled, `executed` means
     103settled, `cancelled` would release the reservation without settling (not yet
     104exercised by any use case, since only market orders — which settle
     105immediately — are implemented). See
     106[UseCase0005](../P3-UseCaseModel/UseCase0005.md) for the reserve-then-settle
     107sequence.
     108
     109**Keys:** candidate `{id}` only — there is no natural key, since the same user
     110can place two identical orders on the same market in the same second, and both
     111are legitimately distinct; primary key **`id`**.
     112
     113| Attribute | Type | Constraints |
     114|---|---|---|
     115| `id` | UUID | PK, required |
     116| `side` | text | required, `buy` or `sell` |
     117| `type` | text | required, `market` or `limit` — the prototype executes only `market`; `limit` exists so the model does not have to change when limit orders are implemented |
     118| `status` | text | required, `open`, `executed` or `cancelled` |
     119| `quantity` | numeric(20,4) | required, > 0 |
     120| `price` | numeric(18,6) | optional — null until the order settles, then the fill price |
     121| `placed_at` | timestamptz | required, defaults to now |
     122| `executed_at` | timestamptz | optional, set when the order settles |
    105123
    106124#### Transactions
    … …  
    110128the project needs.
    111129
    112 - **Candidate keys:** `{id}` only. Primary key: **`id`**.
    113 - **Attributes:**
    114   - `id` — UUID, required, primary key.
    115   - `type` — text, required, restricted to `deposit`, `buy`, `sell`, `fee`.
    116   - `amount` — numeric(18,4), required. Signed: negative for money leaving the
    117     cash balance, positive for money arriving.
    118   - `currency` — text, exactly 3 characters, required, default `USD`.
    119   - `created_at` — timestamptz, required, defaults to now.
    120   - `description` — text, optional, free-form human-readable explanation.
     130**Keys:** candidate `{id}` only; primary key **`id`**.
     131
     132| Attribute | Type | Constraints |
     133|---|---|---|
     134| `id` | UUID | PK, required |
     135| `type` | text | required, `deposit`, `buy`, `sell` or `fee` |
     136| `amount` | numeric(18,4) | required, signed — negative for money leaving the cash balance, positive for money arriving |
     137| `currency` | text(3) | required, default `USD` |
     138| `created_at` | timestamptz | required, defaults to now |
     139| `description` | text | optional, free-form |
    121140
    122141#### MarketTrades
    … …  
    126145writes directly.
    127146
    128 - **Candidate keys:** `{id}`. In principle `{market_id, executed_at}` looks
    129   unique, but two trades can share a timestamp, so it is not a safe key.
    130   Primary key: **`id`** (a plain auto-incrementing integer here rather than a
    131   UUID, because this is the highest-volume entity set and it is only ever read
    132   in timestamp order, never referenced by anything else).
    133 - **Attributes:**
    134   - `id` — integer, required, primary key, auto-generated.
    135   - `executed_at` — timestamptz, required.
    136   - `price` — numeric(18,6), required, must be > 0.
    137   - `quantity` — numeric(20,6), required, must be > 0.
    138   - `side` — text, optional, `buy` or `sell`.
    139   - `source` — text, max 50, required, default `simulation`. Distinguishes a
    140     simulated trade from a user's own fill (`user`).
     147**Keys:** candidate `{id}` — `{market_id, executed_at}` looks unique in
     148principle, but two trades can share a timestamp, so it is not a safe key;
     149primary key **`id`** (a plain auto-incrementing integer here rather than a
     150UUID, because this is the highest-volume entity set and it is only ever read
     151in timestamp order, never referenced by anything else).
     152
     153| Attribute | Type | Constraints |
     154|---|---|---|
     155| `id` | integer | PK, required, auto-generated |
     156| `executed_at` | timestamptz | required |
     157| `price` | numeric(18,6) | required, > 0 |
     158| `quantity` | numeric(20,6) | required, > 0 |
     159| `side` | text | optional, `buy` or `sell` |
     160| `source` | text(50) | required, default `simulation` — distinguishes a simulated trade from a user's own fill (`user`) |
    141161
    142162#### MarketCandles
    … …  
    146166screen refresh does not scale.
    147167
    148 - **Candidate keys:** `{id}`, and `{market_id, timeframe, candle_time}` — a
    149   market has exactly one candle per timeframe per time bucket. Primary key:
    150   **`id`**; the composite is enforced as a uniqueness rule because it is the
    151   real-world constraint and it is what prevents duplicate candles.
    152 - **Attributes:**
    153   - `id` — integer, required, primary key, auto-generated.
    154   - `timeframe` — text, required, restricted to `1m`, `5m`, `1h`, `1d`.
    155   - `open`, `high`, `low`, `close` — numeric(18,6), all required.
    156   - `volume` — numeric(20,6), required.
    157   - `candle_time` — timestamptz, required — the start of the bucket.
     168**Keys:** candidates `{id}`, `{market_id, timeframe, candle_time}` — a market
     169has exactly one candle per timeframe per time bucket; primary key **`id`**, the
     170composite is enforced as a uniqueness rule because it is the real-world
     171constraint and it is what prevents duplicate candles.
     172
     173| Attribute | Type | Constraints |
     174|---|---|---|
     175| `id` | integer | PK, required, auto-generated |
     176| `timeframe` | text | required, `1m`, `5m`, `1h` or `1d` |
     177| `open`, `high`, `low`, `close` | numeric(18,6) | all required |
     178| `volume` | numeric(20,6) | required |
     179| `candle_time` | timestamptz | required — the start of the bucket |
    158180
    159181#### Watchlists
    … …  
    162184several lists ("long term", "watching today") and each needs its own name.
    163185
    164 - **Candidate keys:** `{id}`. `{user_id, name}` would also work if list names
    165   are required to be unique per user; the model does not impose that, so it is
    166   not listed as a candidate key. Primary key: **`id`**.
    167 - **Attributes:**
    168   - `id` — UUID, required, primary key.
    169   - `name` — text, max 100, required.
    170   - `created_at` — timestamptz, required, defaults to now.
     186**Keys:** candidate `{id}` — `{user_id, name}` would also work if list names
     187were required to be unique per user, which the model does not impose, so it is
     188not listed as a candidate key; primary key **`id`**.
     189
     190| Attribute | Type | Constraints |
     191|---|---|---|
     192| `id` | UUID | PK, required |
     193| `name` | text(100) | required |
     194| `created_at` | timestamptz | required, defaults to now |
    171195
    172196### Relationships
    … …  
    175199Ties a market to the asset it trades. One asset can be quoted in many markets;
    176200every market must have exactly one asset, hence total participation on the
    177 `Markets` side. No attributes of its own.
     201`Markets` side. No attributes.
    178202
    179203#### PlacedOn — Markets (1) : Orders (N), total on Orders
    … …  
    182206
    183207#### Places — Users (1) : Orders (N), total on Orders
    184 Records who placed an order. Every order belongs to exactly one user; a new user
    185 has no orders. No attributes.
     208Records who placed an order. Every order belongs to exactly one user; a new
     209user has no orders. No attributes.
    186210
    187211#### Records — Users (1) : Transactions (N), total on Transactions
    188 Attributes each ledger entry to a user. Every entry belongs to exactly one user.
    189 No attributes.
     212Attributes each ledger entry to a user. Every entry belongs to exactly one
     213user. No attributes.
    190214
    191215#### Settles — Orders (1) : Transactions (N), partial on both sides
    192 Links a ledger entry to the order that caused it. Partial on the `Transactions`
    193 side because deposits have no originating order, and partial on the `Orders` side
    194 because an order that never executes never produces a ledger entry. This is why
    195 the corresponding column is nullable in P2. No attributes.
     216Links a ledger entry to the order that caused it. Partial on the
     217`Transactions` side because deposits have no originating order, and partial on
     218the `Orders` side because an order that never executes never produces a
     219ledger entry — which is why the corresponding column is nullable in P2. No
     220attributes.
    196221
    197222#### Fills — Markets (1) : MarketTrades (N), total on MarketTrades
    … …  
    211236entirely described by *which user*, *which asset*, and how much.
    212237
    213 - **Attributes:**
    214   - `quantity` — numeric(20,4), required, must be ≥ 0.
    215   - `avg_price` — numeric(18,6), required, ≥ 0, **derived** (dashed ellipse):
    216     the weighted average of the prices at which the position was accumulated.
    217     It is derivable from the buy history, and is stored anyway so that
    218     unrealised P/L can be shown without replaying the whole ledger.
    219   - `created_at` — timestamptz, required, defaults to now.
    220   - `updated_at` — timestamptz, optional.
     238`reserved_quantity` mirrors `available_balance`/`invested_balance` on `Users`:
     239two independently updated stored numbers, with the amount actually free to use
     240computed on demand rather than stored (`quantity − reserved_quantity` here,
     241`available_balance` alone on the cash side). Without it, nothing stopped a
     242user from placing a second sell order against crypto already promised to a
     243first one — `quantity` alone cannot tell "owned" apart from "owned, but
     244already committed elsewhere." See [history](#entity-relationship-model-history), v03.
     245
     246| Attribute | Type | Constraints |
     247|---|---|---|
     248| `quantity` | numeric(20,4) | required, ≥ 0 — total amount owned |
     249| `reserved_quantity` | numeric(20,4) | required, default 0, `0 ≤ reserved_quantity ≤ quantity` — committed to the user's own open sell orders, not yet removed from the position |
     250| `avg_price` | numeric(18,6) | required, ≥ 0, **derived** (dashed ellipse) — the weighted average of the prices at which the position was accumulated; derivable from the buy history, stored anyway so unrealised P/L can be shown without replaying the whole ledger |
     251| `created_at` | timestamptz | required, defaults to now |
     252| `updated_at` | timestamptz | optional |
    221253
    222254#### Contains — Watchlists (M) : Cryptos (N), partial on both sides, **with attribute**
    … …  
    225257asset need not be on any list.
    226258
    227 - **Attributes:**
    228   - `added_at` — timestamptz, required, defaults to now. Recorded so a list can
    229     be shown in the order the user built it.
     259| Attribute | Type | Constraints |
     260|---|---|---|
     261| `added_at` | timestamptz | required, defaults to now — recorded so a list can be shown in the order the user built it |
    230262
    231263## Entity-Relationship Model History
    … …  
    243275  3. `avg_price` was marked as a derived attribute rather than a plain one, to
    244276     make the denormalisation explicit rather than hidden.
     277- **v02** — Student review pass over the AI-generated v01 in the TerraER GUI.
     278- **v03** — Added `reserved_quantity` to `Holds`, and reworded `Orders.status`
     279  to state its reserve → settle → (cancel) lifecycle explicitly, instead of
     280  leaving `open`/`cancelled` as unused enum values. Triggered by a design
     281  review that pointed out the model had no way to stop a user from placing a
     282  second sell order against crypto already promised to a first, unsettled one
     283  — `quantity` alone cannot distinguish "owned" from "owned, but already
     284  committed." Also redrawn more compactly: every entity and relationship (with
     285  its own attributes moved along with it) was pulled proportionally toward the
     286  diagram's centroid, shrinking the canvas by roughly 45% with the same
     287  topology and no new overlaps. See [ERModelAIUsage](ERModelAIUsage.md) for
     288  the reasoning and how the diagram file itself was produced, and
     289  [RelationalDesign](../P2-RelationalDesign/RelationalDesign.md) and
     290  [UseCase0005](../P3-UseCaseModel/UseCase0005.md) for how the new attribute
     291  is enforced.
    245292
    246293Reasoning for the AI-assisted part of this phase, and the full interaction log,
    247294are on [ERModelAIUsage](ERModelAIUsage.md).
     295
     296> **Student action required.** Open `ERModel_v03.xml` in TerraER, read the
     297> whole diagram — not just the new `reserved_quantity` ellipse — and change
     298> anything you disagree with, including the compaction. The phase rules
     299> require the model to be yours; this is a generated revision to review and
     300> take over, not an answer to submit unread.
  • docs/P1-ConceptualModel/ERModelAIUsage.md

    rdf05838 r9577c79  
    3434by reading it back through TerraER's own reader and comparing the figure count
    3535(144), and it opens and can be edited in TerraER 3.11 like any hand-drawn
    36 diagram. Subsequent versions (`ERModel_v02.xml` onward) are edited by the student
    37 in the GUI.
     36diagram. `ERModel_v02.xml` is the student's own review pass over v01, done by
     37hand in the GUI.
     38
     39`ERModel_v03.xml` / `ERModel_v03.png` (session 3, 2026-09-16) were produced the
     40same way, this time as a genuine load–modify–save round trip through TerraER's
     41own classes rather than a from-scratch build: `ERModel_v02.xml` was read with
     42the application's real `DrawFigureFactory` and `DOMStorableInputOutputFormat`
     43into a live `QuadTreeDrawing`, one `AtributoFigure` was cloned from the
     44existing `quantity` attribute of `Holds` (to inherit its exact styling) and
     45relabelled `reserved_quantity`, a matching `LabeledLineConnectionFigure` was
     46added between it and the `Holds` diamond (`ChopDiamondConnector` /
     47`ChopEllipseConnector`, the same connector pair every other attribute of
     48`Holds` uses), and every entity and relationship — together with its own
     49attributes, moved by the same offset — was translated proportionally toward
     50the diagram's centroid to close up excess canvas space, after which every
     51connection figure had `updateConnection()` called so its drawn path follows
     52the moved figures. The result was written with the real writer and rendered to
     53PNG with TerraER's own `ImageOutputFormat`, and re-verified by reading
     54`ERModel_v03.xml` back and confirming the figure count (146 = 144 + the new
     55attribute + its line) and that all five attributes of `Holds` resolve with the
     56expected connector classes. No figure was hand-edited in XML.
    3857
    3958### Model description
    … …  
    4362## Summary of AI involvement
    4463
    45 Work on this project happened in two working sessions, several months apart.
    46 
    47 | | Session 1 | Session 2 |
    48 |---|---|---|
    49 | **When** | 2026-04-21 | 2026-08-06 / 2026-08-07 |
    50 | **Model** | Claude Opus 4.7 (1M context) | Claude Opus 5 (1M context) |
    51 | **Phases advanced** | P1, P2, P3 and the first working prototype | The ER diagram file, P4 documentation, bug fixes |
    52 | **My starting material** | `ep-diagram.md`, `opis.md`, my existing Go backend and draft SQL | Everything from session 1, plus the phase rubric |
     64Work on this project happened in three working sessions.
     65
     66| | Session 1 | Session 2 | Session 3 |
     67|---|---|---|---|
     68| **When** | 2026-04-21 | 2026-08-06 / 2026-08-07 | 2026-09-16 |
     69| **Model** | Claude Opus 4.7 (1M context) | Claude Opus 5 (1M context) | Claude Sonnet 5 |
     70| **Phases advanced** | P1, P2, P3 and the first working prototype | The ER diagram file, P4 documentation, bug fixes | `Holds.reserved_quantity` added across P1–P4 |
     71| **My starting material** | `ep-diagram.md`, `opis.md`, my existing Go backend and draft SQL | Everything from session 1, plus the phase rubric | Everything from sessions 1–2, plus a design review of the sell-order flow |
    5372
    5473In **session 1** I brought my own data model (`ep-diagram.md`, written in
    … …  
    6584for a review pass over the database and Go code, which turned up three further
    6685bugs (see [PrototypeImplementationAIUsage](../P4-Prototype/PrototypeImplementationAIUsage.md)).
     86
     87In **session 3** I described a concrete edge case I'd spotted in the sell flow
     88— nothing stopped a user from placing a second sell order against crypto
     89already promised to a first, unsettled one — and asked the AI to fix it
     90starting from P1 and carry the fix through every phase. This added
     91`reserved_quantity` to `Holds` and changed the sell path in `trade.go` to use
     92it; see the model history on
     93[ERModel](ERModel.md#entity-relationship-model-history) (v03) and the log
     94below.
    6795
    6896Below is the log, one entry per exchange. My prompts are quoted as **excerpts**:
    … …  
    176204>    any further prompts. The complete transcript is in your Claude Code session
    177205>    history.
     206
     207---
     208
     209### Session 3 — 2026-09-16
     210
     211**Intent:** describe a concrete gap I'd found in the sell-order design — the
     212model had no way to represent crypto committed to an order that had not yet
     213settled — and have the AI trace the fix through every phase rather than patch
     214`trade.go` alone.
     215
     216**Prompt (student, excerpt):**
     217> Suppose the user owns 2 BTC and wants to sell 0.5 BTC at market price. […] You
     218> currently have Holds: quantity, avg_price but you don't have a concept of
     219> reserved/committed crypto. For cash, you already explicitly distinguish
     220> available_balance, invested_balance. For crypto, you probably want the
     221> equivalent: quantity, reserved_quantity, avg_price. […] Can you please start
     222> from Phase 1 and modify everything to make it work like it should, modify
     223> the AIUsage.md in every Phase, and can you make the ConceptualModel a little
     224> bit more put together not that much spread out.
     225
     226**Response (AI, summarised):**
     227- Read every phase document and the live `server/trade.go`, `portfolio.go` and
     228  `schema_creation.sql` to confirm the actual gap: the sell path checked
     229  `holdings.quantity` directly, with nothing recording that part of a position
     230  was already promised to another order.
     231- Added `reserved_quantity` to `Holds` (P1), `holdings` (P2), the sell scenario
     232  SQL (P3), and `trade.go`/`portfolio.go`/`schema_creation.sql` (P4) — one
     233  attribute threaded consistently through all four phases rather than a
     234  patch confined to the Go code.
     235- Changed the sell path to lock the holding row, compute
     236  `available = quantity − reserved_quantity`, reserve (`reserved_quantity +=
     237  qty`) and then settle (`quantity -= qty; reserved_quantity -= qty`) inside
     238  the same transaction — keeping the existing all-or-nothing guarantee rather
     239  than splitting into two commits, which would leave an unresolvable `open`
     240  order if the process died in between and there is no cancel use case to
     241  recover it.
     242- Made both buy and sell orders genuinely pass through `status='open'` before
     243  `status='executed'`, instead of being inserted as `'executed'` directly, so
     244  the lifecycle the ER model already claimed for `Orders` is real.
     245- Added a `CHECK (reserved_quantity >= 0 AND reserved_quantity <= quantity)`
     246  constraint and exposed `reserved_quantity`/`available_quantity` on
     247  `v_portfolio` and in the CLI's portfolio table, so the new field is
     248  observable rather than a column nobody can see.
     249- Restructured this phase's [ERModel](ERModel.md) into short rationale + a
     250  compact attribute table per entity/relationship, per the "more put together,
     251  not that much spread out" request — no content was removed, only
     252  reformatted.
     253- Verified the fix against the live database (`bp_database` on `localhost:5433`):
     254  ran two concurrent `sell 1.0 BTC` attempts against a holding with only 1.5
     255  BTC available — exactly one succeeded, the other correctly reported
     256  insufficient holding — and ran the reserve/settle sequence by hand in `psql`
     257  to show `reserved_quantity` at 0.5 mid-transaction. Both are recorded in
     258  [UseCase0005Implementation](../P4-Prototype/UseCase0005Implementation.md).
     259
     260**What I decided:** to keep reserve and settle inside one transaction rather
     261than two (see the AI's reasoning above — I agreed with it, since a stuck
     262`open` order with no cancel command would be a worse bug than the one being
     263fixed).
     264
     265**Follow-up, same day:** I asked for `ERModel_v03.xml`/`.png` after all,
     266having noticed the PNG still showed v02 with no `reserved_quantity` on it, and
     267asked at the same time for the diagram to be a little more compact — it had a
     268lot of empty canvas in the middle. The AI drove TerraER's own classes directly
     269(load → clone the `quantity` attribute → relabel it → add its connecting line
     270→ pull every cluster toward the centroid → save → render), described in full
     271under [Diagram](#diagram) above, rather than hand-editing the XML or asking me
     272to do it in the GUI. I reviewed the rendered PNG before accepting it.
     273
     274**Second follow-up, same day:** the first `ERModel_v03.png` rendered with a
     275black background instead of white, unlike v01/v02. Cause: TerraER's
     276`ImageOutputFormat` defaults to an ARGB image and paints its background with
     277zero alpha (transparent), not opaque white; whatever displayed the PNG then
     278flattened that transparency onto black instead of white. Fixed by exporting
     279through the same `ImageOutputFormat.toImage(...)` call for figure geometry,
     280but compositing its result onto an explicitly white-filled opaque `RGB` image
     281before saving, rather than trusting the library's own (transparent) output.
     282Verified the fix by reading back the corner pixel of the written PNG as pure
     283white `(255,255,255)`, matching `ERModel_v02.png`.
     284
     285> **Student action required.** Open `ERModel_v03.xml` in TerraER and read it
     286> end to end before submission — see the note at the end of
     287> [ERModel](ERModel.md). Everything else in this session's diff is already
     288> applied to the docs and to `server/`.
Note: See TracChangeset for help on using the changeset viewer.