Enhance pricing logic in DirectorWorker and implement hourly price recalculation: Added a new worth_percent field to the InventoryItem struct and updated SQL queries to incorporate this value in price calculations. Refactored price computation logic to use a base price derived from worth_percent. Introduced a new hourly price recalculation mechanism that adjusts prices based on sales data from the last hour, ensuring dynamic pricing adjustments. Enhanced logging for better monitoring of price updates.
This commit is contained in:
@@ -40,6 +40,7 @@ struct InventoryItem {
|
|||||||
user_id: i32,
|
user_id: i32,
|
||||||
region_id: i32,
|
region_id: i32,
|
||||||
branch_id: i32,
|
branch_id: i32,
|
||||||
|
worth_percent: f64, // Regionaler worth_percent-Wert für die Preisberechnung
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -186,7 +187,8 @@ const QUERY_GET_INVENTORY: &str = r#"
|
|||||||
p.sell_cost,
|
p.sell_cost,
|
||||||
fu.id AS user_id,
|
fu.id AS user_id,
|
||||||
b.region_id,
|
b.region_id,
|
||||||
b.id AS branch_id
|
b.id AS branch_id,
|
||||||
|
COALESCE(tpw.worth_percent, 100.0) AS worth_percent
|
||||||
FROM falukant_data.inventory i
|
FROM falukant_data.inventory i
|
||||||
JOIN falukant_data.stock s
|
JOIN falukant_data.stock s
|
||||||
ON s.id = i.stock_id
|
ON s.id = i.stock_id
|
||||||
@@ -198,6 +200,9 @@ const QUERY_GET_INVENTORY: &str = r#"
|
|||||||
ON d.employer_user_id = fu.id
|
ON d.employer_user_id = fu.id
|
||||||
JOIN falukant_type.product p
|
JOIN falukant_type.product p
|
||||||
ON p.id = i.product_id
|
ON p.id = i.product_id
|
||||||
|
LEFT JOIN falukant_data.town_product_worth tpw
|
||||||
|
ON tpw.region_id = b.region_id
|
||||||
|
AND tpw.product_id = i.product_id
|
||||||
WHERE d.id = $1
|
WHERE d.id = $1
|
||||||
AND b.id = $2;
|
AND b.id = $2;
|
||||||
"#;
|
"#;
|
||||||
@@ -728,6 +733,10 @@ impl DirectorWorker {
|
|||||||
user_id: row.get("user_id")?.parse().ok()?,
|
user_id: row.get("user_id")?.parse().ok()?,
|
||||||
region_id: row.get("region_id")?.parse().ok()?,
|
region_id: row.get("region_id")?.parse().ok()?,
|
||||||
branch_id: row.get("branch_id")?.parse().ok()?,
|
branch_id: row.get("branch_id")?.parse().ok()?,
|
||||||
|
worth_percent: row
|
||||||
|
.get("worth_percent")
|
||||||
|
.and_then(|v| v.parse::<f64>().ok())
|
||||||
|
.unwrap_or(100.0),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,9 +750,19 @@ impl DirectorWorker {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let min_price = item.sell_cost * 0.6;
|
// Neue Preisberechnung gemäß Spezifikation:
|
||||||
let piece_sell_price =
|
// 1. Basispreis = product.sellCost * (worthPercent / 100)
|
||||||
min_price + (item.sell_cost - min_price) * (item.quality as f64 / 100.0);
|
let base_price = item.sell_cost * (item.worth_percent / 100.0);
|
||||||
|
|
||||||
|
// 2. min = basePrice * 0.6, max = basePrice
|
||||||
|
let min_price = base_price * 0.6;
|
||||||
|
let max_price = base_price;
|
||||||
|
|
||||||
|
// 3. price = min + (max - min) * (knowledgeFactor / 100)
|
||||||
|
// knowledgeFactor ist hier item.quality
|
||||||
|
let knowledge_factor = item.quality as f64;
|
||||||
|
let piece_sell_price = min_price + (max_price - min_price) * (knowledge_factor / 100.0);
|
||||||
|
|
||||||
let sell_price = piece_sell_price * item.quantity as f64;
|
let sell_price = piece_sell_price * item.quantity as f64;
|
||||||
|
|
||||||
if let Err(err) = self.base.change_falukant_user_money(
|
if let Err(err) = self.base.change_falukant_user_money(
|
||||||
@@ -836,17 +855,22 @@ impl DirectorWorker {
|
|||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lokalen Stückpreis berechnen
|
// Lokalen Stückpreis berechnen (neue Preisberechnung)
|
||||||
let local_percent = worth_by_region
|
let local_percent = worth_by_region
|
||||||
.get(&item.region_id)
|
.get(&item.region_id)
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(100.0);
|
.unwrap_or(100.0);
|
||||||
|
|
||||||
let local_sell_cost = item.sell_cost * local_percent / 100.0;
|
// 1. Basispreis = product.sellCost * (worthPercent / 100)
|
||||||
let local_min_price = local_sell_cost * 0.6;
|
let local_base_price = item.sell_cost * (local_percent / 100.0);
|
||||||
let quality_factor = item.quality as f64 / 100.0;
|
|
||||||
let local_piece_price =
|
// 2. min = basePrice * 0.6, max = basePrice
|
||||||
local_min_price + (local_sell_cost - local_min_price) * quality_factor;
|
let local_min_price = local_base_price * 0.6;
|
||||||
|
let local_max_price = local_base_price;
|
||||||
|
|
||||||
|
// 3. price = min + (max - min) * (knowledgeFactor / 100)
|
||||||
|
let knowledge_factor = item.quality as f64;
|
||||||
|
let local_piece_price = local_min_price + (local_max_price - local_min_price) * (knowledge_factor / 100.0);
|
||||||
|
|
||||||
let mut best_target_region: Option<i32> = None;
|
let mut best_target_region: Option<i32> = None;
|
||||||
let mut best_quantity: i32 = 0;
|
let mut best_quantity: i32 = 0;
|
||||||
@@ -859,11 +883,17 @@ impl DirectorWorker {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remote-Stückpreis
|
// Remote-Stückpreis berechnen (neue Preisberechnung)
|
||||||
let remote_sell_cost = item.sell_cost * remote_percent / 100.0;
|
// 1. Basispreis = product.sellCost * (worthPercent / 100)
|
||||||
let remote_min_price = remote_sell_cost * 0.6;
|
let remote_base_price = item.sell_cost * (remote_percent / 100.0);
|
||||||
let remote_piece_price =
|
|
||||||
remote_min_price + (remote_sell_cost - remote_min_price) * quality_factor;
|
// 2. min = basePrice * 0.6, max = basePrice
|
||||||
|
let remote_min_price = remote_base_price * 0.6;
|
||||||
|
let remote_max_price = remote_base_price;
|
||||||
|
|
||||||
|
// 3. price = min + (max - min) * (knowledgeFactor / 100)
|
||||||
|
let knowledge_factor = item.quality as f64;
|
||||||
|
let remote_piece_price = remote_min_price + (remote_max_price - remote_min_price) * (knowledge_factor / 100.0);
|
||||||
|
|
||||||
let delta_per_unit = remote_piece_price - local_piece_price;
|
let delta_per_unit = remote_piece_price - local_piece_price;
|
||||||
if delta_per_unit <= 0.0 {
|
if delta_per_unit <= 0.0 {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::db::{ConnectionPool, DbError, Row};
|
use crate::db::{ConnectionPool, DbError, Row};
|
||||||
use crate::message_broker::MessageBroker;
|
use crate::message_broker::MessageBroker;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -75,6 +76,53 @@ const QUERY_GET_SELL_REGIONS: &str = r#"
|
|||||||
GROUP BY region_id;
|
GROUP BY region_id;
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
// Stündliche Preisneuberechnung basierend auf Verkäufen der letzten Stunde
|
||||||
|
// Für jedes Produkt in jeder Stadt: Wenn überdurchschnittlich viel verkauft wurde,
|
||||||
|
// sinkt der Preis um 10% (begrenzt auf 0-100)
|
||||||
|
const QUERY_HOURLY_PRICE_RECALCULATION: &str = r#"
|
||||||
|
WITH city_sales AS (
|
||||||
|
SELECT
|
||||||
|
s.region_id,
|
||||||
|
s.product_id,
|
||||||
|
SUM(s.quantity) AS total_sold
|
||||||
|
FROM falukant_log.sell s
|
||||||
|
WHERE s.sell_timestamp >= NOW() - INTERVAL '1 hour'
|
||||||
|
GROUP BY s.region_id, s.product_id
|
||||||
|
),
|
||||||
|
avg_sales AS (
|
||||||
|
SELECT
|
||||||
|
product_id,
|
||||||
|
AVG(total_sold) AS avg_sold
|
||||||
|
FROM city_sales
|
||||||
|
GROUP BY product_id
|
||||||
|
),
|
||||||
|
price_updates AS (
|
||||||
|
SELECT
|
||||||
|
cs.region_id,
|
||||||
|
cs.product_id,
|
||||||
|
cs.total_sold,
|
||||||
|
COALESCE(av.avg_sold, 0) AS avg_sold,
|
||||||
|
tpw.worth_percent AS current_price
|
||||||
|
FROM city_sales cs
|
||||||
|
JOIN avg_sales av ON av.product_id = cs.product_id
|
||||||
|
JOIN falukant_data.town_product_worth tpw
|
||||||
|
ON tpw.region_id = cs.region_id
|
||||||
|
AND tpw.product_id = cs.product_id
|
||||||
|
WHERE cs.total_sold > COALESCE(av.avg_sold, 0)
|
||||||
|
)
|
||||||
|
UPDATE falukant_data.town_product_worth tpw
|
||||||
|
SET worth_percent = GREATEST(
|
||||||
|
0,
|
||||||
|
LEAST(
|
||||||
|
100,
|
||||||
|
pu.current_price * 0.9 -- 10% Preisreduktion
|
||||||
|
)
|
||||||
|
)
|
||||||
|
FROM price_updates pu
|
||||||
|
WHERE tpw.region_id = pu.region_id
|
||||||
|
AND tpw.product_id = pu.product_id;
|
||||||
|
"#;
|
||||||
|
|
||||||
// Ehen / Beziehungen
|
// Ehen / Beziehungen
|
||||||
const QUERY_SET_MARRIAGES_BY_PARTY: &str = r#"
|
const QUERY_SET_MARRIAGES_BY_PARTY: &str = r#"
|
||||||
WITH updated_relations AS (
|
WITH updated_relations AS (
|
||||||
@@ -168,6 +216,7 @@ impl ValueRecalculationWorker {
|
|||||||
// statt exakte Uhrzeiten nachzubilden – Verhalten ist funktional ähnlich.
|
// statt exakte Uhrzeiten nachzubilden – Verhalten ist funktional ähnlich.
|
||||||
let mut last_product = None;
|
let mut last_product = None;
|
||||||
let mut last_sell_price = None;
|
let mut last_sell_price = None;
|
||||||
|
let mut last_hourly_price_recalc = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if !state.running_worker.load(Ordering::Relaxed) {
|
if !state.running_worker.load(Ordering::Relaxed) {
|
||||||
@@ -192,6 +241,14 @@ impl ValueRecalculationWorker {
|
|||||||
last_sell_price = Some(now);
|
last_sell_price = Some(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stündliche Preisneuberechnung basierend auf Verkäufen der letzten Stunde
|
||||||
|
if should_run_interval(last_hourly_price_recalc, now, Duration::from_secs(3600)) {
|
||||||
|
if let Err(err) = Self::calculate_hourly_price_recalculation_inner(&pool, &broker) {
|
||||||
|
eprintln!("[ValueRecalculationWorker] Fehler in calculateHourlyPriceRecalculation: {err}");
|
||||||
|
}
|
||||||
|
last_hourly_price_recalc = Some(now);
|
||||||
|
}
|
||||||
|
|
||||||
// Ehen & Studium bei jedem Durchlauf
|
// Ehen & Studium bei jedem Durchlauf
|
||||||
if let Err(err) = Self::calculate_marriages_inner(&pool, &broker) {
|
if let Err(err) = Self::calculate_marriages_inner(&pool, &broker) {
|
||||||
eprintln!("[ValueRecalculationWorker] Fehler in calculateMarriages: {err}");
|
eprintln!("[ValueRecalculationWorker] Fehler in calculateMarriages: {err}");
|
||||||
@@ -275,6 +332,51 @@ impl ValueRecalculationWorker {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculate_hourly_price_recalculation_inner(
|
||||||
|
pool: &ConnectionPool,
|
||||||
|
broker: &MessageBroker,
|
||||||
|
) -> Result<(), DbError> {
|
||||||
|
let mut conn = pool
|
||||||
|
.get()
|
||||||
|
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||||
|
|
||||||
|
conn.prepare("hourly_price_recalculation", QUERY_HOURLY_PRICE_RECALCULATION)?;
|
||||||
|
let updated_rows = conn.execute("hourly_price_recalculation", &[])?;
|
||||||
|
|
||||||
|
// Sammle alle betroffenen Regionen für Event-Benachrichtigungen
|
||||||
|
let mut affected_regions = std::collections::HashSet::new();
|
||||||
|
|
||||||
|
// Da die Query bereits die Updates durchführt, müssen wir die betroffenen Regionen
|
||||||
|
// separat abfragen. Alternativ können wir auch einfach alle Regionen benachrichtigen,
|
||||||
|
// die in der letzten Stunde Verkäufe hatten.
|
||||||
|
conn.prepare("get_sell_regions_hourly", r#"
|
||||||
|
SELECT DISTINCT region_id
|
||||||
|
FROM falukant_log.sell
|
||||||
|
WHERE sell_timestamp >= NOW() - INTERVAL '1 hour'
|
||||||
|
"#)?;
|
||||||
|
let regions = conn.execute("get_sell_regions_hourly", &[])?;
|
||||||
|
|
||||||
|
for row in regions {
|
||||||
|
if let Some(region_id) = row.get("region_id").and_then(|v| v.parse::<i32>().ok()) {
|
||||||
|
affected_regions.insert(region_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benachrichtige alle betroffenen Regionen über Preisänderungen
|
||||||
|
for region_id in affected_regions {
|
||||||
|
let message =
|
||||||
|
format!(r#"{{"event":"price_update","region_id":{}}}"#, region_id);
|
||||||
|
broker.publish(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"[ValueRecalculationWorker] Stündliche Preisneuberechnung abgeschlossen. {} Regionen aktualisiert.",
|
||||||
|
affected_regions.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn calculate_marriages_inner(
|
fn calculate_marriages_inner(
|
||||||
pool: &ConnectionPool,
|
pool: &ConnectionPool,
|
||||||
broker: &MessageBroker,
|
broker: &MessageBroker,
|
||||||
|
|||||||
Reference in New Issue
Block a user