Index: docs/P3-UseCaseModel/UseCase0004.md
===================================================================
--- docs/P3-UseCaseModel/UseCase0004.md	(revision 9577c7944ff24ddc9145ff6637494f07d87c3dd4)
+++ docs/P3-UseCaseModel/UseCase0004.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -10,8 +10,9 @@
 
 1. Trader chooses "Place market BUY order".
-2. System lists the available markets with their latest price:
+2. System lists the active markets, numbered, with their latest price:
 
    ```sql
-   SELECT m.id, c.symbol, m.quote_currency, COALESCE(lp.price, 0)
+   SELECT m.id, c.id, c.symbol, m.quote_currency,
+          COALESCE(lp.price, 0) AS price
      FROM project.markets m
      JOIN project.crypto  c  ON c.id = m.crypto_id
@@ -20,14 +21,10 @@
     ORDER BY c.symbol;
    ```
-3. Trader enters a market symbol, e.g. `ETH`.
-4. System resolves the market and looks up the latest price:
+3. Trader picks the market by its number in the listed markets, e.g. `2` (BTC).
+4. System takes the chosen row's market id and crypto id from the list (no lookup by
+   symbol) and looks up the latest price:
 
    ```sql
-   SELECT m.id, c.id AS crypto_id, c.symbol, m.quote_currency
-     FROM project.markets m
-     JOIN project.crypto c ON c.id = m.crypto_id
-    WHERE upper(c.symbol) = upper($1) AND m.is_active = true;
-
-   SELECT price FROM project.v_latest_prices WHERE market_id = $2;
+   SELECT price FROM project.v_latest_prices WHERE market_id = $1;
    ```
 5. Trader enters a quantity.
@@ -91,5 +88,5 @@
 If `available_balance < notional`, the entire transaction rolls back and system shows "Insufficient funds: need X, have Y."
 
-### Alternate flow 4a — market not found
+### Alternate flow 3a — number not in the list
 
-If the entered symbol does not match any active market, system shows "market X not found" and returns to the authenticated menu without opening a transaction.
+If the entered number is not one of the listed market numbers, system shows "Invalid choice, enter a number from 1 to N." and returns to the authenticated menu without opening a transaction.
Index: docs/P3-UseCaseModel/UseCase0005.md
===================================================================
--- docs/P3-UseCaseModel/UseCase0005.md	(revision 9577c7944ff24ddc9145ff6637494f07d87c3dd4)
+++ docs/P3-UseCaseModel/UseCase0005.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -26,7 +26,28 @@
 
 1. Trader chooses "Place market SELL order".
-2. System lists markets (same SQL as UC0004 step 2).
-3. Trader enters market symbol and quantity.
-4. System resolves the market and looks up the latest price (same SQL as UC0004 step 4).
+2. System lists, numbered, the Trader's holdings that still have a quantity free to
+   sell (not reserved by an open sell order), with the quantity held, the free
+   quantity and the latest price:
+
+   ```sql
+   SELECT m.id, c.id, c.symbol, m.quote_currency,
+          h.quantity, h.quantity - h.reserved_quantity AS free,
+          COALESCE(lp.price, 0) AS price
+     FROM project.holdings h
+     JOIN project.crypto  c ON c.id = h.crypto_id
+     JOIN project.markets m ON m.crypto_id = c.id AND m.is_active = true
+     LEFT JOIN project.v_latest_prices lp ON lp.market_id = m.id
+    WHERE h.user_id = $1
+      AND h.quantity - h.reserved_quantity > 0
+    ORDER BY c.symbol;
+   ```
+3. Trader picks the holding by its number in the listed holdings, e.g. `2` (ETH), and
+   enters the quantity.
+4. System takes the chosen row's market id and crypto id from the list (no lookup by
+   symbol) and looks up the latest price:
+
+   ```sql
+   SELECT price FROM project.v_latest_prices WHERE market_id = $1;
+   ```
 5. System opens a transaction:
 
Index: docs/P3-UseCaseModel/UseCase0007.md
===================================================================
--- docs/P3-UseCaseModel/UseCase0007.md	(revision 9577c7944ff24ddc9145ff6637494f07d87c3dd4)
+++ docs/P3-UseCaseModel/UseCase0007.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -40,11 +40,17 @@
 ### Add a crypto
 
+The Trader picks the crypto by its number from the listed cryptos that are not on the watchlist yet:
+
 ```sql
--- 1. resolve the symbol to a crypto_id
-SELECT id FROM project.crypto WHERE upper(symbol) = upper($1);
+-- 1. list, numbered, the cryptos not yet on the watchlist
+SELECT c.id, c.symbol, c.name
+  FROM project.crypto c
+ WHERE NOT EXISTS (SELECT 1 FROM project.watchlist_items wi
+                    WHERE wi.watchlist_id = $1 AND wi.crypto_id = c.id)
+ ORDER BY c.symbol;
 
--- 2. insert the item; do nothing if it's already there
+-- 2. insert the chosen crypto ($2 = its id from the list); do nothing if it's already there
 INSERT INTO project.watchlist_items (watchlist_id, crypto_id)
-VALUES ($watchlist_id, $crypto_id)
+VALUES ($1, $2)
 ON CONFLICT (watchlist_id, crypto_id) DO NOTHING;
 ```
