Skip to main content

chio_market/
settlement.rs

1//! Liability claim payout and settlement reconciliation artifacts.
2
3use serde::{Deserialize, Serialize};
4
5use crate::capability::scope::MonetaryAmount;
6use crate::credit::{
7    validate_capital_execution_authority_step_proof, CapitalBookSourceKind,
8    CapitalExecutionAuthorityStep, CapitalExecutionInstructionAction, CapitalExecutionObservation,
9    CapitalExecutionRail, CapitalExecutionReconciledState, CapitalExecutionRole,
10    CapitalExecutionWindow, SignedCapitalBookReport, SignedCapitalExecutionInstruction,
11};
12use crate::receipt::lineage::SignedExportEnvelope;
13
14use crate::{
15    bounded_market_query_limit, liability_claim_adjudication_payable_amount,
16    validate_positive_money, verify_signed_artifact, SignedLiabilityClaimAdjudication,
17    SignedLiabilityClaimDispute, SignedLiabilityClaimPackage, SignedLiabilityClaimResponse,
18    MAX_LIABILITY_CLAIM_WORKFLOW_LIMIT,
19};
20
21#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
22#[serde(rename_all = "snake_case")]
23pub enum LiabilityClaimPayoutReconciliationState {
24    Matched,
25    AmountMismatch,
26}
27
28#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
29#[serde(rename_all = "snake_case")]
30pub enum LiabilityClaimSettlementKind {
31    RecoveryClearing,
32    ReinsuranceReimbursement,
33    FacilityReimbursement,
34}
35
36#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
37#[serde(rename_all = "snake_case")]
38pub enum LiabilityClaimSettlementReconciliationState {
39    Matched,
40    AmountMismatch,
41    CounterpartyMismatch,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45#[serde(rename_all = "camelCase")]
46pub struct LiabilityClaimSettlementRoleBinding {
47    pub role: CapitalExecutionRole,
48    pub party_id: String,
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub jurisdiction: Option<String>,
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub note: Option<String>,
53}
54
55impl LiabilityClaimSettlementRoleBinding {
56    fn validate(&self, field_name: &str) -> Result<(), String> {
57        if self.party_id.trim().is_empty() {
58            return Err(format!("{field_name} requires a non-empty party_id"));
59        }
60        Ok(())
61    }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
65#[serde(rename_all = "camelCase")]
66pub struct LiabilityClaimSettlementRoleTopology {
67    pub payer: LiabilityClaimSettlementRoleBinding,
68    pub payee: LiabilityClaimSettlementRoleBinding,
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub beneficiary: Option<LiabilityClaimSettlementRoleBinding>,
71}
72
73impl LiabilityClaimSettlementRoleTopology {
74    fn validate(&self) -> Result<(), String> {
75        self.payer.validate("settlement topology payer")?;
76        self.payee.validate("settlement topology payee")?;
77        if self.payer.role == self.payee.role && self.payer.party_id == self.payee.party_id {
78            return Err("settlement topology payer and payee must not be identical".to_string());
79        }
80        if let Some(beneficiary) = self.beneficiary.as_ref() {
81            beneficiary.validate("settlement topology beneficiary")?;
82        }
83        Ok(())
84    }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
88#[serde(rename_all = "camelCase")]
89pub struct LiabilityClaimPayoutInstructionArtifact {
90    pub schema: String,
91    pub payout_instruction_id: String,
92    pub issued_at: u64,
93    pub adjudication: SignedLiabilityClaimAdjudication,
94    pub capital_instruction: SignedCapitalExecutionInstruction,
95    pub payout_amount: MonetaryAmount,
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    pub note: Option<String>,
98}
99
100impl LiabilityClaimPayoutInstructionArtifact {
101    pub fn validate(&self) -> Result<(), String> {
102        verify_signed_artifact(&self.adjudication, "claim payout instruction adjudication")?;
103        self.adjudication.body.validate()?;
104        if !self
105            .capital_instruction
106            .verify_signature()
107            .map_err(|error| error.to_string())?
108        {
109            return Err(
110                "claim payout instruction capital_instruction signature verification failed"
111                    .to_string(),
112            );
113        }
114        validate_positive_money(&self.payout_amount, "payout_amount")?;
115        let awarded_amount = liability_claim_adjudication_payable_amount(&self.adjudication.body)?;
116        if &self.payout_amount != awarded_amount {
117            return Err(
118                "claim payout instruction payout_amount must match adjudication awarded_amount"
119                    .to_string(),
120            );
121        }
122        let capital_instruction = &self.capital_instruction.body;
123        if capital_instruction.action != CapitalExecutionInstructionAction::TransferFunds {
124            return Err(
125                "claim payout instructions require capital_instruction action transfer_funds"
126                    .to_string(),
127            );
128        }
129        if capital_instruction.source_kind != CapitalBookSourceKind::FacilityCommitment {
130            return Err(
131                "claim payout instructions require capital_instruction source_kind facility_commitment"
132                    .to_string(),
133            );
134        }
135        let intended_amount = capital_instruction.amount.as_ref().ok_or_else(|| {
136            "claim payout instructions require capital_instruction amount".to_string()
137        })?;
138        if intended_amount != &self.payout_amount {
139            return Err(
140                "claim payout instruction capital_instruction amount must match payout_amount"
141                    .to_string(),
142            );
143        }
144        let subject_key = &self
145            .adjudication
146            .body
147            .dispute
148            .body
149            .provider_response
150            .body
151            .claim
152            .body
153            .bound_coverage
154            .body
155            .placement
156            .body
157            .quote_response
158            .body
159            .quote_request
160            .body
161            .risk_package
162            .body
163            .subject_key;
164        if &capital_instruction.subject_key != subject_key {
165            return Err(
166                "claim payout instruction capital_instruction subject_key must match the claim subject"
167                    .to_string(),
168            );
169        }
170        if capital_instruction.execution_window.not_after <= self.issued_at {
171            return Err(
172                "claim payout instructions require a non-stale capital_instruction execution window"
173                    .to_string(),
174            );
175        }
176        if capital_instruction.reconciled_state != CapitalExecutionReconciledState::NotObserved
177            || capital_instruction.observed_execution.is_some()
178        {
179            return Err(
180                "claim payout instructions require an unreconciled capital_instruction so payout receipts stay explicit"
181                    .to_string(),
182            );
183        }
184        Ok(())
185    }
186}
187
188pub type SignedLiabilityClaimPayoutInstruction =
189    SignedExportEnvelope<LiabilityClaimPayoutInstructionArtifact>;
190
191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
192#[serde(rename_all = "camelCase")]
193pub struct LiabilityClaimPayoutReceiptArtifact {
194    pub schema: String,
195    pub payout_receipt_id: String,
196    pub issued_at: u64,
197    pub payout_instruction: SignedLiabilityClaimPayoutInstruction,
198    pub payout_receipt_ref: String,
199    pub reconciliation_state: LiabilityClaimPayoutReconciliationState,
200    pub observed_execution: crate::credit::CapitalExecutionObservation,
201    #[serde(default, skip_serializing_if = "Option::is_none")]
202    pub note: Option<String>,
203}
204
205impl LiabilityClaimPayoutReceiptArtifact {
206    pub fn validate(&self) -> Result<(), String> {
207        verify_signed_artifact(
208            &self.payout_instruction,
209            "claim payout receipt payout_instruction",
210        )?;
211        self.payout_instruction.body.validate()?;
212        if self.payout_receipt_ref.trim().is_empty() {
213            return Err("claim payout receipts require a non-empty payout_receipt_ref".to_string());
214        }
215        if self
216            .observed_execution
217            .external_reference_id
218            .trim()
219            .is_empty()
220        {
221            return Err(
222                "claim payout receipts require a non-empty observed_execution external_reference_id"
223                    .to_string(),
224            );
225        }
226        validate_positive_money(
227            &self.observed_execution.amount,
228            "claim payout receipt observed_execution amount",
229        )?;
230        if self.observed_execution.amount.currency
231            != self.payout_instruction.body.payout_amount.currency
232        {
233            return Err(
234                "claim payout receipt observed_execution amount currency must match payout_amount"
235                    .to_string(),
236            );
237        }
238        let execution_window = &self
239            .payout_instruction
240            .body
241            .capital_instruction
242            .body
243            .execution_window;
244        if self.observed_execution.observed_at < execution_window.not_before
245            || self.observed_execution.observed_at > execution_window.not_after
246        {
247            return Err(
248                "claim payout receipt observed_execution timestamp falls outside the payout instruction execution window"
249                    .to_string(),
250            );
251        }
252        match self.reconciliation_state {
253            LiabilityClaimPayoutReconciliationState::Matched => {
254                if self.observed_execution.amount != self.payout_instruction.body.payout_amount {
255                    return Err(
256                        "matched claim payout receipts require observed_execution amount to match payout_amount"
257                            .to_string(),
258                    );
259                }
260            }
261            LiabilityClaimPayoutReconciliationState::AmountMismatch => {
262                if self.observed_execution.amount == self.payout_instruction.body.payout_amount {
263                    return Err(
264                        "amount_mismatch claim payout receipts require observed_execution amount to differ from payout_amount"
265                            .to_string(),
266                    );
267                }
268            }
269        }
270        Ok(())
271    }
272}
273
274pub type SignedLiabilityClaimPayoutReceipt =
275    SignedExportEnvelope<LiabilityClaimPayoutReceiptArtifact>;
276
277#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
278#[serde(rename_all = "camelCase")]
279pub struct LiabilityClaimSettlementInstructionArtifact {
280    pub schema: String,
281    pub settlement_instruction_id: String,
282    pub issued_at: u64,
283    pub payout_receipt: SignedLiabilityClaimPayoutReceipt,
284    pub capital_book: SignedCapitalBookReport,
285    pub settlement_kind: LiabilityClaimSettlementKind,
286    pub settlement_amount: MonetaryAmount,
287    pub topology: LiabilityClaimSettlementRoleTopology,
288    pub authority_chain: Vec<CapitalExecutionAuthorityStep>,
289    pub execution_window: CapitalExecutionWindow,
290    pub rail: CapitalExecutionRail,
291    #[serde(default, skip_serializing_if = "Option::is_none")]
292    pub settlement_reference: Option<String>,
293    #[serde(default, skip_serializing_if = "Option::is_none")]
294    pub note: Option<String>,
295}
296
297impl LiabilityClaimSettlementInstructionArtifact {
298    pub fn validate(&self) -> Result<(), String> {
299        verify_signed_artifact(
300            &self.payout_receipt,
301            "claim settlement instruction payout_receipt",
302        )?;
303        self.payout_receipt.body.validate()?;
304        if !self
305            .capital_book
306            .verify_signature()
307            .map_err(|error| error.to_string())?
308        {
309            return Err(
310                "claim settlement instruction capital_book signature verification failed"
311                    .to_string(),
312            );
313        }
314        validate_positive_money(&self.settlement_amount, "settlement_amount")?;
315        self.topology.validate()?;
316        if self.payout_receipt.body.reconciliation_state
317            != LiabilityClaimPayoutReconciliationState::Matched
318        {
319            return Err(
320                "claim settlement instructions require a matched payout_receipt".to_string(),
321            );
322        }
323        if self.settlement_amount.currency
324            != self
325                .payout_receipt
326                .body
327                .payout_instruction
328                .body
329                .payout_amount
330                .currency
331        {
332            return Err(
333                "claim settlement instruction settlement_amount currency must match payout_amount"
334                    .to_string(),
335            );
336        }
337        if self.settlement_amount.units
338            > self
339                .payout_receipt
340                .body
341                .payout_instruction
342                .body
343                .payout_amount
344                .units
345        {
346            return Err(
347                "claim settlement instruction settlement_amount cannot exceed payout_amount"
348                    .to_string(),
349            );
350        }
351        let subject_key = &self
352            .payout_receipt
353            .body
354            .payout_instruction
355            .body
356            .adjudication
357            .body
358            .dispute
359            .body
360            .provider_response
361            .body
362            .claim
363            .body
364            .bound_coverage
365            .body
366            .placement
367            .body
368            .quote_response
369            .body
370            .quote_request
371            .body
372            .risk_package
373            .body
374            .subject_key;
375        if self.capital_book.body.subject_key != *subject_key {
376            return Err(
377                "claim settlement instruction capital_book subject_key must match the claim subject"
378                    .to_string(),
379            );
380        }
381        if self.capital_book.body.summary.mixed_currency_book {
382            return Err(
383                "claim settlement instructions require a capital_book without mixed-currency ambiguity"
384                    .to_string(),
385            );
386        }
387        if self.authority_chain.is_empty() {
388            return Err(
389                "claim settlement instructions require at least one authority_chain step"
390                    .to_string(),
391            );
392        }
393        if self.rail.rail_id.trim().is_empty() {
394            return Err("claim settlement instructions require rail.rail_id".to_string());
395        }
396        if self.rail.custody_provider_id.trim().is_empty() {
397            return Err(
398                "claim settlement instructions require rail.custody_provider_id".to_string(),
399            );
400        }
401        if self.execution_window.not_before > self.execution_window.not_after {
402            return Err(
403                "claim settlement instructions require execution_window.not_before <= not_after"
404                    .to_string(),
405            );
406        }
407        if self.execution_window.not_after <= self.issued_at {
408            return Err(
409                "claim settlement instructions require a non-stale execution_window".to_string(),
410            );
411        }
412        let mut payer_role_present = false;
413        let mut custodian_present = false;
414        for step in &self.authority_chain {
415            if step.principal_id.trim().is_empty() {
416                return Err(
417                    "claim settlement authority_chain principal_id cannot be empty".to_string(),
418                );
419            }
420            validate_capital_execution_authority_step_proof(step)?;
421            if step.approved_at > step.expires_at {
422                return Err(
423                    "claim settlement authority_chain requires approved_at <= expires_at"
424                        .to_string(),
425                );
426            }
427            if step.expires_at < self.issued_at {
428                return Err(format!(
429                    "claim settlement authority step `{}` is stale at issuance time",
430                    step.principal_id
431                ));
432            }
433            if step.expires_at < self.execution_window.not_after {
434                return Err(format!(
435                    "claim settlement authority step `{}` expires before the execution window closes",
436                    step.principal_id
437                ));
438            }
439            if step.role == self.topology.payer.role {
440                payer_role_present = true;
441            }
442            if step.role == CapitalExecutionRole::Custodian
443                && step.principal_id == self.rail.custody_provider_id
444            {
445                custodian_present = true;
446            }
447        }
448        if !payer_role_present {
449            return Err(
450                "claim settlement authority_chain is missing payer-role approval".to_string(),
451            );
452        }
453        if !custodian_present {
454            return Err(
455                "claim settlement authority_chain is missing the custody-provider execution step"
456                    .to_string(),
457            );
458        }
459        Ok(())
460    }
461}
462
463pub type SignedLiabilityClaimSettlementInstruction =
464    SignedExportEnvelope<LiabilityClaimSettlementInstructionArtifact>;
465
466#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
467#[serde(rename_all = "camelCase")]
468pub struct LiabilityClaimSettlementReceiptArtifact {
469    pub schema: String,
470    pub settlement_receipt_id: String,
471    pub issued_at: u64,
472    pub settlement_instruction: SignedLiabilityClaimSettlementInstruction,
473    pub settlement_receipt_ref: String,
474    pub reconciliation_state: LiabilityClaimSettlementReconciliationState,
475    pub observed_execution: CapitalExecutionObservation,
476    pub observed_payer_id: String,
477    pub observed_payee_id: String,
478    #[serde(default, skip_serializing_if = "Option::is_none")]
479    pub note: Option<String>,
480}
481
482impl LiabilityClaimSettlementReceiptArtifact {
483    pub fn validate(&self) -> Result<(), String> {
484        verify_signed_artifact(
485            &self.settlement_instruction,
486            "claim settlement receipt settlement_instruction",
487        )?;
488        self.settlement_instruction.body.validate()?;
489        if self.settlement_receipt_ref.trim().is_empty() {
490            return Err(
491                "claim settlement receipts require a non-empty settlement_receipt_ref".to_string(),
492            );
493        }
494        if self.observed_payer_id.trim().is_empty() {
495            return Err(
496                "claim settlement receipts require a non-empty observed_payer_id".to_string(),
497            );
498        }
499        if self.observed_payee_id.trim().is_empty() {
500            return Err(
501                "claim settlement receipts require a non-empty observed_payee_id".to_string(),
502            );
503        }
504        if self
505            .observed_execution
506            .external_reference_id
507            .trim()
508            .is_empty()
509        {
510            return Err(
511                "claim settlement receipts require a non-empty observed_execution external_reference_id"
512                    .to_string(),
513            );
514        }
515        validate_positive_money(
516            &self.observed_execution.amount,
517            "claim settlement receipt observed_execution amount",
518        )?;
519        if self.observed_execution.amount.currency
520            != self.settlement_instruction.body.settlement_amount.currency
521        {
522            return Err(
523                "claim settlement receipt observed_execution amount currency must match settlement_amount"
524                    .to_string(),
525            );
526        }
527        let execution_window = &self.settlement_instruction.body.execution_window;
528        if self.observed_execution.observed_at < execution_window.not_before
529            || self.observed_execution.observed_at > execution_window.not_after
530        {
531            return Err(
532                "claim settlement receipt observed_execution timestamp falls outside the settlement execution window"
533                    .to_string(),
534            );
535        }
536        let expected_payer = &self.settlement_instruction.body.topology.payer.party_id;
537        let expected_payee = &self.settlement_instruction.body.topology.payee.party_id;
538        match self.reconciliation_state {
539            LiabilityClaimSettlementReconciliationState::Matched => {
540                if self.observed_execution.amount
541                    != self.settlement_instruction.body.settlement_amount
542                {
543                    return Err(
544                        "matched claim settlement receipts require observed_execution amount to match settlement_amount"
545                            .to_string(),
546                    );
547                }
548                if &self.observed_payer_id != expected_payer
549                    || &self.observed_payee_id != expected_payee
550                {
551                    return Err(
552                        "matched claim settlement receipts require observed payer/payee to match the settlement topology"
553                            .to_string(),
554                    );
555                }
556            }
557            LiabilityClaimSettlementReconciliationState::AmountMismatch => {
558                if self.observed_execution.amount
559                    == self.settlement_instruction.body.settlement_amount
560                {
561                    return Err(
562                        "amount_mismatch claim settlement receipts require observed_execution amount to differ from settlement_amount"
563                            .to_string(),
564                    );
565                }
566                if &self.observed_payer_id != expected_payer
567                    || &self.observed_payee_id != expected_payee
568                {
569                    return Err(
570                        "amount_mismatch claim settlement receipts still require observed payer/payee to match the settlement topology"
571                            .to_string(),
572                    );
573                }
574            }
575            LiabilityClaimSettlementReconciliationState::CounterpartyMismatch => {
576                if &self.observed_payer_id == expected_payer
577                    && &self.observed_payee_id == expected_payee
578                {
579                    return Err(
580                        "counterparty_mismatch claim settlement receipts require at least one observed counterparty to differ from the settlement topology"
581                            .to_string(),
582                    );
583                }
584            }
585        }
586        Ok(())
587    }
588}
589
590pub type SignedLiabilityClaimSettlementReceipt =
591    SignedExportEnvelope<LiabilityClaimSettlementReceiptArtifact>;
592
593#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
594#[serde(rename_all = "camelCase")]
595pub struct LiabilityClaimWorkflowQuery {
596    #[serde(default, skip_serializing_if = "Option::is_none")]
597    pub claim_id: Option<String>,
598    #[serde(default, skip_serializing_if = "Option::is_none")]
599    pub provider_id: Option<String>,
600    #[serde(default, skip_serializing_if = "Option::is_none")]
601    pub agent_subject: Option<String>,
602    #[serde(default, skip_serializing_if = "Option::is_none")]
603    pub jurisdiction: Option<String>,
604    #[serde(default, skip_serializing_if = "Option::is_none")]
605    pub policy_number: Option<String>,
606    #[serde(default, skip_serializing_if = "Option::is_none")]
607    pub limit: Option<usize>,
608}
609
610impl Default for LiabilityClaimWorkflowQuery {
611    fn default() -> Self {
612        Self {
613            claim_id: None,
614            provider_id: None,
615            agent_subject: None,
616            jurisdiction: None,
617            policy_number: None,
618            limit: Some(50),
619        }
620    }
621}
622
623impl LiabilityClaimWorkflowQuery {
624    #[must_use]
625    pub fn limit_or_default(&self) -> usize {
626        bounded_market_query_limit(self.limit, MAX_LIABILITY_CLAIM_WORKFLOW_LIMIT)
627    }
628
629    #[must_use]
630    pub fn normalized(&self) -> Self {
631        let mut normalized = self.clone();
632        normalized.limit = Some(self.limit_or_default());
633        normalized.claim_id = self.claim_id.as_ref().map(|value| value.trim().to_string());
634        normalized.provider_id = self
635            .provider_id
636            .as_ref()
637            .map(|value| value.trim().to_string());
638        normalized.agent_subject = self
639            .agent_subject
640            .as_ref()
641            .map(|value| value.trim().to_string());
642        normalized.jurisdiction = self
643            .jurisdiction
644            .as_ref()
645            .map(|value| value.trim().to_ascii_lowercase());
646        normalized.policy_number = self
647            .policy_number
648            .as_ref()
649            .map(|value| value.trim().to_string());
650        normalized
651    }
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
655#[serde(rename_all = "camelCase")]
656pub struct LiabilityClaimWorkflowRow {
657    pub claim: SignedLiabilityClaimPackage,
658    #[serde(default, skip_serializing_if = "Option::is_none")]
659    pub provider_response: Option<SignedLiabilityClaimResponse>,
660    #[serde(default, skip_serializing_if = "Option::is_none")]
661    pub dispute: Option<SignedLiabilityClaimDispute>,
662    #[serde(default, skip_serializing_if = "Option::is_none")]
663    pub adjudication: Option<SignedLiabilityClaimAdjudication>,
664    #[serde(default, skip_serializing_if = "Option::is_none")]
665    pub payout_instruction: Option<SignedLiabilityClaimPayoutInstruction>,
666    #[serde(default, skip_serializing_if = "Option::is_none")]
667    pub payout_receipt: Option<SignedLiabilityClaimPayoutReceipt>,
668    #[serde(default, skip_serializing_if = "Option::is_none")]
669    pub settlement_instruction: Option<SignedLiabilityClaimSettlementInstruction>,
670    #[serde(default, skip_serializing_if = "Option::is_none")]
671    pub settlement_receipt: Option<SignedLiabilityClaimSettlementReceipt>,
672}
673
674#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
675#[serde(rename_all = "camelCase")]
676pub struct LiabilityClaimWorkflowSummary {
677    pub matching_claims: u64,
678    pub returned_claims: u64,
679    pub provider_responses: u64,
680    pub accepted_responses: u64,
681    pub denied_responses: u64,
682    pub disputes: u64,
683    pub adjudications: u64,
684    pub payout_instructions: u64,
685    pub payout_receipts: u64,
686    pub matched_payout_receipts: u64,
687    pub mismatched_payout_receipts: u64,
688    pub settlement_instructions: u64,
689    pub settlement_receipts: u64,
690    pub matched_settlement_receipts: u64,
691    pub mismatched_settlement_receipts: u64,
692    pub counterparty_mismatch_settlement_receipts: u64,
693}
694
695#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
696#[serde(rename_all = "camelCase")]
697pub struct LiabilityClaimWorkflowReport {
698    pub schema: String,
699    pub generated_at: u64,
700    pub query: LiabilityClaimWorkflowQuery,
701    pub summary: LiabilityClaimWorkflowSummary,
702    pub claims: Vec<LiabilityClaimWorkflowRow>,
703}