diff --git a/docs/FALUKANT_DIRECTOR_PRODUCTION_COST.md b/docs/FALUKANT_DIRECTOR_PRODUCTION_COST.md index e4956d4..b5991db 100644 --- a/docs/FALUKANT_DIRECTOR_PRODUCTION_COST.md +++ b/docs/FALUKANT_DIRECTOR_PRODUCTION_COST.md @@ -41,13 +41,17 @@ effektiv_stückkosten = raw × (1 − discount) | `PRODUCTION_HEADROOM_DISCOUNT_PER_STEP` | `0.035` | Rabatt pro Headroom-Stufe | | `PRODUCTION_HEADROOM_DISCOUNT_CAP` | `0.14` | maximaler Gesamtrabatt | -## Worth in SQL (`QUERY_GET_BEST_PRODUCTION`) +## Ranking in SQL (`QUERY_GET_BEST_PRODUCTION`) -Sortierung nach **regionalem Erlös pro Zeiteinheit**: +Sortierung nach **Gewinn pro Minute**, mit **demselben Verkaufs-Modell** wie `DirectorWorker::compute_piece_price_for_percent` / `compute_piece_sell_price` in `director.rs`: -`(sell_cost * worth_percent/100) / production_time` +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. -Damit entspricht die Auswahl in etwa **„Erlös pro Minute“** (Listenpreis × Markt in der Region ÷ Dauer einer Produktionscharge). **Wissen** fließt hier **nicht** in die Reihenfolge ein (Wissen wirkt bei euch auf Qualität/Preis beim Verkauf, nicht auf die Produktwahl). Die **Stückkosten** kommen nur aus der Rust-Formel beim Abbuchen. +**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. ## Parallelproduktionen diff --git a/src/worker/director.rs b/src/worker/director.rs index 1dc55f3..3149259 100644 --- a/src/worker/director.rs +++ b/src/worker/director.rs @@ -657,7 +657,7 @@ impl DirectorWorker { }) } - // Helper: compute piece sell price from item fields + // Helper: compute piece sell price from item fields (QUERY_GET_BEST_PRODUCTION nutzt dieselbe 0.6/0.4-Logik mit knowledge) fn compute_piece_sell_price(item: &InventoryItem) -> f64 { let base_price = item.sell_cost * (item.worth_percent / 100.0); let min_price = base_price * 0.6; @@ -1028,7 +1028,7 @@ impl DirectorWorker { Ok(map) } - // Helper: compute piece price for an arbitrary worth_percent + /// Wie `QUERY_GET_BEST_PRODUCTION`: Basis `sell_cost×worth/100`, Verkauf linear zwischen 60 % und 100 % der Basis. fn compute_piece_price_for_percent(item: &InventoryItem, percent: f64) -> f64 { let base_price = item.sell_cost * (percent / 100.0); let min_price = base_price * 0.6; diff --git a/src/worker/sql.rs b/src/worker/sql.rs index 5f60daa..705b56d 100644 --- a/src/worker/sql.rs +++ b/src/worker/sql.rs @@ -73,9 +73,14 @@ 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** (wie UI „Gewinn/Minute“) ≈ `(Erlös/Stück − Stückkosten) / Produktionszeit`. -/// Stückkosten = gleiche Formel wie `Director::piece_production_cost` (Basis 6 + Kategorie, Headroom-Rabatt). -/// Marktpreis: mit Fahrzeug `MAX(worth_percent)` über Filialregionen, sonst nur Direktor-Region. **Ohne** Steuer im Ranking. +/// 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. +/// +/// Stückkosten wie `Director::piece_production_cost`. Mit Fahrzeug: `MAX(worth_percent)` über Filialregionen; +/// ohne Fahrzeug: nur Direktor-Region. **Ohne** Steuer im Ranking. 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, @@ -83,15 +88,28 @@ 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 + ( + 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 ) - THEN bw.max_worth_pct - ELSE COALESCE(fdtpw_local.worth_percent::float8, 0.0) - END / 100.0 + ) / 100.0 ) - ( (6.0 + (GREATEST(COALESCE(ftp.category, 1), 1))::float8)