Skip to main content

haima_core/
insurance.rs

1//! Insurance types for the agent insurance facilitation marketplace.
2//!
3//! Defines insurance products, policies, claims, and risk assessment types
4//! that enable agents to purchase coverage against task failures, financial
5//! errors, data breaches, and SLA penalties.
6//!
7//! # Insurance Products
8//!
9//! | Product            | Covers                                   |
10//! |--------------------|------------------------------------------|
11//! | Task Failure       | Customer losses from failed agent tasks   |
12//! | Financial Error    | Erroneous payments/transactions           |
13//! | Data Breach        | Agent-caused data exposure                |
14//! | SLA Penalty        | SLA breach penalties                      |
15//!
16//! # Revenue Model
17//!
18//! - Facilitation commission: 10-20% of premiums
19//! - Risk data licensing: anonymized risk data to insurers
20//! - Self-insurance pool management fee: 2-3% of pool AUM
21
22use chrono::{DateTime, Utc};
23use serde::{Deserialize, Serialize};
24
25use crate::bureau::RiskRating;
26use crate::credit::CreditTier;
27
28// ---------------------------------------------------------------------------
29// Insurance Products
30// ---------------------------------------------------------------------------
31
32/// The category of insurance coverage.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum InsuranceProductType {
36    /// Covers customer losses from failed agent tasks.
37    TaskFailure,
38    /// Covers erroneous payments or transactions.
39    FinancialError,
40    /// Covers agent-caused data exposure incidents.
41    DataBreach,
42    /// Covers SLA breach penalties.
43    SlaPenalty,
44}
45
46impl std::fmt::Display for InsuranceProductType {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        match self {
49            Self::TaskFailure => write!(f, "task_failure"),
50            Self::FinancialError => write!(f, "financial_error"),
51            Self::DataBreach => write!(f, "data_breach"),
52            Self::SlaPenalty => write!(f, "sla_penalty"),
53        }
54    }
55}
56
57/// An insurance product definition offered on the marketplace.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct InsuranceProduct {
60    /// Unique product identifier.
61    pub product_id: String,
62    /// Type of coverage.
63    pub product_type: InsuranceProductType,
64    /// Human-readable product name.
65    pub name: String,
66    /// Detailed description of coverage.
67    pub description: String,
68    /// Base premium rate in basis points per coverage unit per period.
69    pub base_rate_bps: u32,
70    /// Minimum coverage in micro-USD.
71    pub min_coverage_micro_usd: i64,
72    /// Maximum coverage in micro-USD.
73    pub max_coverage_micro_usd: i64,
74    /// Default deductible in micro-USD.
75    pub default_deductible_micro_usd: i64,
76    /// Coverage period in seconds.
77    pub period_secs: u64,
78    /// Minimum trust tier required to purchase.
79    pub min_trust_tier: InsuranceTrustTier,
80    /// Provider offering this product (network pool or external insurer).
81    pub provider_id: String,
82    /// Whether the product is currently available.
83    pub active: bool,
84}
85
86/// Trust tier mapping for insurance eligibility.
87#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
88#[serde(rename_all = "snake_case")]
89pub enum InsuranceTrustTier {
90    /// Any agent can purchase.
91    Any,
92    /// Must be at least Provisional trust.
93    Provisional,
94    /// Must be at least Trusted.
95    Trusted,
96    /// Must be Certified.
97    Certified,
98}
99
100impl std::fmt::Display for InsuranceTrustTier {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        match self {
103            Self::Any => write!(f, "any"),
104            Self::Provisional => write!(f, "provisional"),
105            Self::Trusted => write!(f, "trusted"),
106            Self::Certified => write!(f, "certified"),
107        }
108    }
109}
110
111// ---------------------------------------------------------------------------
112// Insurance Policies
113// ---------------------------------------------------------------------------
114
115/// An active insurance policy bound to an agent.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct InsurancePolicy {
118    /// Unique policy identifier.
119    pub policy_id: String,
120    /// The insured agent.
121    pub agent_id: String,
122    /// Product this policy is based on.
123    pub product_id: String,
124    /// Type of coverage.
125    pub product_type: InsuranceProductType,
126    /// Coverage limit in micro-USD.
127    pub coverage_limit_micro_usd: i64,
128    /// Deductible in micro-USD.
129    pub deductible_micro_usd: i64,
130    /// Premium paid per period in micro-USD.
131    pub premium_micro_usd: i64,
132    /// Policy status.
133    pub status: PolicyStatus,
134    /// Coverage start.
135    pub effective_from: DateTime<Utc>,
136    /// Coverage end.
137    pub effective_until: DateTime<Utc>,
138    /// Total claims paid out under this policy.
139    pub claims_paid_micro_usd: i64,
140    /// Number of claims filed.
141    pub claims_count: u32,
142    /// Provider (pool or external insurer).
143    pub provider_id: String,
144    /// When the policy was issued.
145    pub issued_at: DateTime<Utc>,
146}
147
148/// Policy lifecycle status.
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
150#[serde(rename_all = "snake_case")]
151pub enum PolicyStatus {
152    /// Policy is active and claims can be filed.
153    Active,
154    /// Policy has expired.
155    Expired,
156    /// Policy was cancelled.
157    Cancelled,
158    /// Policy is suspended (pending investigation).
159    Suspended,
160    /// Coverage limit exhausted.
161    Exhausted,
162}
163
164impl std::fmt::Display for PolicyStatus {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        match self {
167            Self::Active => write!(f, "active"),
168            Self::Expired => write!(f, "expired"),
169            Self::Cancelled => write!(f, "cancelled"),
170            Self::Suspended => write!(f, "suspended"),
171            Self::Exhausted => write!(f, "exhausted"),
172        }
173    }
174}
175
176// ---------------------------------------------------------------------------
177// Risk Assessment
178// ---------------------------------------------------------------------------
179
180/// A risk assessment for an agent, used for underwriting and pricing.
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct RiskAssessment {
183    /// The agent being assessed.
184    pub agent_id: String,
185    /// Overall risk score (0.0 = lowest risk, 1.0 = highest risk).
186    pub risk_score: f64,
187    /// Risk rating derived from score.
188    pub risk_rating: RiskRating,
189    /// Credit tier from Haima.
190    pub credit_tier: CreditTier,
191    /// Trust score from Autonomic (0.0 - 1.0).
192    pub trust_score: f64,
193    /// Component scores contributing to the risk assessment.
194    pub components: RiskComponents,
195    /// Premium multiplier based on risk (1.0 = base rate).
196    pub premium_multiplier: f64,
197    /// Whether the agent is insurable at all.
198    pub insurable: bool,
199    /// Reason if not insurable.
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub denial_reason: Option<String>,
202    /// When this assessment was computed.
203    pub assessed_at: DateTime<Utc>,
204}
205
206/// Component scores for risk assessment.
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct RiskComponents {
209    /// Operational reliability (uptime, error rate). 0.0 = risky, 1.0 = reliable.
210    pub operational_reliability: f64,
211    /// Payment history reliability. 0.0 = bad history, 1.0 = perfect.
212    pub payment_reliability: f64,
213    /// Economic stability (balance, burn rate). 0.0 = unstable, 1.0 = stable.
214    pub economic_stability: f64,
215    /// Task completion rate. 0.0 = never completes, 1.0 = always completes.
216    pub task_completion_rate: f64,
217    /// Account maturity factor. 0.0 = brand new, 1.0 = mature.
218    pub account_maturity: f64,
219    /// Prior claims history factor. 0.0 = many claims, 1.0 = no claims.
220    pub claims_history: f64,
221}
222
223impl Default for RiskComponents {
224    fn default() -> Self {
225        Self {
226            operational_reliability: 0.5,
227            payment_reliability: 0.5,
228            economic_stability: 0.5,
229            task_completion_rate: 0.5,
230            account_maturity: 0.0,
231            claims_history: 1.0, // no claims = best
232        }
233    }
234}
235
236// ---------------------------------------------------------------------------
237// Claims
238// ---------------------------------------------------------------------------
239
240/// A claim filed against an insurance policy.
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct InsuranceClaim {
243    /// Unique claim identifier.
244    pub claim_id: String,
245    /// Policy this claim is filed against.
246    pub policy_id: String,
247    /// The insured agent.
248    pub agent_id: String,
249    /// Type of incident.
250    pub incident_type: InsuranceProductType,
251    /// Claimed amount in micro-USD.
252    pub claimed_amount_micro_usd: i64,
253    /// Approved payout amount in micro-USD (after deductible and verification).
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub approved_amount_micro_usd: Option<i64>,
256    /// Claim status.
257    pub status: ClaimStatus,
258    /// Description of the incident.
259    pub description: String,
260    /// Lago event IDs that serve as evidence for this claim.
261    pub evidence_event_ids: Vec<String>,
262    /// Lago session ID where the incident occurred.
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub session_id: Option<String>,
265    /// When the incident occurred.
266    pub incident_at: DateTime<Utc>,
267    /// When the claim was filed.
268    pub filed_at: DateTime<Utc>,
269    /// When the claim was resolved (if resolved).
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub resolved_at: Option<DateTime<Utc>>,
272    /// Resolution notes.
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub resolution_notes: Option<String>,
275    /// Verification result from automated checks.
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub verification: Option<ClaimVerification>,
278}
279
280/// Claim lifecycle status.
281#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
282#[serde(rename_all = "snake_case")]
283pub enum ClaimStatus {
284    /// Claim submitted, awaiting verification.
285    Submitted,
286    /// Automated verification in progress.
287    Verifying,
288    /// Claim verified and approved for payout.
289    Approved,
290    /// Claim denied (evidence insufficient or policy doesn't cover).
291    Denied,
292    /// Payout processed.
293    Paid,
294    /// Claim is under manual investigation.
295    UnderReview,
296    /// Claim was withdrawn by the claimant.
297    Withdrawn,
298}
299
300impl std::fmt::Display for ClaimStatus {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        match self {
303            Self::Submitted => write!(f, "submitted"),
304            Self::Verifying => write!(f, "verifying"),
305            Self::Approved => write!(f, "approved"),
306            Self::Denied => write!(f, "denied"),
307            Self::Paid => write!(f, "paid"),
308            Self::UnderReview => write!(f, "under_review"),
309            Self::Withdrawn => write!(f, "withdrawn"),
310        }
311    }
312}
313
314/// Result of automated claim verification against Lago event history.
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct ClaimVerification {
317    /// Whether the incident is confirmed by event evidence.
318    pub incident_confirmed: bool,
319    /// Number of evidence events found and validated.
320    pub evidence_events_validated: u32,
321    /// Total evidence events submitted.
322    pub evidence_events_total: u32,
323    /// Whether the claimed amount is consistent with the evidence.
324    pub amount_consistent: bool,
325    /// Whether the policy was active at the time of incident.
326    pub policy_active_at_incident: bool,
327    /// Confidence score (0.0 - 1.0) in the verification.
328    pub confidence: f64,
329    /// Verification notes.
330    pub notes: Vec<String>,
331    /// When verification was performed.
332    pub verified_at: DateTime<Utc>,
333}
334
335// ---------------------------------------------------------------------------
336// Self-Insurance Pool
337// ---------------------------------------------------------------------------
338
339/// The network's self-insurance pool state.
340#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct InsurancePool {
342    /// Unique pool identifier.
343    pub pool_id: String,
344    /// Pool name.
345    pub name: String,
346    /// Total reserves in micro-USD.
347    pub reserves_micro_usd: i64,
348    /// Total contributions received.
349    pub total_contributions_micro_usd: i64,
350    /// Total payouts made.
351    pub total_payouts_micro_usd: i64,
352    /// Number of active policies backed by this pool.
353    pub active_policies: u32,
354    /// Total coverage outstanding across all active policies.
355    pub total_coverage_outstanding_micro_usd: i64,
356    /// Reserve ratio: reserves / `total_coverage_outstanding`.
357    pub reserve_ratio: f64,
358    /// Management fee in basis points (2-3% = 200-300 bps).
359    pub management_fee_bps: u32,
360    /// Minimum reserve ratio before new policies are paused.
361    pub min_reserve_ratio: f64,
362    /// Pool status.
363    pub status: PoolStatus,
364    /// When the pool was created.
365    pub created_at: DateTime<Utc>,
366}
367
368/// Pool operational status.
369#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
370#[serde(rename_all = "snake_case")]
371pub enum PoolStatus {
372    /// Accepting new policies and processing claims.
373    Active,
374    /// Not accepting new policies (low reserves), but honoring existing ones.
375    Paused,
376    /// Pool is being wound down.
377    WindingDown,
378}
379
380impl std::fmt::Display for PoolStatus {
381    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382        match self {
383            Self::Active => write!(f, "active"),
384            Self::Paused => write!(f, "paused"),
385            Self::WindingDown => write!(f, "winding_down"),
386        }
387    }
388}
389
390// ---------------------------------------------------------------------------
391// Marketplace
392// ---------------------------------------------------------------------------
393
394/// An insurance provider registered on the marketplace.
395#[derive(Debug, Clone, Serialize, Deserialize)]
396pub struct InsuranceProvider {
397    /// Unique provider identifier.
398    pub provider_id: String,
399    /// Provider name.
400    pub name: String,
401    /// Provider type.
402    pub provider_type: ProviderType,
403    /// Product types this provider offers.
404    pub offered_products: Vec<InsuranceProductType>,
405    /// Commission rate the marketplace charges this provider (basis points).
406    pub commission_rate_bps: u32,
407    /// Whether the provider is currently active.
408    pub active: bool,
409    /// Provider's external API endpoint (if applicable).
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub api_endpoint: Option<String>,
412    /// When the provider joined.
413    pub registered_at: DateTime<Utc>,
414}
415
416/// Type of insurance provider.
417#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
418#[serde(rename_all = "snake_case")]
419pub enum ProviderType {
420    /// Network-owned self-insurance pool.
421    SelfInsurancePool,
422    /// Licensed insurer or Managing General Agent.
423    LicensedInsurer,
424    /// Parametric insurance provider (automated payouts).
425    Parametric,
426}
427
428impl std::fmt::Display for ProviderType {
429    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
430        match self {
431            Self::SelfInsurancePool => write!(f, "self_insurance_pool"),
432            Self::LicensedInsurer => write!(f, "licensed_insurer"),
433            Self::Parametric => write!(f, "parametric"),
434        }
435    }
436}
437
438/// A quote for insurance coverage.
439#[derive(Debug, Clone, Serialize, Deserialize)]
440pub struct InsuranceQuote {
441    /// Unique quote identifier.
442    pub quote_id: String,
443    /// The agent requesting coverage.
444    pub agent_id: String,
445    /// Product being quoted.
446    pub product_id: String,
447    /// Product type.
448    pub product_type: InsuranceProductType,
449    /// Coverage amount in micro-USD.
450    pub coverage_micro_usd: i64,
451    /// Deductible in micro-USD.
452    pub deductible_micro_usd: i64,
453    /// Premium per period in micro-USD.
454    pub premium_micro_usd: i64,
455    /// Coverage period in seconds.
456    pub period_secs: u64,
457    /// Risk assessment used for pricing.
458    pub risk_assessment: RiskAssessment,
459    /// Provider offering this quote.
460    pub provider_id: String,
461    /// Quote expiry.
462    pub valid_until: DateTime<Utc>,
463    /// When the quote was generated.
464    pub quoted_at: DateTime<Utc>,
465}
466
467// ---------------------------------------------------------------------------
468// API Request/Response types
469// ---------------------------------------------------------------------------
470
471/// Request to get an insurance quote.
472#[derive(Debug, Clone, Serialize, Deserialize)]
473pub struct QuoteRequest {
474    /// The agent seeking coverage.
475    pub agent_id: String,
476    /// Type of coverage desired.
477    pub product_type: InsuranceProductType,
478    /// Desired coverage amount in micro-USD.
479    pub coverage_micro_usd: i64,
480    /// Optional preferred provider.
481    #[serde(skip_serializing_if = "Option::is_none")]
482    pub preferred_provider_id: Option<String>,
483}
484
485/// Request to bind (purchase) a policy from a quote.
486#[derive(Debug, Clone, Serialize, Deserialize)]
487pub struct BindRequest {
488    /// Quote to bind.
489    pub quote_id: String,
490    /// The agent purchasing coverage.
491    pub agent_id: String,
492}
493
494/// Request to file an insurance claim.
495#[derive(Debug, Clone, Serialize, Deserialize)]
496pub struct ClaimRequest {
497    /// Policy to claim against.
498    pub policy_id: String,
499    /// The insured agent filing the claim.
500    pub agent_id: String,
501    /// Type of incident.
502    pub incident_type: InsuranceProductType,
503    /// Claimed amount in micro-USD.
504    pub claimed_amount_micro_usd: i64,
505    /// Description of what happened.
506    pub description: String,
507    /// Lago event IDs as evidence.
508    pub evidence_event_ids: Vec<String>,
509    /// Session where incident occurred.
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub session_id: Option<String>,
512    /// When the incident happened.
513    pub incident_at: DateTime<Utc>,
514}
515
516/// Request to contribute to the self-insurance pool.
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct PoolContributionRequest {
519    /// Contributor agent ID.
520    pub agent_id: String,
521    /// Amount to contribute in micro-USD.
522    pub amount_micro_usd: i64,
523}
524
525#[cfg(test)]
526mod tests {
527    use super::*;
528
529    #[test]
530    fn product_type_serde_roundtrip() {
531        let pt = InsuranceProductType::TaskFailure;
532        let json = serde_json::to_string(&pt).unwrap();
533        assert_eq!(json, "\"task_failure\"");
534        let back: InsuranceProductType = serde_json::from_str(&json).unwrap();
535        assert_eq!(back, pt);
536    }
537
538    #[test]
539    fn policy_status_display() {
540        assert_eq!(PolicyStatus::Active.to_string(), "active");
541        assert_eq!(PolicyStatus::Suspended.to_string(), "suspended");
542    }
543
544    #[test]
545    fn claim_status_display() {
546        assert_eq!(ClaimStatus::Submitted.to_string(), "submitted");
547        assert_eq!(ClaimStatus::Paid.to_string(), "paid");
548        assert_eq!(ClaimStatus::UnderReview.to_string(), "under_review");
549    }
550
551    #[test]
552    fn provider_type_serde_roundtrip() {
553        let pt = ProviderType::SelfInsurancePool;
554        let json = serde_json::to_string(&pt).unwrap();
555        assert_eq!(json, "\"self_insurance_pool\"");
556        let back: ProviderType = serde_json::from_str(&json).unwrap();
557        assert_eq!(back, pt);
558    }
559
560    #[test]
561    fn risk_components_default() {
562        let rc = RiskComponents::default();
563        assert_eq!(rc.claims_history, 1.0);
564        assert_eq!(rc.account_maturity, 0.0);
565    }
566
567    #[test]
568    fn insurance_trust_tier_ordering() {
569        assert!(InsuranceTrustTier::Any < InsuranceTrustTier::Provisional);
570        assert!(InsuranceTrustTier::Provisional < InsuranceTrustTier::Trusted);
571        assert!(InsuranceTrustTier::Trusted < InsuranceTrustTier::Certified);
572    }
573
574    #[test]
575    fn pool_status_display() {
576        assert_eq!(PoolStatus::Active.to_string(), "active");
577        assert_eq!(PoolStatus::WindingDown.to_string(), "winding_down");
578    }
579}