diff --git a/src/worker/director.rs b/src/worker/director.rs index 3149259..1e33269 100644 --- a/src/worker/director.rs +++ b/src/worker/director.rs @@ -10,6 +10,7 @@ use super::base::{BaseWorker, Worker, WorkerState, DEFAULT_TAX_PERCENT, DEFAULT_ use crate::worker::sql::{ QUERY_GET_DIRECTORS, QUERY_GET_BEST_PRODUCTION, + QUERY_GET_BEST_PRODUCTION_DEBUG, QUERY_INSERT_PRODUCTION, QUERY_GET_BRANCH_CAPACITY, QUERY_GET_INVENTORY, @@ -208,12 +209,62 @@ impl DirectorWorker { .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; // Initial: Bestes Produkt für diesen Branch ermitteln + conn.prepare("get_to_produce_debug", QUERY_GET_BEST_PRODUCTION_DEBUG)?; + let debug_rows = conn.execute("get_to_produce_debug", &[&director.id, &director.branch_id])?; + for (idx, row) in debug_rows.iter().enumerate() { + let rank = idx + 1; + let product_id = row.get("product_id").map(|s| s.as_str()).unwrap_or("?"); + let label = row.get("label_tr").map(|s| s.as_str()).unwrap_or("?"); + let category = row.get("product_category").map(|s| s.as_str()).unwrap_or("?"); + let production_time = row.get("production_time").map(|s| s.as_str()).unwrap_or("?"); + let sell_cost = row.get("sell_cost").map(|s| s.as_str()).unwrap_or("?"); + let market_percent = row.get("market_percent").map(|s| s.as_str()).unwrap_or("?"); + let knowledge_character = row.get("knowledge_character").map(|s| s.as_str()).unwrap_or("?"); + let knowledge_director = row.get("knowledge_director").map(|s| s.as_str()).unwrap_or("?"); + let effective_percent = row.get("effective_percent").map(|s| s.as_str()).unwrap_or("?"); + let one_piece_cost = row.get("one_piece_cost").map(|s| s.as_str()).unwrap_or("?"); + let revenue_piece = row.get("revenue_piece").map(|s| s.as_str()).unwrap_or("?"); + let worth = row.get("worth").map(|s| s.as_str()).unwrap_or("?"); + eprintln!( + "[DirectorWorker][best_production_debug] director_id={} branch_id={} rank={} product_id={} label={} cat={} prod_time={} sell_cost={} market%={} knowledge(char={},dir={}) effective%={} one_piece_cost={} revenue_piece={} worth={}", + director.id, + director.branch_id, + rank, + product_id, + label, + category, + production_time, + sell_cost, + market_percent, + knowledge_character, + knowledge_director, + effective_percent, + one_piece_cost, + revenue_piece, + worth + ); + } + conn.prepare("get_to_produce", QUERY_GET_BEST_PRODUCTION)?; let rows = conn.execute("get_to_produce", &[&director.id, &director.branch_id])?; if rows.is_empty() { return Ok(()); } + if let Some(row) = rows.first() { + let product_id = row.get("product_id").map(|s| s.as_str()).unwrap_or("?"); + let label = row.get("label_tr").map(|s| s.as_str()).unwrap_or("?"); + let worth = row.get("worth").map(|s| s.as_str()).unwrap_or("?"); + eprintln!( + "[DirectorWorker][best_production_selected] director_id={} branch_id={} product_id={} label={} worth={}", + director.id, + director.branch_id, + product_id, + label, + worth + ); + } + let mut base_plan = match rows.first().and_then(Self::map_row_to_production_plan) { Some(p) => p, None => return Ok(()), diff --git a/src/worker/notify.rs b/src/worker/notify.rs index 05028c8..fbea9ff 100644 --- a/src/worker/notify.rs +++ b/src/worker/notify.rs @@ -1,25 +1,6 @@ -use crate::db::{ConnectionPool, DbConnection, DbError}; -use crate::message_broker::MessageBroker; +use crate::db::{DbConnection, DbError}; use crate::worker::sql::QUERY_INSERT_NOTIFICATION; -/// Schreibt eine Notification in `falukant_log.notification`. -/// -/// - `tr` ist ein (i.d.R. JSON-)String, den das Frontend parst. -/// - `character_id` ist optional (NULL). -pub fn insert_notification( - pool: &ConnectionPool, - user_id: i32, - tr: &str, - character_id: Option, -) -> Result<(), DbError> { - let mut conn = pool - .get() - .map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?; - - insert_notification_conn(&mut conn, user_id, tr, character_id)?; - Ok(()) -} - /// Variante für bestehende DB-Verbindungen (spart Connect/Prepare in Loops). pub fn insert_notification_conn( conn: &mut DbConnection, @@ -35,11 +16,3 @@ pub fn insert_notification_conn( Ok(()) } -/// Informiert das Frontend, dass sich der Status geändert hat (z.B. Branches neu laden). -pub fn publish_update_status(broker: &MessageBroker, user_id: i32) { - broker.publish(format!( - r#"{{"event":"falukantUpdateStatus","user_id":{}}}"#, - user_id - )); -} - diff --git a/src/worker/sql.rs b/src/worker/sql.rs index 294de7d..3adc58f 100644 --- a/src/worker/sql.rs +++ b/src/worker/sql.rs @@ -159,6 +159,152 @@ JOIN falukant_data.knowledge fdk_director ON fdk_director.product_id = ftp.id AN WHERE fdd.id = $1 AND fdb.id = $2 ORDER BY worth DESC LIMIT 1; "#; +/// Debug-Variante zur Nachvollziehbarkeit der Direktoren-Entscheidung. +/// Liefert die Top-5 Kandidaten inkl. Teilmetriken, die in `worth` einfließen. +pub const QUERY_GET_BEST_PRODUCTION_DEBUG: &str = r#" +SELECT + ftp.id AS product_id, + ftp.label_tr, + COALESCE(ftp.category, 1)::int AS product_category, + COALESCE(ftp.production_time, 0)::int AS production_time, + ROUND(ftp.sell_cost::numeric, 2) AS sell_cost, + ROUND( + ( + 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 + )::numeric, + 2 + ) AS market_percent, + COALESCE(fdk_character.knowledge, 0)::int AS knowledge_character, + COALESCE(fdk_director.knowledge, 0)::int AS knowledge_director, + ROUND( + ( + ( + 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 + )::numeric, + 2 + ) AS effective_percent, + ROUND( + ( + ( + 6.0 + GREATEST(COALESCE(ftp.category, 1)::float8, 1.0) + ) * ( + 1.0 - LEAST( + GREATEST( + COALESCE(fdu.certificate, 1)::float8 + - GREATEST(COALESCE(ftp.category, 1)::float8, 1.0), + 0.0 + ) * 0.035, + 0.14 + ) + ) + )::numeric, + 4 + ) AS one_piece_cost, + ROUND( + ( + 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 + ) + + (2.0 * COALESCE(fdk_character.knowledge, 0)::float8 + COALESCE(fdk_director.knowledge, 0)::float8) / 3.0 + ) / 100.0 + )::numeric, + 4 + ) AS revenue_piece, + ROUND( + ( + ( + 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 + ) + + (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)::float8, 1.0) + ) * ( + 1.0 - LEAST( + GREATEST( + COALESCE(fdu.certificate, 1)::float8 + - GREATEST(COALESCE(ftp.category, 1)::float8, 1.0), + 0.0 + ) * 0.035, + 0.14 + ) + ) + ) + ) / (300.0 * NULLIF(ftp.production_time::float8, 0.0)) + )::numeric, + 8 + ) AS worth +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.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, + MAX(tpw.worth_percent)::float8 AS max_worth_pct + FROM falukant_data.town_product_worth tpw + WHERE tpw.region_id IN ( + SELECT DISTINCT br.region_id + FROM falukant_data.branch br + WHERE br.falukant_user_id = (SELECT d0.employer_user_id FROM falukant_data.director d0 WHERE d0.id = $1::int) + ) + GROUP BY tpw.product_id +) bw ON TRUE +JOIN falukant_type.product ftp ON ftp.id = bw.product_id AND ftp.category <= fdu.certificate +LEFT JOIN falukant_data.town_product_worth fdtpw_local + ON fdtpw_local.region_id = fdb.region_id + AND fdtpw_local.product_id = ftp.id +JOIN falukant_data.knowledge fdk_character ON fdk_character.product_id = ftp.id AND fdk_character.character_id = user_character.id +JOIN falukant_data.knowledge fdk_director ON fdk_director.product_id = ftp.id AND fdk_director.character_id = fdd.director_character_id +WHERE fdd.id = $1 AND fdb.id = $2 +ORDER BY worth DESC +LIMIT 5; +"#; + pub const QUERY_INSERT_PRODUCTION: &str = r#" INSERT INTO falukant_data.production (branch_id, product_id, quantity, weather_type_id) VALUES ($1, $2, $3, (SELECT weather_type_id FROM falukant_data.weather WHERE region_id = $4)); "#; @@ -2694,10 +2840,7 @@ pub const QUERY_SET_MARRIAGES_BY_PARTY: &str = r#" SELECT id FROM falukant_type.relationship AS rt WHERE rt.tr = 'married' - ), - marriage_satisfaction = 55, - marriage_drift_high = 0, - marriage_drift_low = 0 + ) WHERE rel.id IN ( SELECT rel2.id FROM falukant_data.party AS p