diff --git a/docs/FALUKANT_DIRECTOR_PRODUCTION_COST.md b/docs/FALUKANT_DIRECTOR_PRODUCTION_COST.md index b5991db..2c22566 100644 --- a/docs/FALUKANT_DIRECTOR_PRODUCTION_COST.md +++ b/docs/FALUKANT_DIRECTOR_PRODUCTION_COST.md @@ -43,15 +43,14 @@ effektiv_stückkosten = raw × (1 − discount) ## Ranking in SQL (`QUERY_GET_BEST_PRODUCTION`) -Sortierung nach **Gewinn pro Minute**, mit **demselben Verkaufs-Modell** wie `DirectorWorker::compute_piece_price_for_percent` / `compute_piece_sell_price` in `director.rs`: +Sortierung nach einer **Legacy/UI-Formel** (historisch C++), damit die Reihenfolge zur **Produkt-Ertrags-Tabelle** passt — **nicht** identisch mit dem tatsächlichen Verkauf im Tick (`compute_piece_sell_price` in `director.rs`). -1. **Regionaler Basispreis:** `sell_cost × (worth_percent / 100)` — mit Fahrzeug `MAX(worth_percent)` über alle Filialregionen des Users, sonst nur Region der Direktor-Filiale. -2. **Wissensband:** effektiver Verkaufspreis = Basis × **`0.6 + 0.4 × (k/100)`** mit k = Wissen 0…100 (linear zwischen 60 % und 100 % der Basis). Entspricht `min_price + (max_price − min_price) × k/100` mit `min = 0.6 × Basis`, `max = Basis`. -3. **k:** `(2 × knowledge Spielercharakter + knowledge Direktor) / 3` aus `falukant_data.knowledge` (Produktzeilen für Charakter bzw. Direktor). -4. **Stückkosten:** wie `piece_production_cost` (siehe oben). -5. **Formel:** `(Verkaufspreis/Stück − Stückkosten) / production_time`. **Steuer** im Ranking nicht abgezogen. +1. **Effektiver Markt-Prozentsatz:** `worth_percent + (2 × knowledge Spielercharakter + knowledge Direktor) / 3`, dann `× sell_cost / 100`. `worth_percent`: mit Fahrzeug `MAX(worth_percent)` über alle Filialregionen des Users, sonst nur Region der Direktor-Filiale. +2. **Spielercharakter:** genau **einer** je User (`character.id` maximal unter `health > 0`), nicht alle `JOIN character ON user_id`. +3. **Kosten im Ranking:** **`6 × category`** (linear, ohne Headroom-Rabatt wie bei `piece_production_cost`). +4. **Formel:** `(sell_cost × (effektiver Prozentsatz) / 100 − 6 × category) / (300 × production_time)`. **Steuer** im Ranking nicht abgezogen. -**UI / Node:** Sollte dieselbe Preislogik wie der Daemon nutzen. Weicht `calcRegionalSellPriceSync` ab (z. B. 70 % statt 60 % unterhalb der Basis), Frontend an **diese** Rust-Formel anpassen — nicht umgekehrt den Daemon „blind“ an eine abweichende UI koppeln. +**Hinweis:** Echter Stückpreis und Buchführung nutzen weiter `piece_production_cost` und das 60 %–100 %-Band auf der Basis — nur die **Auswahl „beste Produktion“** folgt der Legacy-Formel, damit Daemon und UI dieselbe Rangfolge zeigen. ## Parallelproduktionen diff --git a/src/worker/sql.rs b/src/worker/sql.rs index 705b56d..1b6ca08 100644 --- a/src/worker/sql.rs +++ b/src/worker/sql.rs @@ -73,14 +73,13 @@ JOIN falukant_data.branch b ON b.region_id = c.region_id AND b.falukant_user_id WHERE current_time BETWEEN '08:00:00' AND '17:00:00'; "#; -/// Bester Produktstart: **Gewinn pro Minute** ≈ `(Verkaufspreis/Stück − Stückkosten) / Produktionszeit` — an -/// `DirectorWorker::compute_piece_price_for_percent` angeglichen: regionaler Basispreis -/// `sell_cost × worth/100`, dann linear **60 %–100 %** dieser Basis je nach Wissen (0–100), gleiche Idee wie -/// `min_price + (max−min)×k/100` in `director.rs`. Wissensblend `(2×Charakter + Direktor)/3`. UI soll dieselbe -/// Logik nutzen; weicht das Node-Frontend ab (z. B. 70 % statt 60 %), dort angleichen. +/// Bester Produktstart: **Legacy/UI-Formel** (vormals C++ `QUERY_GET_BEST_PRODUCTION`), damit Reihenfolge zur +/// Produkt-Ertrags-Tabelle passt: Wissen geht **additiv** in den Markt-Prozentsatz +/// `(worth + (2×Char + Dir)/3) / 100`, Kosten **`6 × category`**, Skalierung **`/ (300 × production_time)`**. +/// Verkauf/Steuer im laufenden Spiel nutzt weiter `DirectorWorker::compute_piece_sell_price` — nur **Ranking** hier. /// -/// Stückkosten wie `Director::piece_production_cost`. Mit Fahrzeug: `MAX(worth_percent)` über Filialregionen; -/// ohne Fahrzeug: nur Direktor-Region. **Ohne** Steuer im Ranking. +/// `worth_percent`: mit Fahrzeug `MAX` über Filialregionen; ohne Fahrzeug nur Direktor-Region. +/// **Ein** Spielercharakter je User (`ORDER BY id DESC LIMIT 1`, `health > 0`). pub const QUERY_GET_BEST_PRODUCTION: &str = r#" SELECT fdu.id falukant_user_id, CAST(fdu.money AS text) AS money, fdu.certificate, ftp.id product_id, ftp.label_tr, COALESCE(ftp.category, 1)::int AS product_category, fdb.region_id, @@ -88,50 +87,42 @@ COALESCE(ftp.category, 1)::int AS product_category, fdb.region_id, COALESCE((SELECT SUM(COALESCE(fdi.quantity, 0)) FROM falukant_data.stock fds JOIN falukant_data.inventory fdi ON fdi.stock_id = fds.id WHERE fds.branch_id = fdb.id), 0) AS used_in_stock, ( ( - ( - ftp.sell_cost * ( - CASE - WHEN EXISTS ( - SELECT 1 FROM falukant_data.vehicle v - WHERE v.falukant_user_id = fdu.id - ) - THEN bw.max_worth_pct - ELSE COALESCE(fdtpw_local.worth_percent::float8, 0.0) - END / 100.0 - ) - ) * ( - 0.6 + 0.4 * LEAST( - 100.0::float8, - GREATEST( - 0.0::float8, - ( - 2.0 * COALESCE(fdk_character.knowledge, 0)::float8 - + COALESCE(fdk_director.knowledge, 0)::float8 - ) / 3.0 - ) - ) / 100.0 - ) - - ( - (6.0 + (GREATEST(COALESCE(ftp.category, 1), 1))::float8) + ftp.sell_cost * ( - 1.0 - LEAST( - GREATEST( - (GREATEST(COALESCE(fdu.certificate, 1), 1))::float8 - - (GREATEST(COALESCE(ftp.category, 1), 1))::float8, - 0.0 - ) * 0.035, - 0.14 - ) - ) - ) - ) / NULLIF(ftp.production_time::float8, 0.0) + ( + CASE + WHEN EXISTS ( + SELECT 1 FROM falukant_data.vehicle v + WHERE v.falukant_user_id = fdu.id + ) + THEN bw.max_worth_pct + ELSE COALESCE(fdtpw_local.worth_percent::float8, 0.0) + END + ) + + ( + 2.0 * COALESCE(fdk_character.knowledge, 0)::float8 + + COALESCE(fdk_director.knowledge, 0)::float8 + ) + / 3.0 + ) + / 100.0 + - 6.0 * COALESCE(ftp.category, 0)::float8 + ) / (300.0 * NULLIF(ftp.production_time::float8, 0.0)) ) AS worth, fdb.id AS branch_id, (SELECT COUNT(id) FROM falukant_data.production WHERE branch_id = fdb.id) AS running_productions, COALESCE((SELECT SUM(COALESCE(fdp.quantity, 0)) quantity FROM falukant_data.production fdp WHERE fdp.branch_id = fdb.id), 0) AS running_productions_quantity FROM falukant_data.director fdd JOIN falukant_data.character fdc ON fdc.id = fdd.director_character_id JOIN falukant_data.falukant_user fdu ON fdd.employer_user_id = fdu.id -JOIN falukant_data.character user_character ON user_character.user_id = fdu.id +JOIN falukant_data.character user_character + ON user_character.id = ( + SELECT c2.id + FROM falukant_data.character c2 + WHERE c2.user_id = fdu.id + AND c2.health > 0 + ORDER BY c2.id DESC + LIMIT 1 + ) JOIN falukant_data.branch fdb ON fdb.falukant_user_id = fdu.id AND fdb.region_id = fdc.region_id JOIN ( SELECT tpw.product_id,