From ebf20c442467dc8af514782e69a75b060c84c7ee Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 9 Apr 2026 09:36:44 +0200 Subject: [PATCH] Refactor production cost calculations and SQL query: Updated the `QUERY_GET_BEST_PRODUCTION` to implement a legacy formula for ranking based on effective market percentage and knowledge levels. Adjusted the calculation logic for costs and clarified documentation to ensure consistency with the new pricing strategy. Enhanced SQL query structure for improved clarity and performance in production worth assessment. --- docs/FALUKANT_DIRECTOR_PRODUCTION_COST.md | 13 ++-- src/worker/sql.rs | 79 ++++++++++------------- 2 files changed, 41 insertions(+), 51 deletions(-) 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,