Enhance DirectorWorker logic for tax calculations: Updated the DirectorWorker to compute the cumulative tax percentage for net ranking and adjusted SQL queries to include this new parameter in production selection. Improved logging to capture additional metrics related to tax and net piece calculations, enhancing the traceability of production decisions.
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 1m34s

This commit is contained in:
Torsten Schulz (local)
2026-04-13 15:29:11 +02:00
parent 2eaea2481c
commit cd21293fbd
2 changed files with 158 additions and 141 deletions

View File

@@ -208,9 +208,20 @@ impl DirectorWorker {
.get()
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
// Initial: Kumulativen Steuerprozentsatz für Netto-Ranking ermitteln.
conn.prepare("get_director_user", QUERY_GET_DIRECTOR_USER)?;
let user_rows = conn.execute("get_director_user", &[&director.id])?;
let falukant_user_id = user_rows
.first()
.and_then(|row| row.get("falukant_user_id"))
.and_then(|v| v.parse::<i32>().ok())
.unwrap_or(DEFAULT_TREASURY_USER_ID);
let tax_percent = Self::get_cumulative_tax_percent(&mut conn, director.branch_id, falukant_user_id)
.unwrap_or(DEFAULT_TAX_PERCENT);
// 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])?;
let debug_rows = conn.execute("get_to_produce_debug", &[&director.id, &director.branch_id, &tax_percent])?;
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("?");
@@ -224,9 +235,12 @@ impl DirectorWorker {
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 tax_piece = row.get("tax_piece").map(|s| s.as_str()).unwrap_or("?");
let net_piece = row.get("net_piece").map(|s| s.as_str()).unwrap_or("?");
let row_tax_percent = row.get("tax_percent").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={}",
"[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={} tax_piece={} net_piece={} tax%={} worth={}",
director.id,
director.branch_id,
rank,
@@ -241,12 +255,15 @@ impl DirectorWorker {
effective_percent,
one_piece_cost,
revenue_piece,
tax_piece,
net_piece,
row_tax_percent,
worth
);
}
conn.prepare("get_to_produce", QUERY_GET_BEST_PRODUCTION)?;
let rows = conn.execute("get_to_produce", &[&director.id, &director.branch_id])?;
let rows = conn.execute("get_to_produce", &[&director.id, &director.branch_id, &tax_percent])?;
if rows.is_empty() {
return Ok(());
}

View File

@@ -73,12 +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 für den Direktor:
/// Wissen geht additiv in den Markt-Prozentsatz `(worth + (2×Char + Dir)/3) / 100`.
/// Für das Ranking werden dieselben Stückkosten wie im DirectorWorker angesetzt:
/// `raw = 6 + category`, mit Headroom-Rabatt über Zertifikat (`min((cert-cat)*0.035, 0.14)`).
/// Skalierung: `/ (300 × production_time)`.
/// Verkauf/Steuer im laufenden Spiel nutzt weiterhin `DirectorWorker::compute_piece_sell_price` — hier nur Ranking.
/// Bester Produktstart für den Direktor nach erwartbarem **Netto-Gewinn pro Minute**.
/// Erwarteter Erlös/Stück nutzt dieselbe 60%-100%-Bandlogik wie beim Verkauf:
/// `base = sell_cost * market_percent / 100`
/// `quality = clamp((2*knowledge_char + knowledge_dir)/3, 0..100)`
/// `revenue_piece = base * (0.6 + 0.4 * quality/100)`
/// Stückkosten wie im DirectorWorker (`6 + category`, Headroom-Rabatt).
/// Steuerabzug auf Profitanteil über `$3` (kumulativer Steuerprozentsatz für die Branch-Region).
/// Rankinggröße `worth` = `net_piece / production_time`.
///
/// `worth_percent`: mit Fahrzeug `MAX` über Filialregionen; ohne Fahrzeug nur Direktor-Region.
/// **Ein** Spielercharakter je User (`ORDER BY id DESC LIMIT 1`, `health > 0`).
@@ -89,40 +91,96 @@ 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
)
+ (
2.0 * COALESCE(fdk_character.knowledge, 0)::float8
+ COALESCE(fdk_director.knowledge, 0)::float8
)
/ 3.0
)
/ 100.0
(
(
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,
GREATEST(
0.0,
(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))
GREATEST(
(
(
(
(
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,
GREATEST(
0.0,
(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
)
)
)
),
0.0
) * (COALESCE($3::float8, 0.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
)
)
)
)
) / 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
@@ -162,114 +220,39 @@ 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#"
WITH candidates AS (
SELECT
ftp.id::text AS product_id,
ftp.label_tr,
COALESCE(ftp.category, 1)::int::text AS product_category,
COALESCE(ftp.production_time, 0)::int::text AS production_time,
ROUND(ftp.sell_cost::numeric, 2)::text 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
)::text AS market_percent,
COALESCE(fdk_character.knowledge, 0)::int::text AS knowledge_character,
COALESCE(fdk_director.knowledge, 0)::int::text 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
(
CASE
WHEN EXISTS (
SELECT 1 FROM falukant_data.vehicle v
WHERE v.falukant_user_id = fdu.id
)
+ (2.0 * COALESCE(fdk_character.knowledge, 0)::float8 + COALESCE(fdk_director.knowledge, 0)::float8) / 3.0
)::numeric,
2
)::text AS effective_percent,
ROUND(
THEN bw.max_worth_pct
ELSE COALESCE(fdtpw_local.worth_percent::float8, 0.0)
END
) AS market_percent_val,
COALESCE(fdk_character.knowledge, 0)::float8 AS knowledge_char_val,
COALESCE(fdk_director.knowledge, 0)::float8 AS knowledge_dir_val,
(
(
(
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
)
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
)::text 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
)::text 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
)::text AS worth
)
) AS one_piece_cost_val
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
@@ -301,6 +284,23 @@ LEFT JOIN falukant_data.town_product_worth fdtpw_local
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
)
SELECT
product_id,
label_tr,
product_category,
production_time,
sell_cost,
ROUND(market_percent_val::numeric, 2)::text AS market_percent,
ROUND(knowledge_char_val::numeric, 0)::int::text AS knowledge_character,
ROUND(knowledge_dir_val::numeric, 0)::int::text AS knowledge_director,
ROUND(LEAST(100.0, GREATEST(0.0, (2.0 * knowledge_char_val + knowledge_dir_val) / 3.0))::numeric, 2)::text AS effective_percent,
ROUND(one_piece_cost_val::numeric, 4)::text AS one_piece_cost,
ROUND((sell_cost::float8 * market_percent_val / 100.0 * (0.6 + 0.4 * (LEAST(100.0, GREATEST(0.0, (2.0 * knowledge_char_val + knowledge_dir_val) / 3.0)) / 100.0)))::numeric, 4)::text AS revenue_piece,
ROUND((GREATEST((sell_cost::float8 * market_percent_val / 100.0 * (0.6 + 0.4 * (LEAST(100.0, GREATEST(0.0, (2.0 * knowledge_char_val + knowledge_dir_val) / 3.0)) / 100.0)) - one_piece_cost_val), 0.0) * (COALESCE($3::float8, 0.0) / 100.0))::numeric, 4)::text AS tax_piece,
ROUND(((sell_cost::float8 * market_percent_val / 100.0 * (0.6 + 0.4 * (LEAST(100.0, GREATEST(0.0, (2.0 * knowledge_char_val + knowledge_dir_val) / 3.0)) / 100.0))) - (GREATEST((sell_cost::float8 * market_percent_val / 100.0 * (0.6 + 0.4 * (LEAST(100.0, GREATEST(0.0, (2.0 * knowledge_char_val + knowledge_dir_val) / 3.0)) / 100.0)) - one_piece_cost_val), 0.0) * (COALESCE($3::float8, 0.0) / 100.0)) - one_piece_cost_val)::numeric, 4)::text AS net_piece,
ROUND(COALESCE($3::numeric, 0), 2)::text AS tax_percent,
ROUND((((sell_cost::float8 * market_percent_val / 100.0 * (0.6 + 0.4 * (LEAST(100.0, GREATEST(0.0, (2.0 * knowledge_char_val + knowledge_dir_val) / 3.0)) / 100.0))) - (GREATEST((sell_cost::float8 * market_percent_val / 100.0 * (0.6 + 0.4 * (LEAST(100.0, GREATEST(0.0, (2.0 * knowledge_char_val + knowledge_dir_val) / 3.0)) / 100.0)) - one_piece_cost_val), 0.0) * (COALESCE($3::float8, 0.0) / 100.0)) - one_piece_cost_val) / NULLIF(production_time::float8, 0.0))::numeric, 8)::text AS worth
ORDER BY worth DESC
LIMIT 5;
"#;