1use super::*;
2
3impl SqliteReceiptStore {
4 pub fn record_liability_claim_package(
5 &mut self,
6 claim: &SignedLiabilityClaimPackage,
7 ) -> Result<(), ReceiptStoreError> {
8 if !claim
9 .verify_signature()
10 .map_err(|error| ReceiptStoreError::Canonical(error.to_string()))?
11 {
12 return Err(ReceiptStoreError::Conflict(
13 "liability claim package signature verification failed".to_string(),
14 ));
15 }
16 claim.body.validate().map_err(ReceiptStoreError::Conflict)?;
17
18 let artifact = &claim.body;
19 let mut connection = self.connection()?;
20 let tx = connection.transaction()?;
21 let existing = tx
22 .query_row(
23 "SELECT claim_id FROM liability_claim_packages WHERE claim_id = ?1",
24 params![artifact.claim_id],
25 |row| row.get::<_, String>(0),
26 )
27 .optional()?;
28 if existing.is_some() {
29 return Err(ReceiptStoreError::Conflict(format!(
30 "liability claim package `{}` already exists",
31 artifact.claim_id
32 )));
33 }
34
35 let stored_bound_raw_json = tx
36 .query_row(
37 "SELECT raw_json
38 FROM liability_bound_coverages
39 WHERE bound_coverage_id = ?1",
40 params![artifact.bound_coverage.body.bound_coverage_id],
41 |row| row.get::<_, String>(0),
42 )
43 .optional()?
44 .ok_or_else(|| {
45 ReceiptStoreError::NotFound(format!(
46 "liability bound coverage `{}` not found",
47 artifact.bound_coverage.body.bound_coverage_id
48 ))
49 })?;
50 let stored_bound: SignedLiabilityBoundCoverage =
51 serde_json::from_str(&stored_bound_raw_json)?;
52 if stored_bound.body != artifact.bound_coverage.body {
53 return Err(ReceiptStoreError::Conflict(
54 "liability claim package bound_coverage does not match the persisted bound coverage"
55 .to_string(),
56 ));
57 }
58
59 let stored_bond_raw_json = tx
60 .query_row(
61 "SELECT raw_json
62 FROM credit_bonds
63 WHERE bond_id = ?1",
64 params![artifact.bond.body.bond_id],
65 |row| row.get::<_, String>(0),
66 )
67 .optional()?
68 .ok_or_else(|| {
69 ReceiptStoreError::NotFound(format!(
70 "credit bond `{}` not found",
71 artifact.bond.body.bond_id
72 ))
73 })?;
74 let stored_bond: SignedCreditBond = serde_json::from_str(&stored_bond_raw_json)?;
75 if stored_bond.body != artifact.bond.body {
76 return Err(ReceiptStoreError::Conflict(
77 "liability claim package bond does not match the persisted credit bond".to_string(),
78 ));
79 }
80
81 let stored_loss_raw_json = tx
82 .query_row(
83 "SELECT raw_json
84 FROM credit_loss_lifecycle
85 WHERE event_id = ?1",
86 params![artifact.loss_event.body.event_id],
87 |row| row.get::<_, String>(0),
88 )
89 .optional()?
90 .ok_or_else(|| {
91 ReceiptStoreError::NotFound(format!(
92 "credit loss lifecycle event `{}` not found",
93 artifact.loss_event.body.event_id
94 ))
95 })?;
96 let stored_loss: SignedCreditLossLifecycle = serde_json::from_str(&stored_loss_raw_json)?;
97 if stored_loss.body != artifact.loss_event.body {
98 return Err(ReceiptStoreError::Conflict(
99 "liability claim package loss_event does not match the persisted credit loss lifecycle event"
100 .to_string(),
101 ));
102 }
103
104 for receipt_id in &artifact.receipt_ids {
105 let exists = tx
106 .query_row(
107 "SELECT 1 FROM chio_tool_receipts WHERE receipt_id = ?1",
108 params![receipt_id],
109 |row| row.get::<_, i64>(0),
110 )
111 .optional()?;
112 if exists.is_none() {
113 return Err(ReceiptStoreError::NotFound(format!(
114 "receipt {receipt_id} does not exist"
115 )));
116 }
117 }
118
119 tx.execute(
120 "INSERT INTO liability_claim_packages (
121 claim_id, issued_at, provider_id, policy_number, jurisdiction, subject_key,
122 claim_event_at, raw_json, signer_key, signature
123 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
124 params![
125 artifact.claim_id,
126 artifact.issued_at as i64,
127 artifact
128 .bound_coverage
129 .body
130 .placement
131 .body
132 .quote_response
133 .body
134 .quote_request
135 .body
136 .provider_policy
137 .provider_id,
138 artifact.bound_coverage.body.policy_number,
139 artifact
140 .bound_coverage
141 .body
142 .placement
143 .body
144 .quote_response
145 .body
146 .quote_request
147 .body
148 .provider_policy
149 .jurisdiction,
150 artifact
151 .bound_coverage
152 .body
153 .placement
154 .body
155 .quote_response
156 .body
157 .quote_request
158 .body
159 .risk_package
160 .body
161 .subject_key,
162 artifact.claim_event_at as i64,
163 serde_json::to_string(claim)?,
164 claim.signer_key.to_hex(),
165 claim.signature.to_hex(),
166 ],
167 )?;
168
169 tx.commit()?;
170 Ok(())
171 }
172
173 pub fn record_liability_claim_response(
174 &mut self,
175 response: &SignedLiabilityClaimResponse,
176 ) -> Result<(), ReceiptStoreError> {
177 if !response
178 .verify_signature()
179 .map_err(|error| ReceiptStoreError::Canonical(error.to_string()))?
180 {
181 return Err(ReceiptStoreError::Conflict(
182 "liability claim response signature verification failed".to_string(),
183 ));
184 }
185 response
186 .body
187 .validate()
188 .map_err(ReceiptStoreError::Conflict)?;
189
190 let artifact = &response.body;
191 let mut connection = self.connection()?;
192 let tx = connection.transaction()?;
193 let existing = tx
194 .query_row(
195 "SELECT claim_response_id
196 FROM liability_claim_responses
197 WHERE claim_response_id = ?1",
198 params![artifact.claim_response_id],
199 |row| row.get::<_, String>(0),
200 )
201 .optional()?;
202 if existing.is_some() {
203 return Err(ReceiptStoreError::Conflict(format!(
204 "liability claim response `{}` already exists",
205 artifact.claim_response_id
206 )));
207 }
208
209 let stored_claim_raw_json = tx
210 .query_row(
211 "SELECT raw_json
212 FROM liability_claim_packages
213 WHERE claim_id = ?1",
214 params![artifact.claim.body.claim_id],
215 |row| row.get::<_, String>(0),
216 )
217 .optional()?
218 .ok_or_else(|| {
219 ReceiptStoreError::NotFound(format!(
220 "liability claim package `{}` not found",
221 artifact.claim.body.claim_id
222 ))
223 })?;
224 let stored_claim: SignedLiabilityClaimPackage =
225 serde_json::from_str(&stored_claim_raw_json)?;
226 if stored_claim.body != artifact.claim.body {
227 return Err(ReceiptStoreError::Conflict(
228 "liability claim response claim does not match the persisted claim package"
229 .to_string(),
230 ));
231 }
232
233 tx.execute(
234 "INSERT INTO liability_claim_responses (
235 claim_response_id, issued_at, claim_id, provider_id, disposition,
236 raw_json, signer_key, signature
237 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
238 params![
239 artifact.claim_response_id,
240 artifact.issued_at as i64,
241 artifact.claim.body.claim_id,
242 artifact
243 .claim
244 .body
245 .bound_coverage
246 .body
247 .placement
248 .body
249 .quote_response
250 .body
251 .quote_request
252 .body
253 .provider_policy
254 .provider_id,
255 serde_json::to_string(&artifact.disposition)?,
256 serde_json::to_string(response)?,
257 response.signer_key.to_hex(),
258 response.signature.to_hex(),
259 ],
260 )?;
261
262 tx.commit()?;
263 Ok(())
264 }
265
266 pub fn record_liability_claim_dispute(
267 &mut self,
268 dispute: &SignedLiabilityClaimDispute,
269 ) -> Result<(), ReceiptStoreError> {
270 if !dispute
271 .verify_signature()
272 .map_err(|error| ReceiptStoreError::Canonical(error.to_string()))?
273 {
274 return Err(ReceiptStoreError::Conflict(
275 "liability claim dispute signature verification failed".to_string(),
276 ));
277 }
278 dispute
279 .body
280 .validate()
281 .map_err(ReceiptStoreError::Conflict)?;
282
283 let artifact = &dispute.body;
284 let mut connection = self.connection()?;
285 let tx = connection.transaction()?;
286 let existing = tx
287 .query_row(
288 "SELECT dispute_id FROM liability_claim_disputes WHERE dispute_id = ?1",
289 params![artifact.dispute_id],
290 |row| row.get::<_, String>(0),
291 )
292 .optional()?;
293 if existing.is_some() {
294 return Err(ReceiptStoreError::Conflict(format!(
295 "liability claim dispute `{}` already exists",
296 artifact.dispute_id
297 )));
298 }
299
300 let stored_response_raw_json = tx
301 .query_row(
302 "SELECT raw_json
303 FROM liability_claim_responses
304 WHERE claim_response_id = ?1",
305 params![artifact.provider_response.body.claim_response_id],
306 |row| row.get::<_, String>(0),
307 )
308 .optional()?
309 .ok_or_else(|| {
310 ReceiptStoreError::NotFound(format!(
311 "liability claim response `{}` not found",
312 artifact.provider_response.body.claim_response_id
313 ))
314 })?;
315 let stored_response: SignedLiabilityClaimResponse =
316 serde_json::from_str(&stored_response_raw_json)?;
317 if stored_response.body != artifact.provider_response.body {
318 return Err(ReceiptStoreError::Conflict(
319 "liability claim dispute provider_response does not match the persisted claim response"
320 .to_string(),
321 ));
322 }
323
324 tx.execute(
325 "INSERT INTO liability_claim_disputes (
326 dispute_id, issued_at, claim_id, claim_response_id, provider_id,
327 raw_json, signer_key, signature
328 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
329 params![
330 artifact.dispute_id,
331 artifact.issued_at as i64,
332 artifact.provider_response.body.claim.body.claim_id,
333 artifact.provider_response.body.claim_response_id,
334 artifact
335 .provider_response
336 .body
337 .claim
338 .body
339 .bound_coverage
340 .body
341 .placement
342 .body
343 .quote_response
344 .body
345 .quote_request
346 .body
347 .provider_policy
348 .provider_id,
349 serde_json::to_string(dispute)?,
350 dispute.signer_key.to_hex(),
351 dispute.signature.to_hex(),
352 ],
353 )?;
354
355 tx.commit()?;
356 Ok(())
357 }
358
359 pub fn record_liability_claim_adjudication(
360 &mut self,
361 adjudication: &SignedLiabilityClaimAdjudication,
362 ) -> Result<(), ReceiptStoreError> {
363 if !adjudication
364 .verify_signature()
365 .map_err(|error| ReceiptStoreError::Canonical(error.to_string()))?
366 {
367 return Err(ReceiptStoreError::Conflict(
368 "liability claim adjudication signature verification failed".to_string(),
369 ));
370 }
371 adjudication
372 .body
373 .validate()
374 .map_err(ReceiptStoreError::Conflict)?;
375
376 let artifact = &adjudication.body;
377 let mut connection = self.connection()?;
378 let tx = connection.transaction()?;
379 let existing = tx
380 .query_row(
381 "SELECT adjudication_id
382 FROM liability_claim_adjudications
383 WHERE adjudication_id = ?1",
384 params![artifact.adjudication_id],
385 |row| row.get::<_, String>(0),
386 )
387 .optional()?;
388 if existing.is_some() {
389 return Err(ReceiptStoreError::Conflict(format!(
390 "liability claim adjudication `{}` already exists",
391 artifact.adjudication_id
392 )));
393 }
394
395 let stored_dispute_raw_json = tx
396 .query_row(
397 "SELECT raw_json
398 FROM liability_claim_disputes
399 WHERE dispute_id = ?1",
400 params![artifact.dispute.body.dispute_id],
401 |row| row.get::<_, String>(0),
402 )
403 .optional()?
404 .ok_or_else(|| {
405 ReceiptStoreError::NotFound(format!(
406 "liability claim dispute `{}` not found",
407 artifact.dispute.body.dispute_id
408 ))
409 })?;
410 let stored_dispute: SignedLiabilityClaimDispute =
411 serde_json::from_str(&stored_dispute_raw_json)?;
412 if stored_dispute.body != artifact.dispute.body {
413 return Err(ReceiptStoreError::Conflict(
414 "liability claim adjudication dispute does not match the persisted claim dispute"
415 .to_string(),
416 ));
417 }
418
419 tx.execute(
420 "INSERT INTO liability_claim_adjudications (
421 adjudication_id, issued_at, claim_id, dispute_id, outcome,
422 raw_json, signer_key, signature
423 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
424 params![
425 artifact.adjudication_id,
426 artifact.issued_at as i64,
427 artifact
428 .dispute
429 .body
430 .provider_response
431 .body
432 .claim
433 .body
434 .claim_id,
435 artifact.dispute.body.dispute_id,
436 serde_json::to_string(&artifact.outcome)?,
437 serde_json::to_string(adjudication)?,
438 adjudication.signer_key.to_hex(),
439 adjudication.signature.to_hex(),
440 ],
441 )?;
442
443 tx.commit()?;
444 Ok(())
445 }
446
447 pub fn record_liability_claim_payout_instruction(
448 &mut self,
449 payout_instruction: &SignedLiabilityClaimPayoutInstruction,
450 ) -> Result<(), ReceiptStoreError> {
451 if !payout_instruction
452 .verify_signature()
453 .map_err(|error| ReceiptStoreError::Canonical(error.to_string()))?
454 {
455 return Err(ReceiptStoreError::Conflict(
456 "liability claim payout instruction signature verification failed".to_string(),
457 ));
458 }
459 payout_instruction
460 .body
461 .validate()
462 .map_err(ReceiptStoreError::Conflict)?;
463
464 let artifact = &payout_instruction.body;
465 let mut connection = self.connection()?;
466 let tx = connection.transaction()?;
467 let existing = tx
468 .query_row(
469 "SELECT payout_instruction_id
470 FROM liability_claim_payout_instructions
471 WHERE payout_instruction_id = ?1",
472 params![artifact.payout_instruction_id],
473 |row| row.get::<_, String>(0),
474 )
475 .optional()?;
476 if existing.is_some() {
477 return Err(ReceiptStoreError::Conflict(format!(
478 "liability claim payout instruction `{}` already exists",
479 artifact.payout_instruction_id
480 )));
481 }
482
483 let stored_adjudication_raw_json = tx
484 .query_row(
485 "SELECT raw_json
486 FROM liability_claim_adjudications
487 WHERE adjudication_id = ?1",
488 params![artifact.adjudication.body.adjudication_id],
489 |row| row.get::<_, String>(0),
490 )
491 .optional()?
492 .ok_or_else(|| {
493 ReceiptStoreError::NotFound(format!(
494 "liability claim adjudication `{}` not found",
495 artifact.adjudication.body.adjudication_id
496 ))
497 })?;
498 let stored_adjudication: SignedLiabilityClaimAdjudication =
499 serde_json::from_str(&stored_adjudication_raw_json)?;
500 if stored_adjudication.body != artifact.adjudication.body {
501 return Err(ReceiptStoreError::Conflict(
502 "liability claim payout instruction adjudication does not match the persisted adjudication"
503 .to_string(),
504 ));
505 }
506
507 let claim_id = artifact
508 .adjudication
509 .body
510 .dispute
511 .body
512 .provider_response
513 .body
514 .claim
515 .body
516 .claim_id
517 .clone();
518
519 tx.execute(
520 "INSERT INTO liability_claim_payout_instructions (
521 payout_instruction_id, issued_at, claim_id, adjudication_id,
522 payout_amount_units, payout_amount_currency,
523 raw_json, signer_key, signature
524 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
525 params![
526 artifact.payout_instruction_id,
527 artifact.issued_at as i64,
528 claim_id,
529 artifact.adjudication.body.adjudication_id,
530 artifact.payout_amount.units as i64,
531 artifact.payout_amount.currency,
532 serde_json::to_string(payout_instruction)?,
533 payout_instruction.signer_key.to_hex(),
534 payout_instruction.signature.to_hex(),
535 ],
536 )?;
537
538 tx.commit()?;
539 Ok(())
540 }
541
542 pub fn record_liability_claim_payout_receipt(
543 &mut self,
544 payout_receipt: &SignedLiabilityClaimPayoutReceipt,
545 ) -> Result<(), ReceiptStoreError> {
546 if !payout_receipt
547 .verify_signature()
548 .map_err(|error| ReceiptStoreError::Canonical(error.to_string()))?
549 {
550 return Err(ReceiptStoreError::Conflict(
551 "liability claim payout receipt signature verification failed".to_string(),
552 ));
553 }
554 payout_receipt
555 .body
556 .validate()
557 .map_err(ReceiptStoreError::Conflict)?;
558
559 let artifact = &payout_receipt.body;
560 let mut connection = self.connection()?;
561 let tx = connection.transaction()?;
562 let existing = tx
563 .query_row(
564 "SELECT payout_receipt_id
565 FROM liability_claim_payout_receipts
566 WHERE payout_receipt_id = ?1",
567 params![artifact.payout_receipt_id],
568 |row| row.get::<_, String>(0),
569 )
570 .optional()?;
571 if existing.is_some() {
572 return Err(ReceiptStoreError::Conflict(format!(
573 "liability claim payout receipt `{}` already exists",
574 artifact.payout_receipt_id
575 )));
576 }
577
578 let stored_instruction_raw_json = tx
579 .query_row(
580 "SELECT raw_json
581 FROM liability_claim_payout_instructions
582 WHERE payout_instruction_id = ?1",
583 params![artifact.payout_instruction.body.payout_instruction_id],
584 |row| row.get::<_, String>(0),
585 )
586 .optional()?
587 .ok_or_else(|| {
588 ReceiptStoreError::NotFound(format!(
589 "liability claim payout instruction `{}` not found",
590 artifact.payout_instruction.body.payout_instruction_id
591 ))
592 })?;
593 let stored_instruction: SignedLiabilityClaimPayoutInstruction =
594 serde_json::from_str(&stored_instruction_raw_json)?;
595 if stored_instruction.body != artifact.payout_instruction.body {
596 return Err(ReceiptStoreError::Conflict(
597 "liability claim payout receipt payout_instruction does not match the persisted payout instruction"
598 .to_string(),
599 ));
600 }
601
602 let claim_id = artifact
603 .payout_instruction
604 .body
605 .adjudication
606 .body
607 .dispute
608 .body
609 .provider_response
610 .body
611 .claim
612 .body
613 .claim_id
614 .clone();
615
616 tx.execute(
617 "INSERT INTO liability_claim_payout_receipts (
618 payout_receipt_id, issued_at, claim_id, payout_instruction_id,
619 reconciliation_state, raw_json, signer_key, signature
620 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
621 params![
622 artifact.payout_receipt_id,
623 artifact.issued_at as i64,
624 claim_id,
625 artifact.payout_instruction.body.payout_instruction_id,
626 serde_json::to_string(&artifact.reconciliation_state)?,
627 serde_json::to_string(payout_receipt)?,
628 payout_receipt.signer_key.to_hex(),
629 payout_receipt.signature.to_hex(),
630 ],
631 )?;
632
633 tx.commit()?;
634 Ok(())
635 }
636
637 pub fn record_liability_claim_settlement_instruction(
638 &mut self,
639 settlement_instruction: &SignedLiabilityClaimSettlementInstruction,
640 ) -> Result<(), ReceiptStoreError> {
641 if !settlement_instruction
642 .verify_signature()
643 .map_err(|error| ReceiptStoreError::Canonical(error.to_string()))?
644 {
645 return Err(ReceiptStoreError::Conflict(
646 "liability claim settlement instruction signature verification failed".to_string(),
647 ));
648 }
649 settlement_instruction
650 .body
651 .validate()
652 .map_err(ReceiptStoreError::Conflict)?;
653
654 let artifact = &settlement_instruction.body;
655 let mut connection = self.connection()?;
656 let tx = connection.transaction()?;
657 let existing = tx
658 .query_row(
659 "SELECT settlement_instruction_id
660 FROM liability_claim_settlement_instructions
661 WHERE settlement_instruction_id = ?1",
662 params![artifact.settlement_instruction_id],
663 |row| row.get::<_, String>(0),
664 )
665 .optional()?;
666 if existing.is_some() {
667 return Err(ReceiptStoreError::Conflict(format!(
668 "liability claim settlement instruction `{}` already exists",
669 artifact.settlement_instruction_id
670 )));
671 }
672
673 let stored_payout_receipt_raw_json = tx
674 .query_row(
675 "SELECT raw_json
676 FROM liability_claim_payout_receipts
677 WHERE payout_receipt_id = ?1",
678 params![artifact.payout_receipt.body.payout_receipt_id],
679 |row| row.get::<_, String>(0),
680 )
681 .optional()?
682 .ok_or_else(|| {
683 ReceiptStoreError::NotFound(format!(
684 "liability claim payout receipt `{}` not found",
685 artifact.payout_receipt.body.payout_receipt_id
686 ))
687 })?;
688 let stored_payout_receipt: SignedLiabilityClaimPayoutReceipt =
689 serde_json::from_str(&stored_payout_receipt_raw_json)?;
690 if stored_payout_receipt.body != artifact.payout_receipt.body {
691 return Err(ReceiptStoreError::Conflict(
692 "liability claim settlement instruction payout_receipt does not match the persisted payout receipt"
693 .to_string(),
694 ));
695 }
696
697 let claim_id = artifact
698 .payout_receipt
699 .body
700 .payout_instruction
701 .body
702 .adjudication
703 .body
704 .dispute
705 .body
706 .provider_response
707 .body
708 .claim
709 .body
710 .claim_id
711 .clone();
712
713 tx.execute(
714 "INSERT INTO liability_claim_settlement_instructions (
715 settlement_instruction_id, issued_at, claim_id, payout_receipt_id,
716 settlement_kind, payer_role, payer_id, payee_role, payee_id,
717 settlement_amount_units, settlement_amount_currency,
718 raw_json, signer_key, signature
719 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)",
720 params![
721 artifact.settlement_instruction_id,
722 artifact.issued_at as i64,
723 claim_id,
724 artifact.payout_receipt.body.payout_receipt_id,
725 serde_json::to_string(&artifact.settlement_kind)?,
726 serde_json::to_string(&artifact.topology.payer.role)?,
727 artifact.topology.payer.party_id,
728 serde_json::to_string(&artifact.topology.payee.role)?,
729 artifact.topology.payee.party_id,
730 artifact.settlement_amount.units as i64,
731 artifact.settlement_amount.currency,
732 serde_json::to_string(settlement_instruction)?,
733 settlement_instruction.signer_key.to_hex(),
734 settlement_instruction.signature.to_hex(),
735 ],
736 )?;
737
738 tx.commit()?;
739 Ok(())
740 }
741
742 pub fn record_liability_claim_settlement_receipt(
743 &mut self,
744 settlement_receipt: &SignedLiabilityClaimSettlementReceipt,
745 ) -> Result<(), ReceiptStoreError> {
746 if !settlement_receipt
747 .verify_signature()
748 .map_err(|error| ReceiptStoreError::Canonical(error.to_string()))?
749 {
750 return Err(ReceiptStoreError::Conflict(
751 "liability claim settlement receipt signature verification failed".to_string(),
752 ));
753 }
754 settlement_receipt
755 .body
756 .validate()
757 .map_err(ReceiptStoreError::Conflict)?;
758
759 let artifact = &settlement_receipt.body;
760 let mut connection = self.connection()?;
761 let tx = connection.transaction()?;
762 let existing = tx
763 .query_row(
764 "SELECT settlement_receipt_id
765 FROM liability_claim_settlement_receipts
766 WHERE settlement_receipt_id = ?1",
767 params![artifact.settlement_receipt_id],
768 |row| row.get::<_, String>(0),
769 )
770 .optional()?;
771 if existing.is_some() {
772 return Err(ReceiptStoreError::Conflict(format!(
773 "liability claim settlement receipt `{}` already exists",
774 artifact.settlement_receipt_id
775 )));
776 }
777
778 let stored_instruction_raw_json = tx
779 .query_row(
780 "SELECT raw_json
781 FROM liability_claim_settlement_instructions
782 WHERE settlement_instruction_id = ?1",
783 params![
784 artifact
785 .settlement_instruction
786 .body
787 .settlement_instruction_id
788 ],
789 |row| row.get::<_, String>(0),
790 )
791 .optional()?
792 .ok_or_else(|| {
793 ReceiptStoreError::NotFound(format!(
794 "liability claim settlement instruction `{}` not found",
795 artifact
796 .settlement_instruction
797 .body
798 .settlement_instruction_id
799 ))
800 })?;
801 let stored_instruction: SignedLiabilityClaimSettlementInstruction =
802 serde_json::from_str(&stored_instruction_raw_json)?;
803 if stored_instruction.body != artifact.settlement_instruction.body {
804 return Err(ReceiptStoreError::Conflict(
805 "liability claim settlement receipt settlement_instruction does not match the persisted settlement instruction"
806 .to_string(),
807 ));
808 }
809
810 let claim_id = artifact
811 .settlement_instruction
812 .body
813 .payout_receipt
814 .body
815 .payout_instruction
816 .body
817 .adjudication
818 .body
819 .dispute
820 .body
821 .provider_response
822 .body
823 .claim
824 .body
825 .claim_id
826 .clone();
827
828 tx.execute(
829 "INSERT INTO liability_claim_settlement_receipts (
830 settlement_receipt_id, issued_at, claim_id, settlement_instruction_id,
831 reconciliation_state, raw_json, signer_key, signature
832 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
833 params![
834 artifact.settlement_receipt_id,
835 artifact.issued_at as i64,
836 claim_id,
837 artifact
838 .settlement_instruction
839 .body
840 .settlement_instruction_id,
841 serde_json::to_string(&artifact.reconciliation_state)?,
842 serde_json::to_string(settlement_receipt)?,
843 settlement_receipt.signer_key.to_hex(),
844 settlement_receipt.signature.to_hex(),
845 ],
846 )?;
847
848 tx.commit()?;
849 Ok(())
850 }
851
852 pub fn query_liability_claim_workflows(
853 &self,
854 query: &LiabilityClaimWorkflowQuery,
855 ) -> Result<LiabilityClaimWorkflowReport, ReceiptStoreError> {
856 let normalized = query.normalized();
857 let connection = self.connection()?;
858 let mut statement = connection.prepare(
859 "SELECT raw_json
860 FROM liability_claim_packages
861 ORDER BY issued_at DESC, claim_id DESC",
862 )?;
863 let rows = statement.query_map([], |row| row.get::<_, String>(0))?;
864
865 let mut matching_claims = 0_u64;
866 let mut provider_responses = 0_u64;
867 let mut accepted_responses = 0_u64;
868 let mut denied_responses = 0_u64;
869 let mut disputes = 0_u64;
870 let mut adjudications = 0_u64;
871 let mut payout_instructions = 0_u64;
872 let mut payout_receipts = 0_u64;
873 let mut matched_payout_receipts = 0_u64;
874 let mut mismatched_payout_receipts = 0_u64;
875 let mut settlement_instructions = 0_u64;
876 let mut settlement_receipts = 0_u64;
877 let mut matched_settlement_receipts = 0_u64;
878 let mut mismatched_settlement_receipts = 0_u64;
879 let mut counterparty_mismatch_settlement_receipts = 0_u64;
880 let mut claims = Vec::new();
881
882 for row in rows {
883 let raw_json = row?;
884 let claim: SignedLiabilityClaimPackage = serde_json::from_str(&raw_json)?;
885 if !liability_claim_workflow_matches_query(&claim, &normalized) {
886 continue;
887 }
888 matching_claims += 1;
889
890 let provider_response = self
891 .connection()?
892 .query_row(
893 "SELECT raw_json
894 FROM liability_claim_responses
895 WHERE claim_id = ?1
896 ORDER BY issued_at DESC, claim_response_id DESC
897 LIMIT 1",
898 params![claim.body.claim_id],
899 |row| row.get::<_, String>(0),
900 )
901 .optional()?
902 .map(|raw_json| serde_json::from_str::<SignedLiabilityClaimResponse>(&raw_json))
903 .transpose()?;
904 if let Some(response) = provider_response.as_ref() {
905 provider_responses += 1;
906 match response.body.disposition {
907 LiabilityClaimResponseDisposition::Accepted => accepted_responses += 1,
908 LiabilityClaimResponseDisposition::Denied => denied_responses += 1,
909 LiabilityClaimResponseDisposition::Acknowledged => {}
910 }
911 }
912
913 let dispute = self
914 .connection()?
915 .query_row(
916 "SELECT raw_json
917 FROM liability_claim_disputes
918 WHERE claim_id = ?1
919 ORDER BY issued_at DESC, dispute_id DESC
920 LIMIT 1",
921 params![claim.body.claim_id],
922 |row| row.get::<_, String>(0),
923 )
924 .optional()?
925 .map(|raw_json| serde_json::from_str::<SignedLiabilityClaimDispute>(&raw_json))
926 .transpose()?;
927 if dispute.is_some() {
928 disputes += 1;
929 }
930
931 let adjudication = self
932 .connection()?
933 .query_row(
934 "SELECT raw_json
935 FROM liability_claim_adjudications
936 WHERE claim_id = ?1
937 ORDER BY issued_at DESC, adjudication_id DESC
938 LIMIT 1",
939 params![claim.body.claim_id],
940 |row| row.get::<_, String>(0),
941 )
942 .optional()?
943 .map(|raw_json| serde_json::from_str::<SignedLiabilityClaimAdjudication>(&raw_json))
944 .transpose()?;
945 if adjudication.is_some() {
946 adjudications += 1;
947 }
948
949 let payout_instruction = self
950 .connection()?
951 .query_row(
952 "SELECT raw_json
953 FROM liability_claim_payout_instructions
954 WHERE claim_id = ?1
955 ORDER BY issued_at DESC, payout_instruction_id DESC
956 LIMIT 1",
957 params![claim.body.claim_id],
958 |row| row.get::<_, String>(0),
959 )
960 .optional()?
961 .map(|raw_json| {
962 serde_json::from_str::<SignedLiabilityClaimPayoutInstruction>(&raw_json)
963 })
964 .transpose()?;
965 if payout_instruction.is_some() {
966 payout_instructions += 1;
967 }
968
969 let payout_receipt = self
970 .connection()?
971 .query_row(
972 "SELECT raw_json
973 FROM liability_claim_payout_receipts
974 WHERE claim_id = ?1
975 ORDER BY issued_at DESC, payout_receipt_id DESC
976 LIMIT 1",
977 params![claim.body.claim_id],
978 |row| row.get::<_, String>(0),
979 )
980 .optional()?
981 .map(|raw_json| {
982 serde_json::from_str::<SignedLiabilityClaimPayoutReceipt>(&raw_json)
983 })
984 .transpose()?;
985 if let Some(receipt) = payout_receipt.as_ref() {
986 payout_receipts += 1;
987 match receipt.body.reconciliation_state {
988 LiabilityClaimPayoutReconciliationState::Matched => {
989 matched_payout_receipts += 1;
990 }
991 LiabilityClaimPayoutReconciliationState::AmountMismatch => {
992 mismatched_payout_receipts += 1;
993 }
994 }
995 }
996
997 let settlement_instruction = self
998 .connection()?
999 .query_row(
1000 "SELECT raw_json
1001 FROM liability_claim_settlement_instructions
1002 WHERE claim_id = ?1
1003 ORDER BY issued_at DESC, settlement_instruction_id DESC
1004 LIMIT 1",
1005 params![claim.body.claim_id],
1006 |row| row.get::<_, String>(0),
1007 )
1008 .optional()?
1009 .map(|raw_json| {
1010 serde_json::from_str::<SignedLiabilityClaimSettlementInstruction>(&raw_json)
1011 })
1012 .transpose()?;
1013 if settlement_instruction.is_some() {
1014 settlement_instructions += 1;
1015 }
1016
1017 let settlement_receipt = self
1018 .connection()?
1019 .query_row(
1020 "SELECT raw_json
1021 FROM liability_claim_settlement_receipts
1022 WHERE claim_id = ?1
1023 ORDER BY issued_at DESC, settlement_receipt_id DESC
1024 LIMIT 1",
1025 params![claim.body.claim_id],
1026 |row| row.get::<_, String>(0),
1027 )
1028 .optional()?
1029 .map(|raw_json| {
1030 serde_json::from_str::<SignedLiabilityClaimSettlementReceipt>(&raw_json)
1031 })
1032 .transpose()?;
1033 if let Some(receipt) = settlement_receipt.as_ref() {
1034 settlement_receipts += 1;
1035 match receipt.body.reconciliation_state {
1036 LiabilityClaimSettlementReconciliationState::Matched => {
1037 matched_settlement_receipts += 1;
1038 }
1039 LiabilityClaimSettlementReconciliationState::AmountMismatch => {
1040 mismatched_settlement_receipts += 1;
1041 }
1042 LiabilityClaimSettlementReconciliationState::CounterpartyMismatch => {
1043 counterparty_mismatch_settlement_receipts += 1;
1044 }
1045 }
1046 }
1047
1048 if claims.len() < normalized.limit_or_default() {
1049 claims.push(LiabilityClaimWorkflowRow {
1050 claim,
1051 provider_response,
1052 dispute,
1053 adjudication,
1054 payout_instruction,
1055 payout_receipt,
1056 settlement_instruction,
1057 settlement_receipt,
1058 });
1059 }
1060 }
1061
1062 Ok(LiabilityClaimWorkflowReport {
1063 schema: LIABILITY_CLAIM_WORKFLOW_REPORT_SCHEMA.to_string(),
1064 generated_at: unix_now(),
1065 query: normalized,
1066 summary: LiabilityClaimWorkflowSummary {
1067 matching_claims,
1068 returned_claims: claims.len() as u64,
1069 provider_responses,
1070 accepted_responses,
1071 denied_responses,
1072 disputes,
1073 adjudications,
1074 payout_instructions,
1075 payout_receipts,
1076 matched_payout_receipts,
1077 mismatched_payout_receipts,
1078 settlement_instructions,
1079 settlement_receipts,
1080 matched_settlement_receipts,
1081 mismatched_settlement_receipts,
1082 counterparty_mismatch_settlement_receipts,
1083 },
1084 claims,
1085 })
1086 }
1087}