Ignore:
Timestamp:
09/16/26 23:37:15 (13 days 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/P2-RelationalDesign
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • docs/P2-RelationalDesign/RelationalDesign.md

    rdf05838 r9577c79  
    1111- **Markets**(<u>**id**</u>, *crypto_id*, quote_currency, is_active, created_at)
    1212  - Candidate keys: `{id}`, `{crypto_id, quote_currency}`. `UNIQUE(crypto_id, quote_currency)`.
    13 - **Holdings**(<u>**id**</u>, *user_id*, *crypto_id*, quantity, avg_price, created_at, updated_at)
     13- **Holdings**(<u>**id**</u>, *user_id*, *crypto_id*, quantity, reserved_quantity, avg_price, created_at, updated_at)
    1414  - Transformation of the M:N relationship `Holds`. Candidate keys: `{id}` and
    1515    `{user_id, crypto_id}` — the latter is the relationship's own key and is
    … …  
    1717    consistency with the other relations.
    1818  - `avg_price` is `NOT NULL DEFAULT 0 CHECK (avg_price >= 0)`.
     19  - `reserved_quantity` is `NOT NULL DEFAULT 0 CHECK (reserved_quantity >= 0
     20    AND reserved_quantity <= quantity)` — the amount already committed to the
     21    user's own open sell orders. `quantity - reserved_quantity` (the amount
     22    actually free to sell) is not a stored column; it is computed wherever
     23    needed, in `v_portfolio` as `available_quantity` and in the sell path of
     24    [UseCase0005](../P3-UseCaseModel/UseCase0005.md). See
     25    [ERModel](../P1-ConceptualModel/ERModel.md#holds--users-m--cryptos-n-partial-on-both-sides-with-attributes)
     26    for why this mirrors `available_balance`/`invested_balance` on `Users`.
    1927- **Orders**(<u>**id**</u>, *user_id*, *market_id*, side, type, status, quantity, price, placed_at, executed_at)
    2028  - `side ∈ {buy, sell}`, `type ∈ {market, limit}`, `status ∈ {open, executed, cancelled}`.
    … …  
    5664### Normalisation
    5765
     66> **Validated in P5.** [Normalization](../P5-Normalization/Normalization.md) derives this
     67> exact schema independently — starting only from a single de-normalized relation of every
     68> model attribute and its functional dependencies, with no reference to the ER-to-relational
     69> transformation below — and shows it decomposes to **BCNF**, one normal form stronger than
     70> the 3NF claimed here. The two designs agree relation for relation and key for key, so
     71> nothing here changed as a result; see that page's
     72> [discussion](../P5-Normalization/Normalization.md#discussion) for what the one real
     73> difference is (`avg_price`, a stored derived value, not a normalisation issue) and why this
     74> design is still the one used from P5 onward.
     75
    5876All relations are in **3NF**:
    5977
    … …  
    7290  yields `NULL`, so a nullable average would have silently blanked the
    7391  unrealised-P/L column for an existing position instead of failing loudly.
     92- `holdings.reserved_quantity`, unlike `avg_price`, is **not** derived — it is
     93  written directly by the application (`trade.go`) as orders are placed and
     94  settled, the same way `quantity` itself is. `quantity - reserved_quantity`
     95  ("available") is the derived value here, and it is never stored, only
     96  computed where it is needed.
     97
     98### Reservation and the order lifecycle
     99
     100`holdings.reserved_quantity` exists so that placing a sell order can be
     101checked against what a user actually has *free* to sell
     102(`quantity - reserved_quantity`), not against the raw `quantity`, which also
     103counts crypto already promised to another order that has not settled yet.
     104`CHECK (reserved_quantity >= 0 AND reserved_quantity <= quantity)` makes an
     105inconsistent reservation impossible at the database level, regardless of what
     106application code does. The exact statement sequence — lock the row, check the
     107available amount, reserve, then settle — is in
     108[UseCase0005](../P3-UseCaseModel/UseCase0005.md); the same
     109`SELECT … FOR UPDATE` locking that already protected `users.available_balance`
     110on the buy path is what makes two concurrent sell orders against the same
     111holding serialize correctly instead of racing.
    74112
    75113## DDL script
    … …  
    80118- 10 tables with check constraints, primary keys, foreign keys and unique constraints.
    81119- 5 performance indexes.
    82 - 2 views: `v_latest_prices` (latest trade price per market) and `v_portfolio` (per-user holdings valuation with unrealised P/L).
     120- 2 views: `v_latest_prices` (latest trade price per market) and `v_portfolio` (per-user holdings valuation with unrealised P/L, plus `reserved_quantity` and the derived `available_quantity`).
    83121
    84122## DML script (sample data)
  • docs/P2-RelationalDesign/RelationalDesignAIUsage.md

    rdf05838 r9577c79  
    6969> against the faculty database. No AI involvement is possible there — it needs a
    7070> live connection to your assigned database.
     71
     72### Session 3 — 2026-09-16
     73
     74Driven by the same design review logged in full in
     75[ERModelAIUsage](../P1-ConceptualModel/ERModelAIUsage.md#session-3--2026-09-16):
     76a sell order had nothing to check `holdings.quantity` against except itself,
     77so nothing stopped two sell orders from being granted the same units.
     78
     79Changes to the P2 artefacts:
     80
     81- `holdings` gained `reserved_quantity numeric(20,4) NOT NULL DEFAULT 0
     82  CHECK (reserved_quantity >= 0 AND reserved_quantity <= quantity)` in
     83  `schema_creation.sql`.
     84- `v_portfolio` gained `reserved_quantity` and the derived
     85  `available_quantity = quantity - reserved_quantity`.
     86- [RelationalDesign](RelationalDesign.md) gained a "Reservation and the order
     87  lifecycle" section explaining why the check is enforced at the database
     88  level rather than trusted to application code, and why it does not conflict
     89  with the existing `SELECT … FOR UPDATE` locking on the sell path.
     90- `data_load.sql` needed no change — `reserved_quantity` defaults to 0, which
     91  is correct for every seeded holding.
     92
     93Re-run end to end against the live database on `localhost:5433`
     94(`-init` then `-load-data`), and against a manually seeded 2 BTC holding to
     95reproduce the exact scenario that motivated the change — see
     96[UseCase0005Implementation](../P4-Prototype/UseCase0005Implementation.md) for
     97the transcript.
     98
     99**What I decided:** to add the `CHECK` constraint rather than rely on
     100`trade.go` alone to keep the reservation consistent — the same reasoning
     101already applied to `avg_price NOT NULL` in session 2.
Note: See TracChangeset for help on using the changeset viewer.