--- title: Third-Party Services (TPSC) --- # Third-Party Services Catalog ```js const API = "http://127.0.0.1:8000"; let apiOk = true; const catalog = await fetch(`${API}/tpsc/catalog/`) .then(r => r.json()) .catch(() => { apiOk = false; return []; }); const gdprReport = await fetch(`${API}/tpsc/report/gdpr`) .then(r => r.json()) .catch(() => ({ warnings: [], by_maturity: {}, total_services: 0, warning_count: 0 })); const snapshots = await fetch(`${API}/tpsc/snapshots/`) .then(r => r.json()) .catch(() => []); ``` ```js // GDPR maturity colour coding (CNIL/IAPP scale) const maturityColor = { unknown: "#ef4444", // red non_compliant: "#dc2626", // deep red initial: "#f97316", // orange developing: "#eab308", // amber defined: "#84cc16", // lime managed: "#22c55e", // green certified: "#16a34a", // deep green }; const maturityLabel = { unknown: "Unknown", non_compliant: "Non-Compliant", initial: "Initial", developing: "Developing", defined: "Defined", managed: "Managed", certified: "Certified", }; const WARNING_LEVELS = new Set(["unknown", "non_compliant", "initial"]); ``` ```js // KPI summary const warningServices = catalog.filter(s => WARNING_LEVELS.has(s.gdpr_maturity)); const paidServices = catalog.filter(s => ["paid", "usage_based"].includes(s.pricing_model)); ```
${gdprReport.warning_count}
GDPR warnings
${gdprReport.total_services}
Services in catalog
${paidServices.length}
Paid / usage-based
--- ## Service Catalog ```js import {html} from "npm:htl"; function maturityBadge(m) { const color = maturityColor[m] || "#9ca3af"; const label = maturityLabel[m] || m; return html`${label}`; } function pricingBadge(p) { const colors = { paid: "#7c3aed", usage_based: "#7c3aed", freemium: "#0369a1", free: "#166534", unknown: "#6b7280" }; const c = colors[p] || "#6b7280"; return html`${p.replace("_", " ")}`; } const catalogTable = html` ${catalog.map(s => html``)}
Service Provider Category Pricing GDPR Maturity DPA
${s.website_url ? html`${s.name}` : s.name} ${s.provider || "—"} ${s.category || "—"} ${pricingBadge(s.pricing_model)} ${maturityBadge(s.gdpr_maturity)} ${s.dpa_available ? "✅" : "❌"}
`; display(catalogTable); ``` --- ## GDPR Warnings _Services at **Unknown**, **Non-Compliant**, or **Initial** maturity may limit use in GDPR-regulated or corporate environments._ ```js if (gdprReport.warnings.length === 0) { display(html`

✅ No GDPR warnings across active repos.

`); } else { const warningCards = html`
${gdprReport.warnings.map(w => { const color = maturityColor[w.gdpr_maturity] || "#ef4444"; return html`
${w.service_slug} in ${w.repo_slug || "unknown repo"}
${maturityBadge(w.gdpr_maturity)} ${w.pricing_model ? html` ${pricingBadge(w.pricing_model)}` : ""} ${w.purpose ? html`— ${w.purpose}` : ""}
`; })}
`; display(warningCards); } ``` --- ## Per-Repo Breakdown ```js // Build: latest snapshot per repo → service list const repoBreakdown = new Map(); for (const snap of snapshots) { const repoSlug = snap.repo_id || "unknown"; if (!repoBreakdown.has(repoSlug) || snap.snapshot_at > repoBreakdown.get(repoSlug).snapshot_at) { repoBreakdown.set(repoSlug, snap); } } // Enrich with catalog data const catalogBySlug = Object.fromEntries(catalog.map(s => [s.slug, s])); const repoTable = html` ${[...repoBreakdown.entries()].map(([repoSlug, snap]) => html``)}
Repo Services Ingested
${repoSlug} ${snap.entries.map(e => { const cat = catalogBySlug[e.service_slug]; const m = cat?.gdpr_maturity || "unknown"; const color = maturityColor[m] || "#9ca3af"; return html` ${e.service_slug} `; })} ${new Date(snap.snapshot_at).toLocaleDateString()}
`; display(repoTable); ```