bidradar 0.1.1

Auction bid radar and market analyzer for real-time bidding analysis
mod api;
mod engine;
mod numerics;
mod models;

use actix_web::{web, App, HttpServer, HttpResponse, middleware};
use serde::{Deserialize, Serialize};

// ─── Request/Response Types ───

#[derive(Debug, Deserialize)]
pub struct TenderScanRequest {
    pub tender_id: String,
    pub participants: Vec<Participant>,
    pub reference_prices: Vec<f64>,
    pub scan_depth: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct Participant {
    pub company_id: String,
    pub bid_amount: f64,
    pub currency: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct TenderScanResult {
    pub tender_id: String,
    pub risk_score: f64,
    pub flags: Vec<Flag>,
    pub method: String,
    pub confidence: f64,
    pub scan_time_ms: u64,
}

#[derive(Debug, Serialize)]
pub struct Flag {
    pub flag_type: String,
    pub severity: String,
    pub description: String,
}

// ─── API Handlers ───

async fn health() -> HttpResponse {
    HttpResponse::Ok().json(serde_json::json!({
        "service": "bidradar",
        "version": "0.1.0",
        "status": "operational",
        "modules": ["anomaly", "affiliation", "risk", "spectral", "statistics"]
    }))
}

async fn scan_tender(body: web::Json<TenderScanRequest>) -> HttpResponse {
    let start = std::time::Instant::now();

    let prices: Vec<f64> = body.participants.iter()
        .map(|p| p.bid_amount).collect();

    let result = engine::anomaly::detect(
        &prices,
        &body.reference_prices,
    );

    let elapsed = start.elapsed().as_millis() as u64;

    let mut flags = Vec::new();
    if result.clustering_detected {
        flags.push(Flag {
            flag_type: "bid_clustering".into(),
            severity: if result.risk_score > 0.8 { "high".into() } else { "medium".into() },
            description: "Unusual price clustering detected".into(),
        });
    }
    if result.price_anomaly {
        flags.push(Flag {
            flag_type: "price_anomaly".into(),
            severity: "medium".into(),
            description: "Deviation from reference prices".into(),
        });
    }

    HttpResponse::Ok().json(TenderScanResult {
        tender_id: body.tender_id.clone(),
        risk_score: result.risk_score,
        flags,
        method: "svd_spectral_clustering".into(),
        confidence: result.confidence,
        scan_time_ms: elapsed,
    })
}

// ─── Main Server ───

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("╔══════════════════════════════════╗");
    println!("║  BIDRADAR API v0.1.0            ║");
    println!("║  Port: 8080                     ║");
    println!("║  Endpoints:                     ║");
    println!("║    GET  /health                 ║");
    println!("║    POST /v1/scan/tender         ║");
    println!("║    POST /v1/scan/affiliation    ║");
    println!("║    POST /v1/predict/bankruptcy  ║");
    println!("║    POST /v1/analyze/timeseries  ║");
    println!("║    POST /v1/test/correlation    ║");
    println!("╚══════════════════════════════════╝");

    HttpServer::new(|| {
        App::new()
            .wrap(middleware::Logger::default())
            .route("/health", web::get().to(health))
            .route("/v1/scan/tender", web::post().to(scan_tender))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}