@@ -52,12 +58,18 @@
 ### Remove a crypto
 
+The Trader picks the crypto by its number from the listed cryptos on the watchlist:
+
 ```sql
+-- 1. list, numbered, the cryptos on the watchlist
+SELECT c.id, c.symbol, c.name
+  FROM project.watchlist_items wi
+  JOIN project.crypto c ON c.id = wi.crypto_id
+ WHERE wi.watchlist_id = $1
+ ORDER BY c.symbol;
+
+-- 2. delete the chosen crypto ($2 = its id from the list)
 DELETE FROM project.watchlist_items
- WHERE watchlist_id = $1
-   AND crypto_id = (
-       SELECT id FROM project.crypto
-        WHERE upper(symbol) = upper($2)
-   );
+ WHERE watchlist_id = $1 AND crypto_id = $2;
 ```
 
-If the delete affects zero rows, system shows "Not in watchlist."
+If the entered number is not one of the listed numbers, system shows "Invalid choice, enter a number from 1 to N." and nothing is changed.
Index: docs/P3-UseCaseModel/wiki/UseCase0001.md
===================================================================
--- docs/P3-UseCaseModel/wiki/UseCase0001.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
+++ docs/P3-UseCaseModel/wiki/UseCase0001.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -0,0 +1,42 @@
+= Use-case 0001 — Register new account =
+
+'''Initiating actor:''' Visitor
+
+'''Other actors:''' —
+
+A new person creates an account on !EduBerza so they can later log in as a Trader. The system validates input, refuses duplicates, and stores a hashed password.
+
+== Scenario ==
+
+ 1. Visitor chooses "Register" from the anonymous menu.
+ 2. System prompts for username, email, full name and password.
+ 3. Visitor enters values.
+ 4. System validates:
+   * username, email, password are non-empty.
+   * email contains `@`.
+   * password is at least 6 characters.
+ 5. System checks whether the chosen username or email already exists:
+
+{{{
+SELECT EXISTS (
+    SELECT 1 FROM project.users
+     WHERE username = $1 OR email = $2
+);
+}}}
+
+ 6. If the row exists, system informs the user and the scenario ends. Otherwise it creates the account:
+
+{{{
+INSERT INTO project.users (username, email, full_name, password_hash, available_balance)
+VALUES ($1, $2, $3, encode(digest($4, 'sha256'), 'hex'), 0);
+}}}
+
+ 7. System confirms success and returns to the anonymous menu; Visitor can then proceed to UC0002.
+
+=== Alternate flow 3a — invalid email ===
+
+If step 4 fails email validation, system shows "Invalid email." and scenario returns to step 2.
+
+=== Alternate flow 5a — duplicate ===
+
+If step 5 returns `true`, system shows "Username or email already taken." and scenario ends.
Index: docs/P3-UseCaseModel/wiki/UseCase0002.md
===================================================================
--- docs/P3-UseCaseModel/wiki/UseCase0002.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
+++ docs/P3-UseCaseModel/wiki/UseCase0002.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -0,0 +1,35 @@
+= Use-case 0002 — Log in =
+
+'''Initiating actor:''' Visitor
+
+'''Other actors:''' —
+
+A registered user authenticates so the system can treat subsequent actions as a Trader.
+
+== Scenario ==
+
+ 1. Visitor chooses "Login" from the anonymous menu.
+ 2. System prompts for username and password.
+ 3. Visitor enters values.
+ 4. System looks up the user:
+
+{{{
+SELECT id, password_hash
+  FROM project.users
+ WHERE username = $1;
+}}}
+
+ 5. If no row is returned, system responds "Invalid credentials." and scenario ends.
+ 6. If a row is returned, system compares the stored hash against sha256 of the entered password. On mismatch it responds "Invalid credentials." and ends.
+ 7. On match, system records the returned `id` and username in the session and displays the authenticated menu.
+
+=== Alternate flow 4a — authenticated lookup with live balance ===
+
+The system may combine identity lookup with live balance in a single query, for use cases that need both:
+
+{{{
+SELECT id, available_balance, invested_balance
+  FROM project.users
+ WHERE username = $1
+   AND password_hash = encode(digest($2, 'sha256'), 'hex');
+}}}
Index: docs/P3-UseCaseModel/wiki/UseCase0003.md
===================================================================
--- docs/P3-UseCaseModel/wiki/UseCase0003.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
+++ docs/P3-UseCaseModel/wiki/UseCase0003.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -0,0 +1,45 @@
+= Use-case 0003 — Deposit virtual funds =
+
+'''Initiating actor:''' Trader
+
+'''Other actors:''' —
+
+A logged-in Trader tops up their virtual cash balance. This is a simulation-only operation; no real money changes hands. The operation writes to two tables — the user row and the ledger — inside a single transaction.
+
+== Scenario ==
+
+ 1. Trader chooses "Deposit virtual funds" from the authenticated menu.
+ 2. System prompts for an amount in USD.
+ 3. Trader enters an amount.
+ 4. System validates: amount must parse as a positive number.
+ 5. System opens a transaction and increments the balance:
+
+{{{
+BEGIN;
+
+UPDATE project.users
+   SET available_balance = available_balance + $1,
+       updated_at        = now()
+ WHERE id = $2;
+
+INSERT INTO project.transactions (user_id, type, amount, currency, description)
+VALUES ($2, 'deposit', $1, 'USD', 'Virtual deposit');
+
+COMMIT;
+}}}
+
+ 6. System confirms "Deposited X USD." and returns to the authenticated menu.
+
+=== Alternate flow 4a — invalid input ===
+
+If the amount is non-positive or non-numeric, system responds "Invalid amount." and scenario returns to step 2.
+
+=== Verification query ===
+
+To see the balance after the deposit, the Trader can trigger UC0006, or directly:
+
+{{{
+SELECT available_balance, invested_balance
+  FROM project.users
+ WHERE id = $1;
+}}}
Index: docs/P3-UseCaseModel/wiki/UseCase0004.md
===================================================================
--- docs/P3-UseCaseModel/wiki/UseCase0004.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
+++ docs/P3-UseCaseModel/wiki/UseCase0004.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -0,0 +1,95 @@
+= Use-case 0004 — Place market BUY order =
+
+'''Initiating actor:''' Trader
+
+'''Other actors:''' Market Simulator (indirect — supplies the current price via `market_trades`).
+
+A Trader buys a crypto asset at the current market price. The operation touches five tables (`orders`, `users`, `holdings`, `transactions`, `market_trades`) and must either all succeed or all roll back.
+
+== Scenario ==
+
+ 1. Trader chooses "Place market BUY order".
+ 2. System lists the active markets, numbered, with their latest price:
+
+{{{
+SELECT m.id, c.id, c.symbol, m.quote_currency,
+       COALESCE(lp.price, 0) AS price
+  FROM project.markets m
+  JOIN project.crypto  c  ON c.id = m.crypto_id
+  LEFT JOIN project.v_latest_prices lp ON lp.market_id = m.id
+ WHERE m.is_active = true
+ ORDER BY c.symbol;
+}}}
+
+ 3. Trader picks the market by its number in the listed markets, e.g. `2` (BTC).
+ 4. System takes the chosen row's market id and crypto id from the list (no lookup by
+    symbol) and looks up the latest price:
+
+{{{
+SELECT price FROM project.v_latest_prices WHERE market_id = $1;
+}}}
+
+ 5. Trader enters a quantity.
+ 6. System computes notional = quantity × price, opens a transaction, and does:
+
+{{{
+BEGIN;
+
+-- (a) record intent — no trade has happened yet.
+INSERT INTO project.orders
+    (user_id, market_id, side, type, status, quantity, price)
+VALUES
+    ($user_id, $market_id, 'buy', 'market', 'open', $qty, $price)
+RETURNING id;  -- captured as $order_id
+
+-- (b) lock and check the user balance
+SELECT available_balance FROM project.users WHERE id = $user_id FOR UPDATE;
+-- abort if available_balance < notional
+
+-- (c) move cash from available to invested. A buy never reserves crypto
+--     the way a sell does — it only ever adds to the position, so there
+--     is nothing on the holdings side to commit before settling.
+UPDATE project.users
+   SET available_balance = available_balance - $notional,
+       invested_balance  = invested_balance  + $notional,
+       updated_at        = now()
+ WHERE id = $user_id;
+
+-- (d) upsert holding with running weighted-average price:
+SELECT quantity, avg_price
+  FROM project.holdings
+ WHERE user_id = $user_id AND crypto_id = $crypto_id
+ FOR UPDATE;
+
+-- Either INSERT (new holding) or UPDATE (existing), computing
+-- new_avg = (old_qty*old_avg + $qty*$price) / (old_qty + $qty)
+
+-- (e) ledger entry
+INSERT INTO project.transactions
+    (user_id, type, amount, currency, related_order, description)
+VALUES
+    ($user_id, 'buy', -$notional, 'USD', $order_id, 'Market buy ...');
+
+-- (f) record the resulting market trade
+INSERT INTO project.market_trades
+    (market_id, executed_at, price, quantity, side, source)
+VALUES
+    ($market_id, now(), $price, $qty, 'buy', 'user');
+
+-- (g) settle the order itself — it has now actually been filled.
+UPDATE project.orders
+   SET status = 'executed', executed_at = now()
+ WHERE id = $order_id;
+
+COMMIT;
+}}}
+
+ 7. System confirms: `Order executed: buy 0.0100 BTC @ 67140.000000 (notional 671.4000 USD)`.
+
+=== Alternate flow 6a — insufficient funds ===
+
+If `available_balance < notional`, the entire transaction rolls back and system shows "Insufficient funds: need X, have Y."
+
+=== Alternate flow 3a — number not in the list ===
+
+If the entered number is not one of the listed market numbers, system shows "Invalid choice, enter a number from 1 to N." and returns to the authenticated menu without opening a transaction.
Index: docs/P3-UseCaseModel/wiki/UseCase0005.md
===================================================================
--- docs/P3-UseCaseModel/wiki/UseCase0005.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
+++ docs/P3-UseCaseModel/wiki/UseCase0005.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -0,0 +1,141 @@
+= Use-case 0005 — Place market SELL order =
+
+'''Initiating actor:''' Trader
+
+'''Other actors:''' Market Simulator (indirect — supplies the current price).
+
+A Trader sells part or all of a holding at the current market price. Cost basis is preserved so realised P/L can be reconstructed from the ledger.
+
+== Reserve, then settle ==
+
+The crypto being sold is '''reserved''' (`holdings.reserved_quantity`) before it
+is actually removed from the position, so the check a second sell order makes
+is always against what is truly still free (`quantity - reserved_quantity`),
+not against the raw `quantity`, which would also count crypto already
+promised to this order. Because only market orders are implemented, an order
+settles in the same database transaction it is placed in, so reserve and
+settle below are two statements inside one commit rather than two separate
+ones — the existing all-or-nothing guarantee (see
+[wiki:PrototypeImplementation]) is
+kept. They stay logically distinct so that a future limit-order matcher —
+where an order really would sit `open` for a while before a ''later''
+transaction settles it — needs only a second transaction where today there is
+one, not a schema change.
+
+== Scenario ==
+
+ 1. Trader chooses "Place market SELL order".
+ 2. System lists, numbered, the Trader's holdings that still have a quantity free to
+    sell (not reserved by an open sell order), with the quantity held, the free
+    quantity and the latest price:
+
+{{{
+SELECT m.id, c.id, c.symbol, m.quote_currency,
+       h.quantity, h.quantity - h.reserved_quantity AS free,
+       COALESCE(lp.price, 0) AS price
+  FROM project.holdings h
+  JOIN project.crypto  c ON c.id = h.crypto_id
+  JOIN project.markets m ON m.crypto_id = c.id AND m.is_active = true
+  LEFT JOIN project.v_latest_prices lp ON lp.market_id = m.id
+ WHERE h.user_id = $1
+   AND h.quantity - h.reserved_quantity > 0
+ ORDER BY c.symbol;
+}}}
+
+ 3. Trader picks the holding by its number in the listed holdings, e.g. `2` (ETH), and
+    enters the quantity.
+ 4. System takes the chosen row's market id and crypto id from the list (no lookup by
+    symbol) and looks up the latest price:
+
+{{{
+SELECT price FROM project.v_latest_prices WHERE market_id = $1;
+}}}
+
+ 5. System opens a transaction:
+
+{{{
+BEGIN;
+
+-- (a) record intent — no trade has happened yet.
+INSERT INTO project.orders
+    (user_id, market_id, side, type, status, quantity, price)
+VALUES
+    ($user_id, $market_id, 'sell', 'market', 'open', $qty, $price)
+RETURNING id;   -- $order_id
+
+-- (b) lock the holding and check what is actually free to sell.
+SELECT quantity, reserved_quantity, avg_price
+  FROM project.holdings
+ WHERE user_id = $user_id AND crypto_id = $crypto_id
+ FOR UPDATE;
+-- available := quantity - reserved_quantity
+-- abort if row missing or available < $qty
+}}}
+
+ 6. If the check passes, system reserves the crypto, then — since this is a market order — settles it immediately, all inside the same transaction:
+
+{{{
+-- (c) reserve: committed to this order, not yet removed from the position.
+UPDATE project.holdings
+   SET reserved_quantity = reserved_quantity + $qty,
+       updated_at        = now()
+ WHERE user_id = $user_id AND crypto_id = $crypto_id;
+
+-- (d) settle: release the reservation and remove the asset in one step.
+UPDATE project.holdings
+   SET quantity          = quantity - $qty,
+       reserved_quantity = reserved_quantity - $qty,
+       updated_at        = now()
+ WHERE user_id = $user_id AND crypto_id = $crypto_id;
+
+UPDATE project.users
+   SET available_balance = available_balance + $notional,
+       invested_balance  = GREATEST(invested_balance - ($avg_price * $qty), 0),
+       updated_at        = now()
+ WHERE id = $user_id;
+
+INSERT INTO project.transactions
+    (user_id, type, amount, currency, related_order, description)
+VALUES
+    ($user_id, 'sell', $notional, 'USD', $order_id, 'Market sell ...');
+
+INSERT INTO project.market_trades
+    (market_id, executed_at, price, quantity, side, source)
+VALUES
+    ($market_id, now(), $price, $qty, 'sell', 'user');
+
+-- (e) settle the order itself — it has now actually been filled.
+UPDATE project.orders
+   SET status = 'executed', executed_at = now()
+ WHERE id = $order_id;
+
+COMMIT;
+}}}
+
+ 7. System confirms: `Order executed: sell 0.5000 ETH @ 3520.000000 (notional 1760.0000 USD)`.
+
+=== Alternate flow 5a — insufficient holding ===
+
+If the holding row is missing, or `quantity - reserved_quantity < $qty`, the
+entire transaction rolls back — including the `open` order from step 5, which
+was never committed — and system shows:
+`"Insufficient holding: trying to sell X, available Y (of Z held, W reserved)."`
+
+=== Worked example — the case this fixes ===
+
+Alice holds 2 BTC, `reserved_quantity = 0`, and places `sell 0.5 BTC`:
+
+||= =||= quantity =||= reserved_quantity =||= available =||
+|| before || 2.0000 || 0.0000 || 2.0000 ||
+|| after step (c) — reserved || 2.0000 || 0.5000 || 1.5000 ||
+|| after step (d) — settled || 1.5000 || 0.0000 || 1.5000 ||
+
+If a second sell for more than 1.5 BTC is placed concurrently, its own
+`SELECT … FOR UPDATE` in step 5b blocks until the first transaction commits,
+then sees the reduced `quantity` and correctly reports insufficient holding —
+proven under real concurrency in
+[wiki:UseCase0005Implementation].
+
+=== Realised P/L (post-scenario) ===
+
+The realised P/L for a sell is `$notional - ($avg_price * $qty)`. It is not persisted explicitly but can be computed from the ledger and the holding at sell time.
Index: docs/P3-UseCaseModel/wiki/UseCase0006.md
===================================================================
--- docs/P3-UseCaseModel/wiki/UseCase0006.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
+++ docs/P3-UseCaseModel/wiki/UseCase0006.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -0,0 +1,75 @@
+= Use-case 0006 — View portfolio and transaction history =
+
+'''Initiating actor:''' Trader
+
+'''Other actors:''' —
+
+A Trader inspects their current holdings, unrealised P/L, cash balance and recent ledger.
+
+== Scenario ==
+
+=== Portfolio ===
+
+ 1. Trader chooses "View portfolio".
+ 2. System queries the `v_portfolio` view:
+
+{{{
+SELECT symbol,
+       quantity,
+       COALESCE(reserved_quantity,  0),
+       COALESCE(available_quantity, quantity),
+       COALESCE(avg_price,      0),
+       COALESCE(current_price,  0),
+       COALESCE(market_value,   0),
+       COALESCE(unrealized_pnl, 0)
+  FROM project.v_portfolio
+ WHERE user_id = $1
+   AND quantity > 0
+ ORDER BY symbol;
+}}}
+
+`reserved_quantity` is the amount committed to the Trader's own open sell
+orders (see [wiki:UseCase0005]); `available_quantity` is what is
+actually free to sell right now.
+
+ 3. System displays the rows and a computed summary:
+
+{{{
+SELECT available_balance, invested_balance
+  FROM project.users
+ WHERE id = $1;
+}}}
+
+=== Transaction history ===
+
+ 1. Trader chooses "View transaction history".
+ 2. System queries the last 20 ledger entries:
+
+{{{
+SELECT created_at, type, amount, currency, COALESCE(description, '')
+  FROM project.transactions
+ WHERE user_id = $1
+ ORDER BY created_at DESC
+ LIMIT 20;
+}}}
+
+=== Reference — how `v_portfolio` is defined ===
+
+{{{
+CREATE OR REPLACE VIEW project.v_portfolio AS
+SELECT h.user_id,
+       c.symbol,
+       h.quantity,
+       h.reserved_quantity,
+       (h.quantity - h.reserved_quantity)      AS available_quantity,
+       h.avg_price,
+       lp.price                                AS current_price,
+       (h.quantity * lp.price)                 AS market_value,
+       (h.quantity * (lp.price - h.avg_price)) AS unrealized_pnl
+  FROM project.holdings h
+  JOIN project.crypto   c ON c.id = h.crypto_id
+  LEFT JOIN project.markets m
+         ON m.crypto_id = c.id AND m.quote_currency = 'USD'
+  LEFT JOIN project.v_latest_prices lp
+         ON lp.market_id = m.id;
+}}}
Index: docs/P3-UseCaseModel/wiki/UseCase0007.md
===================================================================
--- docs/P3-UseCaseModel/wiki/UseCase0007.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
+++ docs/P3-UseCaseModel/wiki/UseCase0007.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -0,0 +1,76 @@
+= Use-case 0007 — Manage watchlist =
+
+'''Initiating actor:''' Trader
+
+'''Other actors:''' —
+
+A Trader keeps a list of crypto assets they want to monitor. Adding an asset that is already on the list is a no-op (idempotent).
+
+== Scenario ==
+
+ 1. Trader chooses "Manage watchlist".
+ 2. System ensures the Trader has a default watchlist named "Favorites":
+
+{{{
+SELECT id FROM project.watchlists
+ WHERE user_id = $1
+ ORDER BY created_at LIMIT 1;
+
+-- if no row:
+INSERT INTO project.watchlists (user_id, name)
+VALUES ($1, 'Favorites')
+RETURNING id;
+}}}
+
+ 3. System shows the sub-menu: List / Add / Remove / Back.
+
+=== List items ===
+
+{{{
+SELECT c.symbol, c.name, COALESCE(lp.price, 0)
+  FROM project.watchlist_items wi
+  JOIN project.crypto c ON c.id = wi.crypto_id
+  LEFT JOIN project.markets m
+         ON m.crypto_id = c.id AND m.quote_currency = 'USD'
+  LEFT JOIN project.v_latest_prices lp
+         ON lp.market_id = m.id
+ WHERE wi.watchlist_id = $1
+ ORDER BY c.symbol;
+}}}
+
+=== Add a crypto ===
+
+The Trader picks the crypto by its number from the listed cryptos that are not on the watchlist yet:
+
+{{{
+-- 1. list, numbered, the cryptos not yet on the watchlist
+SELECT c.id, c.symbol, c.name
+  FROM project.crypto c
+ WHERE NOT EXISTS (SELECT 1 FROM project.watchlist_items wi
+                    WHERE wi.watchlist_id = $1 AND wi.crypto_id = c.id)
+ ORDER BY c.symbol;
+
+-- 2. insert the chosen crypto ($2 = its id from the list); do nothing if it's already there
+INSERT INTO project.watchlist_items (watchlist_id, crypto_id)
+VALUES ($1, $2)
+ON CONFLICT (watchlist_id, crypto_id) DO NOTHING;
+}}}
+
+=== Remove a crypto ===
+
+The Trader picks the crypto by its number from the listed cryptos on the watchlist:
+
+{{{
+-- 1. list, numbered, the cryptos on the watchlist
+SELECT c.id, c.symbol, c.name
+  FROM project.watchlist_items wi
+  JOIN project.crypto c ON c.id = wi.crypto_id
+ WHERE wi.watchlist_id = $1
+ ORDER BY c.symbol;
+
+-- 2. delete the chosen crypto ($2 = its id from the list)
+DELETE FROM project.watchlist_items
+ WHERE watchlist_id = $1 AND crypto_id = $2;
+}}}
+
+If the entered number is not one of the listed numbers, system shows "Invalid choice, enter a number from 1 to N." and nothing is changed.
Index: docs/P3-UseCaseModel/wiki/UseCaseModel.md
===================================================================
--- docs/P3-UseCaseModel/wiki/UseCaseModel.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
+++ docs/P3-UseCaseModel/wiki/UseCaseModel.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -0,0 +1,118 @@
+= Use-case model =
+
+The detailed pages for this phase are kept in the project's !GitHub repository,
+`https://github.com/StefanTrsunov/bp`, under `docs/P3-UseCaseModel/`. Every
+use case below is documented on its own wiki page ([wiki:UseCase0001] to [wiki:UseCase0007]).
+
+== Actors / Roles ==
+
+'''Visitor''' – Anyone using !EduBerza without an account, who can look at public market
+information, create an account, and log in.
+
+'''Trader''' – A registered, logged-in user who deposits virtual funds, places market buy and
+sell orders, follows the value of their portfolio, and keeps a watchlist of assets they want to
+monitor.
+
+'''Market Simulator''' – An external automated system (the bot in `bots/`) that writes simulated
+trades and candles into the database so prices move without a connection to a real exchange.
+
+== Use-Cases ==
+
+=== Visitor ===
+
+ * UC0001 –
+   '''Register new account''' – Visitor creates an account with a unique username and e-mail; the
+   password is stored as a SHA-256 hash.
+ * UC0002 –
+   '''Log in''' – Visitor authenticates with username and password so the system treats every
+   following action as a Trader.
+
+=== Trader ===
+
+ * UC0003 –
+   '''Deposit virtual funds''' – Trader tops up their virtual cash balance; the user row and the
+   ledger are written in one transaction.
+ * UC0004 –
+   '''Place market BUY order''' – Trader buys a crypto asset at the current market price, which
+   debits cash and upserts the holding at a running weighted-average price.
+ * UC0005 –
+   '''Place market SELL order''' – Trader sells part or all of a holding at the current market
+   price, which reserves the crypto being sold, credits cash and preserves the cost basis.
+ * UC0006 –
+   '''View portfolio and transaction history''' – Trader inspects current holdings, unrealised
+   P/L, cash balances and the most recent ledger entries.
+ * UC0007 –
+   '''Manage watchlist''' – Trader lists, adds and removes crypto assets on a personal watchlist,
+   where adding an asset already on the list is a no-op.
+
+=== Market Simulator ===
+
+The Market Simulator initiates no use case of its own. It participates in
+UC0004 and
+UC0005
+indirectly, by keeping `project.market_trades` populated so that `project.v_latest_prices`
+returns a current price for every active market.
+
+== Use-case model diagram ==
+
+{{{#!comment
+The diagram is optional for P3. Attach the exported image use_case_diagram.png
+to this wiki page and then replace this comment with:
+
+[[Image(use_case_diagram.png)]]
+}}}
+
+== Detailed Use-Cases ==
+
+The following use-cases are documented in detail, with SQL tested against the P2 database:
+
+ * [wiki:UseCase0001] – Visitor registers a new account
+ * [wiki:UseCase0002] – Visitor logs in
+ * [wiki:UseCase0003] – Trader deposits virtual funds
+ * [wiki:UseCase0004] – Trader places a market BUY order
+ * [wiki:UseCase0005] – Trader places a market SELL order
+ * [wiki:UseCase0006] – Trader views portfolio and transaction history
+ * [wiki:UseCase0007] – Trader manages a watchlist
+
+== Realization details on selection of the most important use cases ==
+
+This is a solo project, so '''at least 3 use cases''' are required. '''7 use cases''' are
+documented, for a safety margin. All seven are implemented in the P4 prototype; see
+`server/` for the Go source and
+[wiki:PrototypeImplementation]
+for the documented runs.
+
+||=Use case=||=Importance=||=Why it was selected=||
+||UC0001 – Register||High||Nothing else works without it; demonstrates `INSERT` with a uniqueness check.||
+||UC0002 – Log in||High||Authenticates every Trader action; demonstrates `SELECT` with parameter binding.||
+||UC0003 – Deposit||High||Shows a multi-row transaction: `UPDATE users` plus `INSERT INTO transactions`.||
+||UC0004 – Buy||Very high||Core of the exchange: `INSERT orders`, `UPDATE users`, upsert `holdings`, ledger entry, market trade.||
+||UC0005 – Sell||Very high||Dual of Buy; demonstrates row-level `FOR UPDATE` locking, reservation of committed crypto (`holdings.reserved_quantity`) and cost-basis bookkeeping.||
+||UC0006 – Portfolio||High||Demonstrates joins over `holdings`, `markets` and `crypto`, and the `v_portfolio` view.||
+||UC0007 – Watchlist||Medium||Demonstrates N–M relation handling and `ON CONFLICT` upsert semantics.||
+
+== AI usage ==
+
+AI was used in this phase and is logged in full, per the course rule for P1 onward.
+
+ * '''Phase log:'''
+   [wiki:UseCaseModelAIUsage]
+   – service used, what the AI produced, and what I decided myself.
+ * '''Full conversation transcript:'''
+   [wiki:ERModelAIUsage]
+   – the same conversation produced the P1–P4 artefacts, so the complete prompt/response log is
+   kept in one place. Relevant sections of that page:
+   Session 1 – 2026-04-21,
+   Session 2 – 2026-08-06/07,
+   Session 3 – 2026-09-16.
+
+'''Service:''' Claude Code (Anthropic), `https://claude.com/claude-code` – Claude subscription,
+model Claude Opus 4.7 (1M context) in sessions 1–2, Claude Sonnet 5 in session 3.
+
+'''In short:''' the AI proposed the actor taxonomy and drafted the seven use cases with their SQL
+in session 1. In session 2 the use-case model itself was '''not''' changed – the only work was
+re-executing every scenario, including the failure paths, against a live PostgreSQL 16 database.
+In session 3, UC0004 and UC0005 were revised to reserve the resource an order commits (crypto on
+a sell) before settling it, closing a gap where nothing stopped a second sell order from being
+granted crypto already promised to a first one; see the Session 3 – 2026-09-16 section of
+[wiki:UseCaseModelAIUsage].
Index: docs/P3-UseCaseModel/wiki/UseCaseModelAIUsage.md
===================================================================
--- docs/P3-UseCaseModel/wiki/UseCaseModelAIUsage.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
+++ docs/P3-UseCaseModel/wiki/UseCaseModelAIUsage.md	(revision 0cee8ec194e8e518624fcec244168861a6a7de27)
@@ -0,0 +1,93 @@
+= Use-Case Model AI Usage =
+
+== Name of AI service/solution that was used ==
+
+'''Claude Code''' (Anthropic)
+
+ * '''URL:''' `https://claude.com/claude-code`
+ * '''Type of service/subscription:''' Claude subscription, model Claude Opus 4.7 (1M context).
+
+== Final result ==
+
+=== Results in details / description ===
+
+The AI:
+
+ * Proposed the actor taxonomy (Visitor, Trader, Market Simulator) from the project description and the existing Go code.
+ * Derived a set of 7 use cases covering the full trading loop (register, login, deposit, buy, sell, view portfolio, watchlist).
+ * Wrote each `UseCaseXXXX.md` file with:
+   * initiating actor, other actors, goals
+   * step-by-step dialog-form scenario
+   * '''tested''' SQL statements for every database-touching step, using the `project` schema
+   * alternate flows for the most common failure cases (insufficient funds, insufficient holding, duplicate user, invalid input)
+
+== Summary of AI involvement ==
+
+||= =||= Session 1 — 2026-04-21 =||= Session 2 — 2026-08-06/07 =||
+|| '''What I brought''' || The project description in `opis.md` and the existing Go backend || The use-case model as completed in session 1 ||
+|| '''What the AI did''' || Proposed the actor taxonomy and drafted seven use cases with tested SQL || Nothing — the model was not changed ||
+|| '''What I decided''' || To go solo, and therefore to document seven use cases rather than the three the rubric requires || To re-verify every scenario's SQL against a live database rather than trust the April run ||
+
+This phase was finished in session 1. In session 2 the only work was
+verification: each scenario was executed against a live PostgreSQL 16 database,
+including the failure paths, and the results recorded on the
+`UseCaseXXXXImplementation` pages.
+
+== Entire AI usage log ==
+
+See [wiki:ERModelAIUsage] for the full transcript — the same 2026-04-21 session produced the use-case documentation. The defining student prompt was:
+
+> I'm going solo do everything that you need to do, and tell me after what do I need to do
+
+which led the AI to pick "solo → 3 minimum per rubric → document 7 for a margin" as a default.
+
+> '''Student action required:''' append any future refinements of the use-case list or scenarios here.
+
+
+=== Session 2 — 2026-08-06 / 2026-08-07 ===
+
+No changes were made to the use-case model in this session: the actor list, the
+seven use cases and the scenario SQL in `UseCase0001`–`UseCase0007` are as
+produced on 2026-04-21. The SQL in those scenarios was, however, re-verified by
+executing the corresponding prototype flows against a live PostgreSQL 16
+database, including the failure paths (insufficient funds, insufficient holding,
+duplicate registration, wrong password). The results are documented per use case
+on the `UseCaseXXXXImplementation` pages.
+
+=== Session 3 — 2026-09-16 ===
+
+Driven by the design review logged in full in the Session 3 — 2026-09-16 section of
+[wiki:ERModelAIUsage]:
+placing a sell order checked `holdings.quantity` directly, with no way to
+record that part of a position was already promised to another, unsettled
+order.
+
+'''What changed:'''
+
+ * [wiki:UseCase0005] — the scenario now reserves the crypto
+   (`holdings.reserved_quantity`) before removing it from the position, checks
+   `quantity - reserved_quantity` rather than raw `quantity`, and adds a
+   worked example and a note on why the reserve and settle steps stay inside
+   one transaction rather than two (only market orders are implemented, and
+   splitting into two commits would risk an order stuck `open` with no cancel
+   use case to recover it).
+ * [wiki:UseCase0004] — no change to the balance logic, but the
+   order insert now goes through `status='open'` before a final
+   `UPDATE ... SET status='executed'`, matching the sell side, so `Orders`
+   genuinely has the lifecycle [wiki:ERModel]
+   describes for it rather than a status column that is only ever written
+   once.
+ * [wiki:UseCase0006] — the `v_portfolio` reference and its query
+   gained `reserved_quantity`/`available_quantity`, since the portfolio screen
+   is where a Trader would actually see the new field.
+ * The use-case importance table and UC0005's one-line description in
+   [wiki:UseCaseModel] were reworded to mention the reservation.
+
+Every changed scenario's SQL was re-run against the live database, including a
+two-concurrent-sells test that reproduces the exact bug being fixed: see
+[wiki:UseCase0005Implementation].
+
+'''What I decided:''' to keep this a revision of the existing UC0004/UC0005
+pages rather than a new use case (e.g. "cancel order") — `cancelled` remains
+an unused status, same as before, since nothing in the prototype produces it
+and inventing a cancel flow was not what the review asked for.
