qualia_client_core/wellfair/api/
agency.rs1use super::super::journal::JournalEntry;
4use qualia_cooperative_core::agency_delegation::{
5 agency_delegation_full_json, build_agency_delegation_envelope, delegation_permits,
6 parse_agency_delegation, AccessDecision, AccessRequest, AgencyDelegation, ConsentState,
7 Precedence,
8};
9use qualia_cooperative_core::agency_domain::agency_domain_taxonomy;
10use qualia_cooperative_core::taxonomy::Sphere;
11use qualia_cooperative_core::trigger::TriggerContext;
12use wellfare_core::assessment::{
13 assessment_summary, build_assessment_envelope, instrument, instrument_dto, instruments,
14 parse_assessment, score, AssessmentResult, InstrumentDto,
15};
16
17use super::*;
18
19impl WebizenHostApi {
20 pub fn add_agency_delegation(
31 &mut self,
32 delegation: &AgencyDelegation,
33 ) -> Result<JournalEntry, String> {
34 let asserted = Self::now_unix() as u32;
35 let envelope = build_agency_delegation_envelope(
36 delegation,
37 &self.owner_did,
38 &self.author_did,
39 asserted,
40 );
41 let summary = agency_delegation_full_json(delegation);
42 self.submit_record_with_summary(
43 QAPP_COOPERATIVE,
44 envelope,
45 SOURCE_COOPERATIVE,
46 Some(summary),
47 )?;
48 self.finalize_batch().ok();
49 self.list_journal_by_kind("agency_delegation", 1)?
50 .into_iter()
51 .next()
52 .ok_or_else(|| "agency delegation committed but journal empty".into())
53 }
54
55 #[allow(clippy::too_many_arguments)]
59 pub fn create_agency_delegation(
60 &mut self,
61 principal_did: &str,
62 domain: &str,
63 values_anchor: &str,
64 agent_dids: Vec<String>,
65 precedence: &str,
66 consent: &str,
67 ) -> Result<AgencyDelegation, String> {
68 if agency_domain_taxonomy().get(domain).is_none() {
69 return Err(format!("unknown domain of agency: {domain}"));
70 }
71 let anchor = if values_anchor.trim().is_empty() {
72 "urn:un:hr:udhr"
73 } else {
74 values_anchor
75 };
76 let mut d = AgencyDelegation::new(principal_did, domain, anchor, Self::now_unix() as u32);
77 d.agent_dids = agent_dids
78 .into_iter()
79 .map(|s| s.trim().to_string())
80 .filter(|s| !s.is_empty())
81 .collect();
82 d.precedence = match precedence {
83 "secondary" => Precedence::Secondary,
84 "local_temporary" => Precedence::LocalTemporary,
85 _ => Precedence::Primary,
86 };
87 d.consent = agency_consent_from_str(consent).unwrap_or(ConsentState::Pending);
88 self.add_agency_delegation(&d)?;
89 Ok(d)
90 }
91
92 pub fn list_agency_delegations(&self, limit: usize) -> Result<Vec<AgencyDelegation>, String> {
98 use std::collections::HashSet;
99 let entries = self.list_journal_by_kind("agency_delegation", limit)?;
100 let mut seen: HashSet<String> = HashSet::new();
101 let mut out: Vec<AgencyDelegation> = Vec::new();
102 for e in entries {
103 let Some(summary) = e.summary.as_deref() else {
104 continue;
105 };
106 let Some(d) = parse_agency_delegation(summary) else {
107 continue;
108 };
109 if seen.insert(d.id.clone()) {
110 out.push(d); }
112 }
113 out.sort_by(|a, b| {
114 a.valid_from_unix
115 .cmp(&b.valid_from_unix)
116 .then_with(|| a.id.cmp(&b.id))
117 });
118 Ok(out)
119 }
120
121 pub fn get_agency_delegation(&self, delegation_id: &str) -> Result<AgencyDelegation, String> {
123 self.list_agency_delegations(512)?
124 .into_iter()
125 .find(|d| d.id == delegation_id)
126 .ok_or_else(|| format!("agency delegation '{delegation_id}' not found"))
127 }
128
129 pub fn set_agency_delegation_consent(
131 &mut self,
132 delegation_id: &str,
133 consent: ConsentState,
134 ) -> Result<JournalEntry, String> {
135 let mut d = self.get_agency_delegation(delegation_id)?;
136 d.consent = consent;
137 self.add_agency_delegation(&d)
138 }
139
140 pub fn revoke_agency_delegation(
142 &mut self,
143 delegation_id: &str,
144 ) -> Result<JournalEntry, String> {
145 let mut d = self.get_agency_delegation(delegation_id)?;
146 d.revoked = true;
147 self.add_agency_delegation(&d)
148 }
149
150 pub fn list_agency_domains(&self) -> Vec<AgencyDomainInfo> {
153 let tax = agency_domain_taxonomy();
154 tax.all()
155 .iter()
156 .filter(|t| t.category.is_some())
157 .map(|t| AgencyDomainInfo {
158 id: t.id.clone(),
159 label: t.label.clone(),
160 category: t.category.clone(),
161 description: t.description.clone(),
162 consequential: t.attr("consequential") == Some("true"),
163 selfhood: t.sphere() == Sphere::Selfhood,
164 })
165 .collect()
166 }
167
168 pub fn evaluate_agency_access(
174 &self,
175 delegation_id: &str,
176 action: &str,
177 data_class: &str,
178 ) -> Result<AccessDecision, String> {
179 let d = self.get_agency_delegation(delegation_id)?;
180 let tax = agency_domain_taxonomy();
181 let sphere = match tax.get(&d.domain).map(|t| t.sphere()) {
182 Some(Sphere::Selfhood) => Sphere::Selfhood,
183 _ => Sphere::Personhood,
184 };
185 let request = AccessRequest {
186 domain: d.domain.clone(),
187 data_class: data_class.to_string(),
188 action: action.to_string(),
189 sphere,
190 jurisdiction: None,
191 provenance: None,
192 };
193 let ctx = TriggerContext::at(Self::now_unix() as u32);
194 Ok(delegation_permits(&d, &tax, &request, &ctx))
195 }
196
197 pub fn list_assessment_instruments(&self) -> Vec<InstrumentDto> {
204 instruments().into_iter().map(instrument_dto).collect()
205 }
206
207 pub fn record_assessment(
211 &mut self,
212 instrument_id: &str,
213 responses: Vec<u8>,
214 ) -> Result<AssessmentResult, String> {
215 let inst = instrument(instrument_id)
216 .ok_or_else(|| format!("unknown assessment instrument: {instrument_id}"))?;
217 let now = Self::now_unix() as u32;
218 let result = score(inst, &responses, now)?;
219 let envelope = build_assessment_envelope(&result, &self.owner_did, &self.author_did, now);
220 let summary = assessment_summary(&result);
221 self.submit_record_with_summary(QAPP_WELLBEING, envelope, SOURCE_WELLBEING, Some(summary))?;
222 self.finalize_batch().ok();
223 Ok(result)
224 }
225
226 pub fn list_assessments(&self, limit: usize) -> Result<Vec<AssessmentResult>, String> {
228 let entries = self.list_journal_by_kind("wellbeing_assessment", limit)?;
229 Ok(entries
230 .iter()
231 .filter_map(|e| e.summary.as_deref().and_then(parse_assessment))
232 .collect())
233 }
234}