Skip to main content

datasynth_core/models/sourcing/
bid_evaluation.rs

1//! Bid evaluation and award recommendation models.
2
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7use super::super::graph_properties::{GraphPropertyValue, ToNodeProperties};
8
9/// An individual criterion score entry for a bid.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct BidEvaluationEntry {
12    /// Criterion name
13    pub criterion_name: String,
14    /// Raw score (0-100)
15    pub raw_score: f64,
16    /// Weight from RFx criteria
17    pub weight: f64,
18    /// Weighted score
19    pub weighted_score: f64,
20}
21
22/// A ranked bid with overall scoring.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct RankedBid {
25    /// Bid ID
26    pub bid_id: String,
27    /// Vendor ID
28    pub vendor_id: String,
29    /// Rank (1 = best)
30    pub rank: u32,
31    /// Total weighted score
32    pub total_score: f64,
33    /// Price score component
34    pub price_score: f64,
35    /// Technical/quality score component
36    pub quality_score: f64,
37    /// Total bid amount
38    #[serde(with = "rust_decimal::serde::str")]
39    pub total_amount: Decimal,
40    /// Individual criterion scores
41    pub criterion_scores: Vec<BidEvaluationEntry>,
42}
43
44/// Award recommendation from bid evaluation.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum AwardRecommendation {
48    /// Award to this vendor
49    Award,
50    /// Consider as backup
51    Backup,
52    /// Do not award
53    Reject,
54}
55
56/// Bid evaluation for an RFx event.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct BidEvaluation {
59    /// Unique evaluation ID
60    pub evaluation_id: String,
61    /// RFx event ID
62    pub rfx_id: String,
63    /// Company code
64    pub company_code: String,
65    /// Evaluator ID
66    pub evaluator_id: String,
67    /// Ranked bids (sorted by total_score descending)
68    pub ranked_bids: Vec<RankedBid>,
69    /// Recommended award
70    pub recommendation: AwardRecommendation,
71    /// Recommended vendor ID
72    pub recommended_vendor_id: Option<String>,
73    /// Recommended bid ID
74    pub recommended_bid_id: Option<String>,
75    /// Evaluation notes
76    pub notes: Option<String>,
77    /// Is the evaluation finalized
78    pub is_finalized: bool,
79}
80
81impl ToNodeProperties for BidEvaluation {
82    fn node_type_name(&self) -> &'static str {
83        "bid_evaluation"
84    }
85    fn node_type_code(&self) -> u16 {
86        323
87    }
88    fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
89        let mut p = HashMap::new();
90        p.insert(
91            "evaluationId".into(),
92            GraphPropertyValue::String(self.evaluation_id.clone()),
93        );
94        p.insert(
95            "rfxId".into(),
96            GraphPropertyValue::String(self.rfx_id.clone()),
97        );
98        p.insert(
99            "entityCode".into(),
100            GraphPropertyValue::String(self.company_code.clone()),
101        );
102        p.insert(
103            "evaluatorId".into(),
104            GraphPropertyValue::String(self.evaluator_id.clone()),
105        );
106        p.insert(
107            "recommendation".into(),
108            GraphPropertyValue::String(format!("{:?}", self.recommendation)),
109        );
110        p.insert(
111            "bidCount".into(),
112            GraphPropertyValue::Int(self.ranked_bids.len() as i64),
113        );
114        if let Some(top) = self.ranked_bids.first() {
115            p.insert(
116                "topBidScore".into(),
117                GraphPropertyValue::Float(top.total_score),
118            );
119            p.insert(
120                "topBidAmount".into(),
121                GraphPropertyValue::Decimal(top.total_amount),
122            );
123        }
124        if let Some(ref vendor_id) = self.recommended_vendor_id {
125            p.insert(
126                "recommendedVendorId".into(),
127                GraphPropertyValue::String(vendor_id.clone()),
128            );
129        }
130        p.insert(
131            "isFinalized".into(),
132            GraphPropertyValue::Bool(self.is_finalized),
133        );
134        p
135    }
136}