Add debug functionality for best production selection: Introduced a new SQL query QUERY_GET_BEST_PRODUCTION_DEBUG to retrieve the top 5 production candidates with detailed metrics. Enhanced the DirectorWorker to log debug information during production selection, improving traceability of decision-making processes.
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 1m35s

This commit is contained in:
Torsten Schulz (local)
2026-04-13 15:13:27 +02:00
parent df092d1790
commit 3df83b507e
3 changed files with 199 additions and 32 deletions

View File

@@ -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(()),

View File

@@ -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<i32>,
) -> 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
));
}

View File

@@ -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