|
2 | 2 | theme: [dashboard, air] |
3 | 3 | title: Get analyses by requirements |
4 | 4 | toc: false |
5 | | -sql: |
6 | | - fed_types: ./data/federated-types.db |
7 | 5 | --- |
| 6 | + |
8 | 7 | ```js |
9 | | -import tex from "npm:@observablehq/tex"; |
10 | | -import {sql} from "npm:@observablehq/duckdb"; |
| 8 | +import { Statistic, Algorithm } from "./components/display_statistic.js" |
11 | 9 | ``` |
12 | 10 |
|
13 | 11 | # Available analyses |
14 | 12 |
|
15 | 13 | ## TRE requirements |
16 | 14 |
|
17 | | -<!-- Load analyses --> |
| 15 | +<!-- Load data --> |
18 | 16 | ```js |
19 | | -const analyses = FileAttachment("data/analyses.json").json(); |
| 17 | +const statistics = FileAttachment("./data/statistics.csv").csv(); |
| 18 | +const algorithms = FileAttachment("./data/algorithms.csv").csv(); |
| 19 | +const aliases = FileAttachment("./data/statistic_aliases.csv").csv(); |
| 20 | +const observableData = FileAttachment("./data/observable_data.csv").csv(); |
| 21 | +const statisticRelationships = FileAttachment("./data/statistic_relationships.csv").csv(); |
| 22 | +const statbarns = FileAttachment("./data/statbarns.csv").csv(); |
20 | 23 | ``` |
21 | 24 |
|
22 | | -<!-- Define filter form --> |
| 25 | +<!-- Old filter form |
23 | 26 | ```js |
24 | 27 | const filters = view(Inputs.form({ |
25 | 28 | // Trust requirements |
@@ -83,267 +86,33 @@ const filters = view(Inputs.form({ |
83 | 86 | } |
84 | 87 | ) |
85 | 88 | })); |
86 | | -``` |
87 | | - |
88 | | -```sql |
89 | | --- I've tried filtering by separability but duckdb and observable don't do arrays well |
90 | | --- I'll just have to do it in js |
91 | | -SELECT * |
92 | | -FROM fed_types.algorithms a |
93 | | -WHERE (a.requires_persistence = ${filters.persistence_capable} OR a.requires_persistence = false) |
94 | | -AND (a.requires_branching = ${filters.branching_capable} OR a.requires_branching = false); |
95 | | -``` |
96 | | - |
97 | | - |
98 | | -<!-- Filter function |
99 | | -```js |
100 | | -function filterAnalyses(analyses, filters) { |
101 | | - return analyses |
102 | | - .map(analysis => { |
103 | | - // Filter algorithms within each analysis |
104 | | - const compatibleAlgorithms = analysis.algorithms.filter(algo => { |
105 | | - // Trust level check |
106 | | - if (filters.trust_level === "Aggregate data only") { |
107 | | - const hasRowLevel = algo.trust_requirements.aggregator.some(req => |
108 | | - req.toLowerCase().includes("row-level") |
109 | | - ); |
110 | | - if (hasRowLevel) return false; |
111 | | - } |
112 | | - |
113 | | - // Communication rounds check |
114 | | - if (filters.communication_rounds === "One round only") { |
115 | | - if (algo.communication.rounds !== 1) return false; |
116 | | - } |
117 | | - |
118 | | - // Execution model check |
119 | | - if (!filters.execution_models.includes(algo.computation.execution_model)) { |
120 | | - return false; |
121 | | - } |
122 | | - |
123 | | - // Persistent executors check |
124 | | - if (filters.persistent_executors === "Not required") { |
125 | | - if (algo.computation.persistent_executors === true) return false; |
126 | | - } |
127 | | - |
128 | | - // Decomposability check |
129 | | - if (!filters.decomposability.includes(algo.decomposability)) { |
130 | | - return false; |
131 | | - } |
132 | | - |
133 | | - // Privacy methods check (if any are selected, algorithm must support at least one) |
134 | | - if (filters.privacy_methods.length > 0) { |
135 | | - const algoEncryption = algo.privacy_methods?.encryption || []; |
136 | | - const hasDp = filters.privacy_methods.includes("Differential Privacy") && |
137 | | - algo.privacy_methods?.differential_privacy; |
138 | | - const hasHe = filters.privacy_methods.includes("Homomorphic Encryption") && |
139 | | - algoEncryption.some(e => e.includes("HE")); |
140 | | - const hasMpc = filters.privacy_methods.includes("Secure MPC") && |
141 | | - algoEncryption.some(e => e.includes("MPC")); |
142 | | - |
143 | | - if (!hasDp && !hasHe && !hasMpc) return false; |
144 | | - } |
145 | | - |
146 | | - return true; |
147 | | - }); |
148 | | - |
149 | | - // Return analysis with filtered algorithms, or null if none compatible |
150 | | - if (compatibleAlgorithms.length === 0) return null; |
151 | | - |
152 | | - return { |
153 | | - ...analysis, |
154 | | - algorithms: compatibleAlgorithms, |
155 | | - algorithmCount: compatibleAlgorithms.length |
156 | | - }; |
157 | | - }) |
158 | | - .filter(a => a !== null) |
159 | | - .filter(a => filters.output_types.includes(a.output.data_type)); |
160 | | -} |
161 | | -``` |
162 | | -
|
163 | | -<!-- Apply filters |
164 | | -```js |
165 | | -const filteredAnalyses = filterAnalyses(analyses, filters); |
166 | | -``` |
167 | | -
|
168 | | -<div style= padding: 1rem; border-radius: 0.5rem; margin: 1rem 0;"> |
169 | | - <h3 style="margin-top: 0;">Summary</h3> |
170 | | - <p><strong>${filteredAnalyses.length}</strong> of <strong>${analyses.length}</strong> analyses are compatible with your capabilities</p> |
171 | | - <p><strong>${filteredAnalyses.reduce((sum, a) => sum + a.algorithmCount, 0)}</strong> total compatible algorithms</p> |
172 | | -</div> |
| 89 | +``` --> |
173 | 90 |
|
174 | | -<!-- Display results table |
175 | 91 | ```js |
176 | | -Inputs.table(filteredAnalyses, { |
177 | | - columns: [ |
178 | | - "name", |
179 | | - "output", |
180 | | - "algorithmCount" |
181 | | - ], |
182 | | - header: { |
183 | | - name: "Analysis", |
184 | | - output: "Output", |
185 | | - algorithmCount: "Compatible Algorithms" |
186 | | - }, |
187 | | - format: { |
188 | | - output: d => `${d.data_type} - ${d.description}`, |
189 | | - algorithmCount: d => d |
190 | | - }, |
191 | | - width: { |
192 | | - name: "25%", |
193 | | - output: "60%", |
194 | | - algorithmCount: "15%" |
195 | | - } |
196 | | -}) |
| 92 | +const filters = Inputs.form() |
197 | 93 | ``` |
198 | 94 |
|
199 | 95 | ```js |
200 | | -import tex from "npm:@observablehq/tex"; |
201 | | -
|
202 | | -display(html`<div style="margin-top: 2rem;"> |
203 | | - <h2>Detailed Compatibility</h2> |
204 | | - ${filteredAnalyses.map(analysis => html` |
205 | | - <details style=" |
206 | | - border: 1px solid #e5e7eb; |
207 | | - border-radius: 0.5rem; |
208 | | - padding: 1rem; |
209 | | - margin: 1rem 0; |
210 | | - background: white; |
211 | | - "> |
212 | | - <summary style=" |
213 | | - cursor: pointer; |
214 | | - font-weight: 600; |
215 | | - font-size: 1.1rem; |
216 | | - user-select: none; |
217 | | - "> |
218 | | - ${analysis.mathjax ? tex`${analysis.mathjax}` : analysis.name} |
219 | | - <span style=" |
220 | | - color: #6b7280; |
221 | | - font-weight: normal; |
222 | | - font-size: 0.9rem; |
223 | | - "> — ${analysis.algorithmCount} algorithm(s)</span> |
224 | | - </summary> |
225 | | - |
226 | | - <div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #e5e7eb;"> |
227 | | - <p><strong>Description:</strong> ${analysis.description}</p> |
228 | | - <p><strong>Output:</strong> ${analysis.output.data_type} — ${analysis.output.description}</p> |
229 | | - |
230 | | - <h4>Compatible Algorithms:</h4> |
231 | | - ${analysis.algorithms.map(algo => html` |
232 | | - <div style=" |
233 | | - background: #f9fafb; |
234 | | - padding: 1rem; |
235 | | - margin: 0.5rem 0; |
236 | | - border-radius: 0.375rem; |
237 | | - border-left: 3px solid ${getDecomposabilityColor(algo.decomposability)}; |
238 | | - "> |
239 | | - <div style="display: flex; justify-content: space-between; align-items: start;"> |
240 | | - <h5 style="margin: 0 0 0.5rem 0;">${algo.name}</h5> |
241 | | - <span style=" |
242 | | - background: ${getDecomposabilityColor(algo.decomposability)}; |
243 | | - color: white; |
244 | | - padding: 0.25rem 0.5rem; |
245 | | - border-radius: 0.25rem; |
246 | | - font-size: 0.75rem; |
247 | | - font-weight: 600; |
248 | | - "> |
249 | | - ${algo.decomposability} |
250 | | - </span> |
251 | | - </div> |
252 | | - |
253 | | - ${algo.description ? html`<p style="color: #6b7280; margin: 0.5rem 0;">${algo.description}</p>` : ''} |
254 | | - ${algo.mathjax ? html`<div style="margin: 0.5rem 0; padding: 0.5rem; background: white; border-radius: 0.25rem;">${tex`${algo.mathjax}`}</div>` : ''} |
255 | | - |
256 | | - <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 0.75rem; font-size: 0.875rem;"> |
257 | | - <div> |
258 | | - <strong>Trust requirements:</strong> |
259 | | - <ul style="margin: 0.25rem 0; padding-left: 1.5rem;"> |
260 | | - ${algo.trust_requirements.aggregator.map(req => html`<li>${req}</li>`)} |
261 | | - </ul> |
262 | | - </div> |
263 | | - |
264 | | - <div> |
265 | | - <strong>Communication:</strong> |
266 | | - <ul style="margin: 0.25rem 0; padding-left: 1.5rem;"> |
267 | | - <li>Rounds: ${algo.communication.rounds}</li> |
268 | | - <li>Direction: ${algo.communication.direction}</li> |
269 | | - </ul> |
270 | | - </div> |
271 | | - |
272 | | - <div> |
273 | | - <strong>Computation:</strong> |
274 | | - <ul style="margin: 0.25rem 0; padding-left: 1.5rem;"> |
275 | | - <li>Model: ${algo.computation.execution_model}</li> |
276 | | - <li>Persistent: ${algo.computation.persistent_executors ? 'Yes' : 'No'}</li> |
277 | | - </ul> |
278 | | - </div> |
279 | | - |
280 | | - ${algo.privacy_methods ? html` |
281 | | - <div> |
282 | | - <strong>Privacy methods:</strong> |
283 | | - <ul style="margin: 0.25rem 0; padding-left: 1.5rem;"> |
284 | | - ${algo.privacy_methods.differential_privacy ? html`<li>DP: ${algo.privacy_methods.differential_privacy}</li>` : ''} |
285 | | - ${algo.privacy_methods.encryption?.length > 0 ? html`<li>Encryption: ${algo.privacy_methods.encryption.join(', ')}</li>` : ''} |
286 | | - </ul> |
287 | | - </div> |
288 | | - ` : ''} |
289 | | - </div> |
290 | | - |
291 | | - ${algo.performance ? html` |
292 | | - <div style="margin-top: 0.5rem;"> |
293 | | - <strong>Performance:</strong> |
294 | | - <span style=" |
295 | | - background: ${getPerformanceColor(algo.performance)}; |
296 | | - padding: 0.125rem 0.5rem; |
297 | | - border-radius: 0.25rem; |
298 | | - font-size: 0.875rem; |
299 | | - ">${algo.performance}</span> |
300 | | - </div> |
301 | | - ` : ''} |
302 | | - |
303 | | - ${algo.practical_notes?.length > 0 ? html` |
304 | | - <div style=" |
305 | | - margin-top: 0.75rem; |
306 | | - padding: 0.5rem; |
307 | | - background: #fef3c7; |
308 | | - border-radius: 0.25rem; |
309 | | - font-size: 0.875rem; |
310 | | - "> |
311 | | - <strong>Notes:</strong> |
312 | | - <ul style="margin: 0.25rem 0; padding-left: 1.5rem;"> |
313 | | - ${algo.practical_notes.map(note => html`<li>${note}</li>`)} |
314 | | - </ul> |
315 | | - </div> |
316 | | - ` : ''} |
317 | | - </div> |
318 | | - `)} |
319 | | - </div> |
320 | | - </details> |
321 | | - `)} |
322 | | -</div>`); |
323 | | -``` |
| 96 | +const algorithmList = algorithms.map( |
| 97 | + d => { |
| 98 | + const algo = new Algorithm(d); |
| 99 | + algo.addObservables(observableData); |
| 100 | + return algo |
| 101 | + } |
| 102 | +); |
324 | 103 |
|
325 | | -<!-- Helper functions for colors --> |
326 | | -```js |
327 | | -function getDecomposabilityColor(decomp) { |
328 | | - const colors = { |
329 | | - "fully-decomposable": "#10b981", |
330 | | - "approximately-decomposable": "#3b82f6", |
331 | | - "iteratively-decomposable": "#f59e0b", |
332 | | - "non-decomposable": "#ef4444" |
333 | | - }; |
334 | | - return colors[decomp] || "#6b7280"; |
335 | | -} |
| 104 | +const statisticsList = statistics.map( |
| 105 | + d => { |
| 106 | + const stat = new Statistic(d); |
| 107 | + stat.addAliases(aliases); |
| 108 | + stat.addAlgorithms(algorithmList); |
| 109 | + if (stat.algorithms.length > 0) { |
| 110 | + return stat |
| 111 | + } |
| 112 | + } |
| 113 | +); |
336 | 114 | ``` |
337 | 115 |
|
338 | 116 | ```js |
339 | | -function getPerformanceColor(perf) { |
340 | | - const colors = { |
341 | | - "very fast": "#d1fae5", |
342 | | - "fast": "#a7f3d0", |
343 | | - "moderate": "#fef3c7", |
344 | | - "slow": "#fed7aa", |
345 | | - "very slow": "#fecaca" |
346 | | - }; |
347 | | - return colors[perf] || "#f3f4f6"; |
348 | | -} |
| 117 | +html`${statisticsList.map(stat => stat ? stat.display(): "")}` |
349 | 118 | ``` |
0 commit comments