{
  "versions": {
    "composite": "2.0",
    "impact": "2.0",
    "reliability": "1.3",
    "explanation": "2026-09-27.3"
  },
  "formulas": [
    {
      "id": "notation",
      "title": "1. Notación, datos y elegibilidad",
      "formula": "clamp(x, a, b) = min(b, max(a, x))\nround(x) = floor(x + 0.5), para los valores no negativos de este modelo\nsat(x; k) = 0 si x ≤ 0; x/(x+k) en otro caso",
      "paragraphs": [
        "Las operaciones usan números IEEE-754 de JavaScript. Los redondeos indicados forman parte del algoritmo; no se redondean las etapas intermedias salvo donde se especifica. ∑ suma términos; |S| cuenta elementos distintos de un conjunto; ∅ es el conjunto vacío. Los puntos no representan toneladas de CO₂, porcentajes de ahorro ni impacto neto medido.",
        "El lector del modelo carga perfiles con publication_status = published de las cuatro familias. n es el número de prácticas de ese perfil cuya contribution_provenance es platform_admin, verified_representative o legacy_official. No se aplica otro filtro de magnitud, fecha, tema o medición al contar estas prácticas. Con n = 0 no se publica ni Impacto ni Fiabilidad: el estado es unscored, no una puntuación cero.",
        "Solo los vínculos de sustainable_practice_evidence con status = confirmed entran en la puntuación. Propuestas, disputas y retiradas se excluyen. El lector reconoce una foto, en su defecto un vídeo, en su defecto un documento; el esquema exige una sola referencia por fila. Los vínculos a prácticas no elegibles no intervienen.",
        "Las conexiones deben estar publicadas. La elegibilidad semántica se aplica al Impacto directo y al grafo de Impacto, pero no filtra los niveles de conexión de Fiabilidad ni su grafo. Las reglas son las del código ejecutable; no se debe inferir una exclusión adicional a partir de una etiqueta editorial.",
        "Los fallos de lectura críticos producen unavailable, nunca una cifra inventada. Las consultas de compatibilidad toleran exclusivamente los errores de columna/tabla ausente previstos en el lector: una base anterior a la migración no tiene declaraciones o exclusiones nuevas. Ocultar una puntuación solo enmascara su presentación pública; no elimina el perfil publicado del cálculo ni del ecosistema."
      ]
    },
    {
      "id": "impact",
      "title": "2. Impacto: alcance de las contribuciones, relaciones y escala pública",
      "formula": "v(L0,L1,L2,L3,L4,L5) = (0.0005, 0.005, 0.02, 0.06, 0.15, 0.4)\nL′ = L1 si L ∈ {L4, L5} sin confirmar; L′ = L1 sin lectura vigente\nwᵤ = v(L′ᵤ)\nc_g = max(v(Lbase,g), maxᵤ∈g wᵤ)\nB_g = min(c_g, ∑z∈Z_g c_g(1 − ∏ᵤ∈U_gz(1 − wᵤ/c_g)))\nq_g = c_g(1 − (1 − B_g/c_g) ∏ᵤ∈H_g(1 − wᵤ/c_g))\nQ = ∑g q_g [municipios]; Q = ∑ᵤ wᵤ [otras escalas]\nS = 2.4 Q^0.7\nP = round(1000 (1 − e^(−S)))/10\nG(c) = (1−0.60^c)/(1−0.60)\nD = round(10000 min(4, ∑ₛ bₛ G(cₛ)))/10000\nI₀ = P + D\nN = min(Nᴵ, max(0,4−D))\nIraw = I₀ + N\nIpublic = min(95,Iraw) [modo acumulativo actual]",
      "paragraphs": [
        "Impacto 2.0, provisional, valora cada contribución según su alcance para la escala propia del perfil: municipio, organización, persona o producto. Un lector de IA asigna a cada contribución un nivel de L0, simbólico, a L5, transformador, con reglas publicadas. La lectura se guarda con la huella del texto leído; si el texto cambia, la contribución cuenta como L1 hasta que se vuelve a leer. Cualquier acción concreta es al menos L1; un plan aprobado cuenta como L1 por sí mismo y un plan en preparación como L0.",
        "Revisión: un nivel L4 o L5 cuenta como L1 hasta que una persona lo confirma. Mismo trabajo: las contribuciones que el lector identifica como el mismo trabajo, o como fase o parte de otra, forman una unidad que cuenta una sola vez con el mayor nivel aplicado después de las retenciones por revisión. El nivel propio de cada contribución se conserva y se muestra. Las instrucciones del lector prohíben unir un plan y sus medidas; además, el motor bloquea esa unión cuando las categorías municipales identifican uno como plan y otro como medida.",
        "Solo en los perfiles municipales, las unidades se agrupan según aquello sobre lo que actúan (desplazamientos, residuos, energía municipal, parques, etc.). La capacidad base de cada grupo es L4 para los sistemas físicos de todo el municipio, L3 para el patrimonio municipal, los programas centrados en personas y otras categorías, y L2 para planes y datos. La capacidad efectiva c_g es el mayor valor entre esa base y la unidad de mayor valor aplicado del grupo. Z_g reúne las zonas documentadas distintas; U_gz contiene las unidades de una misma zona; H_g contiene las unidades de zona compartida o desconocida. Dentro de cada zona se combina el solapamiento; las zonas distintas se suman hasta c_g y después se combina el bloque resultante con H_g. Q suma los resultados q_g de los grupos. En los demás tipos de perfil Q suma las unidades directamente: las capacidades municipales no se aplican fuera de esa escala.",
        "D es el Impacto directo por relaciones y N la influencia del ecosistema; ambos conservan su cálculo anterior. Para cada contraparte distinta se conserva una sola categoría semántica, la más alta en el orden de la tabla; cₛ cuenta las contrapartes de la categoría s y bₛ es su peso base, con decaimiento 0.60. El límite conjunto de relaciones directas y ecosistema es 4. La evidencia añade 0 puntos de Impacto directo y se refleja en la Fiabilidad.",
        "La categoría se resuelve normalizando el texto: NFD, sin diacríticos, minúsculas, grupos no alfanuméricos convertidos en espacios y espacios compactados. Primero se comprueban etiquetas exactas; después patrones en este orden: BOOST interés, motor, guía; implementación; grupo de trabajo; red; coordinación; financiación; técnico/administrativo/duplicado. Texto desconocido o vacío se considera asociación genérica. scoring_eligible distinto de true en la lectura de base de datos se convierte en falso y produce categoría técnica de peso 0. El apéndice conserva todos los alias y expresiones regulares exactos.",
        "La deduplicación semántica no consulta las posiciones de confirmación. Por tanto, en el algoritmo vigente una relación disputada puede conservar Impacto directo si su categoría sigue siendo elegible. Su Fiabilidad y su transmisión por el grafo sí son cero. Es una limitación observable del modelo, no una recomendación ni una corrección introducida por esta documentación.",
        "Los identificadores de contraparte se resuelven en las cuatro familias, por lo que una fila inversa no duplica el crédito. Se ignoran las autorrelaciones. Si falta una identidad remota, el contador directo usa una identidad sintética por fila; el grafo exige dos perfiles publicados conocidos. En caso de empate semántico se conserva la primera categoría encontrada."
      ]
    },
    {
      "id": "families",
      "title": "3. Desglose por tipo de perfil y comparación relativa",
      "formula": "Municipio (territorio Municipio, Ayuntamiento de …): P con grupos, capacidades, zonas\nOrganización, persona, producto: P con suma de unidades\nTodas las familias: I = min(95,P+D+N); R = Rtier\nSi se activa el modo relativo: I = 1 + round(99(M−r)/(M−1))\nSi M = 1: I = 100; r = (primera posición + última posición)/2",
      "paragraphs": [
        "Las cuatro familias comparten niveles, pesos, curva, límites, bandas y reglas de evidencia; cambia la escala contra la que se lee cada contribución. Municipio: su población, emisiones, recursos y territorio. Organización: sus propias operaciones y huella, y las personas o el territorio a los que llega su trabajo según su perfil. Persona: su vida y su hogar, y las personas o proyectos a los que llega su trabajo. Producto o servicio: su propia huella y sus usuarios. Los territorios con categoría Municipio y las entidades cuyo nombre empieza por Ayuntamiento de usan la escala municipal.",
        "No hay un ajuste automático por ventas, empleados, renta o superficie: el tamaño entra solo mediante el juicio de alcance del lector, hecho contra la escala del propio perfil. Las capacidades por grupo se aplican únicamente a la escala municipal; las demás familias suman sus unidades hasta que se adapten con criterios propios.",
        "Cada familia está fijada en modo accumulating. La preparación para comparar perfiles es informativa: requiere al menos 25 perfiles con prácticas, al menos 5 con Iraw ≥ 25 y un máximo Iraw ≥ 95. Cruzar estos umbrales no cambia automáticamente la escala.",
        "La rama relative existe pero está inactiva. Ordena Iraw descendente dentro de la misma familia, usa rango medio para empates exactos y M es el número de perfiles con prácticas. El identificador ordena empates de forma estable sin romper su rango medio. La comparación utiliza Iraw sin el techo público de 95; los campos de presentación rawScore e intrinsicScore se limitan a 95. Activar la rama relativa exige un cambio deliberado de versión."
      ]
    },
    {
      "id": "evidence",
      "title": "4. Fiabilidad: de archivos a fuentes y familias",
      "formula": "C = |{p: existe un vínculo puntuable para p}| / n\nB = |{familias de profundidad de vínculos}|\nF = |{familias de profundidad de documentos relevantes}|\nJ = |{familias de independencia con peso > 0}|\nW = ∑ por primera aparición de cada familia con peso > 0: peso(distancia)\nU = |{p: tiene fuente de distancia independent}| / n\nV = |{p: tiene ≥2 familias de independencia y una fuente independent}| / n",
      "paragraphs": [
        "C es cobertura de prácticas, B diversidad de fuentes que respaldan prácticas, F fundamento documental relevante, J número de familias con peso positivo, W independencia ponderada, U fracción de prácticas con al menos una fuente independiente y V fracción corroborada. Cuando n = 0, las fracciones son cero internamente, pero el perfil no recibe puntuación pública.",
        "Un documento vinculado solo se acepta si existe en los documentos del mismo perfil. Las fotos y vídeos vinculados se consideran controlled y comparten la familia propia. No se analiza la imagen, el vídeo ni el PDF para determinar su veracidad. Los enlaces web del sistema de adjuntos genérico no se convierten por sí solos en vínculos puntuables.",
        "Un documento suelto es relevante para F si su título y descripción comparten al menos dos palabras distintas con la unión de título, descripción y theme_slug de las prácticas elegibles. Las palabras se pasan a minúsculas, se descomponen con NFD, se quitan diacríticos y se separan por caracteres ajenos a a–z/0–9. Se conservan palabras de más de 3 caracteres que no estén en la lista de stopwords del apéndice. No se inspecciona el texto del archivo ni se aplica comprensión semántica.",
        "Un reconocimiento con emisor resoluble usa issuer_domain normalizado o, en su defecto, el dominio de su URL. Con scope = practice y una improvement_id elegible crea un vínculo exclusivamente para esa práctica. En cualquier otro caso crea una familia relevante para F. Si no hay emisor resoluble no crea ninguna. Las fechas declaradas de emisión/caducidad no se usan en este cálculo.",
        "Las familias de profundidad colapsan fuentes own y controlled en self-surface:perfil; las demás usan la familia de origen. Las familias de independencia colapsan own, controlled y unknown en self:perfil. Affiliated e independent conservan su familia de origen. J incluye affiliated porque su peso es 0.25; U exige independent. W toma el peso de la primera aparición positiva por familia, no el máximo. Esta distinción forma parte de la implementación vigente."
      ]
    },
    {
      "id": "identity",
      "title": "5. Clasificación exacta de la identidad de las fuentes",
      "formula": "peso(own)=0; peso(controlled)=0; peso(unknown)=0; peso(affiliated)=0.25; peso(independent)=1\nTokens = etiqueta del dominio web (longitud ≥4) ∪ palabras distintivas del título (longitud ≥5)",
      "paragraphs": [
        "El host se obtiene con URL; si falta el esquema se presupone https. Se normaliza a minúsculas y se elimina www. El dominio registrable usa las dos últimas etiquetas, salvo los sufijos especiales bizkaia.eus, gipuzkoa.eus, araba.eus, euskadi.eus, co.uk, com.es, org.es y gob.es: conserva también la etiqueta inmediatamente anterior cuando existe. Es una lista explícita, no la Public Suffix List completa.",
        "Los tokens de identidad se pasan a minúsculas, NFD, sin diacríticos ni caracteres no alfanuméricos. La palabra distintiva de la web es la primera etiqueta de su dominio registrable. Del título se separan espacios y los signos – — , . ( ) / -. Se excluyen los términos genéricos enumerados íntegramente en el apéndice.",
        "La heurística comprueba en orden: (1) cuenta social reconocida: sin cuenta es unknown; si cuenta y token se contienen en algún sentido es controlled; en otro caso independent; (2) archivo subido sin host es controlled; (3) sin dominio es unknown; (4) mismo dominio que la web es own; (5) etiqueta distintiva que coincide o se contiene con un token: affiliated si hay web, own si no la hay; (6) sin web es unknown; en otro caso independent. Es una clasificación heurística, no una verificación legal de independencia.",
        "Se reconocen cuentas en LinkedIn (/company, /school, /showcase, /in), X, Twitter, YouTube (/@, /c, /channel, /user), Facebook, Instagram, Issuu, Medium y Vimeo; los patrones exactos figuran en el apéndice. Un vídeo vinculado como vídeo sigue siendo controlled; una URL social de un documento sigue la heurística de documentos.",
        "Las declaraciones de dominio pueden cubrir el dominio registrable, el host exacto o sus subdominios. En redes sociales deben identificar además la misma cuenta. Las declaraciones normales tienen especificidad 1 y las de cuenta 2; a igual especificidad se elige el menor peso, conservando la primera a igual peso. Una declaración platform_admin se aplica directamente. Representative o inferred solo pueden bajar el peso; si el peso empata solo sustituyen unknown. Nadie puede autodeclarar independent.",
        "La familia de origen es normalmente domain:dominio. En storage.googleapis.com, drive.google.com, docs.google.com, dropbox.com, linkedin.com, x.com, twitter.com, youtube.com, youtu.be, issuu.com y scribd.com usa aggregated:clave-del-artefacto. Los hosts se comparan exactamente para esta lista. Una subida sin host usa uploaded:id; sin dominio usa document:id.",
        "La clave del artefacto da prioridad a storage_path. Si hay URL, elimina fragmento y parámetros de rastreo utm_*, fbclid, gclid, mc_cid, mc_eid, _ga, ref y source; conserva otros parámetros y su orden, elimina barras finales del path (salvo /), omite protocolo y puerto en la clave final y convierte todo a minúsculas. Sin URL válida usa document:id. Las familias no son necesariamente editores jurídicos reales: estos límites de resolución también afectan a la puntuación."
      ]
    },
    {
      "id": "reliability",
      "title": "6. Fiabilidad: selección de banda y calidad dentro de la banda",
      "formula": "T0 si n=0 o C<0.20; banda [0,49]\nT4 si freshnessAvailable y C≥0.80 y U≥0.60 y J≥3 y V≥0.40; banda [85,100]\nT3 si C≥0.60 y U≥0.25 y (J≥2 o (J≥1 y U≥0.60)); banda [70,84]\nT2 si C≥0.60; banda [60,69]\nT1 en otro caso; banda [50,59]\nq = clamp(∑ₜ wₜ xₜ,0,1)\nR = round(L + (H−L)q)\nAporte interno del término t = (H−L)wₜxₜ",
      "paragraphs": [
        "Se evalúan las condiciones en el orden indicado. L y H son el suelo y techo de la banda elegida. La banda representa la situación probatoria y q la calidad dentro de esa situación. Por eso añadir documentación puede cruzar un umbral de banda; no existe un precio fijo universal por archivo.",
        "freshnessAvailable es false: T4 está cerrada. La Fiabilidad máxima teórica actual es 84 (B+), aunque la escala y las etiquetas conservan A−, A y A+ para modelos o configuraciones futuras. No se debe prometer A por subir más documentos ni afirmar que el modelo verifica su actualidad.",
        "Los pesos de T0–T2 y T3–T4 son distintos y se muestran en la tabla. Cada vector suma 1. La cifra R se calcula con q sin redondear. Solo el detalle publicado redondea q y los términos a 3 decimales, y sus aportaciones internas a 2 decimales; sumar esos valores de presentación puede diferir de la cifra final.",
        "La nota pública es la primera banda de nota cuyo mínimo no supera R; si no hay ninguna (incluido R=0), es D. Las bandas de nota no se confunden con T0–T4: por ejemplo T1 abarca D+ y C−."
      ]
    },
    {
      "id": "terms",
      "title": "7. Los ocho términos normalizados de Fiabilidad",
      "formula": "xcoverage = clamp((C−a)/(b−a),0,1)^0.7\n(a,b) = (0,0.20) en T0; (0.20,0.60) en T1; (0.60,1) en T2,T3,T4\nxdepth = B/(B+2)\nxfoundation = F/(F+1.5)\nxindependence = W/(W+3)\nxcorroboration = clamp(V,0,1)^1\nxcompleteness = m/4\nK = min(15, round(∑ᵤ capᵤ nᵤ/(nᵤ+kᵤ)))\nxrelationships = K/15\nxecosystem = min(1,Nᴿ/3)",
      "paragraphs": [
        "Cobertura mide el margen por encima de la cobertura mínima ya exigida por la banda; no vuelve a premiar íntegramente lo necesario para entrar. Profundidad y fundamento saturan con familias distintas. Independencia satura con su suma ponderada. Corroboración es lineal en la proporción de prácticas corroboradas.",
        "m cuenta cuántos de descripción, ubicación, web e imagen contienen texto no vacío tras trim. Cada campo añade 1/4 a completitud antes de aplicar el peso y el ancho de banda. Nombre, subtítulo, categoría, coordenadas, número de seguidores y mera antigüedad no añaden completitud.",
        "K mide las relaciones propias por nivel de confianza. Si cualquiera de las partes declined, el nivel es disputed y vale cero; si no, verificación independiente gana; después ambas partes afirmando (declared o confirmed) es confirmed; solo el perfil afirmando es declared; en otro caso observed. La misma fila puede tener distinto nivel para cada extremo. Por contraparte se conserva únicamente el nivel más alto; una disputa no anula otra fila de mayor nivel con esa contraparte.",
        "Los pares (cap,k) de K son observed (1,8), declared (5,4), confirmed (12,3), verified (15,2). Se suman antes de redondear y aplicar el techo 15. Nᴿ es la contribución entera de aseguramiento del ecosistema, calculada con el grafo siguiente. Sí interviene en la Fiabilidad actual, con el peso pequeño de la tabla."
      ]
    },
    {
      "id": "network",
      "title": "8. Ecosistema: grafo, caminos y dos contribuciones acotadas",
      "formula": "Iintr(j) = min(95,Pⱼ+Dⱼ) si nⱼ>0; 0 si no\nRlegacy(j) = min(100, round(50 eⱼ/nⱼ) + 5mⱼ + Kⱼ + min(15,3tⱼ+2min(lⱼ,3))) si nⱼ>0; 0 si no\nvᴵ(j) = clamp((Iintr(j)/95)(0.5+0.5 clamp(Rlegacy(j)/100,0,1)),0,1)\nvᴿ(j) = clamp((Rlegacy(j)/100) oⱼ,0,1), donde oⱼ=1 si publica puntuación y 0.8 si la oculta\nwᴵ(i,j) = max caminos π de 1..3 pasos: dᴵ[length(π)] ∏ aristas e∈π confianza(e)\nwᴿ(i,j) = max caminos π de 1..3 pasos: dᴿ[length(π)] ∏ aristas e∈π confianza(e)\nzᵃᵤ = ∑ perfiles j cuyo mejor camino tiene nivel u: wᵃ(i,j) vᵃ(j), a∈{I,R}\nNᴵ = round(min(10, ∑ᵤ capᴵᵤ zᴵᵤ/(zᴵᵤ+kᴵᵤ)))\nNᴿ = round(min(3, ∑ᵤ capᴿᵤ zᴿᵤ/(zᴿᵤ+kᴿᵤ)))",
      "paragraphs": [
        "Primero se calculan valores intrínsecos, sin ecosistema. Para cada vecino j, eⱼ es el número de prácticas con algún vínculo confirmado, tⱼ el número de tipos foto/vídeo/documento y lⱼ el número de filas de evidencia confirmada. Estos contadores proceden del lector inicial, antes de resolver familias y reconocimientos. Rlegacy es la fórmula aditiva antigua usada como señal interna por el grafo; no es la Fiabilidad v1.3 publicada. Para nⱼ=0 el nodo transmite cero aunque tenga campos completos. El aporte intrínseco de Impacto usa el modelo vigente de alcance de las mejoras, no la antigua fórmula por recuento.",
        "Se construyen dos grafos simétricos de perfiles publicados: el de aseguramiento admite todas las conexiones publicadas no disputadas; el de Impacto excluye además la categoría técnica/no elegible. Se omiten bucles propios y extremos que no estén entre los perfiles publicados. Filas repetidas entre dos perfiles conservan la confianza más alta. La dirección original no cambia la propagación.",
        "La confianza por arista es verified 1, confirmed 0.7, declared 0.4, observed 0.1, disputed 0. En el grafo, a diferencia del crédito directo de cada extremo, basta una parte afirmando para declared; ambas para confirmed. Declined gana incluso a la verificación.",
        "Los decaimientos son dᴵ=(1,0.25,0.05) y dᴿ=(1,0.05,0.005). El decaimiento se aplica una vez según la longitud total, no multiplicando los decaimientos de los pasos anteriores. El nivel del camino es el menor de sus niveles de arista. Cada perfil alcanzado se cuenta una sola vez por su camino de mayor peso; no se suman rutas alternativas. En un empate exacto se conserva el primer camino recorrido. La búsqueda recorre hasta tres pasos y nunca vuelve al origen, aunque puede repetir otros nodos dentro de ese horizonte.",
        "La relevancia global vale 1: se multiplica por la confianza de cada arista y de nuevo al agregar z, sin cambiar hoy el resultado. Los parámetros son decisiones de calibración, no constantes físicas ni resultados medidos. Los pares de saturación por nivel se muestran en la tabla.",
        "Nᴵ todavía debe pasar por la capacidad restante 4−D de la sección de Impacto. El aseguramiento Nᴿ entra en q como min(1,Nᴿ/3); no se suma directamente al R público de v1.3. No se propagan las puntuaciones finales ajustadas por el ecosistema, por lo que no hay realimentación iterativa.",
        "La visibilidad no reduce vᴵ: un perfil oculto conserva su transmisión de Impacto. Solo vᴿ recibe el factor 0.8. Si algún contribuyente efectivo oculta sus puntuaciones, se ocultan los agregados fraccionarios exactos de ambos términos. Solo quedan sus valores ya utilizados y la indicación de que existe una contribución subentera. Los detalles visibles del ecosistema se redondean a décimas; no son los operandos exactos del cálculo."
      ]
    },
    {
      "id": "inactive",
      "title": "9. Datos que hoy no puntúan y efectos marginales",
      "formula": "ΔI(elemento) = I(datos actuales) − I(datos sin elemento)\nΔR(elemento) = R(datos actuales) − R(datos sin elemento)\nEn general: ∑ elementos ΔI ≠ I y ∑ elementos ΔR ≠ R",
      "paragraphs": [
        "Vincular una mejora a un ODS o registrar un informe VSME no añade puntos de Impacto ni de Fiabilidad por sí mismo. La mejora y sus evidencias se evalúan en sus registros originales; no se cuentan de nuevo aquí. Un borrador no contribuye a la puntuación.",
        "No puntúan directamente: objetivos y porcentajes de progreso; mediciones privadas o compartidas de energía, agua, combustible y residuos; promedios y contexto doméstico; facturas y documentos de lectura privados; adjuntos genéricos a objetivos, reconocimientos o mediciones; fechas declaradas; estrategias guardadas o aplicaciones de estrategia; aportaciones comunitarias no oficializadas; ubicación de servicio y coordenadas del mapa. Un documento o acción puede tener otra función puntuable por separado: su mera presencia en estas secciones no la crea.",
        "Los indicadores i comparan el perfil cargado con el mismo perfil tras retirar una entrada mediante el mismo motor de producción. Para una práctica se retira la práctica; para un recurso se retiran sus vínculos puntuables o su documento; para una relación se retira esa fila y se recalcula también el ecosistema, incluidas las intrínsecas de sus extremos. Para un campo se sustituye por null. No se escribe ni borra nada en la base de datos.",
        "Las modificaciones propias de prácticas, evidencia o identidad mantienen el ecosistema recibido: solo cambiaría el valor intrínseco del propio origen, que nunca se cuenta como vecino ni puede ser visitado de vuelta. Al retirar una conexión sí cambia el grafo y las intrínsecas de contrapartes, por eso se recalcula completo. En documentos el legado interno cuenta filas de vínculo incluso cuando el documento no se resuelve: esta comparación conserva esa semántica real.",
        "Un cero puede significar exclusión, duplicidad de familia, redondeo, techo o sustitución por otra evidencia. Un efecto negativo de Fiabilidad puede ser real: añadir una práctica no respaldada aumenta el denominador de cobertura. Si retirar la última práctica deja el perfil sin puntuación, no se presenta una resta contra cero. Si interviene un agregado reservado se omiten los efectos numéricos para evitar reconstruir puntuaciones ocultas.",
        "El desglose se solicita al abrir por primera vez un indicador i que requiere cálculo y se reutiliza mientras sigue cargado en el perfil. Después de editar, vuelve a cargar el perfil para obtener un desglose actualizado. Estas explicaciones no se presentan como una predicción causal del impacto ambiental ni como un reparto aditivo o Shapley. Una entrada recién añadida sin cálculo disponible recibe la regla general y se indica que falta su desglose."
      ]
    },
    {
      "id": "example",
      "title": "10. Ejemplo numérico reproducible",
      "formula": "n=3; C=1; B=1; F=1; J=W=U=V=0; m=4; K=Nᴵ=Nᴿ=0\nL=(L1,L1,L1); Q=0.015; S=0.1269; P=11.9; D=N=0; Ipublic=11.9\nBanda T2: L=60, H=69\nx=(1, 1/3, 0.4, 0, 0, 1, 0, 0)\nq=0.28×1 + 0.24×(1/3) + 0.14×0.4 + 0.08×1 = 0.496\nR=round(60+9×0.496)=64; nota C\nSin el documento: C=B=F=0; banda T0; q=0.08; R=round(49×0.08)=4\nEfecto del documento: ΔI=0; ΔR=64−4=60",
      "paragraphs": [
        "Supongamos tres prácticas oficiales, un documento propio relevante vinculado y confirmado a las tres, los cuatro campos completos y ninguna conexión. Ese único editor cubre todas las prácticas, pero no crea independencia. El orden de x es cobertura, profundidad, fundamento, independencia, corroboración, completitud, relaciones y ecosistema.",
        "El gran efecto marginal del documento se debe al cambio de banda. No significa que todos los documentos valgan 60 puntos. Un segundo documento equivalente del mismo editor, con los mismos vínculos y relevancia, puede tener efecto marginal cero porque se mantiene la cobertura y no aparece ninguna familia adicional. Si las tres prácticas se leen como L1, retirar una reduce el Impacto de 11.9 a 9.1, pero mantiene la Fiabilidad en 64."
      ]
    },
    {
      "id": "versions",
      "title": "11. Versiones, historia y mantenimiento",
      "formula": "Identidad del cálculo = (versión compuesta, versión Impacto, versión Fiabilidad, revisión explicativa, revisión del código)",
      "paragraphs": [
        "El motor vigente declara modelo compuesto 2.0, Impacto 2.0 y Fiabilidad 1.3. Impacto 2.0 es provisional: el cálculo anterior, Impacto 1.3, sigue en el código y se restaura cambiando una constante. La revisión de este texto es independiente: corregir o ampliar una explicación sin alterar resultados no cambia la versión de la fórmula. Los snapshots de evolución conservan versiones y build revision cuando están disponibles; no se reescribe un resultado antiguo con la fórmula actual.",
        "Historia semántica resumida: compuesto 1.0 usaba un veredicto por arista; 1.1 introdujo niveles por participante y saturación; 1.2 añadió ecosistema; 1.2.1 retiró el bono directo de evidencia de Impacto y fijó la escala por familia; la activación de Fiabilidad 1.3 quedó bajo compuesto 1.3; compuesto 1.4 introdujo las categorías semánticas de relaciones de Impacto 1.3 y el límite conjunto de 4; compuesto 2.0 sustituyó el recuento de prácticas por el alcance de cada contribución, con Impacto 2.0 provisional. El apéndice descargable de esta revisión conserva únicamente el motor que documenta; no pretende reconstruir todos los motores históricos.",
        "Las tablas de esta página se obtienen de las constantes del motor, y los indicadores ejecutan el propio motor. Además, el build verifica las huellas de los archivos que deciden el cálculo contra el manifiesto revisado. Un cambio en esos archivos exige revisar las fórmulas, el apéndice y el manifiesto; los cambios de resultados exigen subir las versiones afectadas y la compuesta. Una refactorización que deje todos los resultados iguales puede conservarlas, documentando y probando la equivalencia.",
        "La metodología explica el algoritmo desplegable de esta revisión, no convierte filosofía futura, mediciones aún no calibradas ni ideas de la hoja de ruta en funcionalidades activas. El archivo descargable permite revisar funciones auxiliares, alias, reglas de lectura y normalización sin depender de comentarios antiguos."
      ]
    }
  ],
  "parameters": {
    "reliability": {
      "weightProfiles": {
        "establishing": {
          "coverage": 0.28,
          "depth": 0.24,
          "foundation": 0.14,
          "independence": 0.1,
          "corroboration": 0.04,
          "completeness": 0.08,
          "relationships": 0.08,
          "ecosystem": 0.04
        },
        "corroborating": {
          "coverage": 0.08,
          "depth": 0.07,
          "foundation": 0.04,
          "independence": 0.34,
          "corroboration": 0.3,
          "completeness": 0.05,
          "relationships": 0.09,
          "ecosystem": 0.03
        }
      },
      "halfSaturation": {
        "foundation": 1.5,
        "depth": 2,
        "independence": 3
      },
      "exponents": {
        "coverage": 0.7,
        "corroboration": 1
      },
      "tiers": {
        "minCoverageT1": 0.2,
        "minCoverageT2": 0.6,
        "minIndependentShareT3": 0.25,
        "minIndependentFamiliesT3": 2,
        "broadSingleSourceShareT3": 0.6,
        "minCoverageT4": 0.8,
        "minIndependentShareT4": 0.6,
        "minIndependentFamiliesT4": 3,
        "minCorroboratedShareT4": 0.4
      },
      "freshnessAvailable": false
    },
    "tiers": [
      {
        "id": "T0",
        "label": "unsupported",
        "floor": 0,
        "ceiling": 49
      },
      {
        "id": "T1",
        "label": "partially documented",
        "floor": 50,
        "ceiling": 59
      },
      {
        "id": "T2",
        "label": "documented, first-party",
        "floor": 60,
        "ceiling": 69
      },
      {
        "id": "T3",
        "label": "independently corroborated",
        "floor": 70,
        "ceiling": 84
      },
      {
        "id": "T4",
        "label": "strongly corroborated and current",
        "floor": 85,
        "ceiling": 100,
        "requiresFreshness": true
      }
    ],
    "network": {
      "hopDecay": [
        1,
        0.25,
        0.05
      ],
      "edgeConfidence": {
        "verified": 1,
        "confirmed": 0.7,
        "declared": 0.4,
        "observed": 0.1,
        "disputed": 0
      },
      "tierCurves": {
        "verified": {
          "cap": 6,
          "halfSaturation": 0.8
        },
        "confirmed": {
          "cap": 4,
          "halfSaturation": 1
        },
        "declared": {
          "cap": 2,
          "halfSaturation": 1.2
        },
        "observed": {
          "cap": 0.8,
          "halfSaturation": 2
        },
        "disputed": {
          "cap": 0,
          "halfSaturation": 1
        }
      },
      "qualityReference": 95,
      "impactCap": 10,
      "relevance": 1,
      "reliabilityHopDecay": [
        1,
        0.05,
        0.005
      ],
      "reliabilityTierCurves": {
        "verified": {
          "cap": 1.6,
          "halfSaturation": 0.8
        },
        "confirmed": {
          "cap": 1,
          "halfSaturation": 1
        },
        "declared": {
          "cap": 0.4,
          "halfSaturation": 1.2
        },
        "observed": {
          "cap": 0.15,
          "halfSaturation": 2
        },
        "disputed": {
          "cap": 0,
          "halfSaturation": 1
        }
      },
      "reliabilityReference": 100,
      "reliabilityCap": 3,
      "reliabilityTrustFloor": 0.5,
      "hiddenAssuranceFactor": 0.8
    },
    "relationshipWeights": {
      "technical/admin/duplicate": 0,
      "generic genuine association": 0.05,
      "sustainability-network membership": 0.15,
      "BOOST equipo interes": 0.15,
      "BOOST equipo motor": 0.3,
      "BOOST equipo guia": 0.5,
      "active working-group participation": 0.35,
      "coordination / leadership": 0.6,
      "sustainability-relevant funding/support": 0.6,
      "implementation partnership": 1
    },
    "benchmark": {
      "entidad": "accumulating",
      "persona": "accumulating",
      "territorio": "accumulating",
      "producto_servicio": "accumulating"
    }
  },
  "sourceCode": {
    "lib/profile-scoring.ts": "if (typeof window !== 'undefined') {\n  throw new Error('lib/profile-scoring.ts is server-only and must never be imported from client code.');\n}\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport { PROFILE_KIND_TABLE } from '@/types/profile-kind';\nimport type { ProfileKind } from '@/types/profile';\nimport type {\n  ProfileImpactScore,\n  ProfileReliabilityScore,\n  ProfileScoreResult,\n  ReliabilityGrade,\n} from '@/types/profile-score';\nimport { PROFILE_CONTENT_PARENT_COLUMN } from '@/lib/profile-content';\nimport {\n  getImpactBenchmarkState,\n  IMPACT_BENCHMARK_MODE,\n  MINIMUM_RELATIVE_BENCHMARK_RELEVANT_RAW_SCORE,\n} from '@/lib/profile-score-benchmark';\nimport {\n  getImpactPracticePoints,\n  MAX_PUBLIC_IMPACT_VALUE,\n} from '@/lib/profile-impact-points';\nimport { computeImpactV2, impactV2Scale, IMPACT_V2_REVISION } from '@/lib/profile-impact-v2';\nimport {\n  countConnectionTiersByProfile,\n  countRelationshipCategoriesByProfile,\n  emptyConnectionTierCounts,\n  emptyRelationshipSemanticCounts,\n  getTieredConnectionContribution,\n  getRelationshipImpactContribution,\n  resolveRelationshipCategory,\n  RELIABILITY_TIER_CURVES,\n  type ConnectionTierCounts,\n  type RelationshipSemanticCounts,\n  type ParticipantPosition,\n} from '@/lib/profile-connection-tiers';\nimport {\n  buildReliabilityFacts,\n  type ReliabilityRecognitionInput,\n  type ReliabilitySourceInput,\n} from '@/lib/profile-reliability-facts';\nimport {\n  computeReliabilityV13,\n  type ReliabilityV13Result,\n} from '@/lib/profile-reliability-v13';\nimport type { ProfileDomainRow } from '@/lib/source-identity';\nimport {\n  buildNetworkGraph,\n  computeNetworkInfluence,\n  resolveEdgeTrust,\n  type NetworkEdge,\n  type NetworkNode,\n} from '@/lib/profile-network-influence';\n\n/**\n * The scoring semantics this module implements, as a deliberate, citable\n * version.\n *\n * Bump it whenever anything that can change a produced score changes: weights,\n * curves, caps, the benchmark logic, the grade bands, or the rules deciding\n * which relationships are eligible to score at all. Do not bump it for\n * refactors that provably leave every output identical.\n *\n * v1.0 was edge-relative: one verdict per relationship applied to both\n * endpoints, a decline excluding the edge globally, community observations\n * contributing nothing at all, and every eligible relationship pouring into a\n * single logarithmic total.\n *\n * v1.1 is profile-relative and tier-saturated. The same relationship is now\n * worth different amounts to its two endpoints, because a participant who put\n * something on the record has staked more than a silent one. Community\n * observation earns a small non-zero Impact and a near-zero Reliability;\n * declaration more; bilateral confirmation and independent verification\n * substantially more; a decline makes the relationship disputed and\n * non-scoring for both sides with no negative penalty. Each tier saturates on\n * its own curve, so unlimited weak observations can never substitute for\n * trusted relationships. See lib/profile-connection-tiers.ts for the curves.\n *\n * Public ranges and the grade bands are unchanged: Reliability connection points\n * remain at 15, while Relationship Scoring v1 caps Impact relationship points at 4.\n *\n * v1.2 leaves all of v1.1 exactly as it is and adds one separately explainable\n * term on top: a bounded contribution from the sustainability of the ecosystem\n * a profile operates in. v1.1's result becomes the *intrinsic* score, and it\n * is the intrinsic value — never the network-adjusted total — that travels the\n * graph, so a profile can never amplify itself through its neighbours. The\n * term is capped, saturating and non-negative. See lib/profile-network-\n * influence.ts for the model and its constants.\n *\n * v1.2.1 removes the direct additive Impact bonus for evidence. Evidence says\n * how trustworthy a practice is, not how consequential it is. The readiness\n * calculation for relative Impact also becomes advisory: the published scale\n * comes from an explicit per-kind map and can change only with a version bump.\n *\n * v1.3 replaces trust-tier connection Impact with deterministic relationship\n * semantics. Eligible relationships receive category-specific weights with\n * 0.60 geometric decay, ineligible and technical rows receive zero, and the\n * combined direct-relationship plus network Impact contribution is capped at 4.\n *\n * Reliability does not inherit a neighbour's Impact. It does receive bounded\n * ecosystem assurance derived from the neighbour's legacy intrinsic\n * Reliability, as one small term in v1.3's within-band quality index.\n */\nexport const SCORING_MODEL_VERSION: string = '2.0';\n\n/**\n * The two halves of the published score, versioned separately.\n *\n * `SCORING_MODEL_VERSION` above is a single composite string, and it was fine\n * while both halves moved together. They no longer do: Impact and Reliability\n * now move on independent versions, and a snapshot\n * row saying \"1.2.1\" cannot express which Reliability produced it. Snapshots\n * therefore record all three (20260829100000), additively. Relationship\n * Scoring v1 moves Impact to 1.3 and the composite to 1.4 so evolution charts\n * mark the scoring change without changing the meaning of older snapshots.\n *\n * Switching Reliability on is one edit here plus a bump of the composite, and\n * the composite must be bumped: it is what the public evolution chart marks\n * \"Modelo actualizado\" from, and v1.3 changes every published Reliability\n * number. Tests assert each independent version so changes are deliberate\n * rather than incidental.\n */\nexport type ReliabilityModelVersion = '1.2' | '1.3';\n/**\n * Impact v2.0 (provisional, 2026-09-27) weighs each contribution by its significance for the profile's own\n * scale instead of counting practices; see lib/profile-impact-v2.ts. Relationship and ecosystem Impact keep\n * their v1.3 calculation. To restore the previous published model set IMPACT_MODEL_VERSION to '1.3' and\n * SCORING_MODEL_VERSION to '1.4': the v1.3 practice formula below is untouched and still selected by it.\n */\nexport type ImpactModelVersion = '1.3' | '2.0';\nexport const IMPACT_MODEL_VERSION: ImpactModelVersion = '2.0';\nexport const IMPACT_MODEL_REVISION: string = IMPACT_MODEL_VERSION === '2.0' ? IMPACT_V2_REVISION : '1.3';\n\n/**\n * Offline comparisons only (scripts/scoring-v2/compare-models.mjs): score the same snapshot under either\n * Impact model in one process. The published app never calls this; it always uses IMPACT_MODEL_VERSION.\n */\nlet impactModelOverride: ImpactModelVersion | null = null;\nexport function withImpactModel<T>(model: ImpactModelVersion, run: () => T): T {\n  const previous = impactModelOverride;\n  impactModelOverride = model;\n  try {\n    return run();\n  } finally {\n    impactModelOverride = previous;\n  }\n}\nfunction activeImpactModel(): ImpactModelVersion {\n  return impactModelOverride ?? IMPACT_MODEL_VERSION;\n}\nexport const RELIABILITY_MODEL_VERSION: ReliabilityModelVersion = '1.3';\n\n/**\n * The contribution tiers a scored practice may carry.\n *\n * Opening contribution to unverified stewards and to the community\n * (20260827201727_contribution_provenance_and_unverified_participation.sql)\n * created two new ways for a row to reach entity_improvements. Left\n * unfiltered, anyone could raise anyone else's Impact simply by adding\n * practices to their profile, and a self-declared steward could manufacture a\n * score without ever being verified. Impact measures what a profile has put on\n * the record, not how many people have typed at it.\n *\n * This is deliberately score-neutral on the day it ships: every row that\n * existed before that migration backfills to one of these three tiers, so no\n * published score moves and SCORING_MODEL_VERSION is NOT bumped. Bumping would\n * stamp \"Modelo actualizado\" on every public evolution chart for a change that\n * altered no number, misleading the very reader the marker exists to inform.\n *\n * Whether steward_unverified practices should eventually count — and whether\n * community ones should count once endorsed — is a product decision about what\n * Impact means, deliberately not smuggled in with a permissions change.\n */\nexport const SCORED_CONTRIBUTION_PROVENANCE: readonly string[] = [\n  'platform_admin',\n  'verified_representative',\n  'legacy_official',\n];\n\n/**\n * The review states in which an evidence relationship counts.\n *\n * 20260829090100 gave sustainable_practice_evidence a status, because an AI\n * proposal, a community suggestion and an unverified steward's self-assertion\n * all needed a way to reach the record without being believed. Only a confirmed\n * relationship scores, and confirming one requires an administrator or the\n * parent profile's verified representative — enforced by database trigger, not\n * only here.\n *\n * Like SCORED_CONTRIBUTION_PROVENANCE above, this is score-neutral on the day\n * it ships: all nine relationships that existed before that migration backfill\n * to 'confirmed', so no published number moves and SCORING_MODEL_VERSION is NOT\n * bumped. What it changes is what happens next, and that is the point — without\n * it, the first proposal written by anything would silently become published\n * Reliability.\n */\nexport const SCORED_EVIDENCE_LINK_STATUS: readonly string[] = ['confirmed'];\n\n/**\n * A missing table or a missing column, as PostgREST and Postgres report them.\n *\n * Preview and production share one database, so a deployment running ahead of\n * its migration must degrade to \"this information does not exist yet\" rather\n * than to \"no scores at all\".\n */\nconst MISSING_SCHEMA_ERROR_CODES: readonly string[] = ['42703', '42P01', 'PGRST205'];\n\n/** Kept for snapshot/review vocabulary; evidence adds no Impact magnitude. */\nexport const IMPACT_EVIDENCE_BONUS_POINTS = 0;\nexport const MAX_IMPACT_CONNECTION_POINTS = 4;\nexport const MAX_RELIABILITY_EVIDENCE_COVERAGE_POINTS = 50;\nexport const MAX_RELIABILITY_PROFILE_COMPLETENESS_POINTS = 20;\nexport const MAX_RELIABILITY_CONNECTION_POINTS = 15;\nexport const MAX_RELIABILITY_EVIDENCE_DIVERSITY_POINTS = 15;\n\nexport const RELIABILITY_GRADE_BANDS: ReadonlyArray<{\n  grade: ReliabilityGrade;\n  minimumScore: number;\n  maximumScore: number;\n}> = [\n  { grade: 'A+', minimumScore: 95, maximumScore: 100 },\n  { grade: 'A', minimumScore: 90, maximumScore: 94 },\n  { grade: 'A-', minimumScore: 85, maximumScore: 89 },\n  { grade: 'B+', minimumScore: 80, maximumScore: 84 },\n  { grade: 'B', minimumScore: 75, maximumScore: 79 },\n  { grade: 'B-', minimumScore: 70, maximumScore: 74 },\n  { grade: 'C+', minimumScore: 65, maximumScore: 69 },\n  { grade: 'C', minimumScore: 60, maximumScore: 64 },\n  { grade: 'C-', minimumScore: 55, maximumScore: 59 },\n  { grade: 'D+', minimumScore: 50, maximumScore: 54 },\n  { grade: 'D', minimumScore: 1, maximumScore: 49 },\n];\n\nexport function unavailableProfileScore(kind: ProfileKind): ProfileScoreResult {\n  return { status: 'unavailable', kind, impact: null, reliability: null };\n}\n\n/**\n * A score that exists but is deliberately not shown in public.\n *\n * The masking happens here, at the single point every public surface reads\n * through, rather than at each place a score is drawn. A surface that forgets\n * to check a flag leaks; a surface that never receives the number cannot.\n */\nexport function hiddenProfileScore(kind: ProfileKind): ProfileScoreResult {\n  return { status: 'hidden', kind, impact: null, reliability: null };\n}\n\n/**\n * One profile's ecosystem term: the rounded points the score uses, and the\n * unrounded figure behind them.\n *\n * Both are carried because early on only the second one moves. A profile whose\n * ecosystem is worth 0.3 points should be able to see that it is worth 0.3 and\n * rising, rather than a flat +0 that looks like the feature is switched off.\n */\n/**\n * One ecosystem term as the score and the interface each need it.\n *\n * `exact` is null whenever any effective contributor withholds its own scores:\n * the arithmetic always uses every profile in full, and only the resolution of\n * the public explanation is coarsened. `positive` is the one bit that survives\n * that coarsening, so a profile can still be told its ecosystem is worth\n * something rather than nothing.\n */\nexport interface NetworkTermShare {\n  points: number;\n  exact: number | null;\n  positive: boolean;\n}\n\nexport interface NetworkImpactShare {\n  impact: NetworkTermShare;\n  reliability: NetworkTermShare;\n}\n\nconst NO_TERM: NetworkTermShare = { points: 0, exact: 0, positive: false };\nconst NO_NETWORK_IMPACT: NetworkImpactShare = { impact: NO_TERM, reliability: NO_TERM };\n\nexport interface ProfileScoreProfileInput {\n  id: string;\n  description: string | null;\n  location: string | null;\n  website: string | null;\n  imagePath: string | null;\n  /**\n   * Reliability v1.3 only. The source resolver reads identity tokens from the\n   * title as well as the website, because the website field is missing on most\n   * published profiles and the title is the only other thing that names the\n   * organisation.\n   */\n  title?: string | null;\n  /** Impact v2 only: the published category decides the scale (for example 'Municipio'). */\n  category?: string | null;\n}\n\nexport interface ProfileScoreImprovementInput {\n  id: string;\n  profileId: string;\n  /** Reliability v1.3 only: the vocabulary a document's relevance is judged against. */\n  title?: string | null;\n  description?: string | null;\n  themeSlug?: string | null;\n}\n\nexport interface ProfileScoreEvidenceInput {\n  /** Identity retained for read-only contribution explanations. */\n  id?: string;\n  mediaId?: string;\n  improvementId: string;\n  type: 'photo' | 'video' | 'document';\n  /** Reliability v1.3 only: which document, so its publisher can be resolved. */\n  documentId?: string | null;\n}\n\n/**\n * What Reliability v1.3 needs that v1.2 never asked for: who published each\n * source, and what else the profile has on file.\n *\n * Optional throughout, and read only when RELIABILITY_MODEL_VERSION says '1.3'.\n * While it says '1.2' these are neither loaded nor consulted, so the published\n * scorer runs exactly the queries and exactly the arithmetic it ran before.\n */\nexport interface ProfileScoreSourceInputs {\n  documents: ReliabilitySourceInput[];\n  declaredDomains: ReadonlyMap<string, ProfileDomainDeclaration[]>;\n  recognitions: ReliabilityRecognitionInput[];\n}\n\ntype ProfileDomainDeclaration = Pick<\n  ProfileDomainRow, 'domain' | 'account_path' | 'relation' | 'declared_by'\n>;\n\nexport interface ProfileScoreConnectionInput {\n  id?: string;\n  /** The endpoint belonging to the profile kind being scored, if any. */\n  sourceProfileId: string | null;\n  targetProfileId: string | null;\n  relationLabel: string | null;\n  scoringEligible: boolean;\n  /**\n   * Cross-kind endpoint identity, used only to tell counterparties apart.\n   * The unique index on ecosystem_connections covers the *ordered* pair, so\n   * A->B and B->A are two permitted rows, and an edge whose far end is another\n   * profile kind exposes no id in this kind's columns at all. Counting\n   * distinct counterparties rather than rows is what stops either fact from\n   * becoming a way to farm the same relationship twice. Defaults to the\n   * same-kind id so existing callers keep their meaning.\n   */\n  sourceKey?: string | null;\n  targetKey?: string | null;\n  /**\n   * Authoritative Phase A4 stance fields. The legacy confirmation_status\n   * column is deliberately absent: most historical rows carry\n   * confirmation_status = 'confirmed' with no genuine counterparty behind it,\n   * so reading it would score an agreement that never happened.\n   */\n  sourcePosition: ParticipantPosition;\n  targetPosition: ParticipantPosition;\n  isIndependentlyVerified: boolean;\n}\n\n\ninterface ScoreFacts {\n  profile: ProfileScoreProfileInput;\n  practiceCount: number;\n  evidenceBackedPracticeCount: number;\n  evidenceItemCount: number;\n  evidenceTypes: Array<'photo' | 'video' | 'document'>;\n  connectionCount: number;\n  confirmedConnectionCount: number;\n  pendingConnectionCount: number;\n  connectionTiers: ConnectionTierCounts;\n  practicePoints: number;\n  evidencePoints: number;\n  impactConnectionPoints: number;\n  /** v1.1 only: practices, evidence and this profile's own relationships. */\n  intrinsicImpactScore: number;\n  networkImpactPoints: number;\n  /** The same ecosystem term before rounding, for presentation only. */\n  networkExactPoints: number | null;\n  /** Coarse stand-in when the exact figure is withheld: positive but under a point. */\n  networkBelowOnePoint: boolean;\n  rawImpactScore: number;\n  impactV2: ProfileImpactScore['v2'];\n  reliability: ProfileReliabilityScore;\n}\n\nfunction hasContent(value: string | null): boolean {\n  return Boolean(value?.trim());\n}\n\n/**\n * v1.1: each trust tier saturates on its own curve and the totals are summed,\n * rather than every relationship being poured into one weighted logarithm.\n * Relationship Impact now uses semantic categories with a capped v1 curve.\n * The strongest category sequence is limited by a profile-wide 4-point\n * relationship cap.\n */\nfunction getImpactConnectionPoints(categoryCounts: RelationshipSemanticCounts): number {\n  const contribution = Math.min(\n    MAX_IMPACT_CONNECTION_POINTS,\n    getRelationshipImpactContribution(categoryCounts),\n  );\n  // Bound floating-point tails without erasing the deliberately small weights.\n  return Math.round(contribution * 10_000) / 10_000;\n}\n\n/**\n * Reliability's own tier curves, which are not Impact's. Reliability is a\n * statement about corroboration, so the community tier is worth at most a\n * single point of the available 15 no matter how many observations exist.\n */\nfunction getReliabilityConnectionPoints(tiers: ConnectionTierCounts): number {\n  return Math.min(\n    MAX_RELIABILITY_CONNECTION_POINTS,\n    Math.round(getTieredConnectionContribution(tiers, RELIABILITY_TIER_CURVES)),\n  );\n}\n\nfunction getReliabilityGrade(score: number): ReliabilityGrade {\n  return RELIABILITY_GRADE_BANDS.find((band) => score >= band.minimumScore)?.grade ?? 'D';\n}\n\nfunction buildScoreFacts(\n  kind: ProfileKind,\n  profile: ProfileScoreProfileInput,\n  improvements: ProfileScoreImprovementInput[],\n  evidenceByImprovement: Map<string, ProfileScoreEvidenceInput[]>,\n  connectionTiers: ConnectionTierCounts,\n  connectionCategories: RelationshipSemanticCounts,\n  network: NetworkImpactShare,\n  sources?: ProfileScoreSourceInputs,\n): ScoreFacts {\n  const profileImprovements = improvements.filter((improvement) => improvement.profileId === profile.id);\n  const evidenceItems = profileImprovements.flatMap(\n    (improvement) => evidenceByImprovement.get(improvement.id) ?? [],\n  );\n  const evidenceBackedPracticeCount = profileImprovements.filter(\n    (improvement) => (evidenceByImprovement.get(improvement.id)?.length ?? 0) > 0,\n  ).length;\n  const evidenceTypes = [...new Set(evidenceItems.map((item) => item.type))];\n  const practiceCount = profileImprovements.length;\n  // Impact v2: significance-weighted contributions on the 0-100 curve (one decimal); v1.3: counted practices.\n  const impactV2 = activeImpactModel() === '2.0' && practiceCount > 0\n    ? computeImpactV2(impactV2Scale(kind, profile.title, profile.category), profileImprovements)\n    : null;\n  const practicePoints = impactV2\n    ? Math.round(impactV2.score * 10) / 10\n    : getImpactPracticePoints(practiceCount);\n  // Evidence determines confidence in a practice, not the practice's Impact.\n  // Any future eligibility coupling is a separate, explicitly deferred model.\n  const evidencePoints = IMPACT_EVIDENCE_BONUS_POINTS;\n  // The v1.0 vocabulary is preserved for the UI and for snapshot continuity:\n  // \"confirmed\" is everything genuinely attested, \"pending\" everything not yet.\n  // Disputed relationships are in neither, and are excluded from the total.\n  const confirmedConnectionCount = connectionTiers.confirmed + connectionTiers.verified;\n  const pendingConnectionCount = connectionTiers.observed + connectionTiers.declared;\n  const connectionCount = confirmedConnectionCount + pendingConnectionCount;\n  const impactConnectionPoints = getImpactConnectionPoints(connectionCategories);\n  const intrinsicImpactScore = practicePoints + evidencePoints + impactConnectionPoints;\n  const remainingRelationshipNetworkCapacity = Math.max(\n    0,\n    MAX_IMPACT_CONNECTION_POINTS - impactConnectionPoints,\n  );\n  const networkImpactPoints = Math.min(\n    network.impact.points,\n    remainingRelationshipNetworkCapacity,\n  );\n  const networkExactPoints = network.impact.exact === null\n    ? null\n    : Math.min(network.impact.exact, remainingRelationshipNetworkCapacity);\n  // v1.2: the ecosystem term is added on top of the intrinsic score, never\n  // folded into it, so both halves stay separately reportable.\n  const rawImpactScore = intrinsicImpactScore + networkImpactPoints;\n\n  const evidenceCoveragePoints = practiceCount === 0\n    ? 0\n    : Math.round((evidenceBackedPracticeCount / practiceCount) * MAX_RELIABILITY_EVIDENCE_COVERAGE_POINTS);\n  const profileCompletenessPoints = [\n    profile.description,\n    profile.location,\n    profile.website,\n    profile.imagePath,\n  ].filter(hasContent).length * 5;\n  const reliabilityConnectionPoints = getReliabilityConnectionPoints(connectionTiers);\n  const evidenceDiversityPoints = Math.min(\n    MAX_RELIABILITY_EVIDENCE_DIVERSITY_POINTS,\n    (evidenceTypes.length * 3) + (Math.min(evidenceItems.length, 3) * 2),\n  );\n  // v1.1's Reliability, untouched, and the number that propagates.\n  const intrinsicReliabilityScore = Math.min(\n    100,\n    evidenceCoveragePoints\n      + profileCompletenessPoints\n      + reliabilityConnectionPoints\n      + evidenceDiversityPoints,\n  );\n  // v1.2 adds a much smaller, much faster-decaying assurance term on top.\n  //\n  // Pinned to 0..100 explicitly. Both inputs are non-negative by construction,\n  // so the lower clamp can never fire today -- it is there so that a later\n  // change which could produce a negative term fails a test rather than\n  // publishing a negative Reliability. The public grade is derived from this\n  // final value, ecosystem term included, not from the intrinsic half.\n  const v12ReliabilityScore = Math.max(0, Math.min(\n    100,\n    intrinsicReliabilityScore + network.reliability.points,\n  ));\n\n  // Reliability v1.3, when it is the published model.\n  //\n  // Deliberately computed here and not earlier: it consumes the same\n  // connection tiers and the same ecosystem term v1.2 produced, so the two\n  // models disagree about how evidence is weighed and about nothing else.\n  // While RELIABILITY_MODEL_VERSION is '1.2' this block is unreachable and\n  // `sources` is never even loaded.\n  let v13: ReliabilityV13Result | null = null;\n  if (RELIABILITY_MODEL_VERSION === '1.3' && sources) {\n    v13 = computeReliabilityV13(buildReliabilityFacts({\n      profile: {\n        id: profile.id,\n        description: profile.description,\n        location: profile.location,\n        website: profile.website,\n        imagePath: profile.imagePath,\n        title: profile.title ?? null,\n      },\n      practices: profileImprovements.map((improvement) => ({\n        id: improvement.id,\n        profileId: profile.id,\n        title: improvement.title ?? null,\n        description: improvement.description ?? null,\n        themeSlug: improvement.themeSlug ?? null,\n      })),\n      documents: sources.documents.filter((document) => document.profileId === profile.id),\n      links: profileImprovements.flatMap((improvement) =>\n        (evidenceByImprovement.get(improvement.id) ?? []).map((item) => ({\n          practiceId: improvement.id,\n          documentId: item.documentId ?? null,\n        }))),\n      declaredDomains: sources.declaredDomains.get(profile.id) ?? [],\n      recognitions: sources.recognitions.filter((r) => r.profileId === profile.id),\n      connectionTiers,\n      networkReliabilityPoints: network.reliability.points,\n    }));\n  }\n\n  const reliabilityScore = v13 ? v13.score : v12ReliabilityScore;\n\n  return {\n    profile,\n    practiceCount,\n    evidenceBackedPracticeCount,\n    evidenceItemCount: evidenceItems.length,\n    evidenceTypes,\n    connectionCount,\n    confirmedConnectionCount,\n    pendingConnectionCount,\n    connectionTiers,\n    practicePoints,\n    evidencePoints,\n    impactConnectionPoints,\n    intrinsicImpactScore,\n    networkImpactPoints,\n    networkExactPoints,\n    networkBelowOnePoint: remainingRelationshipNetworkCapacity > 0\n      && network.impact.positive\n      && networkImpactPoints === 0,\n    rawImpactScore,\n    impactV2: impactV2 ? {\n      scale: impactV2.scale,\n      counted: impactV2.counted,\n      unreadCount: impactV2.unreadCount,\n      heldCount: impactV2.heldCount,\n      sameWorkCount: impactV2.sameWorkCount,\n      levels: impactV2.contributions.reduce<Record<string, number>>((m, c) => ({ ...m, [c.countedLevel]: (m[c.countedLevel] ?? 0) + 1 }), {}),\n    } : null,\n    reliability: {\n      score: reliabilityScore,\n      grade: v13 ? v13.grade : getReliabilityGrade(reliabilityScore),\n      modelVersion: RELIABILITY_MODEL_VERSION,\n      /**\n       * v1.3's account of itself: the tier, why, and what each component\n       * contributed. Null under v1.2, which has no tier to report — a score\n       * nobody can explain is a score nobody should believe, and this is what\n       * a future panel would render from.\n       */\n      v13: v13 ? {\n        tier: v13.tier,\n        tierLabel: v13.tierLabel,\n        q: v13.q,\n        reasons: v13.reasons,\n        terms: v13.terms,\n        contributions: v13.contributions,\n        measures: v13.measures,\n        sourceMix: v13.sourceMix,\n      } : null,\n      intrinsicScore: intrinsicReliabilityScore,\n      networkPoints: network.reliability.points,\n      networkPointsExact: network.reliability.exact,\n      networkBelowOnePoint: network.reliability.positive && network.reliability.points === 0,\n      evidenceCoveragePoints,\n      profileCompletenessPoints,\n      connectionPoints: reliabilityConnectionPoints,\n      evidenceDiversityPoints,\n      evidenceBackedPracticeCount,\n      practiceCount,\n      evidenceItemCount: evidenceItems.length,\n      evidenceTypes,\n      connectionCount,\n      confirmedConnectionCount,\n      pendingConnectionCount,\n      connectionTiers,\n    },\n  };\n}\n\nfunction getAverageRank(sortedFacts: ScoreFacts[], fact: ScoreFacts): number {\n  const firstIndex = sortedFacts.findIndex((candidate) => candidate.rawImpactScore === fact.rawImpactScore);\n  let lastIndex = firstIndex;\n  for (let index = firstIndex + 1; index < sortedFacts.length; index += 1) {\n    if (sortedFacts[index].rawImpactScore !== fact.rawImpactScore) break;\n    lastIndex = index;\n  }\n  return ((firstIndex + 1) + (lastIndex + 1)) / 2;\n}\n\n/**\n * Calculates public scores only from existing, publicly readable profile\n * records. It never writes score data: a profile's score always reflects the\n * practices, linked evidence and ecosystem connections currently on record.\n */\nexport function calculateProfileScores(\n  kind: ProfileKind,\n  profiles: ProfileScoreProfileInput[],\n  improvements: ProfileScoreImprovementInput[],\n  evidenceItems: ProfileScoreEvidenceInput[],\n  connections: ProfileScoreConnectionInput[],\n  /**\n   * v1.2 ecosystem points per profile. Empty for an intrinsic-only pass,\n   * which is what the graph itself is built from.\n   */\n  networkImpactByProfile: ReadonlyMap<string, NetworkImpactShare> = new Map(),\n  /** Reliability v1.3 only; ignored while '1.2' is the published model. */\n  sources?: ProfileScoreSourceInputs,\n): Map<string, ProfileScoreResult> {\n  const evidenceByImprovement = new Map<string, ProfileScoreEvidenceInput[]>();\n  for (const evidence of evidenceItems) {\n    const current = evidenceByImprovement.get(evidence.improvementId) ?? [];\n    current.push(evidence);\n    evidenceByImprovement.set(evidence.improvementId, current);\n  }\n\n  // Tiers are resolved per profile and per distinct counterparty, so the same\n  // relationship recorded twice — or mirrored in the opposite direction — is\n  // worth exactly what it was worth once.\n  const connectionTiersByProfile = countConnectionTiersByProfile(connections);\n  const relationshipCategoryByProfile = countRelationshipCategoriesByProfile(connections);\n\n  const factsById = new Map(\n    profiles.map((profile) => [\n      profile.id,\n      buildScoreFacts(\n        kind,\n        profile,\n        improvements,\n        evidenceByImprovement,\n        connectionTiersByProfile.get(profile.id) ?? emptyConnectionTierCounts(),\n        relationshipCategoryByProfile.get(profile.id) ?? emptyRelationshipSemanticCounts(),\n        networkImpactByProfile.get(profile.id) ?? NO_NETWORK_IMPACT,\n        sources,\n      ),\n    ]),\n  );\n  const eligibleFacts = [...factsById.values()]\n    .filter((fact) => fact.practiceCount > 0)\n    .sort((a, b) => b.rawImpactScore - a.rawImpactScore || a.profile.id.localeCompare(b.profile.id));\n  const benchmark = getImpactBenchmarkState({\n    eligibleProfileCount: eligibleFacts.length,\n    relevantProfileCount: eligibleFacts.filter((fact) => fact.rawImpactScore >= MINIMUM_RELATIVE_BENCHMARK_RELEVANT_RAW_SCORE).length,\n    highestAccumulatedPoints: eligibleFacts[0]?.rawImpactScore ?? 0,\n  });\n  const impactMode = IMPACT_BENCHMARK_MODE[kind];\n  const results = new Map<string, ProfileScoreResult>();\n\n  for (const [profileId, facts] of factsById) {\n    if (facts.practiceCount === 0) {\n      results.set(profileId, { status: 'unscored', kind, impact: null, reliability: null });\n      continue;\n    }\n\n    const rank = impactMode === 'relative' ? getAverageRank(eligibleFacts, facts) : null;\n    const impactScore = impactMode === 'accumulating'\n      ? Math.min(facts.rawImpactScore, MAX_PUBLIC_IMPACT_VALUE)\n      : eligibleFacts.length === 1\n        ? 100\n        : 1 + Math.round(99 * ((eligibleFacts.length - rank!) / (eligibleFacts.length - 1)));\n    const impact: ProfileImpactScore = {\n      mode: impactMode,\n      score: impactScore,\n      rawScore: Math.min(facts.rawImpactScore, MAX_PUBLIC_IMPACT_VALUE),\n      practiceCount: facts.practiceCount,\n      evidenceBackedPracticeCount: facts.evidenceBackedPracticeCount,\n      practicePoints: facts.practicePoints,\n      evidencePoints: facts.evidencePoints,\n      connectionPoints: facts.impactConnectionPoints,\n      intrinsicScore: Math.min(facts.intrinsicImpactScore, MAX_PUBLIC_IMPACT_VALUE),\n      networkPoints: facts.networkImpactPoints,\n      networkPointsExact: facts.networkExactPoints,\n      networkBelowOnePoint: facts.networkBelowOnePoint,\n      connectionCount: facts.connectionCount,\n      confirmedConnectionCount: facts.confirmedConnectionCount,\n      pendingConnectionCount: facts.pendingConnectionCount,\n      connectionTiers: facts.connectionTiers,\n      rank,\n      modelVersion: activeImpactModel(),\n      v2: facts.impactV2 ?? null,\n      benchmark: {\n        isReady: benchmark.isReady,\n        eligibleProfileCount: benchmark.eligibleProfileCount,\n        relevantProfileCount: benchmark.relevantProfileCount,\n        highestAccumulatedPoints: Math.min(benchmark.highestAccumulatedPoints, MAX_PUBLIC_IMPACT_VALUE),\n      },\n    };\n    results.set(profileId, { status: 'scored', kind, impact, reliability: facts.reliability });\n  }\n\n  return results;\n}\n\nfunction sourceColumn(kind: ProfileKind): string {\n  return `source_${kind}_id`;\n}\n\nfunction targetColumn(kind: ProfileKind): string {\n  return `target_${kind}_id`;\n}\n\n/**\n * Every endpoint column of both sides. v1.1 counts distinct counterparties\n * rather than rows, and a counterparty is frequently another profile kind, so\n * the far end has to be identified across all four kinds — the two columns\n * belonging to the kind being scored are not enough to tell two different\n * counterparties apart.\n */\nconst ENDPOINT_KINDS: readonly ProfileKind[] = ['entidad', 'persona', 'territorio', 'producto_servicio'];\nconst SOURCE_ENDPOINT_COLUMNS = ENDPOINT_KINDS.map(sourceColumn);\nconst TARGET_ENDPOINT_COLUMNS = ENDPOINT_KINDS.map(targetColumn);\n\nfunction endpointKey(row: Record<string, unknown>, columns: readonly string[]): string | null {\n  for (const column of columns) {\n    const value = row[column];\n    if (typeof value === 'string' && value.length > 0) return value;\n  }\n  return null;\n}\n\n/** One profile kind's slice of the ecosystem-wide read. */\nexport interface KindScoringInputs {\n  profiles: ProfileScoreProfileInput[];\n  improvements: ProfileScoreImprovementInput[];\n  evidenceItems: ProfileScoreEvidenceInput[];\n  connections: ProfileScoreConnectionInput[];\n}\n\nexport interface EcosystemScoringData {\n  byKind: Record<ProfileKind, KindScoringInputs>;\n  /** Every published relationship, resolved to one symmetric edge trust. */\n  edges: NetworkEdge[];\n  /** Published profiles withholding their public score, across all kinds. */\n  hiddenProfileIds: Set<string>;\n  /**\n   * Source identity, loaded only when Reliability v1.3 is the published model.\n   * Null under v1.2, and under v1.2 the queries behind it are never issued —\n   * so switching models changes what is read, not merely what is computed.\n   */\n  sources: ProfileScoreSourceInputs | null;\n}\n\n/**\n * Reads every input v1.2 scoring needs, for the whole ecosystem, in a fixed\n * number of queries.\n *\n * Scoring stopped being a per-kind question at v1.2. A profile's ecosystem term\n * depends on profiles of *other* kinds up to three hops away, so the graph has\n * to be assembled before any kind can be scored, and assembling it per profile\n * would be an N+1 over the whole platform. Instead this runs thirteen reads\n * regardless of how many profiles or relationships exist — four profile tables,\n * four visibility probes, one read each of improvements, evidence and\n * relationships, and two tolerant exclusion probes (unofficial practices,\n * unconfirmed evidence relationships) — and everything after it is in-memory.\n *\n * The result is deliberately reusable: a caller scoring all four kinds, such as\n * the snapshot capture, loads once and passes the same data to each call.\n */\nexport async function loadEcosystemScoringData(\n  supabase: SupabaseClient,\n): Promise<EcosystemScoringData | null> {\n  const parentColumns = ENDPOINT_KINDS.map((kind) => PROFILE_CONTENT_PARENT_COLUMN[kind]);\n  /**\n   * Reliability v1.3 needs to know who published each source, which means four\n   * reads v1.2 has no use for. Gated rather than always-on: adding queries to\n   * the published scoring path for a model that is not published would be a\n   * cost and a risk with no benefit, and this way flipping the constant is the\n   * whole deploy.\n   */\n  const needsSourceIdentity: boolean = RELIABILITY_MODEL_VERSION === '1.3';\n  // Typed as `string` rather than a literal union on purpose: PostgREST's typed\n  // select parser cannot resolve a union of two column lists, and every other\n  // dynamic select in this function reaches the same generic path by joining an\n  // array. Keeping the shape consistent is what keeps the file readable.\n  const profileColumns: string = needsSourceIdentity || IMPACT_MODEL_VERSION === '2.0'\n    ? 'id, title, category, description, location, website, image_path'\n    : 'id, description, location, website, image_path';\n\n  const [\n    profileResults,\n    hiddenResults,\n    improvementsResult,\n    unofficialImprovementsResult,\n    evidenceResult,\n    documentsResult,\n    domainsResult,\n    recognitionsResult,\n    unconfirmedEvidenceResult,\n    connectionsResult,\n  ] =\n    await Promise.all([\n      Promise.all(ENDPOINT_KINDS.map((kind) => supabase\n        .from(PROFILE_KIND_TABLE[kind])\n        .select(profileColumns)\n        // Explicit, not left to RLS. An administrator can see suppressed\n        // profiles, and without this filter the ecosystem an administrator\n        // scored against would differ from the public one — the same profile\n        // would show two different Impacts depending on who was looking. A\n        // score has to be a property of the data, not of the reader.\n        .eq('publication_status', 'published'))),\n      // Asked for separately and tolerantly, per kind.\n      //\n      // It is an extra round trip, and deliberately so: folding the column into\n      // the queries above would make every score depend on the visibility\n      // migration having been applied. Preview and production share one\n      // database, so that dependency would break scoring outright on any\n      // deployment running ahead of its migration. Read on its own, a missing\n      // column costs nothing — before the migration nothing could be hidden\n      // anyway, so treating everything as visible is not a fallback, it is the\n      // correct answer.\n      Promise.all(ENDPOINT_KINDS.map((kind) => supabase\n        .from(PROFILE_KIND_TABLE[kind])\n        .select('id')\n        .eq('public_scores_hidden', true))),\n      supabase\n        .from('entity_improvements')\n        .select([\n          'id',\n          ...(needsSourceIdentity || IMPACT_MODEL_VERSION === '2.0' ? ['title', 'description', 'theme_slug'] : []),\n          ...parentColumns,\n        ].join(', ')),\n      // Practices that must NOT be scored, asked for separately and\n      // tolerantly, exactly as the visibility probe above is.\n      //\n      // Folding `.in('contribution_provenance', ...)` into the query above\n      // would make every score on the platform depend on the provenance\n      // migration having been applied. Preview and production share one\n      // database, so a deployment running ahead of its migration would get\n      // error 42703 and return no scores at all. Read on its own, a missing\n      // column means nothing is excluded -- which is not a fallback but the\n      // correct answer, because before that migration no unofficial practice\n      // could exist.\n      supabase\n        .from('entity_improvements')\n        .select('id')\n        .not('contribution_provenance', 'in', `(${SCORED_CONTRIBUTION_PROVENANCE.join(',')})`),\n      supabase\n        .from('sustainable_practice_evidence')\n        .select('id, improvement_id, photo_id, video_id, document_id'),\n      // Source identity, for Reliability v1.3 only. Each read is tolerant of\n      // its table or column being absent, exactly as the exclusion probes are:\n      // documents predate everything, `profile_domains` arrives with Phase 2A,\n      // and recognitions gain their scope columns in the same migration. A\n      // database that is behind must degrade to \"no declared identity\", never\n      // to \"no scores\".\n      needsSourceIdentity\n        ? supabase.from('entity_documents')\n          .select(['id', 'url', 'storage_path', 'title', 'description', ...parentColumns].join(', '))\n        : Promise.resolve({ data: [], error: null }),\n      needsSourceIdentity\n        ? supabase.from('profile_domains')\n          .select(['domain', 'account_path', 'relation', 'declared_by', ...parentColumns].join(', '))\n        : Promise.resolve({ data: [], error: null }),\n      needsSourceIdentity\n        ? supabase.from('entity_recognitions')\n          .select(['id', 'scope', 'improvement_id', 'issuer_domain', 'url', 'title', ...parentColumns].join(', '))\n        : Promise.resolve({ data: [], error: null }),\n      // Evidence relationships that have not been confirmed, asked for\n      // separately and tolerantly for exactly the reason the practice-provenance\n      // probe above is.\n      //\n      // 20260829090100 gave this table a review state, because an AI proposal\n      // and a community suggestion must be able to exist on the record without\n      // being believed. Only 'confirmed' scores. Every row that existed before\n      // that migration backfills to 'confirmed', so this moves no published\n      // number on the day it ships and SCORING_MODEL_VERSION is NOT bumped — the\n      // same reasoning recorded above for SCORED_CONTRIBUTION_PROVENANCE.\n      //\n      // A 42703 means the migration has not run on this database, so nothing is\n      // excluded. That is not a fallback: before it ran, an unconfirmed\n      // relationship could not exist.\n      supabase\n        .from('sustainable_practice_evidence')\n        .select('id')\n        .not('status', 'in', `(${SCORED_EVIDENCE_LINK_STATUS.join(',')})`),\n      supabase\n        .from('ecosystem_connections')\n        .select(\n          `id, ${[...SOURCE_ENDPOINT_COLUMNS, ...TARGET_ENDPOINT_COLUMNS].join(', ')}, `\n          + 'source_position, target_position, relation_label, scoring_eligible, verified_at',\n        )\n        // Publication is the whole gate. scoring_eligible is read only to keep\n        // visible-but-ineligible relationships at zero Impact instead of hiding them.\n        //\n        // RLS does not restrict by publication state, so this filter is what\n        // keeps withdrawn and obsolete relationships out of scoring. It is\n        // load-bearing, not decoration.\n        .eq('publication_status', 'published'),\n    ]);\n\n  const failure = profileResults.find((result) => result.error)?.error\n    ?? improvementsResult.error ?? evidenceResult.error ?? connectionsResult.error;\n  if (failure) {\n    console.error('Unable to load profile scoring inputs', failure.message);\n    return null;\n  }\n\n  // Practices that carry somebody else's voice, or an unverified steward's,\n  // are read out here rather than filtered in the query above -- see the probe\n  // for why. A 42703 means the provenance migration has not run on this\n  // database, so nothing is excluded; anything else fails closed, because\n  // silently scoring unofficial practices would inflate published Impact.\n  const unofficialImprovementIds = new Set<string>();\n  if (unofficialImprovementsResult.error) {\n    if (unofficialImprovementsResult.error.code !== '42703') {\n      console.error(\n        'Unable to load practice provenance',\n        unofficialImprovementsResult.error.message,\n      );\n      return null;\n    }\n  } else {\n    for (const row of unofficialImprovementsResult.data ?? []) {\n      unofficialImprovementIds.add((row as { id: string }).id);\n    }\n  }\n\n  const improvementRows = ((improvementsResult.data ?? []) as unknown as Array<Record<string, unknown>>)\n    .filter((row) => !unofficialImprovementIds.has(row.id as string));\n  // Relationships still awaiting confirmation, or disputed or retracted since.\n  // Read out here rather than filtered in the query above — see the probe for\n  // why. A 42703 means the review-state migration has not run, so nothing is\n  // excluded; anything else fails closed, because silently counting proposals\n  // would let anyone with a link picker inflate published Reliability.\n  const unconfirmedEvidenceIds = new Set<string>();\n  if (unconfirmedEvidenceResult.error) {\n    if (unconfirmedEvidenceResult.error.code !== '42703') {\n      console.error(\n        'Unable to load evidence relationship status',\n        unconfirmedEvidenceResult.error.message,\n      );\n      return null;\n    }\n  } else {\n    for (const row of unconfirmedEvidenceResult.data ?? []) {\n      unconfirmedEvidenceIds.add((row as unknown as { id: string }).id);\n    }\n  }\n\n  const evidenceItems: ProfileScoreEvidenceInput[] = [];\n  for (const row of (evidenceResult.data ?? []) as unknown as Array<Record<string, unknown>>) {\n    if (unconfirmedEvidenceIds.has(row.id as string)) continue;\n    const improvementId = row.improvement_id as string;\n    if (row.photo_id) evidenceItems.push({ id: row.id as string, mediaId: row.photo_id as string, improvementId, type: 'photo' });\n    else if (row.video_id) evidenceItems.push({ id: row.id as string, mediaId: row.video_id as string, improvementId, type: 'video' });\n    else if (row.document_id) {\n      evidenceItems.push({ id: row.id as string, improvementId, type: 'document', documentId: row.document_id as string });\n    }\n  }\n  const connectionRows = (connectionsResult.data ?? []) as unknown as Array<Record<string, unknown>>;\n\n  const byKind = {} as Record<ProfileKind, KindScoringInputs>;\n  ENDPOINT_KINDS.forEach((kind, index) => {\n    const parentColumn = PROFILE_CONTENT_PARENT_COLUMN[kind];\n    const source = sourceColumn(kind);\n    const target = targetColumn(kind);\n    byKind[kind] = {\n      profiles: ((profileResults[index].data ?? []) as unknown as Array<Record<string, unknown>>)\n        .map((row) => ({\n          id: row.id as string,\n          description: (row.description ?? null) as string | null,\n          location: (row.location ?? null) as string | null,\n          website: (row.website ?? null) as string | null,\n          imagePath: (row.image_path ?? null) as string | null,\n          title: (row.title ?? null) as string | null,\n          category: (row.category ?? null) as string | null,\n        })),\n      improvements: improvementRows\n        .map((row) => ({\n          id: row.id as string,\n          profileId: row[parentColumn] as string | null,\n          title: (row.title ?? null) as string | null,\n          description: (row.description ?? null) as string | null,\n          themeSlug: (row.theme_slug ?? null) as string | null,\n        }))\n        .filter((row) => Boolean(row.profileId))\n        .map((row) => ({ ...row, profileId: row.profileId as string })),\n      evidenceItems,\n      connections: connectionRows\n        .filter((row) => row[source] != null || row[target] != null)\n        .map((row) => ({\n          sourceProfileId: row[source] as string | null,\n          id: row.id as string,\n          targetProfileId: row[target] as string | null,\n          sourceKey: endpointKey(row, SOURCE_ENDPOINT_COLUMNS),\n          targetKey: endpointKey(row, TARGET_ENDPOINT_COLUMNS),\n          relationLabel: (row.relation_label as string | null) ?? null,\n          scoringEligible: row.scoring_eligible === true,\n          sourcePosition: (row.source_position as ParticipantPosition) ?? 'none',\n          targetPosition: (row.target_position as ParticipantPosition) ?? 'none',\n          isIndependentlyVerified: row.verified_at != null,\n        })),\n    };\n  });\n\n  const edges: NetworkEdge[] = [];\n  for (const row of connectionRows) {\n    const a = endpointKey(row, SOURCE_ENDPOINT_COLUMNS);\n    const b = endpointKey(row, TARGET_ENDPOINT_COLUMNS);\n    if (!a || !b) continue;\n    edges.push({\n      id: row.id as string,\n      a,\n      b,\n      impactEligible: resolveRelationshipCategory({\n        relationLabel: (row.relation_label as string | null) ?? null,\n        scoringEligible: row.scoring_eligible === true,\n      }) !== 'technical/admin/duplicate',\n      trust: resolveEdgeTrust({\n        sourcePosition: (row.source_position as string) ?? 'none',\n        targetPosition: (row.target_position as string) ?? 'none',\n        isIndependentlyVerified: row.verified_at != null,\n      }),\n    });\n  }\n\n  const hiddenProfileIds = new Set<string>();\n  for (const hidden of hiddenResults) {\n    if (hidden.error) {\n      // 42703 is \"column does not exist\": the visibility migration has not run\n      // on this database yet, so nothing can be hidden. Anything else fails\n      // closed — an unreadable visibility list must never let a withheld score\n      // be published, so no scores are returned at all rather than some of\n      // them being wrongly public.\n      if (hidden.error.code !== '42703') {\n        console.error('Unable to load public score visibility', hidden.error.message);\n        return null;\n      }\n      continue;\n    }\n    for (const row of hidden.data ?? []) hiddenProfileIds.add(row.id as string);\n  }\n\n  // Source identity for Reliability v1.3. Each read is tolerant: a table or a\n  // column that is not there yet means \"nothing declared\", which is the correct\n  // reading rather than a fallback — before Phase 2A nobody could declare\n  // anything. Only a failure that is NOT a missing schema stops scoring.\n  let sources: ProfileScoreSourceInputs | null = null;\n  if (needsSourceIdentity) {\n    const missingSchema = (result: { error: { code?: string } | null }) =>\n      Boolean(result.error) && MISSING_SCHEMA_ERROR_CODES.includes(result.error?.code ?? '');\n    for (const [label, result] of [\n      ['documents', documentsResult],\n      ['declared source domains', domainsResult],\n      ['recognitions', recognitionsResult],\n    ] as const) {\n      if (result.error && !missingSchema(result)) {\n        console.error(`Unable to load ${label}`, result.error.message);\n        return null;\n      }\n    }\n    const parentOf = (row: Record<string, unknown>) =>\n      (parentColumns.map((column) => row[column]).find((value) => value != null) ?? null) as string | null;\n\n    const declaredDomains = new Map<string, Array<Pick<ProfileDomainRow, 'domain' | 'account_path' | 'relation' | 'declared_by'>>>();\n    for (const row of (domainsResult.data ?? []) as unknown as Array<Record<string, unknown>>) {\n      const profileId = parentOf(row);\n      if (!profileId) continue;\n      const list = declaredDomains.get(profileId) ?? [];\n      list.push({\n        domain: row.domain as string,\n        account_path: (row.account_path ?? null) as string | null,\n        relation: row.relation as ProfileDomainRow['relation'],\n        declared_by: row.declared_by as ProfileDomainRow['declared_by'],\n      });\n      declaredDomains.set(profileId, list);\n    }\n\n    sources = {\n      documents: ((documentsResult.data ?? []) as unknown as Array<Record<string, unknown>>)\n        .map((row) => ({\n          id: row.id as string,\n          profileId: parentOf(row) ?? '',\n          url: (row.url ?? null) as string | null,\n          storagePath: (row.storage_path ?? null) as string | null,\n          title: (row.title ?? null) as string | null,\n          description: (row.description ?? null) as string | null,\n        }))\n        .filter((document) => document.profileId !== ''),\n      declaredDomains,\n      recognitions: ((recognitionsResult.data ?? []) as unknown as Array<Record<string, unknown>>)\n        .map((row) => ({\n          id: row.id as string,\n          profileId: parentOf(row) ?? '',\n          scope: row.scope === 'practice' ? ('practice' as const) : ('organisation' as const),\n          improvementId: (row.improvement_id ?? null) as string | null,\n          issuerDomain: (row.issuer_domain ?? null) as string | null,\n          url: (row.url ?? null) as string | null,\n          title: (row.title ?? null) as string | null,\n        }))\n        .filter((recognition) => recognition.profileId !== ''),\n    };\n  }\n\n  return { byKind, edges, hiddenProfileIds, sources };\n}\n\n/**\n * Everyone's ecosystem Impact points, computed once for the whole platform.\n *\n * Two passes, and the order matters. The first scores every kind with no\n * network term at all, which yields each profile's *intrinsic* value — the v1.1\n * number, untouched. The graph is then built from those intrinsic values only,\n * so what travels it can never itself have been moved by the network, and a\n * cycle carries no feedback. The second pass, back in fetchProfileScores, adds\n * the resulting points.\n *\n * The propagating quantity is intrinsic **raw** points rather than the\n * published 1-100 Impact. Raw points are an absolute measure of what a profile\n * has actually recorded; the published score is a rank within its own kind's\n * population, so propagating it would let one profile's ecosystem term move\n * when an unrelated profile of another kind published evidence.\n *\n * Reliability propagates on the same principle and from the same pass: what\n * travels is v1.1's intrinsic Reliability, never the assurance-adjusted total.\n */\nconst networkPointsCache = new WeakMap<EcosystemScoringData, Map<string, NetworkImpactShare>>();\n\nexport function computeEcosystemNetworkPoints(\n  data: EcosystemScoringData,\n): Map<string, NetworkImpactShare> {\n  // One ecosystem read scores four kinds, and the network term is the same for\n  // all of them. Computed once per dataset, not once per kind.\n  const cached = networkPointsCache.get(data);\n  if (cached) return cached;\n  const nodes = new Map<string, NetworkNode>();\n\n  for (const kind of ENDPOINT_KINDS) {\n    const inputs = data.byKind[kind];\n    const intrinsic = calculateProfileScores(\n      kind,\n      inputs.profiles,\n      inputs.improvements,\n      inputs.evidenceItems,\n      inputs.connections,\n    );\n    for (const profile of inputs.profiles) {\n      const result = intrinsic.get(profile.id);\n      nodes.set(profile.id, {\n        intrinsicImpact: result?.impact?.intrinsicScore ?? 0,\n        intrinsicReliability: result?.reliability?.intrinsicScore ?? 0,\n        // Withholding a score is a decision about display, not about the\n        // ecosystem. A hidden profile propagates its sustainability in full,\n        // receives in full, and is scored internally exactly as it would have\n        // been -- the flag reaches the model only as a small discount on the\n        // *assurance* it lends, because openness is part of what assurance\n        // means. Privacy is protected by what the explanation may say (see\n        // publishableDetail), not by removing the profile from the graph.\n        //\n        // Unpublished profiles are a different matter and are absent from\n        // `nodes` altogether -- they were never read -- so they cannot\n        // contribute and cannot bridge.\n        scoresPublic: !data.hiddenProfileIds.has(profile.id),\n      });\n    }\n  }\n\n  const graph = buildNetworkGraph(nodes, data.edges);\n  const impactEdges = data.edges.filter((edge) => edge.impactEligible !== false);\n  const impactGraph = impactEdges.length === data.edges.length\n    ? graph\n    : buildNetworkGraph(nodes, impactEdges);\n  const points = new Map<string, NetworkImpactShare>();\n  for (const profileId of nodes.keys()) {\n    const influence = computeNetworkInfluence(graph, profileId);\n    const impactInfluence = impactGraph === graph\n      ? influence\n      : computeNetworkInfluence(impactGraph, profileId);\n    const publishableDetail = influence.publishableDetail && impactInfluence.publishableDetail;\n    if (impactInfluence.impact.exactPoints <= 0 && influence.reliability.exactPoints <= 0) continue;\n    // Rounded to a tenth: the figures are presentational, and a profile does\n    // not need eleven decimal places of its own ecosystem. Null where showing\n    // them would let a reader subtract their way to a withheld score.\n    const tenth = (value: number) => Math.round(value * 10) / 10;\n    const share = (term: { points: number; exactPoints: number }): NetworkTermShare => ({\n      points: term.points,\n      exact: publishableDetail ? tenth(term.exactPoints) : null,\n      positive: term.exactPoints > 0,\n    });\n    points.set(profileId, {\n      impact: share(impactInfluence.impact),\n      reliability: share(influence.reliability),\n    });\n  }\n  networkPointsCache.set(data, points);\n  return points;\n}\n\n/**\n * The public scores for one profile kind.\n *\n * @param revealHiddenForProfileIds Profiles whose withheld score the caller is\n *   entitled to see unmasked — in practice the one profile a verified\n *   representative or an administrator is currently looking at. Empty by\n *   default, so every other caller, including every directory and the homepage,\n *   is masked without having to remember.\n * @param preloaded An ecosystem read to reuse. Callers scoring several kinds in\n *   a row should load once and pass it, rather than re-reading the platform\n *   four times.\n */\nexport async function fetchProfileScores(\n  supabase: SupabaseClient,\n  kind: ProfileKind,\n  revealHiddenForProfileIds: readonly string[] = [],\n  preloaded?: EcosystemScoringData,\n): Promise<Map<string, ProfileScoreResult>> {\n  const data = preloaded ?? await loadEcosystemScoringData(supabase);\n  if (!data) return new Map();\n\n  const inputs = data.byKind[kind];\n  const results = calculateProfileScores(\n    kind,\n    inputs.profiles,\n    inputs.improvements,\n    inputs.evidenceItems,\n    inputs.connections,\n    computeEcosystemNetworkPoints(data),\n    // Null under v1.2, in which case the v1.3 path is unreachable anyway. The\n    // intrinsic pass above deliberately does NOT get it: what travels the graph\n    // is v1.1's intrinsic Impact, and Reliability never propagates at all.\n    data.sources ?? undefined,\n  );\n\n  // Masking is applied after calculation, never before it: the score is still\n  // computed, still snapshot-able and still visible to its representative.\n  // Only the public presentation changes.\n  const reveal = new Set(revealHiddenForProfileIds);\n  for (const profileId of data.hiddenProfileIds) {\n    if (reveal.has(profileId) || !results.has(profileId)) continue;\n    results.set(profileId, hiddenProfileScore(kind));\n  }\n\n  return results;\n}\n",
    "lib/profile-impact-points.ts": "export const MAX_PUBLIC_IMPACT_VALUE = 95;\nexport const IMPACT_INITIAL_PRACTICE_COUNT = 3;\nexport const IMPACT_INITIAL_PRACTICE_POINTS = 5;\nexport const IMPACT_EXTENDED_PRACTICE_COUNT = 4;\nexport const IMPACT_EXTENDED_PRACTICE_POINTS = 3;\nexport const IMPACT_ADDITIONAL_PRACTICE_POINTS = 1;\n\n/** Rewards a starting set of meaningful actions while avoiding linear score inflation. */\nexport function getImpactPracticePoints(practiceCount: number): number {\n  const initialPractices = Math.min(practiceCount, IMPACT_INITIAL_PRACTICE_COUNT);\n  const extendedPractices = Math.min(\n    Math.max(practiceCount - IMPACT_INITIAL_PRACTICE_COUNT, 0),\n    IMPACT_EXTENDED_PRACTICE_COUNT,\n  );\n  const additionalPractices = Math.max(\n    practiceCount - IMPACT_INITIAL_PRACTICE_COUNT - IMPACT_EXTENDED_PRACTICE_COUNT,\n    0,\n  );\n\n  return (initialPractices * IMPACT_INITIAL_PRACTICE_POINTS)\n    + (extendedPractices * IMPACT_EXTENDED_PRACTICE_POINTS)\n    + (additionalPractices * IMPACT_ADDITIONAL_PRACTICE_POINTS);\n}\n",
    "lib/profile-score-benchmark.ts": "import type { ProfileKind } from '@/types/profile';\nimport type { ProfileImpactMode } from '@/types/profile-score';\n\nexport const MINIMUM_RELATIVE_BENCHMARK_PROFILE_COUNT = 25;\nexport const MINIMUM_RELATIVE_BENCHMARK_RELEVANT_PROFILE_COUNT = 5;\nexport const MINIMUM_RELATIVE_BENCHMARK_RELEVANT_RAW_SCORE = 25;\nexport const MINIMUM_RELATIVE_BENCHMARK_TOP_RAW_SCORE = 95;\n\n/**\n * The published Impact scale is an explicit scoring-model decision. Readiness\n * remains useful to administrators, but crossing a data threshold must never\n * silently change what a public score means.\n */\nexport const IMPACT_BENCHMARK_MODE: Readonly<Record<ProfileKind, ProfileImpactMode>> = {\n  entidad: 'accumulating',\n  persona: 'accumulating',\n  territorio: 'accumulating',\n  producto_servicio: 'accumulating',\n};\n\nexport interface ImpactBenchmarkState {\n  isReady: boolean;\n  eligibleProfileCount: number;\n  relevantProfileCount: number;\n  highestAccumulatedPoints: number;\n}\n\nexport function getImpactBenchmarkState({\n  eligibleProfileCount,\n  relevantProfileCount,\n  highestAccumulatedPoints,\n}: Omit<ImpactBenchmarkState, 'isReady'>): ImpactBenchmarkState {\n  return {\n    eligibleProfileCount,\n    relevantProfileCount,\n    highestAccumulatedPoints,\n    isReady: eligibleProfileCount >= MINIMUM_RELATIVE_BENCHMARK_PROFILE_COUNT\n      && relevantProfileCount >= MINIMUM_RELATIVE_BENCHMARK_RELEVANT_PROFILE_COUNT\n      && highestAccumulatedPoints >= MINIMUM_RELATIVE_BENCHMARK_TOP_RAW_SCORE,\n  };\n}\n",
    "lib/profile-connection-tiers.ts": "/**\n * Scoring Model v1.1 relationship semantics.\n *\n * Two ideas replace v1.0's single verdict per relationship.\n *\n * First, a relationship is scored *from a profile's point of view* rather than\n * as one global fact. The same edge is worth different amounts to its two\n * endpoints, because what each of them has actually put on the record differs.\n * A profile that declared a relationship has staked something; the silent\n * counterparty has not, and must not inherit that credit.\n *\n * Second, each trust tier saturates on its own curve instead of everything\n * flowing into one weighted total. That is what makes volume useless as a\n * substitute for trust: the community tier has its own low ceiling, so no\n * number of unattested observations can ever reach what a handful of confirmed\n * relationships is worth.\n *\n * This module holds no value imports on purpose. It is the numeric core of the\n * model, and staying importable by a plain Node test runner is what lets the\n * curve be asserted with real arithmetic rather than by reading the source.\n */\n\n/** A relationship's worth to one endpoint, in ascending order of trust. */\nexport type ConnectionTier = 'observed' | 'declared' | 'confirmed' | 'verified';\n\n/**\n * What a relationship contributes. 'disputed' is a real state, not an absence:\n * someone declined, so the edge stays in the graph and in the audit history\n * but scores nothing for either endpoint.\n */\nexport type ConnectionScoringTier = ConnectionTier | 'disputed';\n\nexport const CONNECTION_TIERS: readonly ConnectionTier[] = [\n  'observed',\n  'declared',\n  'confirmed',\n  'verified',\n];\n\nexport type ConnectionTierCounts = Record<ConnectionTier, number> & { disputed: number };\n\nexport function emptyConnectionTierCounts(): ConnectionTierCounts {\n  return { observed: 0, declared: 0, confirmed: 0, verified: 0, disputed: 0 };\n}\n\nexport type ParticipantPosition = 'none' | 'declared' | 'confirmed' | 'declined';\n\n/** One relationship as seen from one of its two endpoints. */\nexport interface ConnectionPerspective {\n  /** What the profile being scored has put on the record. */\n  selfPosition: ParticipantPosition;\n  /** What the counterparty has put on the record. */\n  otherPosition: ParticipantPosition;\n  /** An EkoPulse verification of the relationship itself, independent of both. */\n  isIndependentlyVerified: boolean;\n}\n\n/**\n * A position counts as an assertion when the participant has affirmatively put\n * the relationship on the record. Declaring and confirming differ in who moved\n * first, not in how much the participant has staked, so both count.\n */\nfunction hasAsserted(position: ParticipantPosition): boolean {\n  return position === 'declared' || position === 'confirmed';\n}\n\n/**\n * The tier one relationship earns for one endpoint.\n *\n * The order of the checks is the model:\n *   1. Either side declining makes the relationship disputed. It scores zero\n *      for both, and never negative — a decline is a disagreement about a fact,\n *      not misconduct, and letting it subtract would make declining an attack.\n *   2. Independent verification is the strongest state and does not depend on\n *      either participant having spoken.\n *   3. Both sides on the record is bilateral confirmation.\n *   4. Only this profile on the record is a declaration: it has staked a claim\n *      that nobody has corroborated.\n *   5. Otherwise this profile has said nothing, and the relationship is a\n *      community observation *to it* — even if the counterparty declared it.\n *      That asymmetry is deliberate and is what stops a profile from farming\n *      participant credit by declaring relationships at silent third parties.\n */\nexport function resolveConnectionTier(perspective: ConnectionPerspective): ConnectionScoringTier {\n  if (perspective.selfPosition === 'declined' || perspective.otherPosition === 'declined') {\n    return 'disputed';\n  }\n  if (perspective.isIndependentlyVerified) return 'verified';\n  if (hasAsserted(perspective.selfPosition) && hasAsserted(perspective.otherPosition)) {\n    return 'confirmed';\n  }\n  if (hasAsserted(perspective.selfPosition)) return 'declared';\n  return 'observed';\n}\n\n/**\n * Saturating tier contribution: `cap × n / (n + k)`.\n *\n * A Michaelis-Menten curve, chosen because it is bounded, monotonic, has no\n * discontinuities to argue about, and is explainable in one sentence: `k` is\n * the number of relationships at which the tier delivers half its ceiling, and\n * the ceiling is never reached. Linear points per relationship would make\n * score a function of effort spent adding rows; this makes the first\n * relationship in a tier worth far more than the fiftieth.\n */\nexport function saturatingContribution(cap: number, halfSaturation: number, count: number): number {\n  if (count <= 0) return 0;\n  return (cap * count) / (count + halfSaturation);\n}\n\nexport interface TierCurve {\n  cap: number;\n  halfSaturation: number;\n}\n\n/**\n * Impact tier curves. Impact answers \"how much is going on around this\n * profile\", so an unattested community observation is genuinely worth\n * something — it is a real signal that someone noticed a relationship — but\n * the whole community tier is capped at 4 of the 20 available connection\n * points. Ten confirmed relationships are worth 10.0; five thousand\n * observations are worth 3.995 and can never reach 4.\n */\nexport const IMPACT_TIER_CURVES: Readonly<Record<ConnectionTier, TierCurve>> = {\n  observed: { cap: 4, halfSaturation: 6 },\n  declared: { cap: 8, halfSaturation: 5 },\n  confirmed: { cap: 14, halfSaturation: 4 },\n  verified: { cap: 18, halfSaturation: 3 },\n};\n\n/**\n * Reliability tier curves. Reliability is a claim about how much of this\n * profile is corroborated, so an observation nobody has stood behind is worth\n * almost nothing: the community tier tops out at 1 point of 15 and needs eight\n * observations to reach half of even that. Bilateral confirmation is where the\n * value is, and independent verification is strictly above it at every count.\n */\nexport const RELIABILITY_TIER_CURVES: Readonly<Record<ConnectionTier, TierCurve>> = {\n  observed: { cap: 1, halfSaturation: 8 },\n  declared: { cap: 5, halfSaturation: 4 },\n  confirmed: { cap: 12, halfSaturation: 3 },\n  verified: { cap: 15, halfSaturation: 2 },\n};\n\n/** Sums the per-tier curves. Deliberately unrounded and uncapped. */\nexport function getTieredConnectionContribution(\n  counts: ConnectionTierCounts,\n  curves: Readonly<Record<ConnectionTier, TierCurve>>,\n): number {\n  let total = 0;\n  for (const tier of CONNECTION_TIERS) {\n    const curve = curves[tier];\n    total += saturatingContribution(curve.cap, curve.halfSaturation, counts[tier]);\n  }\n  return total;\n}\n\n/**\n * Tier precedence when the same counterparty appears on more than one row.\n * Only the strongest relationship with a counterparty counts.\n *\n * 'disputed' ranks lowest rather than dominating: a decline on one edge must\n * not reach across and cancel a genuinely confirmed relationship with the same\n * counterparty. It contributes zero, and zero is simply the least of these.\n */\nconst TIER_RANK: Readonly<Record<ConnectionScoringTier, number>> = {\n  disputed: 0,\n  observed: 1,\n  declared: 2,\n  confirmed: 3,\n  verified: 4,\n};\n\n/** The minimum a relationship must expose to be counted into tiers. */\nexport interface CountableConnection {\n  /** The endpoint belonging to the profile kind being scored, if any. */\n  sourceProfileId: string | null;\n  targetProfileId: string | null;\n  /**\n   * Cross-kind endpoint identity, used only to tell counterparties apart.\n   * Defaults to the same-kind id when absent.\n   */\n  sourceKey?: string | null;\n  targetKey?: string | null;\n  sourcePosition: ParticipantPosition;\n  targetPosition: ParticipantPosition;\n  isIndependentlyVerified: boolean;\n  relationLabel?: string | null;\n  scoringEligible?: boolean | null;\n}\n\n/**\n * Counts each profile's relationships by tier, resolved to distinct\n * counterparties first.\n *\n * Deduplicating by counterparty is a load-bearing anti-gaming property, not a\n * tidiness measure. The unique index on ecosystem_connections covers the\n * *ordered* pair, so A->B and B->A are two permitted rows describing one\n * relationship; counting rows would let anyone double a relationship's worth\n * by adding its mirror. Only the strongest tier reached with each counterparty\n * survives.\n */\nexport function countConnectionTiersByProfile(\n  connections: readonly CountableConnection[],\n): Map<string, ConnectionTierCounts> {\n  const bestTierByProfile = new Map<string, Map<string, ConnectionScoringTier>>();\n\n  for (let index = 0; index < connections.length; index += 1) {\n    const connection = connections[index];\n    const sourceKey = connection.sourceKey ?? connection.sourceProfileId;\n    const targetKey = connection.targetKey ?? connection.targetProfileId;\n    const endpoints = [\n      {\n        profileId: connection.sourceProfileId,\n        counterpartyKey: targetKey,\n        selfPosition: connection.sourcePosition,\n        otherPosition: connection.targetPosition,\n      },\n      {\n        profileId: connection.targetProfileId,\n        counterpartyKey: sourceKey,\n        selfPosition: connection.targetPosition,\n        otherPosition: connection.sourcePosition,\n      },\n    ];\n\n    for (const endpoint of endpoints) {\n      if (!endpoint.profileId) continue;\n      // A relationship with no identifiable far end cannot be deduplicated\n      // against anything, so it is treated as its own counterparty rather than\n      // being silently merged with unrelated edges.\n      const counterparty = endpoint.counterpartyKey ?? `row:${index}`;\n      // A profile is not its own ecosystem.\n      if (counterparty === endpoint.profileId) continue;\n\n      const tier = resolveConnectionTier({\n        selfPosition: endpoint.selfPosition,\n        otherPosition: endpoint.otherPosition,\n        isIndependentlyVerified: connection.isIndependentlyVerified,\n      });\n      const byCounterparty =\n        bestTierByProfile.get(endpoint.profileId) ?? new Map<string, ConnectionScoringTier>();\n      const existing = byCounterparty.get(counterparty);\n      if (existing === undefined || TIER_RANK[tier] > TIER_RANK[existing]) {\n        byCounterparty.set(counterparty, tier);\n      }\n      bestTierByProfile.set(endpoint.profileId, byCounterparty);\n    }\n  }\n\n  const tiersByProfile = new Map<string, ConnectionTierCounts>();\n  for (const [profileId, byCounterparty] of bestTierByProfile) {\n    const counts = emptyConnectionTierCounts();\n    for (const tier of byCounterparty.values()) counts[tier] += 1;\n    tiersByProfile.set(profileId, counts);\n  }\n  return tiersByProfile;\n}\n\n/**\n * Relationship semantic weights for v1 scoring.\n *\n * The values are intentionally small and intentionally bounded at the model\n * level so that even a large amount of weak relationships does not dominate\n * any one profile.\n */\nexport type RelationshipSemanticCategory =\n  | 'technical/admin/duplicate'\n  | 'generic genuine association'\n  | 'sustainability-network membership'\n  | 'BOOST equipo interes'\n  | 'BOOST equipo motor'\n  | 'BOOST equipo guia'\n  | 'active working-group participation'\n  | 'coordination / leadership'\n  | 'sustainability-relevant funding/support'\n  | 'implementation partnership';\n\nexport const RELATIONSHIP_SEMANTIC_BASE_WEIGHTS: Readonly<Record<RelationshipSemanticCategory, number>> = {\n  'technical/admin/duplicate': 0,\n  'generic genuine association': 0.05,\n  'sustainability-network membership': 0.15,\n  'BOOST equipo interes': 0.15,\n  'BOOST equipo motor': 0.30,\n  'BOOST equipo guia': 0.50,\n  'active working-group participation': 0.35,\n  'coordination / leadership': 0.60,\n  'sustainability-relevant funding/support': 0.60,\n  'implementation partnership': 1.00,\n};\n\nexport const RELATIONSHIP_CATEGORY_DEMOTION = 0.6;\nexport const MAX_RELATIONSHIP_IMPACT_POINTS = 4;\n\nexport type RelationshipSemanticCounts = Record<RelationshipSemanticCategory, number>;\n\nexport function emptyRelationshipSemanticCounts(): RelationshipSemanticCounts {\n  return {\n    'technical/admin/duplicate': 0,\n    'generic genuine association': 0,\n    'sustainability-network membership': 0,\n    'BOOST equipo interes': 0,\n    'BOOST equipo motor': 0,\n    'BOOST equipo guia': 0,\n    'active working-group participation': 0,\n    'coordination / leadership': 0,\n    'sustainability-relevant funding/support': 0,\n    'implementation partnership': 0,\n  };\n}\n\nexport function normalizeRelationLabel(label: string | null | undefined): string {\n  if (!label) return '';\n  return label\n    .normalize('NFD')\n    .replace(/[\\u0300-\\u036f]/g, '')\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, ' ')\n    .replace(/\\s+/g, ' ')\n    .trim();\n}\n\nfunction isScoringEligible(connection: Pick<CountableConnection, 'scoringEligible'>): boolean {\n  return connection.scoringEligible !== false;\n}\n\nfunction mapExplicitLabel(label: string): RelationshipSemanticCategory | null {\n  const map: Record<string, RelationshipSemanticCategory> = {\n    // Explicit BOOST role labels.\n    'equipo de interes': 'BOOST equipo interes',\n    'equipo de motor': 'BOOST equipo motor',\n    'equipo de guia': 'BOOST equipo guia',\n    'equipo interes': 'BOOST equipo interes',\n    'equipo interest': 'BOOST equipo interes',\n    'equipo motor': 'BOOST equipo motor',\n    'equipo guia': 'BOOST equipo guia',\n    'grupo de trabajo activo': 'active working-group participation',\n    'active working group': 'active working-group participation',\n    'membership in sustainability network': 'sustainability-network membership',\n    'sostenibilidad network membership': 'sustainability-network membership',\n    'miembro de red': 'sustainability-network membership',\n    'coordinador': 'coordination / leadership',\n    'coordinacion': 'coordination / leadership',\n    'coordination': 'coordination / leadership',\n    'sustainability relevant funding': 'sustainability-relevant funding/support',\n    'sustainability relevant funding support': 'sustainability-relevant funding/support',\n    'financiacion': 'sustainability-relevant funding/support',\n    'partnering for implementation': 'implementation partnership',\n    'implementation partnership': 'implementation partnership',\n    'partnership implementation': 'implementation partnership',\n    'administracion tecnica': 'technical/admin/duplicate',\n    'admin': 'technical/admin/duplicate',\n    'administrativo': 'technical/admin/duplicate',\n    'tecnico': 'technical/admin/duplicate',\n    'duplicado': 'technical/admin/duplicate',\n    'duplicada': 'technical/admin/duplicate',\n  };\n\n  const direct = map[label];\n  if (direct) return direct;\n\n  // Production labels are human-readable phrases rather than enum values. The\n  // resolver therefore recognises explicit phrases anywhere in the normalised\n  // label, while remaining deterministic and conservative for unknown text.\n  if (/\\bequipo (?:de )?interes\\b/.test(label)) return 'BOOST equipo interes';\n  if (/\\bequipo (?:de )?motor\\b/.test(label)) return 'BOOST equipo motor';\n  if (/\\bequipo (?:de )?guia\\b/.test(label)) return 'BOOST equipo guia';\n  if (/\\b(?:partnering for implementation|implementation partnership|partnership implementation|alianza de implementacion|alianza para la implementacion|colaboracion para la implementacion|socio de implementacion)\\b/.test(label)) {\n    return 'implementation partnership';\n  }\n  if (/\\b(?:grupo de trabajo activo|participacion activa en (?:el )?grupo de trabajo|active working group)\\b/.test(label)) {\n    return 'active working-group participation';\n  }\n  if (/\\b(?:membership in sustainability network|sustainability network membership|miembro de red|membresia de red)\\b/.test(label)\n    || /\\bpertenencia\\b.*\\bred\\b/.test(label)) {\n    return 'sustainability-network membership';\n  }\n  if (/\\b(?:coordinador|coordinadora|coordinacion|coordination|leadership|liderazgo)\\b/.test(label)) {\n    return 'coordination / leadership';\n  }\n  if (/\\b(?:financiacion|funding|sustainability support|apoyo a la sostenibilidad)\\b/.test(label)) {\n    return 'sustainability-relevant funding/support';\n  }\n  if (/\\b(?:administracion tecnica|admin|administrativo|administrativa|tecnico|tecnica|duplicado|duplicada|duplicate)\\b/.test(label)) {\n    return 'technical/admin/duplicate';\n  }\n\n  return null;\n}\n\nexport function resolveRelationshipCategory(connection: Pick<CountableConnection, 'relationLabel' | 'scoringEligible'>): RelationshipSemanticCategory {\n  if (!isScoringEligible(connection)) return 'technical/admin/duplicate';\n\n  const normalizedLabel = normalizeRelationLabel(connection.relationLabel ?? '');\n  if (!normalizedLabel) return 'generic genuine association';\n  const direct = mapExplicitLabel(normalizedLabel);\n  if (direct) return direct;\n\n  return 'generic genuine association';\n}\n\nfunction geometricWeighting(count: number): number {\n  if (count <= 0) return 0;\n  return (1 - RELATIONSHIP_CATEGORY_DEMOTION ** count) / (1 - RELATIONSHIP_CATEGORY_DEMOTION);\n}\n\nconst RELATIONSHIP_RANK: Readonly<Record<RelationshipSemanticCategory, number>> = {\n  'technical/admin/duplicate': 0,\n  'generic genuine association': 1,\n  'sustainability-network membership': 2,\n  'BOOST equipo interes': 3,\n  'BOOST equipo motor': 4,\n  'BOOST equipo guia': 5,\n  'active working-group participation': 6,\n  'coordination / leadership': 7,\n  'sustainability-relevant funding/support': 8,\n  'implementation partnership': 9,\n};\n\nexport function getRelationshipImpactContribution(categoryCounts: RelationshipSemanticCounts): number {\n  let contribution = 0;\n  for (const [category, baseWeight] of Object.entries(RELATIONSHIP_SEMANTIC_BASE_WEIGHTS)) {\n    const count = categoryCounts[category as RelationshipSemanticCategory] ?? 0;\n    if (count <= 0) continue;\n    contribution += baseWeight * geometricWeighting(count);\n  }\n  return Math.min(MAX_RELATIONSHIP_IMPACT_POINTS, contribution);\n}\n\nexport function countRelationshipCategoriesByProfile(\n  connections: readonly CountableConnection[],\n): Map<string, RelationshipSemanticCounts> {\n  const bestCategoryByProfile = new Map<string, Map<string, RelationshipSemanticCategory>>();\n\n  for (let index = 0; index < connections.length; index += 1) {\n    const connection = connections[index];\n    const sourceKey = connection.sourceKey ?? connection.sourceProfileId;\n    const targetKey = connection.targetKey ?? connection.targetProfileId;\n    const endpoints = [\n      {\n        profileId: connection.sourceProfileId,\n        counterpartyKey: targetKey,\n        category: resolveRelationshipCategory(connection),\n      },\n      {\n        profileId: connection.targetProfileId,\n        counterpartyKey: sourceKey,\n        category: resolveRelationshipCategory(connection),\n      },\n    ];\n\n    for (const endpoint of endpoints) {\n      if (!endpoint.profileId) continue;\n      const counterparty = endpoint.counterpartyKey ?? `row:${index}`;\n      if (counterparty === endpoint.profileId) continue;\n\n      const byCounterparty =\n        bestCategoryByProfile.get(endpoint.profileId) ?? new Map<string, RelationshipSemanticCategory>();\n      const existing = byCounterparty.get(counterparty);\n      if (existing === undefined || RELATIONSHIP_RANK[endpoint.category] > RELATIONSHIP_RANK[existing]) {\n        byCounterparty.set(counterparty, endpoint.category);\n      }\n      bestCategoryByProfile.set(endpoint.profileId, byCounterparty);\n    }\n  }\n\n  const countsByProfile = new Map<string, RelationshipSemanticCounts>();\n  for (const [profileId, byCounterparty] of bestCategoryByProfile) {\n    const counts = emptyRelationshipSemanticCounts();\n    for (const category of byCounterparty.values()) {\n      counts[category] += 1;\n    }\n    countsByProfile.set(profileId, counts);\n  }\n  return countsByProfile;\n}\n",
    "lib/profile-network-influence.ts": "/**\n * Scoring Model v1.2: bounded ecosystem influence.\n *\n * A profile's sustainability depends mostly on what it does, and a little on\n * the ecosystem it operates in. v1.2 adds the second part as two separate,\n * separately explainable terms on top of the v1.1 score, which is left exactly\n * as it was and becomes the *intrinsic* value.\n *\n * The one structural decision everything else follows from: what travels the\n * graph is each profile's **intrinsic** value, never its network-adjusted\n * total. Because intrinsic values cannot themselves have been moved by the\n * network, A -> B -> A carries no feedback at all. The loop problem is absent\n * rather than damped, and no iteration or convergence check is needed.\n *\n * That is not a retreat to direct neighbours. Traversal still reaches three\n * hops; what it carries is simply an unamplifiable quantity.\n *\n * ## Two terms, deliberately unequal\n *\n * **Impact** asks what is going on around this profile. It is the larger term,\n * reaches further, and is blind to whether a neighbour publishes its numbers —\n * sustainability does not disappear because a score is not on display.\n *\n * **Reliability** asks how well corroborated this profile's record is. A\n * well-evidenced ecosystem is weak evidence about *my* claims, so its term is\n * much smaller, decays much faster, and is the only place where a neighbour's\n * openness counts for anything.\n *\n * No value imports, so the model can be exercised with plain numbers.\n */\n\n// ---------------------------------------------------------------- constants --\n\n/**\n * How far Impact influence reaches, and how fast it fades.\n *\n * Index 0 is one hop. Steeply geometric, and hard-stopped after three: a hard\n * horizon keeps the traversal finite and, more importantly, keeps the rule\n * explainable — \"your ecosystem within three steps counts\" is a sentence a\n * participant can act on.\n *\n * Provisional. These are calibration parameters, not findings.\n */\nexport const HOP_DECAY: readonly number[] = [1.0, 0.25, 0.05];\nexport const MAX_HOPS = HOP_DECAY.length;\n\n/**\n * The same for assurance, and deliberately far steeper.\n *\n * My supplier's supplier being well evidenced says almost nothing about\n * whether *my* claims are evidenced. Impact spreads through an ecosystem;\n * corroboration barely travels at all, and the numbers say so: a fifth of\n * Impact's second-hop weight and a tenth of its third.\n */\nexport const RELIABILITY_HOP_DECAY: readonly number[] = [1.0, 0.05, 0.005];\n\n/** A relationship's trust in one direction: as it applies to what flows *out*. */\nexport type EdgeTrust = 'verified' | 'confirmed' | 'declared' | 'observed' | 'disputed';\n\n/** The evidentiary ladder, weakest first. Disputed is outside it, at zero. */\nexport const EDGE_TRUST_LADDER: readonly EdgeTrust[] = [\n  'observed', 'declared', 'confirmed', 'verified',\n];\n\n/**\n * How far a profile's sustainability travels along an edge at each level of\n * evidence.\n *\n * Progressive rather than binary. An observation nobody has stood behind is\n * still someone noticing something real, so it carries a tenth; a declaration\n * four tenths; bilateral confirmation seven; an independent verification all\n * of it. Only a disputed relationship carries nothing, because while the\n * parties disagree that it exists there is no relationship to carry anything.\n *\n * This is one of two knobs. This one sets how *far* value travels — it also\n * multiplies along multi-hop paths. The tier curves below set how *much* any\n * one level may ever be worth. Abuse is constrained by the pair, plus\n * saturation, the caps, one-count-per-profile and the depth limit — not by\n * refusing to count early-stage relationships at all.\n *\n * Applies **only** to v1.2 propagation; v1.1's own tier treatment is\n * untouched. **Provisional.**\n */\nexport const EDGE_CONFIDENCE: Readonly<Record<EdgeTrust, number>> = {\n  verified: 1.0,\n  confirmed: 0.7,\n  declared: 0.4,\n  observed: 0.1,\n  disputed: 0,\n};\n\nexport interface NetworkTierCurve {\n  cap: number;\n  halfSaturation: number;\n}\n\n/**\n * What each level of evidence may ever be worth in Impact, however much of it\n * there is.\n *\n * The ladder is progressive, not a gate: an unattested observation is a real\n * if weak signal and earns a real if weak amount. What stops volume from\n * substituting for substance is not a zero, it is this ceiling — the community\n * tier tops out at 0.8 of the 10 available points and approaches it slowly, so\n * a thousand observations remain worth less than a single verification.\n *\n * The caps sum to 12.8 against an overall cap of 10, deliberately close. An\n * earlier set summed to 22.5, which made the overall cap the binding\n * constraint in almost every healthy case and left the ladder no resolution\n * above it: a plainly well-connected profile hit the ceiling exactly and\n * stopped being distinguishable from an extraordinary one.\n *\n * `cap` is in Impact points. `halfSaturation` is in weighted-value units,\n * where 1.0 is one perfectly sustainable profile reached directly at full\n * confidence.\n *\n * Same shape and the same argument as v1.1's IMPACT_TIER_CURVES, deliberately:\n * a participant who has understood one ladder has understood both.\n *\n * **Provisional.** See `docs/scoring/V1_2_SENSITIVITY.md`.\n */\nexport const NETWORK_TIER_CURVES: Readonly<Record<EdgeTrust, NetworkTierCurve>> = {\n  verified: { cap: 6, halfSaturation: 0.8 },\n  confirmed: { cap: 4, halfSaturation: 1.0 },\n  declared: { cap: 2, halfSaturation: 1.2 },\n  observed: { cap: 0.8, halfSaturation: 2.0 },\n  disputed: { cap: 0, halfSaturation: 1 },\n};\n\n/**\n * The same ladder for assurance, at a small fraction of the scale.\n *\n * Reliability is measured out of 100 rather than 95, so these caps are far\n * smaller relative to their scale than the Impact ones are to theirs — about a\n * thirtieth of the score against about a tenth. That ratio is the point: a\n * corroborated ecosystem is worth noticing, and it is not evidence about me.\n */\nexport const RELIABILITY_TIER_CURVES: Readonly<Record<EdgeTrust, NetworkTierCurve>> = {\n  verified: { cap: 1.6, halfSaturation: 0.8 },\n  confirmed: { cap: 1.0, halfSaturation: 1.0 },\n  declared: { cap: 0.4, halfSaturation: 1.2 },\n  observed: { cap: 0.15, halfSaturation: 2.0 },\n  disputed: { cap: 0, halfSaturation: 1 },\n};\n\n/**\n * Relationship-type relevance. Neutral in v1.2 on purpose.\n *\n * `relation_label` is free text today — real values include \"Empleador\" and\n * \"Colaborador\" — with no structured taxonomy behind it. Deriving weights by\n * reading those strings would be guessing at meaning, and inventing a taxonomy\n * migration to complete this slice would be a much larger change than the\n * slice deserves. So every edge is equally relevant until the data can say\n * otherwise, and typed relevance becomes a later refinement.\n */\nexport const RELATIONSHIP_RELEVANCE = 1.0;\n\n/**\n * The intrinsic Impact treated as \"fully sustainable\" when normalising.\n *\n * Matches the public Impact ceiling, so quality is the profile's share of the\n * best score the platform can express rather than a second scale nobody else\n * uses.\n */\nexport const NETWORK_QUALITY_REFERENCE = 95;\n\n/** Reliability's own reference. It is already a 0-100 measure. */\nexport const RELIABILITY_REFERENCE = 100;\n\n/**\n * The most Impact the ecosystem may ever add.\n *\n * Own performance must dominate: against a 95-point ceiling, ten points means\n * a strong ecosystem is worth real effort but can never carry a profile that\n * has done nothing itself.\n */\nexport const NETWORK_IMPACT_CAP = 10;\n\n/**\n * The most assurance the ecosystem may ever add, out of 100.\n *\n * A thirtieth of the score, against Impact's tenth. Being surrounded by\n * well-evidenced profiles is a mild positive signal about a record; it is not\n * a substitute for evidencing it, and it must never be able to move a grade\n * band on its own from anywhere but its very edge.\n */\nexport const RELIABILITY_NETWORK_CAP = 3;\n\n/**\n * How much a neighbour's own Reliability modulates the Impact it lends.\n *\n * A neighbour whose claims are poorly corroborated is still doing something —\n * its Impact is not fiction — but the platform is less sure of it, so it lends\n * less:\n *\n * ```\n *   trust(N) = floor + (1 - floor) × reliability(N) / 100\n * ```\n *\n * At 0.5 a completely uncorroborated neighbour still lends half. Setting this\n * to 1.0 disables the modulation entirely, which is how the sensitivity sweep\n * measures what it is worth.\n */\nexport const RELIABILITY_TRUST_FLOOR = 0.5;\n\n/**\n * The transparency multiplier on the **assurance** term only.\n *\n * A profile that publishes its scores is open to being checked, and openness\n * is part of what assurance means — so corroboration lent by a profile that\n * withholds its numbers counts slightly less. A modest discount, not a\n * penalty, and it deliberately touches nothing else.\n *\n * **Impact is untouched by visibility, and that is load-bearing.** Hiding a\n * score is a presentation choice about a number; it says nothing about whether\n * the work happened. A profile that withholds its score contributes exactly\n * the same sustainability to its neighbours as one that publishes it, and its\n * own scores are calculated exactly as they would have been.\n */\nexport const HIDDEN_ASSURANCE_FACTOR = 0.8;\n\n/**\n * Every provisional number in one object, so alternatives can be *run* rather\n * than argued about.\n *\n * The constants above are the defaults and are what production would use. This\n * exists because none of them is calibrated yet — there is not enough attested\n * graph to calibrate against — so the honest thing is to make changing them\n * cheap and their consequences measurable. See\n * `scripts/profile-scoring/sensitivity.mjs`.\n */\nexport interface NetworkCalibration {\n  hopDecay: readonly number[];\n  edgeConfidence: Readonly<Record<EdgeTrust, number>>;\n  tierCurves: Readonly<Record<EdgeTrust, NetworkTierCurve>>;\n  qualityReference: number;\n  impactCap: number;\n  relevance: number;\n  reliabilityHopDecay: readonly number[];\n  reliabilityTierCurves: Readonly<Record<EdgeTrust, NetworkTierCurve>>;\n  reliabilityReference: number;\n  reliabilityCap: number;\n  reliabilityTrustFloor: number;\n  hiddenAssuranceFactor: number;\n}\n\nexport const DEFAULT_CALIBRATION: NetworkCalibration = {\n  hopDecay: HOP_DECAY,\n  edgeConfidence: EDGE_CONFIDENCE,\n  tierCurves: NETWORK_TIER_CURVES,\n  qualityReference: NETWORK_QUALITY_REFERENCE,\n  impactCap: NETWORK_IMPACT_CAP,\n  relevance: RELATIONSHIP_RELEVANCE,\n  reliabilityHopDecay: RELIABILITY_HOP_DECAY,\n  reliabilityTierCurves: RELIABILITY_TIER_CURVES,\n  reliabilityReference: RELIABILITY_REFERENCE,\n  reliabilityCap: RELIABILITY_NETWORK_CAP,\n  reliabilityTrustFloor: RELIABILITY_TRUST_FLOOR,\n  hiddenAssuranceFactor: HIDDEN_ASSURANCE_FACTOR,\n};\n\n// -------------------------------------------------------------------- graph --\n\nexport interface NetworkEdge {\n  id?: string;\n  a: string;\n  b: string;\n  /** False keeps the visible edge out of Impact propagation only. */\n  impactEligible?: boolean;\n  /**\n   * One trust for the relationship, applied in both directions.\n   *\n   * **Provisional, and a deliberate neutral-direction model.** Sustainability\n   * plainly does not flow equally both ways in every relationship — a\n   * restaurant buying from a sustainable farm is not the same claim as the\n   * farm supplying the restaurant — but `relation_label` is free text today,\n   * so the platform cannot tell which relationship it is looking at. Guessing\n   * a direction from an unstructured label would be inventing semantics, and\n   * inventing them wrongly is worse than declining to.\n   *\n   * So v1.2 treats a qualifying relationship as symmetric and lets the trust\n   * tier carry the confidence. When typed relationships exist, this is where\n   * directional relevance goes: supplier to customer, product to organisation,\n   * territory to resident. The graph is already built one direction at a time,\n   * so that change is a resolver change, not a traversal change.\n   */\n  trust: EdgeTrust;\n}\n\nexport interface NetworkNode {\n  /** The v1.1 Impact points, untouched by any network term. */\n  intrinsicImpact: number;\n  /** The v1.1 Reliability score, likewise untouched. */\n  intrinsicReliability: number;\n  /**\n   * Whether this profile publishes its scores.\n   *\n   * Affects the assurance term alone. It is **not** a propagation switch:\n   * every profile in the graph propagates its Impact in full, and a profile is\n   * kept out of the ecosystem only by not being published at all — in which\n   * case it is never loaded and never appears here.\n   */\n  scoresPublic: boolean;\n}\n\nexport interface NetworkStep {\n  to: string;\n  confidence: number;\n  /** The evidence level this step rests on, carried so a path can be graded. */\n  trust: EdgeTrust;\n}\n\nexport interface NetworkGraph {\n  nodes: Map<string, NetworkNode>;\n  adjacency: Map<string, NetworkStep[]>;\n}\n\n/**\n * Builds the adjacency once for the whole population.\n *\n * The adjacency is stored per direction even though both directions currently\n * carry the same trust. That is not waste: it is the seam typed relevance\n * needs, and keeping it means adding directional semantics later touches one\n * resolver rather than the traversal.\n *\n * Directions whose confidence is zero are never stored. Today that is only\n * `disputed`, which can carry nothing because while the parties disagree that\n * the relationship exists there is no relationship to carry anything.\n */\nexport function buildNetworkGraph(\n  nodes: Map<string, NetworkNode>,\n  edges: readonly NetworkEdge[],\n  calibration: NetworkCalibration = DEFAULT_CALIBRATION,\n): NetworkGraph {\n  const adjacency = new Map<string, NetworkStep[]>();\n\n  for (const edge of edges) {\n    if (edge.a === edge.b) continue;\n    if (!nodes.has(edge.a) || !nodes.has(edge.b)) continue;\n\n    for (const [from, to, trust] of [\n      [edge.a, edge.b, edge.trust],\n      [edge.b, edge.a, edge.trust],\n    ] as const) {\n      const confidence = calibration.edgeConfidence[trust] * calibration.relevance;\n      if (confidence <= 0) continue;\n\n      const list = adjacency.get(from) ?? [];\n      // Two rows describing the same pair keep only the stronger.\n      const existing = list.find((entry) => entry.to === to);\n      if (existing) {\n        if (confidence > existing.confidence) {\n          existing.confidence = confidence;\n          existing.trust = trust;\n        }\n      } else {\n        list.push({ to, confidence, trust });\n      }\n      adjacency.set(from, list);\n    }\n  }\n\n  return { nodes, adjacency };\n}\n\n// ---------------------------------------------------------------- traversal --\n\nexport interface ReachedProfile {\n  profileId: string;\n  hops: number;\n  /** Decay times the product of edge confidences along the strongest path. */\n  pathWeight: number;\n  /**\n   * The weakest evidence level on that path — the level the whole path is\n   * graded at. A chain is only as substantiated as its least substantiated\n   * link, so a verified relationship reached *through* an unattested one is\n   * treated as unattested.\n   */\n  trust: EdgeTrust;\n}\n\n/**\n * Every profile reachable within the horizon, each counted once, by its\n * strongest path.\n *\n * A profile reached through several routes contributes on the best one, never\n * the sum: summing paths is precisely how a dense cluster of controlled\n * profiles would manufacture influence, since adding another route between two\n * profiles that already know each other creates no new information.\n *\n * The traversal is a breadth-first relaxation over the decay schedule it is\n * given. It terminates because the depth is bounded, so cycles are simply\n * paths that stop being improvements — no cycle detection is needed beyond\n * keeping the best weight per node, and the origin is never counted.\n *\n * `hopDecay` is a parameter rather than a constant because Impact and\n * assurance fade at different rates, and a different schedule can make a\n * different path the strongest one. Running the walk twice is cheaper than\n * being approximately right.\n */\nexport function reachableProfiles(\n  graph: NetworkGraph,\n  origin: string,\n  hopDecay: readonly number[] = HOP_DECAY,\n): ReachedProfile[] {\n  const best = new Map<string, ReachedProfile>();\n  let frontier: Array<{ profileId: string; weight: number; trust: EdgeTrust }> = [\n    { profileId: origin, weight: 1, trust: 'verified' },\n  ];\n\n  for (let depth = 1; depth <= hopDecay.length; depth += 1) {\n    const decay = hopDecay[depth - 1];\n    const next: Array<{ profileId: string; weight: number; trust: EdgeTrust }> = [];\n\n    for (const current of frontier) {\n      for (const edge of graph.adjacency.get(current.profileId) ?? []) {\n        // A profile never influences itself, at any distance.\n        if (edge.to === origin) continue;\n\n        const rawWeight = current.weight * edge.confidence;\n        if (rawWeight <= 0) continue;\n        const pathWeight = rawWeight * decay;\n        const trust = weakerTrust(current.trust, edge.trust);\n\n        const known = best.get(edge.to);\n        if (known === undefined || pathWeight > known.pathWeight) {\n          best.set(edge.to, { profileId: edge.to, hops: depth, pathWeight, trust });\n        }\n        next.push({ profileId: edge.to, weight: rawWeight, trust });\n      }\n    }\n\n    frontier = next;\n    if (frontier.length === 0) break;\n  }\n\n  // Sorted so the result is byte-identical run to run regardless of Map order.\n  return [...best.values()].sort((a, b) => a.profileId.localeCompare(b.profileId));\n}\n\nfunction weakerTrust(a: EdgeTrust, b: EdgeTrust): EdgeTrust {\n  if (a === 'disputed' || b === 'disputed') return 'disputed';\n  return EDGE_TRUST_LADDER.indexOf(a) <= EDGE_TRUST_LADDER.indexOf(b) ? a : b;\n}\n\n// ------------------------------------------------------------------- scoring --\n\nexport interface NetworkTerm {\n  /** Points as the score uses them. */\n  points: number;\n  /** The same figure before rounding. */\n  exactPoints: number;\n  /** Value-weighted reach per evidence level, for explanation. */\n  weightedByTrust: Record<EdgeTrust, number>;\n  /** Points contributed per evidence level, before the overall cap. */\n  pointsByTrust: Record<EdgeTrust, number>;\n  contributingProfileCount: number;\n  reachedProfileCount: number;\n  /** How many of the contributors sat at each hop. */\n  contributorsByHop: number[];\n}\n\nexport interface NetworkInfluence {\n  impact: NetworkTerm;\n  reliability: NetworkTerm;\n/**\n   * Whether the unrounded terms may be shown in public.\n   *\n   * False as soon as *any* effective contributor withholds its own scores.\n   *\n   * An earlier version required a minimum number of contributors instead, on\n   * the theory that a hidden profile hides in a crowd. It does not: if two of\n   * three contributors publish their scores, a reader with the formula can\n   * compute their share and subtract it, and the remainder is the hidden one.\n   * Anonymity sets do not survive an aggregate that its own components can be\n   * removed from, and the arithmetic to do it is a subtraction.\n   *\n   * So the rule is the conservative one, and it needs no mathematics: precise\n   * fractional display requires that every contributor be public. Otherwise\n   * the explanation is coarse — the integer the score already discloses, and\n   * at most the single bit that a sub-integer term is greater than zero.\n   *\n   * The scores themselves are never affected. Only the resolution of the\n   * explanation is.\n   */\n  publishableDetail: boolean;\n  /** Contributors that withhold their scores. Never published; drives the gate. */\n  hiddenContributorCount: number;\n}\n\nconst clamp01 = (value: number): number => Math.min(1, Math.max(0, value));\n\nconst emptyByTrust = (): Record<EdgeTrust, number> => ({\n  verified: 0, confirmed: 0, declared: 0, observed: 0, disputed: 0,\n});\n\n/** Saturating curve, the same shape v1.1 already uses for its tiers. */\nexport function networkSaturation(cap: number, halfSaturation: number, weighted: number): number {\n  if (weighted <= 0) return 0;\n  return (cap * weighted) / (weighted + halfSaturation);\n}\n\ninterface TermSpec {\n  hopDecay: readonly number[];\n  curves: Readonly<Record<EdgeTrust, NetworkTierCurve>>;\n  cap: number;\n  relevance: number;\n  /** What this node lends, normalised to 0..1. */\n  valueOf: (node: NetworkNode) => number;\n}\n\nfunction computeTerm(graph: NetworkGraph, origin: string, spec: TermSpec): {\n  term: NetworkTerm;\n  hiddenContributors: number;\n} {\n  const reached = reachableProfiles(graph, origin, spec.hopDecay);\n\n  const weightedByTrust = emptyByTrust();\n  let contributingProfileCount = 0;\n  let hiddenContributors = 0;\n  const contributorsByHop = new Array<number>(spec.hopDecay.length).fill(0);\n\n  for (const entry of reached) {\n    const node = graph.nodes.get(entry.profileId);\n    if (!node) continue;\n\n    const value = clamp01(spec.valueOf(node));\n    if (value <= 0) continue;\n\n    weightedByTrust[entry.trust] += entry.pathWeight * spec.relevance * value;\n    contributingProfileCount += 1;\n    if (!node.scoresPublic) hiddenContributors += 1;\n    contributorsByHop[entry.hops - 1] += 1;\n  }\n\n  const pointsByTrust = emptyByTrust();\n  let total = 0;\n  for (const trust of EDGE_TRUST_LADDER) {\n    const curve = spec.curves[trust];\n    const points = networkSaturation(curve.cap, curve.halfSaturation, weightedByTrust[trust]);\n    pointsByTrust[trust] = points;\n    total += points;\n  }\n\n  const exactPoints = Math.min(spec.cap, total);\n  return {\n    term: {\n      points: Math.round(exactPoints),\n      exactPoints,\n      weightedByTrust,\n      pointsByTrust,\n      contributingProfileCount,\n      reachedProfileCount: reached.length,\n      contributorsByHop,\n    },\n    hiddenContributors,\n  };\n}\n\n/**\n * How much of its Impact a neighbour lends, given how well corroborated it is.\n *\n * Not a gate and not a visibility check: a neighbour with no corroboration at\n * all still lends the floor, because a poorly evidenced profile is not a\n * fictional one. It scales what the platform is willing to pass on, which is a\n * statement about confidence rather than about the neighbour's honesty.\n */\nexport function reliabilityTrust(node: NetworkNode, calibration: NetworkCalibration): number {\n  const floor = calibration.reliabilityTrustFloor;\n  const share = clamp01(node.intrinsicReliability / calibration.reliabilityReference);\n  return floor + (1 - floor) * share;\n}\n\n/**\n * The ecosystem's contribution to one profile, in both currencies.\n *\n * ```\n *   impactValue(N)     = clamp(intrinsicImpact(N) / 95)  ×  reliabilityTrust(N)\n *   assuranceValue(N)  = clamp(intrinsicReliability(N) / 100)  ×  openness(N)\n *\n *   weighted(t)        = Σ over N graded at level t:  pathWeight(N) · relevance · value(N)\n *   points(t)          = cap(t) · weighted(t) / (weighted(t) + k(t))\n *   term               = min( CAP, Σ over t  points(t) )\n * ```\n *\n * Aggregated **per evidence level**, and that is what makes the ladder\n * progressive without making it exploitable. Every level pays something from\n * its first relationship, and every level has a ceiling it approaches and\n * never reaches, so more of a weak thing is always worth less than a little of\n * a strong one. It is the same argument, and the same arithmetic, as v1.1's\n * per-tier connection curves.\n *\n * A weighted sum within each level rather than a mean, deliberately. A mean\n * would fall when a profile gains a neighbour that has not published evidence\n * yet, which on a platform this early would punish ordinary incompleteness and\n * discourage exactly the connections we want recorded; it would also make one\n * excellent neighbour indistinguishable from twenty mediocre ones, since\n * dividing by the count throws away how much substantiated ecosystem a profile\n * actually has. The per-level ceilings do the work a mean was wanted for,\n * without either side effect.\n *\n * The two terms differ in exactly three ways: the decay schedule, the caps and\n * curves, and whether a neighbour's openness counts. Everything else — the\n * traversal, the trust ladder, one-count-per-profile — is shared, so there is\n * one model to understand rather than two.\n *\n * Both results are bounded, non-negative, and monotonic: adding a relationship\n * can never lower either term.\n */\nexport function computeNetworkInfluence(\n  graph: NetworkGraph,\n  origin: string,\n  calibration: NetworkCalibration = DEFAULT_CALIBRATION,\n): NetworkInfluence {\n  const impact = computeTerm(graph, origin, {\n    hopDecay: calibration.hopDecay,\n    curves: calibration.tierCurves,\n    cap: calibration.impactCap,\n    relevance: calibration.relevance,\n    // Visibility is deliberately absent here. A profile's sustainability is\n    // the same whether or not it publishes the number.\n    valueOf: (node) => (node.intrinsicImpact / calibration.qualityReference)\n      * reliabilityTrust(node, calibration),\n  });\n\n  const reliability = computeTerm(graph, origin, {\n    hopDecay: calibration.reliabilityHopDecay,\n    curves: calibration.reliabilityTierCurves,\n    cap: calibration.reliabilityCap,\n    relevance: calibration.relevance,\n    valueOf: (node) => (node.intrinsicReliability / calibration.reliabilityReference)\n      * (node.scoresPublic ? 1 : calibration.hiddenAssuranceFactor),\n  });\n\n  // Either term having a hidden contributor withholds detail on both. They are\n  // computed from the same graph, so a precise figure for one narrows the\n  // other, and a rule that is easy to state is a rule that stays true.\n  const hiddenContributorCount = Math.max(\n    impact.hiddenContributors,\n    reliability.hiddenContributors,\n  );\n\n  return {\n    impact: impact.term,\n    reliability: reliability.term,\n    publishableDetail: hiddenContributorCount === 0,\n    hiddenContributorCount,\n  };\n}\n\n// ------------------------------------------------------------- edge trust --\n\n/**\n * The one trust a relationship carries, seen from the edge rather than an end.\n *\n * v1.1 resolves a tier *per endpoint*, and deliberately asymmetrically: a\n * profile that declares a relationship at a silent counterparty earns\n * `declared`, while the counterparty only ever sees an `observed` claim made\n * about it. That asymmetry is about *participant credit* — who has staked\n * something — and it stays exactly as it is.\n *\n * Propagation asks a different question: how well substantiated is this\n * relationship? That has one answer, not two, so v1.2 resolves a single trust\n * and reads it in both directions, taking the weaker reading whenever the ends\n * differ. One voice on the record is `declared`, both is `confirmed`, silence\n * is `observed`, and an independent verification outranks all of it.\n *\n * An earlier build made this directional, so a unilateral declaration carried\n * value outward and nothing back. That was withdrawn deliberately: it assumed\n * sustainability flows *from* the party who spoke, and with `relation_label`\n * still free text there is nothing in the data to justify that direction — see\n * the note on NetworkEdge. Accepting a small amount of influence from\n * unilateral information is safe here because the ladder keeps it small: low\n * confidence, an early-saturating curve, a small tier cap, an overall cap, one\n * count per profile on its strongest path, and a bounded horizon.\n */\nexport function resolveEdgeTrust(input: {\n  sourcePosition: string;\n  targetPosition: string;\n  isIndependentlyVerified: boolean;\n}): EdgeTrust {\n  const asserted = (position: string) => position === 'declared' || position === 'confirmed';\n\n  // A decline is checked first and wins over verification: while the parties\n  // disagree that the relationship exists, nothing travels it in either\n  // direction — and it still costs nothing, because disputed carries zero\n  // rather than a negative.\n  if (input.sourcePosition === 'declined' || input.targetPosition === 'declined') return 'disputed';\n  if (input.isIndependentlyVerified) return 'verified';\n\n  const sourceAsserted = asserted(input.sourcePosition);\n  const targetAsserted = asserted(input.targetPosition);\n  if (sourceAsserted && targetAsserted) return 'confirmed';\n  if (sourceAsserted || targetAsserted) return 'declared';\n  return 'observed';\n}\n",
    "lib/profile-reliability-v13.ts": "/**\n * Reliability v1.3 — the tier-first model.\n *\n * Published when RELIABILITY_MODEL_VERSION is 1.3 (the current model).\n * The public mathematical specification is /metodologia/algoritmo.\n *\n * ---------------------------------------------------------------------------\n * Why a tier and a quality index rather than one sum\n * ---------------------------------------------------------------------------\n *\n * The ladder to encode:\n *\n *   D  little or no meaningful supporting documentation\n *   C  meaningfully documented, but predominantly first-party / self-reported\n *   B  good evidence coverage WITH meaningful independent corroboration\n *   A  strong, diverse, current and substantially independently corroborated\n *\n * A single additive score over 100 cannot express it, and the obstruction is\n * arithmetic rather than taste. Where a wholly self-reported profile lands is\n * fixed by how many points the self-reachable components carry. Putting\n * \"documented but self-reported\" on C needs those to total ~60, which leaves\n * ~40 for independence, relationships and ecosystem together — and then a\n * self-published profile with a good relationship graph *adds* its way into B,\n * which the ladder forbids. Push the other way and independence becomes\n * decisive but every documented profile is stranded in D.\n *\n * So the model states two things separately:\n *\n *     tier   = the evidential situation      -> which band the score sits in\n *     q      = quality within that situation -> where in the band it sits\n *     score  = round(floor + (ceiling - floor) * q)\n *\n * The bands ARE the published grade bands, so the ladder is not an\n * interpretation laid over the score; it is the score. Two consequences, which\n * are the point rather than side effects:\n *\n *   - No volume of self-published material reaches B. Not fifty PDFs, not a\n *     complete profile, not a rich relationship graph. Crossing into B requires\n *     a publisher other than the subject, and that cannot be manufactured.\n *   - No profile is dragged below its evidential tier by cosmetics. The floor\n *     protects genuinely evidenced work from being marked down for a missing\n *     photo — which is the failure v1.2 has today, in reverse.\n *\n * Reliability answers \"how well supported is what this profile says?\" and never\n * \"how consequential is it?\". That second question is Impact's, and the two\n * must not borrow from each other.\n */\nimport {\n  getTieredConnectionContribution,\n  RELIABILITY_TIER_CURVES,\n  type ConnectionTierCounts,\n} from '@/lib/profile-connection-tiers';\nimport { DISTANCE_WEIGHT, type SourceDistance } from '@/lib/source-identity';\nimport type { ReliabilityGrade } from '@/types/profile-score';\n\nexport const MAX_RELIABILITY_CONNECTION_POINTS = 15;\n\n/** Identical to v1.2's bands, deliberately: v1.3 changes what a grade means, not where it starts. */\nexport const RELIABILITY_V13_GRADE_BANDS: ReadonlyArray<{ grade: ReliabilityGrade; min: number }> = [\n  { grade: 'A+', min: 95 }, { grade: 'A', min: 90 }, { grade: 'A-', min: 85 },\n  { grade: 'B+', min: 80 }, { grade: 'B', min: 75 }, { grade: 'B-', min: 70 },\n  { grade: 'C+', min: 65 }, { grade: 'C', min: 60 }, { grade: 'C-', min: 55 },\n  { grade: 'D+', min: 50 }, { grade: 'D', min: 1 },\n];\n\nexport function gradeOf(score: number): ReliabilityGrade {\n  return RELIABILITY_V13_GRADE_BANDS.find((band) => score >= band.min)?.grade ?? 'D';\n}\n\nexport type ReliabilityTierId = 'T0' | 'T1' | 'T2' | 'T3' | 'T4';\n\nexport interface ReliabilityTier {\n  id: ReliabilityTierId;\n  /** Machine-readable; the public wording is chosen in the UI, not here. */\n  label: string;\n  floor: number;\n  ceiling: number;\n  requiresFreshness?: boolean;\n}\n\n/**\n * The ladder. `floor`/`ceiling` are inclusive and tile 0–100 exactly once.\n *\n * T4 carries a freshness requirement it cannot currently satisfy: no document\n * in the corpus has a publication date, so \"current\" is unmeasurable. Until\n * that data exists the model runs with T4 closed and B+ (84) is the platform\n * ceiling. That is a product decision — an honest B+ is preferred to an A\n * awarded without temporal evidence, and a threshold is far easier to relax\n * later than to tighten once it has been published.\n */\nexport const RELIABILITY_TIERS: readonly ReliabilityTier[] = [\n  { id: 'T0', label: 'unsupported', floor: 0, ceiling: 49 },\n  { id: 'T1', label: 'partially documented', floor: 50, ceiling: 59 },\n  { id: 'T2', label: 'documented, first-party', floor: 60, ceiling: 69 },\n  { id: 'T3', label: 'independently corroborated', floor: 70, ceiling: 84 },\n  { id: 'T4', label: 'strongly corroborated and current', floor: 85, ceiling: 100, requiresFreshness: true },\n];\n\nexport type ReliabilityTermName =\n  | 'coverage' | 'depth' | 'foundation' | 'independence'\n  | 'corroboration' | 'completeness' | 'relationships' | 'ecosystem';\n\nexport type ReliabilityWeights = Record<ReliabilityTermName, number>;\n\n/**\n * Weights of the within-band quality index — one vector per KIND of band.\n *\n * A single global vector cannot place a profile sensibly inside its own band.\n * In the first simulation every corroborated profile scored 79–82, because\n * coverage and completeness were already maxed out by the act of qualifying,\n * so the terms that actually separate a B- from a B+ were fighting over the\n * remainder: fifteen points of band, three points of usable spread.\n *\n * So the weights follow the question the band is asking:\n *\n *   establishing   (T0–T2) \"is this documented at all, and how thoroughly?\"\n *   corroborating  (T3–T4) \"coverage is a given — who else says so?\"\n *\n * Each vector sums to 1, so q stays in 0..1 and bands never overlap.\n */\nexport const RELIABILITY_WEIGHT_PROFILES: Readonly<Record<'establishing' | 'corroborating', ReliabilityWeights>> = {\n  establishing: {\n    coverage: 0.28,\n    depth: 0.24,\n    foundation: 0.14,\n    independence: 0.10,\n    corroboration: 0.04,\n    /**\n     * Deliberately small. Under v1.2, profile completeness supplies 16.8 of a\n     * 19.15 mean Reliability — 88% of a trust score paid for filling in four\n     * fields — and the first pass at these weights still let it, rather than\n     * source breadth, decide C from C+ among the imported profiles. Filling in\n     * a website is table stakes, not evidence.\n     */\n    completeness: 0.08,\n    relationships: 0.08,\n    ecosystem: 0.04,\n  },\n  corroborating: {\n    coverage: 0.08,\n    depth: 0.07,\n    foundation: 0.04,\n    independence: 0.34,\n    corroboration: 0.30,\n    completeness: 0.05,\n    relationships: 0.09,\n    ecosystem: 0.03,\n  },\n};\n\nexport interface ReliabilityV13Config {\n  weightProfiles: typeof RELIABILITY_WEIGHT_PROFILES;\n  halfSaturation: { foundation: number; depth: number; independence: number };\n  exponents: { coverage: number; corroboration: number };\n  tiers: {\n    minCoverageT1: number;\n    minCoverageT2: number;\n    minIndependentShareT3: number;\n    minIndependentFamiliesT3: number;\n    broadSingleSourceShareT3: number;\n    minCoverageT4: number;\n    minIndependentShareT4: number;\n    minIndependentFamiliesT4: number;\n    minCorroboratedShareT4: number;\n  };\n  freshnessAvailable: boolean;\n}\n\nexport const RELIABILITY_V13_CONFIG: ReliabilityV13Config = {\n  weightProfiles: RELIABILITY_WEIGHT_PROFILES,\n  /**\n   * Half-saturation constants: the count at which a term reaches half its max.\n   * `independence: 3` says three distinct outside publishers is a middling\n   * amount of independent attention, not a lot. Two is not yet diversity.\n   */\n  halfSaturation: { foundation: 1.5, depth: 2.0, independence: 3.0 },\n  /**\n   * Coverage is concave — the first sources establish that a profile is\n   * documented at all, which is worth more than the last few. Corroboration is\n   * LINEAR: corroborating 30% of a profile's claims is 30% of the job, and\n   * flattering that with a curve is how \"two sources on a fifth of the claims\"\n   * would come to read as broadly corroborated.\n   */\n  exponents: { coverage: 0.7, corroboration: 1.0 },\n  tiers: {\n    minCoverageT1: 0.20,\n    minCoverageT2: 0.60,\n    minIndependentShareT3: 0.25,\n    minIndependentFamiliesT3: 2,\n    /**\n     * One broad independent source is still corroboration. A single regulator\n     * auditing most of a profile reaches B- without a second publisher —\n     * demanding two would punish exactly the strongest evidence EkoPulse could\n     * ever hold.\n     */\n    broadSingleSourceShareT3: 0.60,\n    minCoverageT4: 0.80,\n    minIndependentShareT4: 0.60,\n    minIndependentFamiliesT4: 3,\n    minCorroboratedShareT4: 0.40,\n  },\n  freshnessAvailable: false,\n};\n\n/**\n * The parameters that stopped being proposals on 2026-08-28.\n *\n * Kept as data and asserted by test, so retuning one is a deliberate act with a\n * red test attached rather than an edit nobody notices. Weights are NOT here:\n * the sensitivity sweep shows they flip no grades on real data, so they stay\n * free to tune while the tier constants do not.\n */\nexport const RELIABILITY_V13_APPROVED_DECISIONS = {\n  minCoverageT2: 0.60,\n  freshnessAvailable: false,\n  affiliatedWeight: 0.25,\n  /** No renormalisation of the D band to preserve legacy numbers. */\n  renormaliseUnsupportedBand: false,\n} as const;\n\n/** One practice-to-source relationship, already resolved against the subject. */\nexport interface ReliabilityEvidenceLink {\n  practiceId: string;\n  /** Depth bucket: the subject's own surfaces collapse to one. */\n  family: string;\n  /** Independence bucket: every self / controlled / unknown surface collapses to one. */\n  independenceFamily: string;\n  distance: SourceDistance;\n}\n\n/** A document sitting on the profile, whether or not it backs a practice. */\nexport interface ReliabilityProfileDocument {\n  family: string;\n  /** Does its text overlap what this profile actually claims? */\n  relevant: boolean;\n}\n\nexport interface ReliabilityFacts {\n  profileId: string;\n  practiceCount: number;\n  links: ReliabilityEvidenceLink[];\n  profileDocuments: ReliabilityProfileDocument[];\n  /** 0..4: description, location, website, image. */\n  completenessFields: number;\n  connectionTiers: ConnectionTierCounts;\n  /** v1.2's bounded ecosystem assurance, reused unchanged. */\n  networkReliabilityPoints: number;\n}\n\nexport interface ReliabilityMeasures {\n  practiceCount: number;\n  coveredCount: number;\n  coverageFraction: number;\n  backingFamilyCount: number;\n  relevantFamilyCount: number;\n  independentFamilyCount: number;\n  weightedIndependence: number;\n  independentPracticeCount: number;\n  independentShare: number;\n  corroboratedPracticeCount: number;\n  corroboratedShare: number;\n}\n\n/** v1.1's saturating curve, reused so every \"more is worth less\" term has one shape. */\nexport function saturating(halfSaturation: number, count: number): number {\n  if (count <= 0) return 0;\n  return count / (count + halfSaturation);\n}\n\nfunction concave(fraction: number, exponent = 0.7): number {\n  return fraction <= 0 ? 0 : Math.min(1, fraction) ** exponent;\n}\n\n/** Everything both the tier test and the quality index need, computed once. */\nexport function measureReliability(\n  facts: ReliabilityFacts,\n  config: ReliabilityV13Config = RELIABILITY_V13_CONFIG,\n): ReliabilityMeasures {\n  void config;\n  const practiceCount = facts.practiceCount;\n  const covered = new Set(facts.links.map((link) => link.practiceId));\n  const coverageFraction = practiceCount === 0 ? 0 : covered.size / practiceCount;\n\n  // Families that actually back a practice — breadth of the evidence in use,\n  // as opposed to breadth of the material sitting on the profile.\n  const backingFamilies = new Set(facts.links.map((link) => link.family));\n\n  // Independence is counted in independence-families, where every self-published\n  // surface has already collapsed into one bucket.\n  const independentFamilies = new Set<string>();\n  let weightedIndependence = 0;\n  const independentPractices = new Set<string>();\n  for (const link of facts.links) {\n    const weight = DISTANCE_WEIGHT[link.distance] ?? 0;\n    if (weight <= 0) continue;\n    if (!independentFamilies.has(link.independenceFamily)) {\n      independentFamilies.add(link.independenceFamily);\n      weightedIndependence += weight;\n    }\n    if (link.distance === 'independent') independentPractices.add(link.practiceId);\n  }\n\n  // Corroboration is two DIFFERENT publishers standing behind one claim, at\n  // least one of them not the subject. Two pages of the same site are not two\n  // publishers, which is why this counts independence-families.\n  const familiesByPractice = new Map<string, Set<string>>();\n  for (const link of facts.links) {\n    const set = familiesByPractice.get(link.practiceId) ?? new Set<string>();\n    set.add(link.independenceFamily);\n    familiesByPractice.set(link.practiceId, set);\n  }\n  let corroboratedPractices = 0;\n  for (const [practiceId, families] of familiesByPractice) {\n    if (families.size >= 2 && independentPractices.has(practiceId)) corroboratedPractices += 1;\n  }\n\n  const relevantFamilies = new Set(\n    facts.profileDocuments.filter((doc) => doc.relevant).map((doc) => doc.family),\n  );\n\n  return {\n    practiceCount,\n    coveredCount: covered.size,\n    coverageFraction,\n    backingFamilyCount: backingFamilies.size,\n    relevantFamilyCount: relevantFamilies.size,\n    independentFamilyCount: independentFamilies.size,\n    weightedIndependence,\n    independentPracticeCount: independentPractices.size,\n    independentShare: practiceCount === 0 ? 0 : independentPractices.size / practiceCount,\n    corroboratedPracticeCount: corroboratedPractices,\n    corroboratedShare: practiceCount === 0 ? 0 : corroboratedPractices / practiceCount,\n  };\n}\n\n/** Which rung of the ladder does this profile stand on? */\nexport function evidentialTier(\n  m: ReliabilityMeasures,\n  config: ReliabilityV13Config = RELIABILITY_V13_CONFIG,\n): ReliabilityTier {\n  const t = config.tiers;\n  if (m.practiceCount === 0 || m.coverageFraction < t.minCoverageT1) return RELIABILITY_TIERS[0];\n\n  const corroborates = m.independentFamilyCount >= t.minIndependentFamiliesT3\n    || (m.independentFamilyCount >= 1 && m.independentShare >= t.broadSingleSourceShareT3);\n\n  if (config.freshnessAvailable\n    && m.coverageFraction >= t.minCoverageT4\n    && m.independentShare >= t.minIndependentShareT4\n    && m.independentFamilyCount >= t.minIndependentFamiliesT4\n    && m.corroboratedShare >= t.minCorroboratedShareT4) {\n    return RELIABILITY_TIERS[4];\n  }\n  if (m.coverageFraction >= t.minCoverageT2\n    && m.independentShare >= t.minIndependentShareT3\n    && corroborates) {\n    return RELIABILITY_TIERS[3];\n  }\n  if (m.coverageFraction >= t.minCoverageT2) return RELIABILITY_TIERS[2];\n  return RELIABILITY_TIERS[1];\n}\n\n/**\n * Coverage measured as HEADROOM above what the profile's own band already\n * requires, rather than as a raw fraction.\n *\n * Without it every C-band profile scores coverage at ~1.0 — they are all at or\n * near 100% — and the term stops distinguishing anybody. With it, a profile\n * that just cleared the 60% bar scores 0 and one at 100% scores 1, so the\n * band's ten points are actually used. The rule generalises: a band never\n * re-pays a profile for the thing that got it into the band.\n */\nfunction coverageHeadroom(\n  fraction: number,\n  tier: ReliabilityTier,\n  config: ReliabilityV13Config,\n): number {\n  const t = config.tiers;\n  const [low, high] = tier.id === 'T0'\n    ? [0, t.minCoverageT1]\n    : tier.id === 'T1'\n      ? [t.minCoverageT1, t.minCoverageT2]\n      : [t.minCoverageT2, 1];\n  if (high <= low) return 0;\n  return concave((fraction - low) / (high - low), config.exponents.coverage);\n}\n\nexport function weightsFor(\n  tier: ReliabilityTier,\n  config: ReliabilityV13Config = RELIABILITY_V13_CONFIG,\n): ReliabilityWeights {\n  return tier.id === 'T3' || tier.id === 'T4'\n    ? config.weightProfiles.corroborating\n    : config.weightProfiles.establishing;\n}\n\nexport function reliabilityConnectionPoints(tiers: ConnectionTierCounts): number {\n  return Math.min(\n    MAX_RELIABILITY_CONNECTION_POINTS,\n    Math.round(getTieredConnectionContribution(tiers, RELIABILITY_TIER_CURVES)),\n  );\n}\n\n/**\n * How good an example of its own band is this profile? 0..1.\n *\n * Every term is normalised before weighting, so the weights are directly\n * comparable and can be argued about one at a time — which a model of mixed\n * point caps could not offer.\n */\nexport function qualityIndex(\n  facts: ReliabilityFacts,\n  m: ReliabilityMeasures,\n  tier: ReliabilityTier,\n  config: ReliabilityV13Config = RELIABILITY_V13_CONFIG,\n): { q: number; terms: Record<ReliabilityTermName, number>; weights: ReliabilityWeights } {\n  const w = weightsFor(tier, config);\n  const k = config.halfSaturation;\n  const terms: Record<ReliabilityTermName, number> = {\n    coverage: coverageHeadroom(m.coverageFraction, tier, config),\n    depth: saturating(k.depth, m.backingFamilyCount),\n    foundation: saturating(k.foundation, m.relevantFamilyCount),\n    independence: saturating(k.independence, m.weightedIndependence),\n    corroboration: concave(m.corroboratedShare, config.exponents.corroboration),\n    completeness: facts.completenessFields / 4,\n    relationships: reliabilityConnectionPoints(facts.connectionTiers) / MAX_RELIABILITY_CONNECTION_POINTS,\n    ecosystem: Math.min(1, (facts.networkReliabilityPoints ?? 0) / 3),\n  };\n  let q = 0;\n  for (const name of Object.keys(w) as ReliabilityTermName[]) q += w[name] * terms[name];\n  return { q: Math.max(0, Math.min(1, q)), terms, weights: w };\n}\n\n/**\n * A machine-readable account of why a profile landed where it did.\n *\n * A trust score nobody can explain is a trust score nobody should believe. The\n * codes are stable identifiers; the Spanish shown to a reader is chosen in the\n * UI so the wording can be revised without a scoring change.\n */\nexport type ReliabilityReasonCode =\n  | 'no_practices'\n  | 'coverage_below_minimum'\n  | 'coverage_partial'\n  | 'documented_first_party'\n  | 'documented_single_publisher'\n  | 'documented_several_publishers'\n  | 'independent_corroboration'\n  | 'independent_broad_single_source'\n  | 'freshness_unavailable';\n\nexport interface ReliabilityReason {\n  code: ReliabilityReasonCode;\n  /** Numbers a UI can put in a sentence without recomputing anything. */\n  detail: Record<string, number>;\n}\n\nexport interface ReliabilityV13Result {\n  score: number;\n  grade: ReliabilityGrade;\n  tier: ReliabilityTierId;\n  tierLabel: string;\n  q: number;\n  measures: ReliabilityMeasures;\n  terms: Record<ReliabilityTermName, number>;\n  /** Points each component contributed inside the band, for the public panel. */\n  contributions: Record<ReliabilityTermName, number>;\n  reasons: ReliabilityReason[];\n  /** Distance histogram over the links that were counted. */\n  sourceMix: Record<SourceDistance, number>;\n}\n\nfunction buildReasons(\n  m: ReliabilityMeasures,\n  tier: ReliabilityTier,\n  config: ReliabilityV13Config,\n): ReliabilityReason[] {\n  const reasons: ReliabilityReason[] = [];\n  const round = (value: number) => Number(value.toFixed(3));\n\n  if (m.practiceCount === 0) {\n    reasons.push({ code: 'no_practices', detail: {} });\n    return reasons;\n  }\n  if (tier.id === 'T0') {\n    reasons.push({\n      code: 'coverage_below_minimum',\n      detail: { coverage: round(m.coverageFraction), minimum: config.tiers.minCoverageT1 },\n    });\n    return reasons;\n  }\n  if (tier.id === 'T1') {\n    reasons.push({\n      code: 'coverage_partial',\n      detail: { coverage: round(m.coverageFraction), minimum: config.tiers.minCoverageT2 },\n    });\n    return reasons;\n  }\n  if (tier.id === 'T2') {\n    reasons.push({\n      code: 'documented_first_party',\n      detail: {\n        coverage: round(m.coverageFraction),\n        independentShare: round(m.independentShare),\n        required: config.tiers.minIndependentShareT3,\n      },\n    });\n    reasons.push({\n      code: m.backingFamilyCount > 1 ? 'documented_several_publishers' : 'documented_single_publisher',\n      detail: { publishers: m.backingFamilyCount },\n    });\n    return reasons;\n  }\n  // T3 / T4\n  if (m.independentFamilyCount >= config.tiers.minIndependentFamiliesT3) {\n    reasons.push({\n      code: 'independent_corroboration',\n      detail: {\n        independentPublishers: m.independentFamilyCount,\n        independentShare: round(m.independentShare),\n        corroboratedShare: round(m.corroboratedShare),\n      },\n    });\n  } else {\n    reasons.push({\n      code: 'independent_broad_single_source',\n      detail: { independentShare: round(m.independentShare) },\n    });\n  }\n  if (!config.freshnessAvailable) {\n    reasons.push({ code: 'freshness_unavailable', detail: { ceiling: RELIABILITY_TIERS[3].ceiling } });\n  }\n  return reasons;\n}\n\nexport function computeReliabilityV13(\n  facts: ReliabilityFacts,\n  config: ReliabilityV13Config = RELIABILITY_V13_CONFIG,\n): ReliabilityV13Result {\n  const m = measureReliability(facts, config);\n  const tier = evidentialTier(m, config);\n  const { q, terms, weights } = qualityIndex(facts, m, tier, config);\n  const score = Math.round(tier.floor + (tier.ceiling - tier.floor) * q);\n\n  const sourceMix: Record<SourceDistance, number> = {\n    own: 0, controlled: 0, affiliated: 0, independent: 0, unknown: 0,\n  };\n  for (const link of facts.links) sourceMix[link.distance] += 1;\n\n  const round3 = (value: number) => Number(value.toFixed(3));\n  const span = tier.ceiling - tier.floor;\n  return {\n    score,\n    grade: gradeOf(score),\n    tier: tier.id,\n    tierLabel: tier.label,\n    q: round3(q),\n    measures: m,\n    terms: Object.fromEntries(\n      (Object.keys(terms) as ReliabilityTermName[]).map((name) => [name, round3(terms[name])]),\n    ) as Record<ReliabilityTermName, number>,\n    contributions: Object.fromEntries(\n      (Object.keys(weights) as ReliabilityTermName[]).map((name) => [\n        name,\n        Number((span * weights[name] * terms[name]).toFixed(2)),\n      ]),\n    ) as Record<ReliabilityTermName, number>,\n    reasons: buildReasons(m, tier, config),\n    sourceMix,\n  };\n}\n\n/** Overrides for simulation and sweeps; production always uses the default. */\nexport function withReliabilityConfig(\n  overrides: Partial<ReliabilityV13Config> & {\n    weightProfiles?: Partial<Record<'establishing' | 'corroborating', Partial<ReliabilityWeights>>>;\n  },\n): ReliabilityV13Config {\n  return {\n    ...RELIABILITY_V13_CONFIG,\n    ...overrides,\n    weightProfiles: {\n      establishing: {\n        ...RELIABILITY_V13_CONFIG.weightProfiles.establishing,\n        ...(overrides.weightProfiles?.establishing ?? {}),\n      },\n      corroborating: {\n        ...RELIABILITY_V13_CONFIG.weightProfiles.corroborating,\n        ...(overrides.weightProfiles?.corroborating ?? {}),\n      },\n    },\n    halfSaturation: { ...RELIABILITY_V13_CONFIG.halfSaturation, ...(overrides.halfSaturation ?? {}) },\n    exponents: { ...RELIABILITY_V13_CONFIG.exponents, ...(overrides.exponents ?? {}) },\n    tiers: { ...RELIABILITY_V13_CONFIG.tiers, ...(overrides.tiers ?? {}) },\n  };\n}\n",
    "lib/profile-reliability-facts.ts": "/**\n * Turning what the platform stores into what Reliability v1.3 measures.\n *\n * Kept apart from the model itself (lib/profile-reliability-v13.ts) so the two\n * can be argued about separately: this file decides what counts as a source,\n * the model decides what a source is worth. The simulation harness and the\n * production scorer both build facts through here, so a scenario cannot win by\n * being fed differently from the way production would feed it.\n *\n * Nothing here is reached while RELIABILITY_MODEL_VERSION is '1.2'.\n */\nimport {\n  depthFamily,\n  independenceFamily,\n  normalizeDeclaredDomain,\n  registrableDomain,\n  toDeclaredDomains,\n  type DeclaredDomain,\n  type ProfileDomainRow,\n  type SourceDistance,\n  type SourceSubject,\n} from '@/lib/source-identity';\nimport type {\n  ReliabilityEvidenceLink,\n  ReliabilityFacts,\n  ReliabilityProfileDocument,\n} from '@/lib/profile-reliability-v13';\nimport type { ConnectionTierCounts } from '@/lib/profile-connection-tiers';\n\n/** A document, photo or video row, as far as source identity is concerned. */\nexport interface ReliabilitySourceInput {\n  id: string;\n  profileId: string;\n  url?: string | null;\n  storagePath?: string | null;\n  title?: string | null;\n  description?: string | null;\n}\n\nexport interface ReliabilityPracticeInput {\n  id: string;\n  profileId: string;\n  title?: string | null;\n  description?: string | null;\n  themeSlug?: string | null;\n}\n\nexport interface ReliabilityLinkInput {\n  practiceId: string;\n  documentId?: string | null;\n  photoId?: string | null;\n  videoId?: string | null;\n}\n\n/**\n * A recognition, once Phase 2A's scope column exists.\n *\n * `scope` decides what it may touch and the decision is structural: an\n * organisation-scoped attestation can only ever reach the documentary\n * foundation term, and has no way to become evidence for a practice. That is\n * the whole point — an ISO 14001 certificate says something real about a\n * management system and nothing whatever about twenty individual practices\n * listed further down the page, and a model that let coverage inherit it would\n * be laundering one attestation into twenty.\n */\nexport interface ReliabilityRecognitionInput {\n  id: string;\n  profileId: string;\n  scope: 'organisation' | 'practice';\n  improvementId?: string | null;\n  issuerDomain?: string | null;\n  url?: string | null;\n  title?: string | null;\n}\n\nexport interface ReliabilityProfileInput extends SourceSubject {\n  id: string;\n  description?: string | null;\n  location?: string | null;\n  website?: string | null;\n  imagePath?: string | null;\n  title?: string | null;\n}\n\nconst STOPWORDS = new Set([\n  'de', 'la', 'el', 'los', 'las', 'y', 'en', 'para', 'del', 'con', 'por',\n  'un', 'una', 'al', 'que', 'se', 'su', 'sus',\n  'the', 'of', 'and', 'to', 'in', 'for', 'a', 'on',\n]);\n\n/** Content words, accent-folded, long enough to carry meaning. */\nexport function contentWords(text: unknown): Set<string> {\n  return new Set(\n    String(text ?? '')\n      .toLowerCase()\n      .normalize('NFD')\n      .replace(/[̀-ͯ]/g, '')\n      .split(/[^a-z0-9]+/)\n      .filter((word) => word.length > 3 && !STOPWORDS.has(word)),\n  );\n}\n\n/**\n * How many of a document's content words this profile actually claims.\n *\n * The documentary-foundation term has to be gated on something, or it rewards\n * uploading fifty unrelated PDFs. Two words of overlap is a low bar on purpose:\n * it is meant to exclude the obviously irrelevant, not to judge quality, and\n * anything stricter would start silently discarding real evidence written in\n * different words. Judging genuine relevance is retrieve-then-judge's job.\n */\nexport const MIN_RELEVANCE_OVERLAP = 2;\n\nexport interface ReliabilityFactInputs {\n  profile: ReliabilityProfileInput;\n  practices: ReliabilityPracticeInput[];\n  documents: ReliabilitySourceInput[];\n  links: ReliabilityLinkInput[];\n  connectionTiers: ConnectionTierCounts;\n  networkReliabilityPoints: number;\n  declaredDomains?: readonly Pick<ProfileDomainRow, 'domain' | 'account_path' | 'relation' | 'declared_by'>[];\n  recognitions?: ReliabilityRecognitionInput[];\n}\n\nexport function buildReliabilityFacts(inputs: ReliabilityFactInputs): ReliabilityFacts {\n  const { profile } = inputs;\n  const practices = inputs.practices.filter((practice) => practice.profileId === profile.id);\n  const practiceIds = new Set(practices.map((practice) => practice.id));\n  const documents = inputs.documents.filter((document) => document.profileId === profile.id);\n  const documentsById = new Map(documents.map((document) => [document.id, document]));\n  const declarations: DeclaredDomain[] = inputs.declaredDomains\n    ? toDeclaredDomains(inputs.declaredDomains)\n    : [];\n\n  // The vocabulary this profile actually claims, so relevance is measured\n  // against its own practices rather than against a generic sustainability\n  // dictionary that every PDF would match.\n  const vocabulary = new Set<string>();\n  for (const practice of practices) {\n    for (const word of contentWords(\n      `${practice.title ?? ''} ${practice.description ?? ''} ${practice.themeSlug ?? ''}`,\n    )) {\n      vocabulary.add(word);\n    }\n  }\n\n  const profileDocuments: ReliabilityProfileDocument[] = documents.map((document) => {\n    const overlap = [...contentWords(`${document.title ?? ''} ${document.description ?? ''}`)]\n      .filter((word) => vocabulary.has(word)).length;\n    const ref = { id: document.id, url: document.url, storage_path: document.storagePath };\n    return {\n      // depthFamily, not sourceFamily. The foundation term asks how many\n      // distinct sources stand behind a profile's documentary presence, and\n      // `sourceFamily` deliberately gives every aggregator URL its own identity\n      // — right for telling two publishers apart behind one storage bucket,\n      // wrong here, where it let a profile buy breadth by cross-posting the\n      // same announcement to its own LinkedIn, YouTube and X. The anti-gaming\n      // suite found that: four own social surfaces were reading as four\n      // documentary sources and paying a point.\n      family: depthFamily(ref, profile, declarations),\n      relevant: overlap >= MIN_RELEVANCE_OVERLAP,\n    };\n  });\n\n  const links: ReliabilityEvidenceLink[] = [];\n  for (const link of inputs.links) {\n    if (!practiceIds.has(link.practiceId)) continue;\n    if (link.documentId) {\n      const document = documentsById.get(link.documentId);\n      if (!document) continue;\n      const ref = { id: document.id, url: document.url, storage_path: document.storagePath };\n      const resolved = independenceFamily(ref, profile, declarations);\n      links.push({\n        practiceId: link.practiceId,\n        family: depthFamily(ref, profile, declarations),\n        independenceFamily: resolved.family,\n        distance: resolved.distance,\n      });\n    } else {\n      // A photo or a video is first-party by construction: whoever manages the\n      // profile uploaded it. There is no publisher to resolve.\n      links.push({\n        practiceId: link.practiceId,\n        family: `self-surface:${profile.id}`,\n        independenceFamily: `self:${profile.id}`,\n        distance: 'controlled' as SourceDistance,\n      });\n    }\n  }\n\n  // Recognitions, on the terms Phase 2A's `scope` column set out.\n  for (const recognition of inputs.recognitions ?? []) {\n    if (recognition.profileId !== profile.id) continue;\n    const domain = normalizeDeclaredDomain(recognition.issuerDomain)\n      ?? registrableDomain(recognition.url ?? null);\n    if (!domain) continue;\n    const ref = { id: recognition.id, url: `https://${domain}` };\n\n    if (recognition.scope === 'practice' && recognition.improvementId\n      && practiceIds.has(recognition.improvementId)) {\n      // Practice-scoped: joins the evidence graph for that one practice, and\n      // only that one. Its distance is resolved from the issuer exactly like\n      // any other source, so a body certifying itself through a foundation it\n      // controls is visible as such rather than counting as outside attention.\n      const resolved = independenceFamily(ref, profile, declarations);\n      links.push({\n        practiceId: recognition.improvementId,\n        family: depthFamily(ref, profile, declarations),\n        independenceFamily: resolved.family,\n        distance: resolved.distance,\n      });\n      continue;\n    }\n    // Organisation-scoped: the documentary foundation and nothing else. It can\n    // raise no coverage, no independence share and no corroboration, because it\n    // is not attached to a claim.\n    profileDocuments.push({ family: depthFamily(ref, profile, declarations), relevant: true });\n  }\n\n  return {\n    profileId: profile.id,\n    practiceCount: practices.length,\n    links,\n    profileDocuments,\n    completenessFields: [profile.description, profile.location, profile.website, profile.imagePath]\n      .filter((value) => String(value ?? '').trim().length > 0).length,\n    connectionTiers: inputs.connectionTiers,\n    networkReliabilityPoints: inputs.networkReliabilityPoints,\n  };\n}\n",
    "lib/source-identity.ts": "/**\n * Source identity — who published a thing, and how far that publisher stands\n * from the profile the thing is offered as evidence about.\n *\n * This module is the production home of what the Reliability review prototyped\n * in `scripts/reliability-review/source-identity.mjs` and\n * `source-organisation.mjs`. Those two files now re-export from here so there\n * is exactly one implementation: trust logic that exists twice drifts, and the\n * simulations are only worth something if they run the code production runs.\n *\n * Used by the published Reliability 1.3 model. The resolver remains heuristic;\n * its exact rules and limitations are documented at /metodologia/algoritmo.\n *\n * ## Three nested notions\n *\n *   evidenceItem   one row — one link between a practice and one artefact\n *   source         one underlying artefact — the same PDF linked five times is\n *                  one source, matched on normalised URL\n *   sourceFamily   one publisher — every page under vitoria-gasteiz.org,\n *                  including cea. and blogs. subdomains, is one family\n *\n * Coverage is counted in evidence items; diversity and corroboration must be\n * counted in families, or a profile can manufacture both by pasting its own\n * sitemap.\n *\n * ## The one rule that makes declarations safe\n *\n * A profile may tell EkoPulse that a domain is *closer* to it than EkoPulse\n * would otherwise have guessed. It may never tell EkoPulse that a domain is\n * further away. See `resolveSourceDistance` — the rule is enforced by\n * comparing trust weights, not by trusting the vocabulary, and the database\n * additionally refuses to store the word `independent` as a declaration at all.\n */\n\n/**\n * Public-suffix segments that appear one label deeper than the usual eTLD.\n *\n * `.eus` is a normal TLD, so `bizkaia.eus` is the registrable domain and\n * `garbiker.bizkaia.eus` collapses into it. That is the correct default for\n * independence — Garbiker's pages on the provincial domain are not an\n * independent corroboration of Garbiker — but it is listed explicitly here so\n * the choice is visible and arguable rather than an accident of string\n * splitting. Add to this list only with a reason.\n */\nconst DEEP_SUFFIXES = [\n  'bizkaia.eus',\n  'gipuzkoa.eus',\n  'araba.eus',\n  'euskadi.eus',\n  'co.uk',\n  'com.es',\n  'org.es',\n  'gob.es',\n];\n\n/** Hosts that publish other people's material and must never merge two sources. */\nconst AGGREGATOR_HOSTS = new Set([\n  'storage.googleapis.com',\n  'drive.google.com',\n  'docs.google.com',\n  'dropbox.com',\n  'linkedin.com',\n  'x.com',\n  'twitter.com',\n  'youtube.com',\n  'youtu.be',\n  'issuu.com',\n  'scribd.com',\n]);\n\n/**\n * Hosts where the PATH, not the domain, identifies the publisher.\n *\n * A page on one of these is attributed to whoever the path names. That makes\n * `linkedin.com/company/mutualia` self-published for Mutualia and third-party\n * for everyone else, which is the correct reading of both.\n */\nexport const SOCIAL_HOSTS: ReadonlyMap<string, RegExp> = new Map([\n  ['linkedin.com', /\\/(?:company|school|showcase|in)\\/([^/?#]+)/i],\n  ['x.com', /^\\/([^/?#]+)/],\n  ['twitter.com', /^\\/([^/?#]+)/],\n  ['youtube.com', /\\/(?:@|c\\/|channel\\/|user\\/)([^/?#]+)/i],\n  ['facebook.com', /^\\/([^/?#]+)/],\n  ['instagram.com', /^\\/([^/?#]+)/],\n  ['issuu.com', /^\\/([^/?#]+)/],\n  ['medium.com', /^\\/@?([^/?#]+)/],\n  ['vimeo.com', /^\\/([^/?#]+)/],\n]);\n\n/** Words that carry no organisational identity and must never match on. */\nconst GENERIC_TOKENS = new Set([\n  'ayuntamiento', 'udala', 'city', 'council', 'fundacion', 'fundazioa', 'foundation',\n  'grupo', 'group', 'taldea', 'asociacion', 'elkartea', 'association', 'sociedad',\n  'society', 'centro', 'zentroa', 'center', 'centre', 'instituto', 'institute',\n  'agencia', 'agency', 'consorcio', 'cluster', 'network', 'sarea',\n  'empresa', 'company', 'servicios', 'services', 'proyecto', 'project',\n  'sostenible', 'sostenibilidad', 'sustainable', 'sustainability', 'iraunkorra',\n  'green', 'verde', 'energia', 'energy', 'site', 'news', 'blog', 'press', 'prensa',\n]);\n\nconst TRACKING_PARAMS = /^(?:utm_[a-z0-9_]*|fbclid|gclid|mc_cid|mc_eid|_ga|ref|source)$/i;\n\n/** How far a source stands from the profile it is offered as evidence about. */\nexport type SourceDistance = 'own' | 'controlled' | 'affiliated' | 'independent' | 'unknown';\n\n/**\n * The subset of `SourceDistance` a profile may actually declare.\n *\n * `independent` and `unknown` are conclusions the platform reaches, never\n * statements anybody makes about themselves — that asymmetry is the whole\n * anti-gaming design, and `profile_domains` enforces it with a CHECK\n * constraint so it cannot be worked around by calling the API directly.\n */\nexport type DomainRelation = 'own' | 'controlled' | 'affiliated';\n\n/** Who said so. Only `representative` is treated as self-serving. */\nexport type DomainDeclaredBy = 'representative' | 'platform_admin' | 'inferred';\n\nexport interface SourceRef {\n  id?: string | null;\n  url?: string | null;\n  storage_path?: string | null;\n}\n\nexport interface SourceSubject {\n  id?: string | null;\n  title?: string | null;\n  website?: string | null;\n}\n\nexport interface DeclaredDomain {\n  domain: string;\n  /** Set for platform accounts: `company/mutualia`, `@ekopulse`. */\n  accountPath?: string | null;\n  relation: DomainRelation;\n  declaredBy?: DomainDeclaredBy;\n}\n\nexport function hostOf(url: string | null | undefined): string | null {\n  if (!url || typeof url !== 'string') return null;\n  const trimmed = url.trim();\n  if (!trimmed) return null;\n  try {\n    const parsed = new URL(trimmed.includes('://') ? trimmed : `https://${trimmed}`);\n    return parsed.hostname.toLowerCase().replace(/^www\\./, '') || null;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * The publisher-level identity of a URL.\n *\n * Returns null rather than guessing when there is no host to read, so callers\n * can tell \"no source family\" apart from \"some source family I collapsed\".\n */\nexport function registrableDomain(url: string | null | undefined): string | null {\n  const host = hostOf(url);\n  if (!host) return null;\n  const labels = host.split('.');\n  if (labels.length <= 2) return host;\n  for (const suffix of DEEP_SUFFIXES) {\n    if (host === suffix) return host;\n    if (host.endsWith(`.${suffix}`)) {\n      const remainder = host.slice(0, -(suffix.length + 1)).split('.');\n      return `${remainder[remainder.length - 1]}.${suffix}`;\n    }\n  }\n  return labels.slice(-2).join('.');\n}\n\n/**\n * A stable identity for the underlying artefact, so the same report linked to\n * five practices is one source rather than five.\n *\n * Query strings are kept, because they routinely select a different document on\n * public-sector sites; fragments are dropped, because they select a position\n * within the same one. Tracking parameters are stripped so that the same PDF\n * shared from a newsletter and from a homepage does not read as two sources.\n */\nexport function sourceKey(document: SourceRef): string {\n  if (document.storage_path) return `storage:${document.storage_path}`;\n  const host = hostOf(document.url);\n  if (!host) return `document:${document.id}`;\n  try {\n    const raw = document.url as string;\n    const parsed = new URL(raw.includes('://') ? raw : `https://${raw}`);\n    parsed.hash = '';\n    parsed.hostname = host;\n    parsed.protocol = 'https:';\n    for (const key of [...parsed.searchParams.keys()]) {\n      if (TRACKING_PARAMS.test(key)) parsed.searchParams.delete(key);\n    }\n    const path = parsed.pathname.replace(/\\/+$/, '') || '/';\n    return `url:${parsed.hostname}${path}${parsed.search}`.toLowerCase();\n  } catch {\n    return `document:${document.id}`;\n  }\n}\n\n/**\n * The family a document belongs to for diversity and corroboration purposes.\n *\n * An aggregator host is never a family: two PDFs behind the same Google Storage\n * bucket may come from entirely different publishers, so each keeps its own\n * identity rather than collapsing into a fake shared source.\n */\nexport function sourceFamily(document: SourceRef): string {\n  const host = hostOf(document.url);\n  if (host && AGGREGATOR_HOSTS.has(host)) return `aggregated:${sourceKey(document)}`;\n  if (document.storage_path && !host) return `uploaded:${document.id}`;\n  const domain = registrableDomain(document.url);\n  return domain ? `domain:${domain}` : `document:${document.id}`;\n}\n\n/**\n * Is this document published by the profile it sits on?\n *\n * Returns 'self' / 'third-party' / 'unknown'. 'unknown' is a real answer and\n * must not be silently treated as independence: a profile with no website\n * declared gives no baseline to compare against, and guessing in the generous\n * direction is exactly how a self-published corpus would come to be scored as\n * corroborated.\n */\nexport function independence(\n  document: SourceRef,\n  profile: SourceSubject | null | undefined,\n): 'self' | 'third-party' | 'unknown' {\n  const own = registrableDomain(profile?.website);\n  const docDomain = registrableDomain(document.url);\n  if (!docDomain) return 'unknown';\n  if (!own) return 'unknown';\n  return docDomain === own ? 'self' : 'third-party';\n}\n\nexport function normalizeToken(value: unknown): string {\n  return String(value ?? '')\n    .toLowerCase()\n    .normalize('NFD')\n    .replace(/[̀-ͯ]/g, '')\n    .replace(/[^a-z0-9]+/g, '');\n}\n\n/** The distinctive label of a registrable domain — `iberdrola` from `iberdrola.com`. */\nexport function domainLabel(domain: string | null | undefined): string | null {\n  if (!domain) return null;\n  const label = normalizeToken(domain.split('.')[0]);\n  return label.length >= 4 ? label : null;\n}\n\n/**\n * The identity tokens a profile publishes under.\n *\n * Drawn from its declared website and from its title, because the website field\n * is missing on most published profiles and the title is the only other thing\n * that names the organisation. Title tokens are the weaker signal and are used\n * only to demote a source to `affiliated`, never to promote one to `own`.\n */\nexport function organisationTokens(profile: SourceSubject | null | undefined): Set<string> {\n  const tokens = new Set<string>();\n  const own = domainLabel(registrableDomain(profile?.website));\n  if (own) tokens.add(own);\n  for (const word of String(profile?.title ?? '').split(/[\\s–—,.()/-]+/)) {\n    const token = normalizeToken(word);\n    if (token.length >= 5 && !GENERIC_TOKENS.has(token)) tokens.add(token);\n  }\n  return tokens;\n}\n\n/** The account a social URL is published by, or null if the host is not social. */\nexport function socialAccount(\n  url: string | null | undefined,\n): { host: string; account: string | null } | null {\n  const host = hostOf(url);\n  if (!host) return null;\n  const key = [...SOCIAL_HOSTS.keys()].find((h) => host === h || host.endsWith(`.${h}`));\n  if (!key) return null;\n  let path: string;\n  try {\n    const raw = url as string;\n    path = new URL(raw.includes('://') ? raw : `https://${raw}`).pathname;\n  } catch {\n    return null;\n  }\n  const match = path.match(SOCIAL_HOSTS.get(key) as RegExp);\n  return { host: key, account: match ? normalizeToken(match[match.length - 1]) : null };\n}\n\n/**\n * How far is this source from the subject it describes, judged with no help\n * from the subject — the heuristic baseline.\n *\n *   own          the profile's own registrable domain\n *   controlled   a social account or upload the profile plainly runs itself\n *   affiliated   a domain sharing the profile's distinctive name — a second\n *                domain, a foundation, a parent body. Not independent, but not\n *                provably the same legal publisher either.\n *   independent  a different publisher\n *   unknown      no baseline to judge against (no website, no parseable host)\n *\n * `unknown` is NOT independence, and callers must not treat it as such.\n *\n * Prefer `resolveSourceDistance`, which layers declared identity on top of\n * this. This function is exported because it is the floor that declarations\n * are measured against, and because the anti-gaming report reads it directly.\n */\nexport function sourceDistance(\n  document: SourceRef,\n  profile: SourceSubject | null | undefined,\n): SourceDistance {\n  const tokens = organisationTokens(profile);\n  const social = socialAccount(document.url);\n  if (social) {\n    if (!social.account) return 'unknown';\n    const mine = [...tokens].some(\n      (t) => social.account!.includes(t) || t.includes(social.account!),\n    );\n    return mine ? 'controlled' : 'independent';\n  }\n  if (document.storage_path && !hostOf(document.url)) return 'controlled';\n\n  const docDomain = registrableDomain(document.url);\n  if (!docDomain) return 'unknown';\n  const own = registrableDomain(profile?.website);\n  if (own && docDomain === own) return 'own';\n\n  const label = domainLabel(docDomain);\n  if (label && !GENERIC_TOKENS.has(label)) {\n    for (const token of tokens) {\n      if (label === token || label.includes(token) || token.includes(label)) {\n        // With no declared website, a domain carrying the profile's own name IS\n        // the profile's own domain — `unetxea.org` on UNetxea. Reading that as\n        // weak corroboration would credit a profile for citing itself, so an\n        // undeclared baseline resolves toward self, never away from it.\n        return own ? 'affiliated' : 'own';\n      }\n    }\n  }\n  if (!own) return 'unknown';\n  return 'independent';\n}\n\n/**\n * Weight a source lends to the independence terms.\n *\n * `affiliated` keeps a small non-zero weight rather than zero: a foundation\n * writing about its parent is weak corroboration, but it is not nothing, and\n * scoring it at zero would leave the model indifferent between a second domain\n * and no source at all.\n *\n * This table is also the ordering the declaration rule is enforced against, so\n * changing a number here changes what a profile is allowed to declare about\n * itself. It is not only a scoring constant.\n */\nexport const DISTANCE_WEIGHT: Readonly<Record<SourceDistance, number>> = {\n  own: 0,\n  controlled: 0,\n  unknown: 0,\n  affiliated: 0.25,\n  independent: 1,\n};\n\nexport const DISTANCES: readonly SourceDistance[] = [\n  'own', 'controlled', 'unknown', 'affiliated', 'independent',\n];\n\n/**\n * The shape `profile_domains.domain` accepts, restated.\n *\n * Kept byte-compatible with the CHECK constraint in 20260829090000 on purpose:\n * `normalizeDeclaredDomain` must return null in exactly the cases the database\n * would refuse the row. A value that normalises to something storable but\n * unmatchable would be worse than a rejection — it would sit in the table\n * looking like a declaration and silently apply to nothing.\n */\nconst DECLARABLE_DOMAIN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/;\n\n/**\n * Normalises anything paste-shaped into the host identity we store.\n *\n * Deliberately does NOT collapse subdomains, and `public.normalize_source_domain`\n * in 20260829090000 does the same stripping in the same order, so a value\n * normalised by the database and one normalised here are the same string.\n * Collapsing here would have meant restating DEEP_SUFFIXES in SQL, and a suffix\n * list maintained in two languages is a suffix list that disagrees with itself.\n * Subdomain reach is handled at match time instead, by `declaredSourceDistance`,\n * where it costs nothing to get right.\n *\n * The one place this does MORE than the SQL is internationalised names: a host\n * read out of a URL is already punycode, so `münchen.de` is punycoded here to\n * match it. The database has no punycode and will refuse the raw form, which is\n * the right failure — a loud rejection rather than a row that matches nothing.\n */\nexport function normalizeDeclaredDomain(input: string | null | undefined): string | null {\n  if (!input || typeof input !== 'string') return null;\n  const stripped = input\n    .trim()\n    .toLowerCase()\n    .replace(/^[a-z][a-z0-9+.-]*:\\/\\//, '')\n    .replace(/[/?#].*$/, '')\n    .replace(/^[^@]*@/, '')\n    .replace(/:\\d*$/, '')\n    .replace(/^www\\./, '')\n    .replace(/\\.$/, '');\n  if (!stripped) return null;\n  // hostOf punycodes, and drops a further leading www. that stripping a\n  // userinfo segment may have exposed.\n  const host = hostOf(stripped) ?? stripped;\n  // A bare label with no dot is not a domain, however confidently it was typed.\n  return DECLARABLE_DOMAIN.test(host) ? host : null;\n}\n\n/** Normalises the account segment of a platform URL, or an already-bare handle. */\nexport function normalizeAccountPath(input: string | null | undefined): string | null {\n  if (!input || typeof input !== 'string') return null;\n  const trimmed = input.trim();\n  if (!trimmed) return null;\n  const fromUrl = socialAccount(trimmed);\n  if (fromUrl?.account) return fromUrl.account;\n  const token = normalizeToken(trimmed.split('/').filter(Boolean).pop() ?? '');\n  return token.length > 0 ? token : null;\n}\n\n/**\n * The distance the profile's own declarations claim for this source, or null\n * when nothing declared covers it.\n *\n * On a platform host the domain alone is never enough: declaring `linkedin.com`\n * must not turn all of LinkedIn into the subject's own surface, so a social URL\n * matches only a declaration whose account path matches it too.\n */\nexport function declaredSourceDistance(\n  document: SourceRef,\n  declarations: readonly DeclaredDomain[] | null | undefined,\n): { relation: DomainRelation; declaredBy: DomainDeclaredBy } | null {\n  if (!declarations || declarations.length === 0) return null;\n  const domain = registrableDomain(document.url);\n  const host = hostOf(document.url);\n  if (!domain || !host) return null;\n  const social = socialAccount(document.url);\n\n  // A declaration reaches a URL when it names the URL's publisher, the URL's\n  // exact host, or a parent of that host. `reports.example.com` therefore\n  // covers only itself, while `example.com` covers the whole site — which is\n  // the reading somebody typing either one intends.\n  const covers = (declared: string) =>\n    declared === domain || declared === host || host.endsWith(`.${declared}`);\n\n  let best: { relation: DomainRelation; declaredBy: DomainDeclaredBy } | null = null;\n  let bestSpecificity = -1;\n  for (const declaration of declarations) {\n    const declaredDomain = normalizeDeclaredDomain(declaration.domain);\n    if (!declaredDomain || !covers(declaredDomain)) continue;\n\n    const declaredAccount = normalizeAccountPath(declaration.accountPath);\n    let specificity: number;\n    if (social) {\n      // A platform host: only an account-level declaration can speak for it.\n      if (!declaredAccount || declaredAccount !== social.account) continue;\n      specificity = 2;\n    } else {\n      if (declaredAccount) continue; // account declaration, non-account URL\n      specificity = 1;\n    }\n\n    const candidate = {\n      relation: declaration.relation,\n      declaredBy: declaration.declaredBy ?? 'representative',\n    };\n    if (\n      specificity > bestSpecificity\n      || (specificity === bestSpecificity\n        && best !== null\n        && DISTANCE_WEIGHT[candidate.relation] < DISTANCE_WEIGHT[best.relation])\n    ) {\n      best = candidate;\n      bestSpecificity = specificity;\n    }\n  }\n  return best;\n}\n\n/**\n * The distance actually used, once the profile's declared source identity is\n * taken into account.\n *\n * **A declaration may lower the weight a source lends. It may never raise it.**\n *\n * That is the entire anti-gaming rule, and it is enforced here by comparing\n * `DISTANCE_WEIGHT`, not by trusting the words. Three things have to hold\n * together for it to be safe, and all three are tested:\n *\n * 1. `independent` is not in `DomainRelation`, and `profile_domains` refuses to\n *    store it — so nobody can declare a regulator independent of themselves.\n * 2. A declaration that is not a moderator's is only applied when it does not\n *    increase the weight. Declaring your own domain to be an `affiliated`\n *    foundation, to convert a 0 into a 0.25, resolves back to `own`.\n * 3. Deleting a declaration cannot buy independence either, because removing it\n *    only falls back to `sourceDistance`, which is the floor the declaration\n *    was measured against in the first place.\n *\n * A declaration recorded by a moderator (`platform_admin`) is applied as\n * written: it is not a statement the subject made about itself, and moderation\n * is the only route by which a source may be made *less* self-published than\n * the heuristic thought. `inferred` rows are deliberately NOT authoritative —\n * they are the platform's own guess, seeded from data it already holds, and a\n * guess that can raise a weight is a guess that can be gamed by arranging the\n * data it is guessed from.\n */\nexport function resolveSourceDistance(\n  document: SourceRef,\n  profile: SourceSubject | null | undefined,\n  declarations?: readonly DeclaredDomain[] | null,\n): SourceDistance {\n  const heuristic = sourceDistance(document, profile);\n  const declared = declaredSourceDistance(document, declarations);\n  if (!declared) return heuristic;\n\n  if (declared.declaredBy === 'platform_admin') return declared.relation;\n\n  const declaredWeight = DISTANCE_WEIGHT[declared.relation];\n  const heuristicWeight = DISTANCE_WEIGHT[heuristic];\n  if (declaredWeight < heuristicWeight) return declared.relation;\n  if (declaredWeight > heuristicWeight) return heuristic;\n  // Equal weight: the declaration is more specific than `unknown` and costs\n  // nothing, and naming a surface `own` narrows depth rather than widening it.\n  return heuristic === 'unknown' ? declared.relation : heuristic;\n}\n\n/**\n * The family key used for INDEPENDENCE counting.\n *\n * Differs from `sourceFamily` in one decisive way: every self-published or\n * unattributable surface collapses into a single `self:<profile>` bucket, so a\n * profile cannot manufacture breadth out of its own channels. `sourceFamily`\n * must not do this — coverage and diversity need the split — which is why both\n * live here side by side rather than one being expressed as the other.\n */\nexport function independenceFamily(\n  document: SourceRef,\n  profile: SourceSubject | null | undefined,\n  declarations?: readonly DeclaredDomain[] | null,\n): { family: string; distance: SourceDistance } {\n  const distance = resolveSourceDistance(document, profile, declarations);\n  if (distance === 'own' || distance === 'controlled' || distance === 'unknown') {\n    return { family: `self:${profile?.id ?? 'unknown'}`, distance };\n  }\n  return { family: sourceFamily(document), distance };\n}\n\n/**\n * The family key used for the DEPTH term — \"how many distinct sources actually\n * back this profile's claims?\".\n *\n * Sits between the other two. A separately-branded domain — a second company\n * site, a foundation — stays distinct, because it is genuinely more material\n * than one page; the independence terms are what decline to call it a different\n * publisher. But the subject's OWN surfaces all collapse into one bucket: its\n * declared site, its uploads, its LinkedIn, its YouTube, its X account are one\n * organisation talking, on five channels. Without that collapse `sourceFamily`\n * hands every aggregator URL its own identity — correct for telling two\n * publishers apart behind one Google Storage bucket, and exactly wrong here,\n * where it would let a profile buy breadth by cross-posting one announcement.\n */\nexport function depthFamily(\n  document: SourceRef,\n  profile: SourceSubject | null | undefined,\n  declarations?: readonly DeclaredDomain[] | null,\n): string {\n  const distance = resolveSourceDistance(document, profile, declarations);\n  if (distance === 'own' || distance === 'controlled') {\n    return `self-surface:${profile?.id ?? 'unknown'}`;\n  }\n  return sourceFamily(document);\n}\n\n/** Row shape of `public.profile_domains`, as stored. */\nexport interface ProfileDomainRow {\n  id: string;\n  domain: string;\n  account_path: string | null;\n  relation: DomainRelation;\n  label: string | null;\n  declared_by: DomainDeclaredBy;\n  evidence_note: string | null;\n  confirmed_at: string | null;\n  created_at: string;\n}\n\n/** Adapts stored rows to the shape the resolver takes. */\nexport function toDeclaredDomains(\n  rows: readonly Pick<\n    ProfileDomainRow, 'domain' | 'account_path' | 'relation' | 'declared_by'\n  >[],\n): DeclaredDomain[] {\n  return rows.map((row) => ({\n    domain: row.domain,\n    accountPath: row.account_path,\n    relation: row.relation,\n    declaredBy: row.declared_by,\n  }));\n}\n",
    "lib/profile-content.ts": "import type { SupabaseClient } from '@supabase/supabase-js';\nimport type {\n  EntityDocument,\n  EntityGoal,\n  EntityImprovement,\n  EntityPhoto,\n  EntityRecognition,\n  EntityVideo,\n} from '@/types/entity';\nimport type { ProfileKind } from '@/types/profile';\nimport type { FrameworkRecord } from '@/lib/frameworks';\n\nexport const PROFILE_CONTENT_PARENT_COLUMN: Record<ProfileKind, string> = {\n  entidad: 'entity_id',\n  persona: 'persona_id',\n  territorio: 'territorio_id',\n  producto_servicio: 'producto_servicio_id',\n};\n\nconst PROFILE_STORAGE_PREFIX: Record<ProfileKind, string> = {\n  entidad: 'entities',\n  persona: 'personas',\n  territorio: 'territorios',\n  producto_servicio: 'productos_servicios',\n};\n\nexport interface ProfileContentCollections {\n  frameworks?: FrameworkRecord[];\n  improvements: EntityImprovement[];\n  recognitions: EntityRecognition[];\n  goals: EntityGoal[];\n  videos: EntityVideo[];\n  photos: EntityPhoto[];\n  documents: EntityDocument[];\n}\n\nexport function buildProfileContentParent(\n  kind: ProfileKind,\n  profileId: string,\n): Record<string, string> {\n  return { [PROFILE_CONTENT_PARENT_COLUMN[kind]]: profileId };\n}\n\nexport function buildProfileMediaStoragePath(\n  kind: ProfileKind,\n  profileId: string,\n  filename: string,\n): string {\n  if (kind === 'entidad') return `${profileId}/photos/${filename}`;\n  return `${PROFILE_STORAGE_PREFIX[kind]}/${profileId}/photos/${filename}`;\n}\n\nexport function buildProfileDocumentStoragePath(\n  kind: ProfileKind,\n  profileId: string,\n  filename: string,\n): string {\n  return `${PROFILE_STORAGE_PREFIX[kind]}/${profileId}/${filename}`;\n}\n\nexport async function fetchProfileContent(\n  supabase: SupabaseClient,\n  kind: ProfileKind,\n  profileId: string,\n): Promise<ProfileContentCollections> {\n  const parentColumn = PROFILE_CONTENT_PARENT_COLUMN[kind];\n  const [\n    improvementsResult,\n    recognitionsResult,\n    goalsResult,\n    videosResult,\n    photosResult,\n    documentsResult,\n    frameworksResult,\n  ] = await Promise.all([\n    supabase\n      .from('entity_improvements')\n      .select('*')\n      .eq(parentColumn, profileId)\n      .order('position', { ascending: true }),\n    supabase\n      .from('entity_recognitions')\n      .select('*')\n      .eq(parentColumn, profileId)\n      .order('position', { ascending: true }),\n    supabase\n      .from('entity_goals')\n      .select('*')\n      .eq(parentColumn, profileId)\n      .order('position', { ascending: true }),\n    supabase\n      .from('entity_videos')\n      .select('*')\n      .eq(parentColumn, profileId)\n      .order('created_at', { ascending: true }),\n    supabase\n      .from('entity_photos')\n      .select('*')\n      .eq(parentColumn, profileId)\n      .order('created_at', { ascending: true }),\n    supabase\n      .from('entity_documents')\n      .select('*')\n      .eq(parentColumn, profileId)\n      .order('position', { ascending: true }),\n    supabase.from('profile_framework_records').select('*,actions:profile_framework_actions(*)')\n      .eq('profile_kind', kind).eq('profile_id', profileId).order('created_at'),\n  ]);\n\n  for (const result of [\n    improvementsResult,\n    recognitionsResult,\n    goalsResult,\n    videosResult,\n    photosResult,\n    documentsResult,\n    frameworksResult,\n  ]) {\n    if (result.error) {\n      throw new Error(`Unable to load profile content: ${result.error.message}`);\n    }\n  }\n\n  return {\n    frameworks: (frameworksResult.data ?? []) as FrameworkRecord[],\n    improvements: (improvementsResult.data ?? []) as EntityImprovement[],\n    recognitions: (recognitionsResult.data ?? []) as EntityRecognition[],\n    goals: (goalsResult.data ?? []) as EntityGoal[],\n    videos: (videosResult.data ?? []) as EntityVideo[],\n    photos: (photosResult.data ?? []) as EntityPhoto[],\n    documents: (documentsResult.data ?? []) as EntityDocument[],\n  };\n}\n",
    "types/profile-kind.ts": "import type { ProfileClaimStatus, ProfileKind, ProfileLifecycleColumns } from './profile';\n\n/**\n * Row shape shared by `entities`, `personas`, `territorios` and\n * `productos_servicios` — all four tables mirror the real live `entities`\n * schema (see supabase/migrations/005_profile_kind_tables.sql) so a single\n * generic CRUD layer can serve all four profile kinds.\n */\nexport interface ProfileKindRow extends ProfileLifecycleColumns {\n  id: string;\n  owner_user_id: string | null;\n  created_by_user_id: string;\n  claim_status: ProfileClaimStatus;\n  title: string;\n  subtitle: string | null;\n  /**\n   * Null only for `personas`, whose category is optional since\n   * 20260827193220_optional_persona_category.sql. The other three tables are\n   * still NOT NULL; the type is widened for all four because one row shape\n   * serves every kind, so every reader has to cope with the absence anyway.\n   */\n  category: string | null;\n  location: string | null;\n  website: string | null;\n  description: string | null;\n  image_path: string | null;\n  created_at: string;\n}\n\nexport const PROFILE_KIND_TABLE: Record<ProfileKind, string> = {\n  entidad: 'entities',\n  persona: 'personas',\n  territorio: 'territorios',\n  producto_servicio: 'productos_servicios',\n};\n\n/**\n * Storage path prefix within the shared `entity-images` bucket. Entities\n * keep their existing, already-live `${id}/filename` paths (no prefix);\n * the three new kinds are namespaced under their own prefix so the storage\n * RLS policies in 006_profile_kind_storage.sql can scope narrowly without\n * touching existing entity image permissions.\n */\nexport const PROFILE_KIND_IMAGE_PREFIX: Record<ProfileKind, string | null> = {\n  entidad: null,\n  persona: 'personas',\n  territorio: 'territorios',\n  producto_servicio: 'productos_servicios',\n};\n\n/**\n * Suggested categories per profile kind.\n *\n * For `persona` these are suggestions rather than a closed set: the creation\n * form offers them through a datalist on a free-text input, so somebody whose\n * situation is not listed types their own instead of being pushed into \"Otro\".\n * That is also what covers \"Otro, and describe it\" without a second\n * conditional field -- the description simply *is* the value.\n *\n * The person list deliberately spans situations, not only sustainability job\n * titles. The previous seven were all professional roles, which left a\n * student, a retiree, a volunteer or a public employee nothing accurate to\n * choose; the reliable outcome of a mandatory field plus an inapplicable list\n * is people picking something untrue to get past the form, which is precisely\n * the noise a trust directory cannot afford.\n */\nexport const PROFILE_KIND_CATEGORIES: Record<ProfileKind, string[]> = {\n  entidad: ['Empresa', 'ONG', 'Cooperativa', 'Fundación', 'Asociación', 'Administración Pública', 'Universidad', 'Otro'],\n  persona: [\n    'Estudiante',\n    'Investigador/a',\n    'Docente / Educador/a',\n    'Directivo/a',\n    'Emprendedor/a',\n    'Autónomo/a',\n    'Consultor/a',\n    'Profesional / Especialista',\n    'Empleado/a del sector privado',\n    'Funcionario/a / Empleado/a público',\n    'Tercer sector / ONG',\n    'Activista / Voluntario/a',\n    'Jubilado/a',\n    'Otro',\n  ],\n  territorio: ['Municipio', 'Provincia', 'Región', 'Parque natural', 'Comarca', 'País', 'Otro'],\n  producto_servicio: ['Energía', 'Movilidad', 'Vivienda', 'Alimentación', 'Consultoría', 'Servicio financiero', 'Gestión de residuos', 'Otro'],\n};\n\n/**\n * Kinds whose category may be left unset.\n *\n * Mirrors the database exactly: only `personas.category` is nullable (see\n * 20260827193220_optional_persona_category.sql). Offering to skip it on the\n * other three would build a form the database then rejects on submit.\n */\nexport const PROFILE_KIND_CATEGORY_OPTIONAL: Record<ProfileKind, boolean> = {\n  entidad: false,\n  persona: true,\n  territorio: false,\n  producto_servicio: false,\n};\n\n/** What to show where a category is absent. Never stored. */\nexport const ABSENT_CATEGORY_LABEL = 'Sin categoría';\n\nexport const PROFILE_KIND_FORM_LABELS: Record<ProfileKind, { title: string; subtitle: string; category: string; createCta: string }> = {\n  entidad: { title: 'Título', subtitle: 'Subtítulo', category: 'Categoría', createCta: 'Crear entidad' },\n  persona: { title: 'Nombre', subtitle: 'Rol / cargo', category: 'Categoría', createCta: 'Crear persona' },\n  territorio: { title: 'Nombre del territorio', subtitle: 'Subtítulo', category: 'Tipo de territorio', createCta: 'Crear territorio' },\n  producto_servicio: { title: 'Nombre', subtitle: 'Subtítulo', category: 'Categoría', createCta: 'Crear producto/servicio' },\n};\n\nexport const PROFILE_KIND_COLORS: Record<ProfileKind, string> = {\n  persona: '#4997D0',\n  entidad: '#27ae60',\n  producto_servicio: '#e67e22',\n  territorio: '#9b59b6',\n};\n\nexport function buildProfileImagePublicUrl(imagePath: string | null, supabaseUrl: string): string | null {\n  if (!imagePath) return null;\n  if (/^https?:\\/\\//i.test(imagePath)) return imagePath;\n  return `${supabaseUrl}/storage/v1/object/public/entity-images/${imagePath}`;\n}\n\nexport function buildProfileImageStoragePath(kind: ProfileKind, id: string, filename: string): string {\n  const prefix = PROFILE_KIND_IMAGE_PREFIX[kind];\n  return prefix ? `${prefix}/${id}/${filename}` : `${id}/${filename}`;\n}\n",
    "lib/scoring-methodology.ts": "/** Human mathematical specification. Reviewed together with the source manifest. */\nexport const METHODOLOGY_REVISION = '2026-09-27.3';\nexport const METHODOLOGY_SECTIONS = [\n  {\n    id: 'notation', title: '1. Notación, datos y elegibilidad',\n    formula: 'clamp(x, a, b) = min(b, max(a, x))\\nround(x) = floor(x + 0.5), para los valores no negativos de este modelo\\nsat(x; k) = 0 si x ≤ 0; x/(x+k) en otro caso',\n    paragraphs: [\n      'Las operaciones usan números IEEE-754 de JavaScript. Los redondeos indicados forman parte del algoritmo; no se redondean las etapas intermedias salvo donde se especifica. ∑ suma términos; |S| cuenta elementos distintos de un conjunto; ∅ es el conjunto vacío. Los puntos no representan toneladas de CO₂, porcentajes de ahorro ni impacto neto medido.',\n      'El lector del modelo carga perfiles con publication_status = published de las cuatro familias. n es el número de prácticas de ese perfil cuya contribution_provenance es platform_admin, verified_representative o legacy_official. No se aplica otro filtro de magnitud, fecha, tema o medición al contar estas prácticas. Con n = 0 no se publica ni Impacto ni Fiabilidad: el estado es unscored, no una puntuación cero.',\n      'Solo los vínculos de sustainable_practice_evidence con status = confirmed entran en la puntuación. Propuestas, disputas y retiradas se excluyen. El lector reconoce una foto, en su defecto un vídeo, en su defecto un documento; el esquema exige una sola referencia por fila. Los vínculos a prácticas no elegibles no intervienen.',\n      'Las conexiones deben estar publicadas. La elegibilidad semántica se aplica al Impacto directo y al grafo de Impacto, pero no filtra los niveles de conexión de Fiabilidad ni su grafo. Las reglas son las del código ejecutable; no se debe inferir una exclusión adicional a partir de una etiqueta editorial.',\n      'Los fallos de lectura críticos producen unavailable, nunca una cifra inventada. Las consultas de compatibilidad toleran exclusivamente los errores de columna/tabla ausente previstos en el lector: una base anterior a la migración no tiene declaraciones o exclusiones nuevas. Ocultar una puntuación solo enmascara su presentación pública; no elimina el perfil publicado del cálculo ni del ecosistema.',\n    ],\n  },\n  {\n    id: 'impact', title: '2. Impacto: alcance de las contribuciones, relaciones y escala pública',\n    formula: 'v(L0,L1,L2,L3,L4,L5) = (0.0005, 0.005, 0.02, 0.06, 0.15, 0.4)\\nL′ = L1 si L ∈ {L4, L5} sin confirmar; L′ = L1 sin lectura vigente\\nwᵤ = v(L′ᵤ)\\nc_g = max(v(Lbase,g), maxᵤ∈g wᵤ)\\nB_g = min(c_g, ∑z∈Z_g c_g(1 − ∏ᵤ∈U_gz(1 − wᵤ/c_g)))\\nq_g = c_g(1 − (1 − B_g/c_g) ∏ᵤ∈H_g(1 − wᵤ/c_g))\\nQ = ∑g q_g [municipios]; Q = ∑ᵤ wᵤ [otras escalas]\\nS = 2.4 Q^0.7\\nP = round(1000 (1 − e^(−S)))/10\\nG(c) = (1−0.60^c)/(1−0.60)\\nD = round(10000 min(4, ∑ₛ bₛ G(cₛ)))/10000\\nI₀ = P + D\\nN = min(Nᴵ, max(0,4−D))\\nIraw = I₀ + N\\nIpublic = min(95,Iraw) [modo acumulativo actual]',\n    paragraphs: [\n      'Impacto 2.0, provisional, valora cada contribución según su alcance para la escala propia del perfil: municipio, organización, persona o producto. Un lector de IA asigna a cada contribución un nivel de L0, simbólico, a L5, transformador, con reglas publicadas. La lectura se guarda con la huella del texto leído; si el texto cambia, la contribución cuenta como L1 hasta que se vuelve a leer. Cualquier acción concreta es al menos L1; un plan aprobado cuenta como L1 por sí mismo y un plan en preparación como L0.',\n      'Revisión: un nivel L4 o L5 cuenta como L1 hasta que una persona lo confirma. Mismo trabajo: las contribuciones que el lector identifica como el mismo trabajo, o como fase o parte de otra, forman una unidad que cuenta una sola vez con el mayor nivel aplicado después de las retenciones por revisión. El nivel propio de cada contribución se conserva y se muestra. Las instrucciones del lector prohíben unir un plan y sus medidas; además, el motor bloquea esa unión cuando las categorías municipales identifican uno como plan y otro como medida.',\n      'Solo en los perfiles municipales, las unidades se agrupan según aquello sobre lo que actúan (desplazamientos, residuos, energía municipal, parques, etc.). La capacidad base de cada grupo es L4 para los sistemas físicos de todo el municipio, L3 para el patrimonio municipal, los programas centrados en personas y otras categorías, y L2 para planes y datos. La capacidad efectiva c_g es el mayor valor entre esa base y la unidad de mayor valor aplicado del grupo. Z_g reúne las zonas documentadas distintas; U_gz contiene las unidades de una misma zona; H_g contiene las unidades de zona compartida o desconocida. Dentro de cada zona se combina el solapamiento; las zonas distintas se suman hasta c_g y después se combina el bloque resultante con H_g. Q suma los resultados q_g de los grupos. En los demás tipos de perfil Q suma las unidades directamente: las capacidades municipales no se aplican fuera de esa escala.',\n      'D es el Impacto directo por relaciones y N la influencia del ecosistema; ambos conservan su cálculo anterior. Para cada contraparte distinta se conserva una sola categoría semántica, la más alta en el orden de la tabla; cₛ cuenta las contrapartes de la categoría s y bₛ es su peso base, con decaimiento 0.60. El límite conjunto de relaciones directas y ecosistema es 4. La evidencia añade 0 puntos de Impacto directo y se refleja en la Fiabilidad.',\n      'La categoría se resuelve normalizando el texto: NFD, sin diacríticos, minúsculas, grupos no alfanuméricos convertidos en espacios y espacios compactados. Primero se comprueban etiquetas exactas; después patrones en este orden: BOOST interés, motor, guía; implementación; grupo de trabajo; red; coordinación; financiación; técnico/administrativo/duplicado. Texto desconocido o vacío se considera asociación genérica. scoring_eligible distinto de true en la lectura de base de datos se convierte en falso y produce categoría técnica de peso 0. El apéndice conserva todos los alias y expresiones regulares exactos.',\n      'La deduplicación semántica no consulta las posiciones de confirmación. Por tanto, en el algoritmo vigente una relación disputada puede conservar Impacto directo si su categoría sigue siendo elegible. Su Fiabilidad y su transmisión por el grafo sí son cero. Es una limitación observable del modelo, no una recomendación ni una corrección introducida por esta documentación.',\n      'Los identificadores de contraparte se resuelven en las cuatro familias, por lo que una fila inversa no duplica el crédito. Se ignoran las autorrelaciones. Si falta una identidad remota, el contador directo usa una identidad sintética por fila; el grafo exige dos perfiles publicados conocidos. En caso de empate semántico se conserva la primera categoría encontrada.',\n    ],\n  },\n  {\n    id: 'families', title: '3. Desglose por tipo de perfil y comparación relativa',\n    formula: 'Municipio (territorio Municipio, Ayuntamiento de …): P con grupos, capacidades, zonas\\nOrganización, persona, producto: P con suma de unidades\\nTodas las familias: I = min(95,P+D+N); R = Rtier\\nSi se activa el modo relativo: I = 1 + round(99(M−r)/(M−1))\\nSi M = 1: I = 100; r = (primera posición + última posición)/2',\n    paragraphs: [\n      'Las cuatro familias comparten niveles, pesos, curva, límites, bandas y reglas de evidencia; cambia la escala contra la que se lee cada contribución. Municipio: su población, emisiones, recursos y territorio. Organización: sus propias operaciones y huella, y las personas o el territorio a los que llega su trabajo según su perfil. Persona: su vida y su hogar, y las personas o proyectos a los que llega su trabajo. Producto o servicio: su propia huella y sus usuarios. Los territorios con categoría Municipio y las entidades cuyo nombre empieza por Ayuntamiento de usan la escala municipal.',\n      'No hay un ajuste automático por ventas, empleados, renta o superficie: el tamaño entra solo mediante el juicio de alcance del lector, hecho contra la escala del propio perfil. Las capacidades por grupo se aplican únicamente a la escala municipal; las demás familias suman sus unidades hasta que se adapten con criterios propios.',\n      'Cada familia está fijada en modo accumulating. La preparación para comparar perfiles es informativa: requiere al menos 25 perfiles con prácticas, al menos 5 con Iraw ≥ 25 y un máximo Iraw ≥ 95. Cruzar estos umbrales no cambia automáticamente la escala.',\n      'La rama relative existe pero está inactiva. Ordena Iraw descendente dentro de la misma familia, usa rango medio para empates exactos y M es el número de perfiles con prácticas. El identificador ordena empates de forma estable sin romper su rango medio. La comparación utiliza Iraw sin el techo público de 95; los campos de presentación rawScore e intrinsicScore se limitan a 95. Activar la rama relativa exige un cambio deliberado de versión.',\n    ],\n  },\n  {\n    id: 'evidence', title: '4. Fiabilidad: de archivos a fuentes y familias',\n    formula: 'C = |{p: existe un vínculo puntuable para p}| / n\\nB = |{familias de profundidad de vínculos}|\\nF = |{familias de profundidad de documentos relevantes}|\\nJ = |{familias de independencia con peso > 0}|\\nW = ∑ por primera aparición de cada familia con peso > 0: peso(distancia)\\nU = |{p: tiene fuente de distancia independent}| / n\\nV = |{p: tiene ≥2 familias de independencia y una fuente independent}| / n',\n    paragraphs: [\n      'C es cobertura de prácticas, B diversidad de fuentes que respaldan prácticas, F fundamento documental relevante, J número de familias con peso positivo, W independencia ponderada, U fracción de prácticas con al menos una fuente independiente y V fracción corroborada. Cuando n = 0, las fracciones son cero internamente, pero el perfil no recibe puntuación pública.',\n      'Un documento vinculado solo se acepta si existe en los documentos del mismo perfil. Las fotos y vídeos vinculados se consideran controlled y comparten la familia propia. No se analiza la imagen, el vídeo ni el PDF para determinar su veracidad. Los enlaces web del sistema de adjuntos genérico no se convierten por sí solos en vínculos puntuables.',\n      'Un documento suelto es relevante para F si su título y descripción comparten al menos dos palabras distintas con la unión de título, descripción y theme_slug de las prácticas elegibles. Las palabras se pasan a minúsculas, se descomponen con NFD, se quitan diacríticos y se separan por caracteres ajenos a a–z/0–9. Se conservan palabras de más de 3 caracteres que no estén en la lista de stopwords del apéndice. No se inspecciona el texto del archivo ni se aplica comprensión semántica.',\n      'Un reconocimiento con emisor resoluble usa issuer_domain normalizado o, en su defecto, el dominio de su URL. Con scope = practice y una improvement_id elegible crea un vínculo exclusivamente para esa práctica. En cualquier otro caso crea una familia relevante para F. Si no hay emisor resoluble no crea ninguna. Las fechas declaradas de emisión/caducidad no se usan en este cálculo.',\n      'Las familias de profundidad colapsan fuentes own y controlled en self-surface:perfil; las demás usan la familia de origen. Las familias de independencia colapsan own, controlled y unknown en self:perfil. Affiliated e independent conservan su familia de origen. J incluye affiliated porque su peso es 0.25; U exige independent. W toma el peso de la primera aparición positiva por familia, no el máximo. Esta distinción forma parte de la implementación vigente.',\n    ],\n  },\n  {\n    id: 'identity', title: '5. Clasificación exacta de la identidad de las fuentes',\n    formula: 'peso(own)=0; peso(controlled)=0; peso(unknown)=0; peso(affiliated)=0.25; peso(independent)=1\\nTokens = etiqueta del dominio web (longitud ≥4) ∪ palabras distintivas del título (longitud ≥5)',\n    paragraphs: [\n      'El host se obtiene con URL; si falta el esquema se presupone https. Se normaliza a minúsculas y se elimina www. El dominio registrable usa las dos últimas etiquetas, salvo los sufijos especiales bizkaia.eus, gipuzkoa.eus, araba.eus, euskadi.eus, co.uk, com.es, org.es y gob.es: conserva también la etiqueta inmediatamente anterior cuando existe. Es una lista explícita, no la Public Suffix List completa.',\n      'Los tokens de identidad se pasan a minúsculas, NFD, sin diacríticos ni caracteres no alfanuméricos. La palabra distintiva de la web es la primera etiqueta de su dominio registrable. Del título se separan espacios y los signos – — , . ( ) / -. Se excluyen los términos genéricos enumerados íntegramente en el apéndice.',\n      'La heurística comprueba en orden: (1) cuenta social reconocida: sin cuenta es unknown; si cuenta y token se contienen en algún sentido es controlled; en otro caso independent; (2) archivo subido sin host es controlled; (3) sin dominio es unknown; (4) mismo dominio que la web es own; (5) etiqueta distintiva que coincide o se contiene con un token: affiliated si hay web, own si no la hay; (6) sin web es unknown; en otro caso independent. Es una clasificación heurística, no una verificación legal de independencia.',\n      'Se reconocen cuentas en LinkedIn (/company, /school, /showcase, /in), X, Twitter, YouTube (/@, /c, /channel, /user), Facebook, Instagram, Issuu, Medium y Vimeo; los patrones exactos figuran en el apéndice. Un vídeo vinculado como vídeo sigue siendo controlled; una URL social de un documento sigue la heurística de documentos.',\n      'Las declaraciones de dominio pueden cubrir el dominio registrable, el host exacto o sus subdominios. En redes sociales deben identificar además la misma cuenta. Las declaraciones normales tienen especificidad 1 y las de cuenta 2; a igual especificidad se elige el menor peso, conservando la primera a igual peso. Una declaración platform_admin se aplica directamente. Representative o inferred solo pueden bajar el peso; si el peso empata solo sustituyen unknown. Nadie puede autodeclarar independent.',\n      'La familia de origen es normalmente domain:dominio. En storage.googleapis.com, drive.google.com, docs.google.com, dropbox.com, linkedin.com, x.com, twitter.com, youtube.com, youtu.be, issuu.com y scribd.com usa aggregated:clave-del-artefacto. Los hosts se comparan exactamente para esta lista. Una subida sin host usa uploaded:id; sin dominio usa document:id.',\n      'La clave del artefacto da prioridad a storage_path. Si hay URL, elimina fragmento y parámetros de rastreo utm_*, fbclid, gclid, mc_cid, mc_eid, _ga, ref y source; conserva otros parámetros y su orden, elimina barras finales del path (salvo /), omite protocolo y puerto en la clave final y convierte todo a minúsculas. Sin URL válida usa document:id. Las familias no son necesariamente editores jurídicos reales: estos límites de resolución también afectan a la puntuación.',\n    ],\n  },\n  {\n    id: 'reliability', title: '6. Fiabilidad: selección de banda y calidad dentro de la banda',\n    formula: 'T0 si n=0 o C<0.20; banda [0,49]\\nT4 si freshnessAvailable y C≥0.80 y U≥0.60 y J≥3 y V≥0.40; banda [85,100]\\nT3 si C≥0.60 y U≥0.25 y (J≥2 o (J≥1 y U≥0.60)); banda [70,84]\\nT2 si C≥0.60; banda [60,69]\\nT1 en otro caso; banda [50,59]\\nq = clamp(∑ₜ wₜ xₜ,0,1)\\nR = round(L + (H−L)q)\\nAporte interno del término t = (H−L)wₜxₜ',\n    paragraphs: [\n      'Se evalúan las condiciones en el orden indicado. L y H son el suelo y techo de la banda elegida. La banda representa la situación probatoria y q la calidad dentro de esa situación. Por eso añadir documentación puede cruzar un umbral de banda; no existe un precio fijo universal por archivo.',\n      'freshnessAvailable es false: T4 está cerrada. La Fiabilidad máxima teórica actual es 84 (B+), aunque la escala y las etiquetas conservan A−, A y A+ para modelos o configuraciones futuras. No se debe prometer A por subir más documentos ni afirmar que el modelo verifica su actualidad.',\n      'Los pesos de T0–T2 y T3–T4 son distintos y se muestran en la tabla. Cada vector suma 1. La cifra R se calcula con q sin redondear. Solo el detalle publicado redondea q y los términos a 3 decimales, y sus aportaciones internas a 2 decimales; sumar esos valores de presentación puede diferir de la cifra final.',\n      'La nota pública es la primera banda de nota cuyo mínimo no supera R; si no hay ninguna (incluido R=0), es D. Las bandas de nota no se confunden con T0–T4: por ejemplo T1 abarca D+ y C−.',\n    ],\n  },\n  {\n    id: 'terms', title: '7. Los ocho términos normalizados de Fiabilidad',\n    formula: 'xcoverage = clamp((C−a)/(b−a),0,1)^0.7\\n(a,b) = (0,0.20) en T0; (0.20,0.60) en T1; (0.60,1) en T2,T3,T4\\nxdepth = B/(B+2)\\nxfoundation = F/(F+1.5)\\nxindependence = W/(W+3)\\nxcorroboration = clamp(V,0,1)^1\\nxcompleteness = m/4\\nK = min(15, round(∑ᵤ capᵤ nᵤ/(nᵤ+kᵤ)))\\nxrelationships = K/15\\nxecosystem = min(1,Nᴿ/3)',\n    paragraphs: [\n      'Cobertura mide el margen por encima de la cobertura mínima ya exigida por la banda; no vuelve a premiar íntegramente lo necesario para entrar. Profundidad y fundamento saturan con familias distintas. Independencia satura con su suma ponderada. Corroboración es lineal en la proporción de prácticas corroboradas.',\n      'm cuenta cuántos de descripción, ubicación, web e imagen contienen texto no vacío tras trim. Cada campo añade 1/4 a completitud antes de aplicar el peso y el ancho de banda. Nombre, subtítulo, categoría, coordenadas, número de seguidores y mera antigüedad no añaden completitud.',\n      'K mide las relaciones propias por nivel de confianza. Si cualquiera de las partes declined, el nivel es disputed y vale cero; si no, verificación independiente gana; después ambas partes afirmando (declared o confirmed) es confirmed; solo el perfil afirmando es declared; en otro caso observed. La misma fila puede tener distinto nivel para cada extremo. Por contraparte se conserva únicamente el nivel más alto; una disputa no anula otra fila de mayor nivel con esa contraparte.',\n      'Los pares (cap,k) de K son observed (1,8), declared (5,4), confirmed (12,3), verified (15,2). Se suman antes de redondear y aplicar el techo 15. Nᴿ es la contribución entera de aseguramiento del ecosistema, calculada con el grafo siguiente. Sí interviene en la Fiabilidad actual, con el peso pequeño de la tabla.',\n    ],\n  },\n  {\n    id: 'network', title: '8. Ecosistema: grafo, caminos y dos contribuciones acotadas',\n    formula: 'Iintr(j) = min(95,Pⱼ+Dⱼ) si nⱼ>0; 0 si no\\nRlegacy(j) = min(100, round(50 eⱼ/nⱼ) + 5mⱼ + Kⱼ + min(15,3tⱼ+2min(lⱼ,3))) si nⱼ>0; 0 si no\\nvᴵ(j) = clamp((Iintr(j)/95)(0.5+0.5 clamp(Rlegacy(j)/100,0,1)),0,1)\\nvᴿ(j) = clamp((Rlegacy(j)/100) oⱼ,0,1), donde oⱼ=1 si publica puntuación y 0.8 si la oculta\\nwᴵ(i,j) = max caminos π de 1..3 pasos: dᴵ[length(π)] ∏ aristas e∈π confianza(e)\\nwᴿ(i,j) = max caminos π de 1..3 pasos: dᴿ[length(π)] ∏ aristas e∈π confianza(e)\\nzᵃᵤ = ∑ perfiles j cuyo mejor camino tiene nivel u: wᵃ(i,j) vᵃ(j), a∈{I,R}\\nNᴵ = round(min(10, ∑ᵤ capᴵᵤ zᴵᵤ/(zᴵᵤ+kᴵᵤ)))\\nNᴿ = round(min(3, ∑ᵤ capᴿᵤ zᴿᵤ/(zᴿᵤ+kᴿᵤ)))',\n    paragraphs: [\n      'Primero se calculan valores intrínsecos, sin ecosistema. Para cada vecino j, eⱼ es el número de prácticas con algún vínculo confirmado, tⱼ el número de tipos foto/vídeo/documento y lⱼ el número de filas de evidencia confirmada. Estos contadores proceden del lector inicial, antes de resolver familias y reconocimientos. Rlegacy es la fórmula aditiva antigua usada como señal interna por el grafo; no es la Fiabilidad v1.3 publicada. Para nⱼ=0 el nodo transmite cero aunque tenga campos completos. El aporte intrínseco de Impacto usa el modelo vigente de alcance de las mejoras, no la antigua fórmula por recuento.',\n      'Se construyen dos grafos simétricos de perfiles publicados: el de aseguramiento admite todas las conexiones publicadas no disputadas; el de Impacto excluye además la categoría técnica/no elegible. Se omiten bucles propios y extremos que no estén entre los perfiles publicados. Filas repetidas entre dos perfiles conservan la confianza más alta. La dirección original no cambia la propagación.',\n      'La confianza por arista es verified 1, confirmed 0.7, declared 0.4, observed 0.1, disputed 0. En el grafo, a diferencia del crédito directo de cada extremo, basta una parte afirmando para declared; ambas para confirmed. Declined gana incluso a la verificación.',\n      'Los decaimientos son dᴵ=(1,0.25,0.05) y dᴿ=(1,0.05,0.005). El decaimiento se aplica una vez según la longitud total, no multiplicando los decaimientos de los pasos anteriores. El nivel del camino es el menor de sus niveles de arista. Cada perfil alcanzado se cuenta una sola vez por su camino de mayor peso; no se suman rutas alternativas. En un empate exacto se conserva el primer camino recorrido. La búsqueda recorre hasta tres pasos y nunca vuelve al origen, aunque puede repetir otros nodos dentro de ese horizonte.',\n      'La relevancia global vale 1: se multiplica por la confianza de cada arista y de nuevo al agregar z, sin cambiar hoy el resultado. Los parámetros son decisiones de calibración, no constantes físicas ni resultados medidos. Los pares de saturación por nivel se muestran en la tabla.',\n      'Nᴵ todavía debe pasar por la capacidad restante 4−D de la sección de Impacto. El aseguramiento Nᴿ entra en q como min(1,Nᴿ/3); no se suma directamente al R público de v1.3. No se propagan las puntuaciones finales ajustadas por el ecosistema, por lo que no hay realimentación iterativa.',\n      'La visibilidad no reduce vᴵ: un perfil oculto conserva su transmisión de Impacto. Solo vᴿ recibe el factor 0.8. Si algún contribuyente efectivo oculta sus puntuaciones, se ocultan los agregados fraccionarios exactos de ambos términos. Solo quedan sus valores ya utilizados y la indicación de que existe una contribución subentera. Los detalles visibles del ecosistema se redondean a décimas; no son los operandos exactos del cálculo.',\n    ],\n  },\n  {\n    id: 'inactive', title: '9. Datos que hoy no puntúan y efectos marginales',\n    formula: 'ΔI(elemento) = I(datos actuales) − I(datos sin elemento)\\nΔR(elemento) = R(datos actuales) − R(datos sin elemento)\\nEn general: ∑ elementos ΔI ≠ I y ∑ elementos ΔR ≠ R',\n    paragraphs: [\n      'Vincular una mejora a un ODS o registrar un informe VSME no añade puntos de Impacto ni de Fiabilidad por sí mismo. La mejora y sus evidencias se evalúan en sus registros originales; no se cuentan de nuevo aquí. Un borrador no contribuye a la puntuación.',\n      'No puntúan directamente: objetivos y porcentajes de progreso; mediciones privadas o compartidas de energía, agua, combustible y residuos; promedios y contexto doméstico; facturas y documentos de lectura privados; adjuntos genéricos a objetivos, reconocimientos o mediciones; fechas declaradas; estrategias guardadas o aplicaciones de estrategia; aportaciones comunitarias no oficializadas; ubicación de servicio y coordenadas del mapa. Un documento o acción puede tener otra función puntuable por separado: su mera presencia en estas secciones no la crea.',\n      'Los indicadores i comparan el perfil cargado con el mismo perfil tras retirar una entrada mediante el mismo motor de producción. Para una práctica se retira la práctica; para un recurso se retiran sus vínculos puntuables o su documento; para una relación se retira esa fila y se recalcula también el ecosistema, incluidas las intrínsecas de sus extremos. Para un campo se sustituye por null. No se escribe ni borra nada en la base de datos.',\n      'Las modificaciones propias de prácticas, evidencia o identidad mantienen el ecosistema recibido: solo cambiaría el valor intrínseco del propio origen, que nunca se cuenta como vecino ni puede ser visitado de vuelta. Al retirar una conexión sí cambia el grafo y las intrínsecas de contrapartes, por eso se recalcula completo. En documentos el legado interno cuenta filas de vínculo incluso cuando el documento no se resuelve: esta comparación conserva esa semántica real.',\n      'Un cero puede significar exclusión, duplicidad de familia, redondeo, techo o sustitución por otra evidencia. Un efecto negativo de Fiabilidad puede ser real: añadir una práctica no respaldada aumenta el denominador de cobertura. Si retirar la última práctica deja el perfil sin puntuación, no se presenta una resta contra cero. Si interviene un agregado reservado se omiten los efectos numéricos para evitar reconstruir puntuaciones ocultas.',\n      'El desglose se solicita al abrir por primera vez un indicador i que requiere cálculo y se reutiliza mientras sigue cargado en el perfil. Después de editar, vuelve a cargar el perfil para obtener un desglose actualizado. Estas explicaciones no se presentan como una predicción causal del impacto ambiental ni como un reparto aditivo o Shapley. Una entrada recién añadida sin cálculo disponible recibe la regla general y se indica que falta su desglose.',\n    ],\n  },\n  {\n    id: 'example', title: '10. Ejemplo numérico reproducible',\n    formula: 'n=3; C=1; B=1; F=1; J=W=U=V=0; m=4; K=Nᴵ=Nᴿ=0\\nL=(L1,L1,L1); Q=0.015; S=0.1269; P=11.9; D=N=0; Ipublic=11.9\\nBanda T2: L=60, H=69\\nx=(1, 1/3, 0.4, 0, 0, 1, 0, 0)\\nq=0.28×1 + 0.24×(1/3) + 0.14×0.4 + 0.08×1 = 0.496\\nR=round(60+9×0.496)=64; nota C\\nSin el documento: C=B=F=0; banda T0; q=0.08; R=round(49×0.08)=4\\nEfecto del documento: ΔI=0; ΔR=64−4=60',\n    paragraphs: [\n      'Supongamos tres prácticas oficiales, un documento propio relevante vinculado y confirmado a las tres, los cuatro campos completos y ninguna conexión. Ese único editor cubre todas las prácticas, pero no crea independencia. El orden de x es cobertura, profundidad, fundamento, independencia, corroboración, completitud, relaciones y ecosistema.',\n      'El gran efecto marginal del documento se debe al cambio de banda. No significa que todos los documentos valgan 60 puntos. Un segundo documento equivalente del mismo editor, con los mismos vínculos y relevancia, puede tener efecto marginal cero porque se mantiene la cobertura y no aparece ninguna familia adicional. Si las tres prácticas se leen como L1, retirar una reduce el Impacto de 11.9 a 9.1, pero mantiene la Fiabilidad en 64.',\n    ],\n  },\n  {\n    id: 'versions', title: '11. Versiones, historia y mantenimiento',\n    formula: 'Identidad del cálculo = (versión compuesta, versión Impacto, versión Fiabilidad, revisión explicativa, revisión del código)',\n    paragraphs: [\n      'El motor vigente declara modelo compuesto 2.0, Impacto 2.0 y Fiabilidad 1.3. Impacto 2.0 es provisional: el cálculo anterior, Impacto 1.3, sigue en el código y se restaura cambiando una constante. La revisión de este texto es independiente: corregir o ampliar una explicación sin alterar resultados no cambia la versión de la fórmula. Los snapshots de evolución conservan versiones y build revision cuando están disponibles; no se reescribe un resultado antiguo con la fórmula actual.',\n      'Historia semántica resumida: compuesto 1.0 usaba un veredicto por arista; 1.1 introdujo niveles por participante y saturación; 1.2 añadió ecosistema; 1.2.1 retiró el bono directo de evidencia de Impacto y fijó la escala por familia; la activación de Fiabilidad 1.3 quedó bajo compuesto 1.3; compuesto 1.4 introdujo las categorías semánticas de relaciones de Impacto 1.3 y el límite conjunto de 4; compuesto 2.0 sustituyó el recuento de prácticas por el alcance de cada contribución, con Impacto 2.0 provisional. El apéndice descargable de esta revisión conserva únicamente el motor que documenta; no pretende reconstruir todos los motores históricos.',\n      'Las tablas de esta página se obtienen de las constantes del motor, y los indicadores ejecutan el propio motor. Además, el build verifica las huellas de los archivos que deciden el cálculo contra el manifiesto revisado. Un cambio en esos archivos exige revisar las fórmulas, el apéndice y el manifiesto; los cambios de resultados exigen subir las versiones afectadas y la compuesta. Una refactorización que deje todos los resultados iguales puede conservarlas, documentando y probando la equivalencia.',\n      'La metodología explica el algoritmo desplegable de esta revisión, no convierte filosofía futura, mediciones aún no calibradas ni ideas de la hoja de ruta en funcionalidades activas. El archivo descargable permite revisar funciones auxiliares, alias, reglas de lectura y normalización sin depender de comentarios antiguos.',\n    ],\n  },\n] as const;\n",
    "lib/score-transparency.ts": "/** Server-only explanations. Every delta executes the production scorer; no second formula. */\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { ProfileKind } from '@/types/profile';\nimport type { ProfileScoreResult } from '@/types/profile-score';\nimport type { ScoreTransparency, ScoreItemExplanation } from '@/types/score-transparency';\nimport {\n  calculateProfileScores, computeEcosystemNetworkPoints, fetchProfileScores,\n  loadEcosystemScoringData, SCORING_MODEL_VERSION, IMPACT_MODEL_VERSION,\n  RELIABILITY_MODEL_VERSION, type EcosystemScoringData,\n} from '@/lib/profile-scoring';\nimport { resolveRelationshipCategory, resolveConnectionTier } from '@/lib/profile-connection-tiers';\nimport { buildReliabilityFacts } from '@/lib/profile-reliability-facts';\nimport { resolveSourceDistance, toDeclaredDomains } from '@/lib/source-identity';\nimport { countConnectionTiersByProfile, emptyConnectionTierCounts } from '@/lib/profile-connection-tiers';\nimport { MAX_RELATIONSHIP_IMPACT_POINTS } from '@/lib/profile-connection-tiers';\nimport { computeImpactV2 } from '@/lib/profile-impact-v2';\nimport { impactV2PracticeExplanation } from '@/lib/profile-score-explanations';\nimport { IMPACT_INITIAL_PRACTICE_COUNT, IMPACT_INITIAL_PRACTICE_POINTS, IMPACT_EXTENDED_PRACTICE_COUNT, IMPACT_EXTENDED_PRACTICE_POINTS, IMPACT_ADDITIONAL_PRACTICE_POINTS } from '@/lib/profile-impact-points';\n\nexport function explainProfileScore(data: EcosystemScoringData, kind: ProfileKind, id: string, baseline: ProfileScoreResult): ScoreTransparency {\n  const result: ScoreTransparency = {\n    status: baseline.status === 'scored' ? 'ready' : baseline.status,\n    calculatedAt: new Date().toISOString(), compositeVersion: SCORING_MODEL_VERSION,\n    impactVersion: IMPACT_MODEL_VERSION, reliabilityVersion: RELIABILITY_MODEL_VERSION, items: {},\n  };\n  // Never send withheld scores, facts or counterfactuals to a public client.\n  if (baseline.status !== 'scored') return result;\n  const inputs = data.byKind[kind];\n  const profile = inputs.profiles.find(p => p.id === id);\n  if (!profile || !data.sources) { result.status = 'unavailable'; return result; }\n  const network = computeEcosystemNetworkPoints(data);\n  const evaluate = (changed: EcosystemScoringData, changedNetwork = false) => {\n    const slice = changed.byKind[kind];\n    return calculateProfileScores(kind, slice.profiles, slice.improvements, slice.evidenceItems,\n      slice.connections, changedNetwork ? computeEcosystemNetworkPoints(changed) : network,\n      changed.sources ?? undefined).get(id)!;\n  };\n  const local = (patch: Partial<typeof inputs>): EcosystemScoringData => ({ ...data, byKind: { ...data.byKind, [kind]: { ...inputs, ...patch } } });\n  const add = (key: string, explanation: string | string[], changed: EcosystemScoringData, changedNetwork = false) => {\n    const without = evaluate(changed, changedNetwork);\n    const privateNetwork = baseline.impact.networkPointsExact === null || baseline.reliability.networkPointsExact === null\n      || (without.status === 'scored' && (without.impact.networkPointsExact === null || without.reliability.networkPointsExact === null));\n    const parts = Array.isArray(explanation) ? [...explanation] : undefined;\n    const item: ScoreItemExplanation = { explanation: parts ? parts.join(' ') : explanation as string, ...(parts ? { explanationParts: parts } : {}), impactDelta: null, reliabilityDelta: null, withoutStatus: without.status };\n    if (without.status === 'scored' && !privateNetwork) {\n      item.impactDelta = Number((baseline.impact.score - without.impact.score).toFixed(4));\n      item.reliabilityDelta = baseline.reliability.score - without.reliability.score;\n      item.withoutGrade = without.reliability.grade;\n    } else if (privateNetwork) {\n      const notice = 'El detalle numérico se reserva porque intervienen puntuaciones no públicas del ecosistema.';\n      item.explanation += ` ${notice}`;\n      item.explanationParts?.push(notice);\n    }\n    result.items[key] = item;\n  };\n  const practices = inputs.improvements.filter(p => p.profileId === id);\n  const practiceIds = new Set(practices.map(p => p.id));\n  const evidence = inputs.evidenceItems.filter(e => practiceIds.has(e.improvementId));\n  const impactV2 = baseline.impact.v2 ? computeImpactV2(baseline.impact.v2.scale, practices) : null;\n  for (const p of practices) add(`practice:${p.id}`,\n    impactV2 ? impactV2PracticeExplanation(impactV2, p.id) : [\n      `Esta práctica es elegible y forma parte de las ${practices.length} prácticas puntuadas.`,\n      `El modelo por recuento aplica ${IMPACT_INITIAL_PRACTICE_POINTS} puntos a cada una de las ${IMPACT_INITIAL_PRACTICE_COUNT} primeras mejoras, ${IMPACT_EXTENDED_PRACTICE_POINTS} a las ${IMPACT_EXTENDED_PRACTICE_COUNT} siguientes y ${IMPACT_ADDITIONAL_PRACTICE_POINTS} a las restantes. El orden visual no asigna puntos a una mejora concreta.`,\n      'En Fiabilidad también forma parte del denominador de cobertura. Retirarla puede reducir el Impacto y aumentar o reducir la Fiabilidad.',\n    ],\n    local({ improvements: inputs.improvements.filter(x => x.id !== p.id) }));\n  for (const field of ['description', 'location', 'website', 'imagePath', 'title'] as const) add(`field:${field}`,\n    field === 'title' || field === 'website'\n      ? 'Este dato identifica al sujeto al clasificar las fuentes propias, controladas, afiliadas o independientes. La web, si no está vacía, también completa uno de los cuatro campos de Fiabilidad. El título no añade completitud. No concede Impacto directo.'\n      : 'La presencia de este campo completa uno de los cuatro campos de Fiabilidad (descripción, ubicación, web e imagen). No se evalúa su calidad ni se añade Impacto directo. Su peso depende de la banda de Fiabilidad.',\n    local({ profiles: inputs.profiles.map(p => p.id === id ? { ...p, [field]: null } : p) }));\n  const facts = buildReliabilityFacts({ profile, practices,\n    documents: data.sources.documents, links: evidence.map(e => ({ practiceId: e.improvementId, documentId: e.documentId })),\n    declaredDomains: data.sources.declaredDomains.get(id), recognitions: data.sources.recognitions,\n    connectionTiers: countConnectionTiersByProfile(inputs.connections).get(id) ?? emptyConnectionTierCounts(), networkReliabilityPoints: 0 });\n  for (const doc of data.sources.documents.filter(d => d.profileId === id)) {\n    const links = evidence.filter(e => e.documentId === doc.id);\n    const distance = resolveSourceDistance({ id: doc.id, url: doc.url, storage_path: doc.storagePath }, profile, toDeclaredDomains(data.sources.declaredDomains.get(id) ?? []));\n    const index = data.sources.documents.filter(d => d.profileId === id).findIndex(d => d.id === doc.id);\n    const relevant = facts.profileDocuments[index]?.relevant === true;\n    add(`document:${doc.id}`, `Este documento tiene ${links.length} vínculo(s) confirmado(s) con prácticas elegibles. Clasificación de fuente: ${distance}. Relevancia para fundamento documental: ${relevant ? 'sí' : 'no'} (al menos dos palabras significativas compartidas entre título/descripción y las prácticas). Los vínculos pueden aportar cobertura, profundidad, independencia y corroboración según su editor. Repetir un editor no crea una nueva familia. No se lee el contenido del PDF para esta fórmula y no añade Impacto directo.`,\n      { ...data, sources: { ...data.sources, documents: data.sources.documents.filter(d => d.id !== doc.id) } });\n  }\n  for (const e of evidence) {\n    const matching = facts.links.filter(l => l.practiceId === e.improvementId);\n    const detail = e.type === 'document'\n      ? 'La fuente documental se clasifica por su editor. La cobertura cuenta prácticas distintas y la profundidad cuenta familias, no archivos repetidos.'\n      : 'Esta foto o vídeo se trata como fuente controlada por el perfil. Comparte la familia propia: puede cubrir una práctica, pero no aporta independencia por sí solo.';\n    if (e.id) add(`evidence:${e.id}`, `Vínculo confirmado con una práctica elegible. ${detail} No añade Impacto directo. La práctica tiene ${matching.length} vínculo(s) puntuable(s), incluidos reconocimientos de alcance específico.`,\n      local({ evidenceItems: inputs.evidenceItems.filter(x => x.id !== e.id) }));\n  }\n  for (const type of ['photo', 'video'] as const) {\n    const mediaIds = new Set(evidence.filter(e => e.type === type).map(e => e.mediaId).filter(Boolean));\n    for (const mediaId of mediaIds) add(`${type}:${mediaId}`,\n      'Este recurso respalda prácticas mediante vínculos confirmados. Es una fuente controlada: puede aportar cobertura y profundidad dentro de la familia propia, pero no independencia. El cálculo retira todos sus vínculos puntuables. No añade Impacto directo.',\n      local({ evidenceItems: inputs.evidenceItems.filter(e => !(e.type === type && e.mediaId === mediaId)) }));\n  }\n  for (const r of data.sources.recognitions.filter(r => r.profileId === id)) add(`recognition:${r.id}`,\n    r.scope === 'practice' && r.improvementId && practiceIds.has(r.improvementId)\n      ? 'Reconocimiento de alcance específico: si se puede resolver su emisor, respalda únicamente la práctica indicada. Puede cambiar cobertura, independencia y corroboración. Sus archivos adjuntos no se convierten automáticamente en evidencias de práctica. No añade Impacto directo.'\n      : 'Si se puede resolver el dominio del emisor, este reconocimiento aporta una familia relevante al fundamento documental. No aumenta la cobertura ni corrobora todas las prácticas del perfil. Sus archivos adjuntos no puntúan por separado. No añade Impacto directo.',\n    { ...data, sources: { ...data.sources, recognitions: data.sources.recognitions.filter(x => x.id !== r.id) } });\n  for (const declaration of data.sources.declaredDomains.get(id) ?? []) {\n    const declarations = new Map(data.sources.declaredDomains);\n    declarations.set(id, declarations.get(id)!.filter(d => d !== declaration));\n    add(`domain:${declaration.domain}:${declaration.account_path ?? ''}`, 'Esta declaración modifica cómo se agrupan y clasifican las fuentes. Una declaración de representante no puede aumentar la independencia respecto a la inferencia del sistema; una declaración administrativa sí es autoritativa. No añade puntos por sí misma.',\n      { ...data, sources: { ...data.sources, declaredDomains: declarations } });\n  }\n  for (const c of inputs.connections.filter(c => c.id && (c.sourceProfileId === id || c.targetProfileId === id))) {\n    const source = c.sourceProfileId === id;\n    const tier = resolveConnectionTier({ selfPosition: source ? c.sourcePosition : c.targetPosition, otherPosition: source ? c.targetPosition : c.sourcePosition, isIndependentlyVerified: c.isIndependentlyVerified });\n    const changed: EcosystemScoringData = { ...data, byKind: { ...data.byKind }, edges: data.edges.filter(e => e.id !== c.id) };\n    for (const k of Object.keys(data.byKind) as ProfileKind[]) changed.byKind[k] = { ...data.byKind[k], connections: data.byKind[k].connections.filter(x => x.id !== c.id) };\n    add(`connection:${c.id}`, `Categoría de Impacto: ${resolveRelationshipCategory(c)}. Nivel de Fiabilidad para este perfil: ${tier}. Solo se conserva la categoría y el nivel más altos por contraparte, por separado. La aportación directa de Impacto y el ecosistema comparten un máximo de ${MAX_RELATIONSHIP_IMPACT_POINTS} puntos. El cálculo retira esta relación y recalcula también el ecosistema. Una relación disputada no transmite influencia ni aporta Fiabilidad; el cálculo directo de Impacto vigente clasifica su semántica sin consultar la disputa.`, changed, true);\n  }\n  return result;\n}\n\nexport async function fetchProfileScoresWithExplanations(supabase: SupabaseClient, kind: ProfileKind, reveal: readonly string[], id: string) {\n  const data = await loadEcosystemScoringData(supabase);\n  if (!data) return new Map<string, ProfileScoreResult & { transparency?: ScoreTransparency }>();\n  const scores: Map<string, ProfileScoreResult & { transparency?: ScoreTransparency }> = await fetchProfileScores(supabase, kind, reveal, data);\n  const score = scores.get(id);\n  if (score) scores.set(id, { ...score, transparency: explainProfileScore(data, kind, id, score) });\n  return scores;\n}\n",
    "lib/profile-impact-v2.ts": "/**\n * Impact v2 (provisional, 2026-09-27): significance-weighted contributions.\n *\n * Replaces the count-based practice points P(n) with the method developed and blind-tested on Basque town\n * halls (research checkpoint \"Frozen rules v0.5.1\", Port EkoPulse/Prototype_TownHalls_v0.1, kept outside\n * this repository). Relationship and ecosystem Impact are NOT part of this module: they keep their\n * existing calculation in lib/profile-scoring.ts.\n *\n *   1. Every scored contribution carries a reading: a significance level L0–L5 judged against the\n *      profile's own scale (municipality, organisation, person or product), stored in\n *      lib/scoring-v2/contribution-readings.ts (generated from data/scoring-v2/contribution-readings.json) with a fingerprint of the text it was read from.\n *      A contribution without a current reading counts provisionally as L1 (the floor for any concrete\n *      action) and is reported as unread.\n *   2. Review hold: a reading of L4 or L5 counts as L1 until a person confirms it (confirmed: true).\n *   3. Same work counted once: contributions the reader links as the same works or as a phase or\n *      component of another count once, at the highest-rated member.\n *   4. Municipal profiles only: overlap groups with capacities (L4 whole physical town systems, L3 council\n *      estate and people-centred programmes, L2 plans and data). Documented distinct zones add up to the\n *      capacity; shared or undocumented overlap combines as \"each acts on what the others left\".\n *      Other profile types sum their units: these capacities were designed for municipalities and are\n *      not applied elsewhere.\n *   5. S = 2.4 · R^0.7 and I = 100 · (1 − e^(−S)), R being the counted sum of level points.\n */\nimport { CONTRIBUTION_READINGS } from '@/lib/scoring-v2/contribution-readings';\nimport { createHash } from 'node:crypto';\n\nexport const IMPACT_V2_REVISION = '2.0-provisional';\n\nexport type ImpactLevel = 'L0' | 'L1' | 'L2' | 'L3' | 'L4' | 'L5';\nexport type ImpactScale = 'municipality' | 'organisation' | 'person' | 'product';\n\nexport const IMPACT_V2_LEVEL_POINTS: Readonly<Record<ImpactLevel, number>> = {\n  L0: 0.0005, L1: 0.005, L2: 0.02, L3: 0.06, L4: 0.15, L5: 0.4,\n};\nconst LEVELS: readonly ImpactLevel[] = ['L0', 'L1', 'L2', 'L3', 'L4', 'L5'];\nexport const IMPACT_V2_CURVE = { k: 2.4, exponent: 0.7 } as const;\nexport const IMPACT_V2_UNREAD_LEVEL: ImpactLevel = 'L1';\nexport const IMPACT_V2_HOLD_LEVELS: readonly ImpactLevel[] = ['L4', 'L5'];\nexport const IMPACT_V2_HOLD_COUNTS_AS: ImpactLevel = 'L1';\n\nexport interface ContributionReading {\n  hash: string;\n  level: ImpactLevel;\n  status?: string;\n  category?: string;\n  partOf?: string | null;\n  sameWorksAs?: string[];\n  zone?: string;\n  reason?: string;\n  confirmed?: boolean;\n}\n\nconst READINGS: Readonly<Record<string, ContributionReading>> = CONTRIBUTION_READINGS as unknown as Record<string, ContributionReading>;\n\n/** Municipal overlap groups: [label, categories, capacity level]. Research checkpoint v0.5.1. */\nexport const MUNICIPAL_OVERLAP_GROUPS: Readonly<Record<string, readonly [string, readonly string[], ImpactLevel]>> = {\n  car_trips: ['Desplazamientos: menos viajes en coche y más limpios', ['public_transport', 'cycling', 'street_redesign', 'low_emission_zone'], 'L4'],\n  public_fleet: ['Vehículos municipales y autobuses', ['municipal_fleet'], 'L4'],\n  freight: ['Reparto de mercancías', ['last_mile_logistics'], 'L4'],\n  council_energy: ['Energía de edificios e instalaciones municipales', ['municipal_buildings_efficiency', 'municipal_renewables'], 'L3'],\n  street_lighting: ['Alumbrado público', ['street_lighting'], 'L4'],\n  home_energy: ['Energía en viviendas y empresas', ['building_renovation_support', 'citizen_energy', 'heat_network'], 'L4'],\n  waste: ['Residuos domésticos y comerciales', ['waste_collection', 'circular_economy'], 'L4'],\n  food: ['Sistema alimentario y huertos', ['sustainable_food'], 'L4'],\n  water: ['Ciclo del agua', ['water_management'], 'L4'],\n  flood: ['Riesgo de inundación y ríos', ['flood_defence_river'], 'L4'],\n  heat: ['Riesgo por calor', ['climate_shelters'], 'L4'],\n  green_space: ['Parques, arbolado e infraestructura verde', ['urban_greening'], 'L4'],\n  habitats: ['Hábitats y especies', ['biodiversity'], 'L4'],\n  education: ['Educación ambiental', ['education_awareness'], 'L3'],\n  participation: ['Participación ciudadana', ['participation'], 'L3'],\n  inclusion: ['Pobreza energética e inclusión', ['social_inclusion'], 'L3'],\n  procurement: ['Compra pública y operaciones municipales', ['procurement_operations'], 'L3'],\n  other: ['Otras', ['other'], 'L3'],\n  data: ['Seguimiento y datos abiertos', ['monitoring_data'], 'L2'],\n  plans: ['Planes y estrategias', ['climate_energy_plan', 'adaptation_plan'], 'L2'],\n};\nconst CATEGORY_GROUP: ReadonlyMap<string, string> = new Map(\n  Object.entries(MUNICIPAL_OVERLAP_GROUPS).flatMap(([group, [, categories]]) => categories.map((c) => [c, group])),\n);\nconst PLAN_CATEGORIES: ReadonlySet<string> = new Set(['climate_energy_plan', 'adaptation_plan']);\nconst UNSCOPED_ZONES: ReadonlySet<string> = new Set(['', 'shared', 'unknown', 'n/a', 'town-wide', 'all residents']);\n\nexport function impactV2ContentHash(title: string | null | undefined, description: string | null | undefined): string {\n  return createHash('sha256').update(`${title ?? ''}\\n${description ?? ''}`.replace(/\\r\\n/g, '\\n').trim())\n    .digest('hex').slice(0, 16);\n}\n\n/** Which scale a profile is read against. Only published profile fields decide it. */\nexport function impactV2Scale(kind: string, title: string | null | undefined, category: string | null | undefined): ImpactScale {\n  if (kind === 'territorio' && category === 'Municipio') return 'municipality';\n  if (kind === 'entidad' && /^Ayuntamiento de /i.test(title ?? '')) return 'municipality';\n  if (kind === 'persona') return 'person';\n  if (kind === 'producto_servicio') return 'product';\n  return 'organisation';\n}\n\nexport interface ImpactV2ContributionInput {\n  id: string;\n  title?: string | null;\n  description?: string | null;\n}\n\nexport interface ImpactV2ContributionResult {\n  id: string;\n  level: ImpactLevel;\n  countedLevel: ImpactLevel;\n  ownPoints: number;\n  countedPoints: number;\n  read: boolean;\n  held: boolean;\n  sameWorkAs: string | null;\n  group: string | null;\n}\n\nexport interface ImpactV2Result {\n  scale: ImpactScale;\n  /** R: the counted sum of level points. */\n  counted: number;\n  /** The contributions' Impact on the 0–100 curve, before relationships and ecosystem. */\n  score: number;\n  unreadCount: number;\n  heldCount: number;\n  sameWorkCount: number;\n  contributions: ImpactV2ContributionResult[];\n}\n\nfunction combine(units: Array<{ points: number; zone: string }>, capacity: number): number {\n  const cap = Math.max(capacity, ...units.map((u) => u.points));\n  const zoned = new Map<string, number[]>();\n  const shared: number[] = [];\n  for (const u of units) {\n    if (UNSCOPED_ZONES.has(u.zone)) shared.push(u.points);\n    else zoned.set(u.zone, [...(zoned.get(u.zone) ?? []), u.points]);\n  }\n  let block = 0;\n  for (const points of zoned.values()) {\n    block += cap * (1 - points.reduce((left, p) => left * (1 - p / cap), 1));\n  }\n  block = Math.min(block, cap);\n  const left = shared.reduce((l, p) => l * (1 - p / cap), 1 - block / cap);\n  return cap * (1 - left);\n}\n\nexport function impactV2Curve(counted: number): number {\n  if (counted <= 0) return 0;\n  const strength = IMPACT_V2_CURVE.k * counted ** IMPACT_V2_CURVE.exponent;\n  return 100 * (1 - Math.exp(-strength));\n}\n\nexport function computeImpactV2(\n  scale: ImpactScale,\n  contributions: readonly ImpactV2ContributionInput[],\n  readings: Readonly<Record<string, ContributionReading>> = READINGS,\n): ImpactV2Result {\n  const rows = contributions.map((c) => {\n    const reading = readings[c.id];\n    const read = Boolean(reading && reading.hash === impactV2ContentHash(c.title, c.description)\n      && LEVELS.includes(reading.level));\n    const level: ImpactLevel = read ? reading!.level : IMPACT_V2_UNREAD_LEVEL;\n    const held = IMPACT_V2_HOLD_LEVELS.includes(level) && !(read && reading!.confirmed);\n    const countedLevel = held ? IMPACT_V2_HOLD_COUNTS_AS : level;\n    const category = read && scale === 'municipality' ? reading!.category ?? 'other' : null;\n    return {\n      id: c.id, level, countedLevel, read, held, category,\n      points: IMPACT_V2_LEVEL_POINTS[countedLevel],\n      links: read ? [reading!.partOf, ...(reading!.sameWorksAs ?? [])].filter((x): x is string => Boolean(x)) : [],\n      zone: read ? String(reading!.zone ?? 'shared').trim().toLowerCase() : 'shared',\n    };\n  });\n\n  // Same work counted once: connected components of the reader's links, within this profile only,\n  // never joining a plan and a measure (municipal readings carry the category that says which is which).\n  const index = new Map(rows.map((r, i) => [r.id, i]));\n  const parent = rows.map((_, i) => i);\n  const find = (i: number): number => (parent[i] === i ? i : (parent[i] = find(parent[i])));\n  rows.forEach((r, i) => {\n    for (const link of r.links) {\n      const j = index.get(link);\n      if (j === undefined || j === i) continue;\n      const other = rows[j];\n      if (r.category && other.category && PLAN_CATEGORIES.has(r.category) !== PLAN_CATEGORIES.has(other.category)) continue;\n      parent[find(i)] = find(j);\n    }\n  });\n  const components = new Map<number, number[]>();\n  rows.forEach((_, i) => components.set(find(i), [...(components.get(find(i)) ?? []), i]));\n\n  const counted = rows.map(() => 0);\n  const sameWorkAs: Array<string | null> = rows.map(() => null);\n  const groupOf: Array<string | null> = rows.map(() => null);\n  const units: Array<{ row: number; points: number; zone: string; group: string }> = [];\n  for (const members of components.values()) {\n    const rep = members.reduce((best, i) => (rows[i].points > rows[best].points ? i : best), members[0]);\n    for (const i of members) if (i !== rep) sameWorkAs[i] = rows[rep].id;\n    const group = scale === 'municipality' ? CATEGORY_GROUP.get(rows[rep].category ?? 'other') ?? 'other' : 'all';\n    units.push({ row: rep, points: rows[rep].points, zone: rows[rep].zone, group });\n    for (const i of members) groupOf[i] = scale === 'municipality' ? group : null;\n  }\n\n  if (scale === 'municipality') {\n    const byGroup = new Map<string, typeof units>();\n    for (const u of units) byGroup.set(u.group, [...(byGroup.get(u.group) ?? []), u]);\n    for (const [group, groupUnits] of byGroup) {\n      const own = groupUnits.reduce((s, u) => s + u.points, 0);\n      if (own <= 0) continue;\n      const capacity = IMPACT_V2_LEVEL_POINTS[MUNICIPAL_OVERLAP_GROUPS[group]?.[2] ?? 'L3'];\n      const factor = combine(groupUnits, capacity) / own;\n      for (const u of groupUnits) counted[u.row] = u.points * factor;\n    }\n  } else {\n    for (const u of units) counted[u.row] = u.points;\n  }\n\n  const total = counted.reduce((s, x) => s + x, 0);\n  return {\n    scale,\n    counted: total,\n    score: impactV2Curve(total),\n    unreadCount: rows.filter((r) => !r.read).length,\n    heldCount: rows.filter((r) => r.held).length,\n    sameWorkCount: sameWorkAs.filter(Boolean).length,\n    contributions: rows.map((r, i) => ({\n      id: r.id, level: r.level, countedLevel: r.countedLevel, ownPoints: r.points, countedPoints: counted[i],\n      read: r.read, held: r.held, sameWorkAs: sameWorkAs[i], group: groupOf[i],\n    })),\n  };\n}\n",
    "lib/scoring-v2/contribution-readings.ts": "/** Generated by scripts/scoring-v2/merge-readings.mjs from data/scoring-v2/contribution-readings.json. Do not edit by hand. */\nexport const CONTRIBUTION_READINGS: Record<string, { hash: string; level: string; status?: string; category?: string; partOf?: string | null; sameWorksAs?: string[]; zone?: string; reason?: string; confirmed?: boolean }> = {\n \"00ebd811-567f-46dc-9e24-f0ae4601bfb8\": {\n  \"hash\": \"cbe02ddd2ee9b199\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Self-consumption renewable projects at 3 named centers under PRTR incentives.\"\n },\n \"0117f311-086d-49e3-8f14-13074c9db464\": {\n  \"hash\": \"4588c9169bd318c1\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Individualized job-insertion itineraries for people with mental-health conditions; no numbers given.\"\n },\n \"01bb759d-2507-4566-9ab6-fa771ed1588d\": {\n  \"hash\": \"f94c873554bdb199\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Sustainability reports published annually since 2002; a reporting practice, not a direct measure.\"\n },\n \"01e89d82-97fc-55c0-9c68-1a6fe4df9ef4\": {\n  \"hash\": \"ab5fbb1d75c43816\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing chemical-recycling research for hard-to-value plastic waste streams; one discrete project.\"\n },\n \"0256fff1-382c-5a72-a625-1681377c117d\": {\n  \"hash\": \"0c76a2b81e036f8b\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Long-term strategic plan integrating social, economic and territorial development; per rules, plan itself is L1.\"\n },\n \"0277282b-5e5b-5478-a599-8309ee3662ce\": {\n  \"hash\": \"c3fa3c8ad4115a14\",\n  \"level\": \"L3\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Leads flagship territorial climate programme coordinating administrations region-wide.\"\n },\n \"02db2197-20f7-52ee-9f41-b7ec8ce817db\": {\n  \"hash\": \"c9c153c97501a99b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Created a dedicated environmental hub; concrete single facility.\"\n },\n \"03555b25-399b-445c-9ed8-49ceb9c29111\": {\n  \"hash\": \"74cc6f2806441b7b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Refurbishment service for used threading tools enabling reuse; no volume data.\"\n },\n \"03e50e7e-7e04-59b2-914b-aa824de30b71\": {\n  \"hash\": \"1aede8365dc9230a\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Developing a transportable green-hydrogen electrolyser for industrial sites; one discrete technology project.\"\n },\n \"042aeeda-c289-4182-a53d-dc4598ecb9fa\": {\n  \"hash\": \"36efeff062113ba9\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Cardboard pallets replace wood, supplier packaging reused; concrete, narrow scope.\"\n },\n \"051481f1-69b7-41b8-a7f3-f1df38168296\": {\n  \"hash\": \"547a4efbf8e8bd6e\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"New superheater enables over 90% gas-consumption cuts in a mode used part of the year.\"\n },\n \"05479ffe-31b7-504b-a81d-6e2078effea8\": {\n  \"hash\": \"d1b331f0b1bce7af\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"climate_shelters\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Phased climate-shelter rollout for heat protection; some sites usable now, others still need upgrades.\"\n },\n \"056a6047-38ab-4707-ba49-c35e5a800cc2\": {\n  \"hash\": \"2e7d1681960707fb\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Sustainable-fishing ecological product line with organic ingredients; scoped to one line.\"\n },\n \"05753fc4-688b-507b-9f91-aa10418c67ec\": {\n  \"hash\": \"be274fb52e9a586e\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Bike-share expansion to about 1,250 bikes and 68 stations city-wide.\"\n },\n \"057a3783-c5a6-5491-8f16-330513ebe305\": {\n  \"hash\": \"534e2e8aa90940b4\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Extensive textile collection network via agreements with numerous municipalities; core operational infrastructure.\"\n },\n \"05a26b77-efeb-53c3-8cc2-363b6c72b048\": {\n  \"hash\": \"5180d4aa788fe5f8\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Permanent environmental-education facility (decades running) offering workshops and eco-itineraries; no visitor numbers.\"\n },\n \"0645f444-4e92-5d19-998a-28fbe9400904\": {\n  \"hash\": \"5c39d5b91c916205\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Energy-sector role in a shared clean-technology incentive instrument.\"\n },\n \"06c11bdd-c4c6-4b1e-8ca7-b88364785429\": {\n  \"hash\": \"3f83492565c42d3b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Energy-poverty support reached 142 people but only 5 home audits were done in 2025.\"\n },\n \"078529cf-6865-46e1-b4e4-bc706ae40794\": {\n  \"hash\": \"90a41a21808ab385\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Solar-park acquisition secures 100% renewable electricity, the main lever of decarbonisation.\"\n },\n \"07c01bee-7343-524e-95d2-281a0b282220\": {\n  \"hash\": \"c1d7c8255a47a74f\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Develops processes to valorise ash into construction products with Basque companies; discrete project.\"\n },\n \"08100e54-10d1-4953-8e87-7891b6bef4ae\": {\n  \"hash\": \"1a09ef7afde98632\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"88e81ba6-bab1-4aeb-8e73-ef9e573a14fd\"\n  ],\n  \"reason\": \"No personal car; relies on public transport, a concrete but lightly detailed choice.\"\n },\n \"081f511c-0287-5a7e-8c2e-e56219be56c7\": {\n  \"hash\": \"9607645e60806509\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Portal de Legutiano\",\n  \"reason\": \"Street renovation begun, applying the Los Herrán green-corridor model to one axis.\"\n },\n \"08269966-5d66-4d1e-8976-eb2dbaeaf527\": {\n  \"hash\": \"8d84dcf0f3a7d3c1\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Urban biodiversity outreach programme with sculptures and a route, just launched in 2026.\"\n },\n \"0963edc5-7be7-5448-a85b-273b3f8e1845\": {\n  \"hash\": \"8cb17b4e5cf22794\",\n  \"level\": \"L0\",\n  \"status\": \"unclear\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Proposed educational case-study methodology; not stated as adopted or implemented.\"\n },\n \"0991101a-06bd-504a-b0f3-7caed933cf57\": {\n  \"hash\": \"4ff0d8b6777f9baa\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Publishes permanent energy information tools; usage not quantified.\"\n },\n \"09bdc163-d69f-54a1-8cfd-58a0da888892\": {\n  \"hash\": \"c79eb9a893229901\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Transnational R&D/capacity-building framework project for circular economy; scope not quantified.\"\n },\n \"09cc06af-2513-56cc-abf7-f73dcd3b01a4\": {\n  \"hash\": \"ed9c0c32d02b04e7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Publicly funded youth training in green/circular economy and employability.\"\n },\n \"09eae2b8-eda3-5a10-955a-2ab0a1f51b1e\": {\n  \"hash\": \"48b51a96c2bcc0b7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Sustained individualised employment-insertion methodology; concrete, no reach figures given.\"\n },\n \"0a470652-0c9f-540a-8b98-613cadf14cbc\": {\n  \"hash\": \"f8cedeeb291c4b94\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring decarbonisation/green-finance service line; reach or scale not quantified.\"\n },\n \"0a572f55-8b4b-421a-ac31-2f5861503612\": {\n  \"hash\": \"46b13789c4e54aba\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed 43-hectare green-corridor restoration connecting several villages; large, finished project.\"\n },\n \"0af4e7b1-c978-5c23-ade0-447d8fb2fb14\": {\n  \"hash\": \"a4194c7ce8df23ae\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Edible-forest urban greening expanded across several neighbourhoods (Aiete, Intxaurrondo, Gantxegi, Putzueta).\"\n },\n \"0b085c59-ab5c-50b1-8efc-a8ab675f40f7\": {\n  \"hash\": \"9f5431512af4ea35\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Sustained social-aid programme for workers in special need, case-assessed by dedicated commission.\"\n },\n \"0b51ded8-c53c-4578-ba75-9657d0743fbf\": {\n  \"hash\": \"57e541a38870257d\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Elder-care residence's core wellbeing service; no resident numbers or new initiative stated.\"\n },\n \"0b7e5b9e-cb94-4086-901a-c403a565531a\": {\n  \"hash\": \"5cbc546c4f267136\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Annual environmental monitoring of receiving waters covers 12 treatment plants plus Urdaibai.\"\n },\n \"0b919258-028f-4e53-a227-683e7deb88f3\": {\n  \"hash\": \"b777b62ff724055b\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Fog-water-capture project for water-scarce Bolivian communities, approved 2024; reach not quantified.\"\n },\n \"0be06f5d-4cf1-53c0-9c75-d36ced908308\": {\n  \"hash\": \"d490eb141c77838b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Seabin marine-conservation project in Pasaia developed with a partner organisation.\"\n },\n \"0c458ee5-2ca1-51e2-ba6e-9142591cc8b6\": {\n  \"hash\": \"4242ac1f43e85027\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Platform developed and operating; usage reach not quantified.\"\n },\n \"0c56379a-2ac8-4c3e-846c-ea6c79eef8ee\": {\n  \"hash\": \"b383386faf9c27d4\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Uses a dishwasher to save water and energy versus hand-washing; concrete household habit.\"\n },\n \"0ca728c2-aaa7-5018-ab2d-56c980b10bed\": {\n  \"hash\": \"ccd09eb1cf8cfceb\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"c080686c-be2b-4f23-9d7b-e5ae7cfa0f65\"\n  ],\n  \"reason\": \"Agenda 2030 Escolar rolled out to 20 municipalities and 76 schools in 2020-21.\"\n },\n \"0d7947b6-11a4-5d28-8e7d-1563ed24f49b\": {\n  \"hash\": \"cb105d518480a6ac\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Cross-border hydrogen R&D cooperation project; scale not quantified.\"\n },\n \"0dcbc7f2-62f6-512b-9499-cb6d53620ace\": {\n  \"hash\": \"642dc20893e62f66\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"street_redesign\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Traffic-calming superblock model extended from centre pilot to other neighbourhoods.\"\n },\n \"0e66c0db-8e90-5fca-a7f1-1867cfcdda1a\": {\n  \"hash\": \"fec7984a05b40837\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Developed and field-tested net-monitoring sensor tech; concrete but adoption/reach not stated.\"\n },\n \"0e80d197-8c6d-5da9-836c-78857c14a859\": {\n  \"hash\": \"6285cde3b60f57f1\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Coordination group supporting 6 municipalities on ZBE implementation.\"\n },\n \"0ea3a149-64ff-4c56-8bf9-0e80a89d4101\": {\n  \"hash\": \"53ff5e8118cbe6b3\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Knowledge-transfer project on sustainability for the traditional dairy/cheese sector.\"\n },\n \"0f18de30-1a2f-4262-ad1a-8fe6d4328b1a\": {\n  \"hash\": \"ce4544b9368042dc\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Long-running training programme on gender-equality practices since 2014; no reach data.\"\n },\n \"0f2b7301-c177-5f39-a996-e7263c3e96d9\": {\n  \"hash\": \"a78671e7e6ce3b17\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Territory-wide reception network across Cáritas Bizkaia's whole operating area.\"\n },\n \"0f9fc21f-b470-5c3a-a0af-bfdc272d0693\": {\n  \"hash\": \"e8d6c5b1943c7a12\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": \"49762197-cc21-5cc7-aeb2-cd52d51c0d7c\",\n  \"sameWorksAs\": [],\n  \"reason\": \"Award recognising the VITISAD project; a symbolic distinction, not an independent new action.\"\n },\n \"0fc0e4da-2222-5739-8970-4ccaad6a3871\": {\n  \"hash\": \"4c502a4bda5a1a7d\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Applied research project on critical raw materials for energy transition.\"\n },\n \"0fd92a23-57e4-567a-aac3-dd8958100997\": {\n  \"hash\": \"c38f5891f6fbf6be\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single integrated hydrogen demonstrator plant; concrete but limited industrial reach so far.\"\n },\n \"102ac775-4b24-5119-8791-966c1da9c4fd\": {\n  \"hash\": \"edd0183a7f1a0ce8\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Zone-specific commercial waste collection (e.g., Parte Vieja) with individualized pickups and separation support.\"\n },\n \"106da73b-ec79-54d6-8e31-5e049c66ff16\": {\n  \"hash\": \"7b58aa406bbfa947\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"School-municipality SDG collaboration program; 12 schools participated in 2025, no total-school comparison given.\"\n },\n \"10786d71-21fe-48e4-924d-d688b002e7c4\": {\n  \"hash\": \"ebd5fb04fd0bd8ae\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Metro-area e-bike service, 824 bikes/109 stations across 9 municipalities, daily use.\"\n },\n \"11be03be-f49c-4f40-bb6a-180d9a9a0046\": {\n  \"hash\": \"10b0a01434ecb1fe\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Solar generation at three plants; no share-of-consumption figures given.\"\n },\n \"124cc5a3-1f3e-52a5-a9e3-66ed887537e9\": {\n  \"hash\": \"5cfed9a89d921545\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"La Concha, Ondarreta, Zurriola\",\n  \"reason\": \"Three municipal beaches under ISO14001/EMAS environmental management since 2003; permanent operational program.\"\n },\n \"12a609fd-8f3d-5891-991b-9b23a9060ade\": {\n  \"hash\": \"0380e93749fd1b1b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Stable master's programme training circular-economy professionals; no scale given.\"\n },\n \"12be43fb-2413-527e-ac26-98d261406658\": {\n  \"hash\": \"1288265f2adcccc5\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Concrete last-mile delivery service by e-bike/e-moto with inclusive jobs; reach unspecified.\"\n },\n \"133bdd0e-3a53-5952-a21e-939fed700d97\": {\n  \"hash\": \"23f95a3bd87913aa\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Methodological adaptation tool for local administrations; reach not quantified.\"\n },\n \"134dd9bb-751e-5202-8cb0-d5d2c481aa12\": {\n  \"hash\": \"b4cd9bf20e02ae5e\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"biodiversidad\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Nest-box network installed in parks and green areas for birds and bats; small-scale urban biodiversity support.\"\n },\n \"13ae8795-d03b-57b8-8280-63cafcd493de\": {\n  \"hash\": \"4ff6ad072e03dcfa\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Developing connected green infrastructure; extent not quantified.\"\n },\n \"1409d50d-83e5-4f6e-8ebb-4eb697861b60\": {\n  \"hash\": \"76f1902f6e9d9738\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single client PV installation avoiding about 277 t CO2/year; one project among many.\"\n },\n \"1418389c-2173-5d92-a802-f42cdc11dc9d\": {\n  \"hash\": \"b872e187ae58e219\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing certification-support service for fishing fleets and administrations, sector-wide applicability.\"\n },\n \"148312e2-da2d-5c4f-8b27-5a0ce838b8ed\": {\n  \"hash\": \"da564dead5a3436c\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single contracted engineering role on one green-hydrogen project.\"\n },\n \"14852395-bfda-4b6c-8a77-86f3660a29fe\": {\n  \"hash\": \"4ed463f760a950ac\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Tourist e-bike service, 100 bikes/7 stations across 6 Encartaciones municipalities.\"\n },\n \"14e6d406-cb7e-40b3-a8f8-9885b7e7890a\": {\n  \"hash\": \"3821a288c06fea46\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Uses low-consumption LED bulbs; concrete household measure, no further detail given.\"\n },\n \"1506a896-f3c7-49d9-814f-be9714bc699a\": {\n  \"hash\": \"f891e4ac94a1c984\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Household line-drying of laundry instead of an electric dryer; minor energy saving.\"\n },\n \"15781041-39d0-59dc-ac20-70d5c6bcc42d\": {\n  \"hash\": \"76a13ec65ea30307\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Course combining environmental/quality management with digital and AI tools for youth employability.\"\n },\n \"15a8bab9-eb9e-50bb-bbbf-4b74574cbe7d\": {\n  \"hash\": \"ff16b834c7cf735a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing school climate program links annual student proposals to municipal officials; no participation figures given.\"\n },\n \"15fbb9e8-8008-5169-a4fa-ae3cddc78a12\": {\n  \"hash\": \"ff4394e5b6751f24\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Manu Gainza\",\n  \"reason\": \"Getxo's first secure public bike-parking facility opened in Manu Gainza for privately owned bicycles.\"\n },\n \"1618215d-005c-503a-93ce-d8b17515f4a4\": {\n  \"hash\": \"fb76dffe186b7bb7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Energy recovery reserved for textile that cannot be reused or recycled, avoiding landfill disposal.\"\n },\n \"1667b815-f474-5db3-8455-f47e3edd983c\": {\n  \"hash\": \"ff5e7a870e4752f2\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"A curated public directory of sustainable shops; visibility only, no attributed impact.\"\n },\n \"16e32f3a-43e4-4050-8a12-fe5cfde24ea9\": {\n  \"hash\": \"7491326f750ef9e2\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"300 rooftop solar panels cover 5% of electricity and all exhibition-hall lighting needs.\"\n },\n \"172a18b3-e380-5e0a-adf4-c7163c688a91\": {\n  \"hash\": \"6e8eb5d506768b35\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Binding building energy ordinance since 2021 with annual reports and tools; operative city-wide regulation.\"\n },\n \"17497875-95f7-5bd9-97cb-adefe88fe81f\": {\n  \"hash\": \"7330c86ca66b6998\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing wave-energy R&D device, single demonstrator installed at BiMEP.\"\n },\n \"17613312-9250-48bc-a268-8bef218f2894\": {\n  \"hash\": \"e69059ae1c1bf6bc\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single artisan bakery uses organic sourdough exclusively; small-scale operation.\"\n },\n \"179b6109-6859-598f-91b7-47e77ced8455\": {\n  \"hash\": \"e2d183a3c4c7cfa1\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring fair reached about 2,000 people with a high satisfaction rating.\"\n },\n \"18074623-2367-5706-917c-73312ab4fae0\": {\n  \"hash\": \"fd58a7dda64d31bc\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"monitoring_data\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Climate monitoring and governance framework; specific outcomes not quantified.\"\n },\n \"1820c156-e6ff-5aee-8f6c-eec51244e27a\": {\n  \"hash\": \"787c4bbf0d4c653c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Own vehicle-renewal aid line, distinct from MOVES; reach not quantified.\"\n },\n \"18758a44-d70f-5ff6-b4e1-c6be8ed16d1b\": {\n  \"hash\": \"e3d99ad23b3c8803\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Produced a teaching guide for sustainability education; adoption or reach not quantified.\"\n },\n \"19380046-d30c-564a-ad98-86c79859561d\": {\n  \"hash\": \"ca07d7dbe74f14e2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"citizen_energy\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Public solar-potential mapping tool per building; helps accelerate individual self-consumption decisions, no uptake data.\"\n },\n \"1a3ba27d-0cec-55f9-b8fe-0c3a86a49fa3\": {\n  \"hash\": \"b06f43e1bcbb7eb3\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Basaldea y Monasterioguren\",\n  \"reason\": \"Farmer-incubation test-farm programme now in its third cohort; small-scale reach.\"\n },\n \"1a45b09a-76b8-5b1a-b7d3-bcae7684dac0\": {\n  \"hash\": \"7457f6bbf95d9528\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Established stable network of fixed municipal recycling centres complementing collection services.\"\n },\n \"1a4c4bdc-ef75-510c-85f1-27dbb5514233\": {\n  \"hash\": \"3f09fb64977c6425\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Research project on social acceptance of local renewable projects.\"\n },\n \"1a761cf4-4cae-5dfd-816c-778e3aad0647\": {\n  \"hash\": \"24f2184313c6f0b4\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Awareness fair and initiatives around sustainability; declared activity, no measured change.\"\n },\n \"1aa2d0d5-279f-5dcd-a250-fa7276296b2d\": {\n  \"hash\": \"f283f93838047d3a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Structured GRI-based sustainability reporting system for public performance tracking.\"\n },\n \"1ae22354-cbf0-5264-8051-031b7c68a226\": {\n  \"hash\": \"db41692a6df60ef6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Guidance for hotels and caterers on food-waste prevention; concrete but modest.\"\n },\n \"1b12f427-3bb9-4d3e-ad4b-9532c0ee8eb2\": {\n  \"hash\": \"faaee09553a04deb\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"2.5 MW electrolyzer installed, first in Euskadi; modest scale for a refinery.\"\n },\n \"1b6ba2fa-86a5-5766-9c93-40dfe4553ef3\": {\n  \"hash\": \"39df34c1b0ac4a98\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"climate_shelters\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Maintains a climate-shelter network; capacity and reach not quantified.\"\n },\n \"1b7c4ef2-bf2a-4cd2-a09e-838f752c523e\": {\n  \"hash\": \"63398ad3c5f3e1f7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operating secondhand shop; concrete but no reach or volume data given.\"\n },\n \"1bf96df0-5848-51dc-99c8-eb680e4c81e8\": {\n  \"hash\": \"aa3cafd05cbb0e01\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Sectoral efficiency financing for tourism businesses; reach not quantified.\"\n },\n \"1d533ac8-96bd-4396-a6af-9dc5f40e86b4\": {\n  \"hash\": \"cef56327d802bd28\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"PV self-consumption installed at two campus buildings; no campus-wide share stated.\"\n },\n \"1e5106da-6e02-4b7c-98e8-be255fd15441\": {\n  \"hash\": \"baa042f570828dd3\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Long-term 40,000 m2 agricultural trial network generating transferable knowledge across crops and forestry.\"\n },\n \"1e97a2db-606f-536e-a422-c42cbff4c20e\": {\n  \"hash\": \"1a1881211a5aea7a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"circular_economy\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Home-composting pilot program with practical training for participating households; no participation numbers given.\"\n },\n \"20539a80-802f-489b-911e-2d47d19d2578\": {\n  \"hash\": \"e81907ae47dbee7c\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"100% renewable electricity since June 2024 cut total footprint by over a third.\"\n },\n \"21022ada-2486-5ba8-be9b-d6a7b39b143a\": {\n  \"hash\": \"aa438ce40931cc44\",\n  \"level\": \"L0\",\n  \"status\": \"in_progress\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"2026-2030 mobility plan update still being prepared: diagnosis and participation stage.\"\n },\n \"213bed1a-79a1-4d1b-8a71-aab8c2051c63\": {\n  \"hash\": \"d298ebd2bcc5d17f\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Erasmus+ community-mapping project across five countries on environmental and social challenges.\"\n },\n \"217813eb-3834-4198-9915-b2867f4f6304\": {\n  \"hash\": \"b5cbd85135a2c48d\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Cycling-tourism programme launched jointly with Enkartur; no scope details given here.\"\n },\n \"21dbebc4-6c8f-52f7-be7a-fd3917993a07\": {\n  \"hash\": \"d114f4a91e6c96f9\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Designing one technology centre building; single project underway.\"\n },\n \"21e7f830-d415-5231-8d77-0964111939d1\": {\n  \"hash\": \"ed72937dd67bd7f2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Places young professionals in SMEs; scale not quantified.\"\n },\n \"220425a1-a25d-49ee-9aa4-02db66771f93\": {\n  \"hash\": \"e2b7e67b90083abb\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"53% of energy consumed is renewable in 2024, plus expanded EV charging.\"\n },\n \"22910bbd-656a-5938-bc6e-330108b8efbe\": {\n  \"hash\": \"e88809fa576b6222\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Adopted sustainable-tourism plan 2023-2027; per rules, the plan itself is L1 regardless of scope.\"\n },\n \"22f6acb3-66d1-4a55-87ce-c345488d93f0\": {\n  \"hash\": \"c1492398d497c397\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One class grew and harvested vegetables served in the school cafeteria.\"\n },\n \"23129624-d10f-5769-8859-f0f9c02e0eef\": {\n  \"hash\": \"7a7fda8289f06f54\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"circular_economy\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing home/community composting programme diverting part of organic waste locally.\"\n },\n \"23451c39-d1f5-4ab7-868b-c73d82f06481\": {\n  \"hash\": \"6eb3470702701286\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Manufactures direct-drive wind generators; a product line, no market-share data.\"\n },\n \"235c376a-460a-5346-bda1-6fd5b75f56d4\": {\n  \"hash\": \"0b5d20a3565399b7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Grant-call programme selecting and supporting social-innovation proposals aligned with the SDGs.\"\n },\n \"23c4b43c-c4e7-478c-bc30-2bd6fd136275\": {\n  \"hash\": \"b8175b40966c0e09\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Long-running food-surplus redistribution space (since 1997) handled 645.6 t in 2025.\"\n },\n \"2470c5db-c049-49b9-8a4f-42c449d8c5b4\": {\n  \"hash\": \"abc57049c6710bf6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Own garden/orchard providing partial food self-sufficiency; concrete, small scale.\"\n },\n \"247d870f-7e54-4add-9d2d-9d26abb935e6\": {\n  \"hash\": \"c4cac5240937cc19\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Large secondhand-clothing retailer (two stores); no quantified volume or customer reach.\"\n },\n \"24b7acf2-7fb1-463f-9c66-db0343b54e2a\": {\n  \"hash\": \"4d23fdde16e6b464\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single experimental forest plot established for climate research; concrete but narrow research scope.\"\n },\n \"25644db9-e282-5dd0-9187-ab1e41345e44\": {\n  \"hash\": \"2dba273e2acf229c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"procurement_operations\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Municipal green-procurement practice: staff training, best-practice dossier, tracked environmental-clause indicator.\"\n },\n \"25ef4128-e6a7-5948-b76a-394f97b8b98f\": {\n  \"hash\": \"54077e6e772a67f0\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Decade-long transversal gender programme spanning training, tools, alliances and internal organisational change.\"\n },\n \"26bab934-373e-5e68-968c-28a2b1b52f46\": {\n  \"hash\": \"6c2c79397886c039\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"municipal_renewables\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Getxo Kirolak\",\n  \"reason\": \"Getxo Kirolak sports facilities draw ~14% of energy from biomass/PV (2024), cutting CO2; long-running.\"\n },\n \"27f74c4f-e488-5cd1-be81-69998e2c5cf3\": {\n  \"hash\": \"797793ad9e1a00db\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"d0fe6ac4-a13e-5c4b-8dfe-10484a0bfccb\"\n  ],\n  \"reason\": \"Reuse-priority routes conserve product use-value ahead of recycling or disposal within the waste system.\"\n },\n \"284111b4-9558-4bd7-aea2-2eb59f4877dc\": {\n  \"hash\": \"9bfc7f0524dc8584\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Hydroelectric plant since 1998 produces 5-8 GWh/yr; share of total energy use unclear.\"\n },\n \"28edae84-c852-5f32-9039-44b42b1c4316\": {\n  \"hash\": \"5b91c68bf35b43e5\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Current mobility plan framework 2024-2029; per rules the plan itself is L1, its measures are separate.\"\n },\n \"28f2838b-6786-5d18-ac41-cc42dadd9071\": {\n  \"hash\": \"b820ae65134a0e22\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"citizen_energy\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ten completed subsidised community solar installations across several named neighbourhoods.\"\n },\n \"29c75b09-355b-58d0-9e3f-f453798b5e7a\": {\n  \"hash\": \"aaafda459da7cc6b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Awareness and collection campaigns with educational centres; participant volume not consolidated or reported.\"\n },\n \"29db1eaa-746d-55b8-893b-bcd351fd3c92\": {\n  \"hash\": \"3ec33d7685232ab5\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Formal multi-year research alliance with Ihobe; concrete but institutional-scale only.\"\n },\n \"29e4e3e2-d12b-43de-a48d-f2964aa7c37b\": {\n  \"hash\": \"f045466a05612dcb\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Second-hand shops functioning as circular-economy and clean points, three locations.\"\n },\n \"2a3f53d0-c0b6-5cab-9afd-8d1c057607f8\": {\n  \"hash\": \"d07c1c26471d0a77\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring advisory service line; no quantified share of IDOM's activity stated.\"\n },\n \"2a683533-ede5-5de1-9c54-f778050a84ee\": {\n  \"hash\": \"804ba57bebf2aceb\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single headquarters building certified LEED for efficiency; concrete but one-building scope.\"\n },\n \"2a6c6309-5725-5f8a-9932-d375d2ef29d7\": {\n  \"hash\": \"bd736600112369be\",\n  \"level\": \"L3\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Multi-GW pipeline of new renewable capacity under construction, expanding total generation base.\"\n },\n \"2adc2d57-0a70-595c-93f9-a216c4955145\": {\n  \"hash\": \"9c1cad07f058a422\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Jundiz\",\n  \"reason\": \"Cycling/pedestrian path under construction linking one industrial estate to nearby districts.\"\n },\n \"2aedd38b-6acc-56eb-b4ce-4e911d9f215c\": {\n  \"hash\": \"fbefaa34ca00c10a\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing environmental-education programming across CEA facilities, complementing school curricula citywide.\"\n },\n \"2af572b9-c62b-5620-ae44-9ca7e59d5904\": {\n  \"hash\": \"86a627a92955c439\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"EFR work-life-balance management practice embedded in organisational audits.\"\n },\n \"2bb629e5-fd29-474f-93b9-13a4d21d6fab\": {\n  \"hash\": \"324c5c60cf2b1e9d\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Cooperative's entire electricity retail line is 100% renewable for all members.\"\n },\n \"2c8c5f31-9741-51dd-9f62-9e74938a30a4\": {\n  \"hash\": \"0792a24c74dad5fb\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing Safe School Routes program fostering walking and independent mobility for primary pupils; no reach figures.\"\n },\n \"2cc5aba9-31a9-5606-b9ad-d90c292b272b\": {\n  \"hash\": \"44d6495f19bd7317\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Biodegradable FAD material trials completed with a partner; concrete tests, not yet fleet-wide.\"\n },\n \"2cc66de4-2ed3-5331-9c23-248ff1ec2ffc\": {\n  \"hash\": \"076d4315f202e5d5\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Coordinates an ongoing marine-observation project expanding autonomous monitoring along the Basque coast.\"\n },\n \"2ceb21ef-277b-47b0-a157-00b9fe8fbe52\": {\n  \"hash\": \"cea94cc70ce3ce52\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operating environmental-education park with several exhibits; no visitor-reach figures given.\"\n },\n \"2d3da0fe-fc75-5742-8b38-672eca5042f1\": {\n  \"hash\": \"a2929aa98fa87d1f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Launched a comprehensive hydrogen technology laboratory; one significant discrete infrastructure investment.\"\n },\n \"2d6ea13c-0f15-481f-b660-00de06f5e5eb\": {\n  \"hash\": \"197d16fa2e686e18\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing schools programme (Agenda 2030) providing advisory, training and resources citywide.\"\n },\n \"2dc229f7-51e7-4725-8d72-060acbd3ddea\": {\n  \"hash\": \"33a46ccb845b3f0f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Monthly escorted bike-bus for pupils, running since the 2022/2023 school year.\"\n },\n \"2e779b80-020f-54ad-b109-8156a153a800\": {\n  \"hash\": \"438d5fca32f24c05\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring 30-hour training line on climate and human rights; concrete but reach not quantified.\"\n },\n \"2e7b1cc8-aaf8-54c4-b104-89617e655fb3\": {\n  \"hash\": \"68bc8170da9da7d2\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"low_emission_zone\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Ensanche y Casco Medieval\",\n  \"reason\": \"Low-emission zone operating in two central districts, with measured traffic/emission reductions.\"\n },\n \"2eac0b42-f1b8-5e24-8d34-1612dd60ddf2\": {\n  \"hash\": \"88cf8dfbd4d28747\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"ISO 50001-based energy management system structuring monitoring across facilities.\"\n },\n \"2ededdce-6f8a-56c3-b6e7-b8caf8a8ef3e\": {\n  \"hash\": \"be3d1ad661ae852f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Public-private ecodesign alliance; membership impact not quantified.\"\n },\n \"2f84d3c3-7a49-4ecd-aa62-cf89822ba589\": {\n  \"hash\": \"a85ec1e3e1b4b2cf\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Personal professional specialisation in sustainability since 1995; no concrete action described.\"\n },\n \"2fa29402-e8c2-5e5f-9eae-83e1461ffbcb\": {\n  \"hash\": \"34ca98c8380c8ac0\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Reported carbon-intensity outcome for the European generation mix; an indicator, not a new action.\"\n },\n \"2fe597db-ca81-52d8-bd10-ad2e2583515d\": {\n  \"hash\": \"715d1fb21fe399de\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Citywide programme greening multiple school playgrounds since 2021, still expanding to new schools.\"\n },\n \"30389d49-f4f3-45f6-af27-ee9efffe15aa\": {\n  \"hash\": \"b7783be804422233\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Largest experimental agrovoltaic unit in Spain (8,000 m2), supplying all power for one campus's self-consumption.\"\n },\n \"3088504a-ca76-4ffa-8e03-cffb03b20ce6\": {\n  \"hash\": \"792829d7b9180401\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Water Explorer programme: over 1000 schools, 11 countries, thousands of m3 water saved.\"\n },\n \"3122dea8-4a39-47fe-a982-9958c559a87e\": {\n  \"hash\": \"9e0e677f2908a1a3\",\n  \"level\": \"L0\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Advocacy campaign demanding EU bathing-water quality policy; no concrete change achieved yet.\"\n },\n \"3135a483-f883-51e5-bf12-747eac4b834d\": {\n  \"hash\": \"9eec492b18a107df\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"International R&D project on grape-stem animal feed; still at optimisation stage, no deployment yet.\"\n },\n \"3163312c-ba12-5214-86e3-877df682014d\": {\n  \"hash\": \"54477278653d0821\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Educational projects on ecosocial challenges used as a tool in vocational centres; reach unstated.\"\n },\n \"31669f02-5038-5c01-898a-5333f2eecb10\": {\n  \"hash\": \"defb174b06a8cdf1\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Commercial charging solutions for homes and businesses under the Smart Mobility Plan; reach unspecified.\"\n },\n \"32e3231c-ee8c-4075-8776-7633bc69e509\": {\n  \"hash\": \"6d354c037a90e0f4\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Uses upcycled/eco ingredients and offers packaging return; no uptake data given.\"\n },\n \"33ac1390-e6b1-5ea3-bde0-40151658e9ad\": {\n  \"hash\": \"9e99302c340748cd\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Contributes safety evaluations to a Talgo-led hydrogen rail-propulsion project; discrete technical role.\"\n },\n \"33dbd8d4-8c83-525a-932a-e994b3463490\": {\n  \"hash\": \"9d32452773286946\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Citywide separate organic-waste collection via identified-access brown bins; matches L3 example.\"\n },\n \"34304009-6108-4059-b1e1-bd36f63f6bc8\": {\n  \"hash\": \"a871693fa0b29ebb\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Weekly forest-school model covers all of one full grade level since 2022/23.\"\n },\n \"347b8953-03f1-4a26-ad1f-96a85eb87723\": {\n  \"hash\": \"23435f7998af47b6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Household habit of dressing for indoor climate to cut heating/cooling energy use.\"\n },\n \"34eb3e6d-8500-5853-b43b-98ebe2ae1fc1\": {\n  \"hash\": \"6181df26198f0484\",\n  \"level\": \"L0\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Innovation initiative explicitly without mature operational results yet.\"\n },\n \"35890dcf-bfff-5b2c-a2b4-62ec58c85961\": {\n  \"hash\": \"928c7dedee348599\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in validating flexible electrolysers as grid-responsive industrial loads; discrete R&D.\"\n },\n \"35afcded-58ff-45b5-a44f-30f6570305a4\": {\n  \"hash\": \"c161c72dc295b867\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Waste-education visits and workshops reached 65 participants in 2025.\"\n },\n \"35d10d75-2f8c-59f7-a3f2-2f7c4b15f7e2\": {\n  \"hash\": \"b062f7e44ada2181\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in a project developing nature-based solutions for soil management; discrete research.\"\n },\n \"35f6e17c-0eb7-404e-a6b5-450fe9040817\": {\n  \"hash\": \"b22510497a40262b\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"8% of consumption self-generated plus renewable-origin certificates redeemed for the rest.\"\n },\n \"3662c3e3-4d50-5629-9074-741409765f4b\": {\n  \"hash\": \"143b0cb3036c1e64\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"public_transport\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Network of park-and-ride sites (Igara, Miramón, Zuatzu, Ficoba, Illunbe) intercepting car trips before centre.\"\n },\n \"373f6d9b-5221-5ba5-abd1-7e8918f129dd\": {\n  \"hash\": \"d62865fb1a01ca18\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Bicycle fleet for sustainable mobility maintained by training cycles at one campus.\"\n },\n \"37543b4f-012b-5498-8f6d-0b5519504af5\": {\n  \"hash\": \"5d281e77ba6911df\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"climate_energy_plan\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"f34881fb-37ff-56a3-b93c-c5f4e1406573\"\n  ],\n  \"zone\": \"shared\",\n  \"reason\": \"Adopted climate/energy plan; an adopted plan counts as L1.\"\n },\n \"37567eb9-24d0-4b7d-a464-25e320c3a200\": {\n  \"hash\": \"474c08fc9c0e5b73\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Continuous region-wide wildlife-health surveillance across the whole Basque Country, with real 2023 detections.\"\n },\n \"3766006e-3a6c-5b44-8538-79ece7861b35\": {\n  \"hash\": \"187af6148f0380a7\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Established mobility plan continues guiding cycling, traffic-calming, public-space projects; per rules, plan is L1.\"\n },\n \"39493b81-d1ed-59c1-90a0-f6cac79382f2\": {\n  \"hash\": \"ec7ba40fac02f5d4\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"San Inazio, Iruarteta, Indautxu\",\n  \"reason\": \"Greening of three named school surroundings; localized scale.\"\n },\n \"3a55830b-51b5-53d1-9daf-b54ea76b98ac\": {\n  \"hash\": \"7aee0fbbb70d8863\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in developing robotic and AI-based automated garment classification; discrete R&D project.\"\n },\n \"3ad86fdf-7a73-55f9-82cb-95702c22b8a4\": {\n  \"hash\": \"0a3768d0e2eff172\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"SDG integration into governance and procurement criteria; outcomes unquantified.\"\n },\n \"3ae161cb-b757-5b1f-8fdf-389d392dcbf1\": {\n  \"hash\": \"791f5efc764f4b3a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"monitoring_data\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Multi-year citizen-science soil-monitoring programme with continued public participation.\"\n },\n \"3af0d57a-9874-452f-ba5e-8568a4d6c44e\": {\n  \"hash\": \"1086a45a983e9166\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Facade rehabilitation with insulation across the school building; no savings quantified.\"\n },\n \"3b388a55-6c9c-5253-9faa-bfdedf1c1806\": {\n  \"hash\": \"51b0e27a385c5e38\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Applies ARIES architecture to natural-capital accounting for institutional decision-making; a discrete service.\"\n },\n \"3b476108-d58a-5b1a-b956-06b49c5bae18\": {\n  \"hash\": \"802c9a6131246357\",\n  \"level\": \"L0\",\n  \"status\": \"committed\",\n  \"category\": \"climate_energy_plan\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Only the procurement to draft a Climate and Energy Plan; the plan itself not yet written.\"\n },\n \"3b59df5e-81a5-498a-b121-80aa7862de30\": {\n  \"hash\": \"67655deb1deb27d9\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Over 75% of company travel uses a solar-charged electric vehicle.\"\n },\n \"3b9efa03-af54-4dbd-8ba5-d87c1bcbfcf7\": {\n  \"hash\": \"d52926e27360c492\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Water consumption cut 20% in 2025 via cooling-tower and lake-treatment fixes.\"\n },\n \"3bd3ac87-aad1-4fc3-95cf-d83d3bfa0161\": {\n  \"hash\": \"d186a66cc202b767\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Company produces and consumes mostly its own solar power, majority of own energy use.\"\n },\n \"3c6d871f-18ec-44ce-a126-2e5203a3ab00\": {\n  \"hash\": \"3988de67bc5533dc\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"2013 technical assistance in river-corridor restoration removing invasive species and replanting.\"\n },\n \"3cd93846-0ab3-5f96-9b56-2b5fa3a1f3db\": {\n  \"hash\": \"4050ebe21a99b764\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing assistance service to public administrations; reach not quantified.\"\n },\n \"3cede4fc-f30e-597d-a614-616845b550b5\": {\n  \"hash\": \"527ebcd8e1f5a7a0\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"public_transport\",\n  \"partOf\": \"f851d8a9-11a3-57e6-a4cc-c3f8b695dbf2\",\n  \"sameWorksAs\": [],\n  \"zone\": \"cocheras de Dbus\",\n  \"reason\": \"Depot electrification infrastructure supporting the Dbus fleet transition; single-facility, EU-funded, still underway.\"\n },\n \"3cef56db-d958-4a8b-8885-0d3fe768196d\": {\n  \"hash\": \"72b0118adb671549\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Carpool initiative maintained across the tech park; no participation data.\"\n },\n \"3cfa9f22-4e22-486e-8be9-c0c3cd76b4e1\": {\n  \"hash\": \"7a2d17dc7d908dae\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Renewable port energy community project in development; no capacity or completion date given.\"\n },\n \"3d8e5c3b-25ac-53f5-830a-0ff7ed5875ee\": {\n  \"hash\": \"166f586f2191d5e1\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Acquired methane-measurement equipment enabling ruminant emissions research; infrastructure, not a reduction itself.\"\n },\n \"3e677305-bdba-5367-8e41-a108d0ab9b6b\": {\n  \"hash\": \"a17f68d7417ff63c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Environmental/quality training itinerary for agrifood-industry professionals.\"\n },\n \"3ef26fa2-3e68-45c3-8d8d-ed9066d30ff1\": {\n  \"hash\": \"ba4dfef57b80e15c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One electric company car, partly solar-charged; concrete, small reach.\"\n },\n \"3f1e494e-c750-551b-a1c9-9539f2946e34\": {\n  \"hash\": \"0b01961e02021343\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"biodiversidad\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Enobieta\",\n  \"reason\": \"Under construction: partial dam opening at Enobieta restoring river connectivity, part of LIFE KANTAURIBAI.\"\n },\n \"3f8b1cf0-7947-43ae-b9bc-92eb8c859cce\": {\n  \"hash\": \"89a1d8bfd3f7b579\",\n  \"level\": \"L5\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Total footprint cut 55.95% versus 2019, exceeding the 50% corporate target.\"\n },\n \"3f9a795b-b733-5f5f-b0ca-ae0219936601\": {\n  \"hash\": \"59a33d7c4da6cd05\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Channels separately-collected organic waste into composting; a distinct household-to-compost service flow.\"\n },\n \"3ff70118-31e7-5c44-8890-85bbec04cd13\": {\n  \"hash\": \"2a093c46449fb9e1\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"category\": \"street_redesign\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"30km/h limit implemented on every street in the municipality since 2013.\"\n },\n \"40367018-88d0-5db6-a306-112d86a4d649\": {\n  \"hash\": \"eb6d2f61d9c671b7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Offers tailored collection services for organisations and large volumes beyond standard street containers.\"\n },\n \"403b3403-f433-4369-b156-35a9dd732d8d\": {\n  \"hash\": \"5c16ff69efd2566b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Tap aerators installed to reduce water flow; minor concrete fixture.\"\n },\n \"40b62c8c-4154-5db1-8918-48ecba86116a\": {\n  \"hash\": \"15e6fe8e91462cda\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"VR-based sustainable-food education programme for youth across training centres.\"\n },\n \"40fe71ce-c6fd-44b4-9c62-a1455f93a569\": {\n  \"hash\": \"045bd46dada1d197\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Cardboard/paper recycling; concrete small-scale operational practice.\"\n },\n \"410f5349-b271-440c-979b-3bf72e6175fe\": {\n  \"hash\": \"a733f8760dccef2c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Marine science outreach and conservation activities; no reach figures given.\"\n },\n \"414d5c87-2b69-5a52-be53-260a1b71e67e\": {\n  \"hash\": \"efa74a1557bf6057\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in a cross-border Interreg initiative structuring a circular textile value chain.\"\n },\n \"41c94521-076a-48e5-aece-1c4d56984ee0\": {\n  \"hash\": \"2d025f3f2f15aa75\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Salon offers plant-based mud hair coloring; single small business service.\"\n },\n \"41fd163f-84c1-565f-9b4f-b8fab74f90c5\": {\n  \"hash\": \"a7886f2d5f9b2a2e\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Building new sustainable-mobility infrastructure with low-carbon design; one discrete capital project.\"\n },\n \"4229411a-8f6e-46a6-83ab-f4581a5a5642\": {\n  \"hash\": \"a7f68f156694a27a\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Multi-year GHG inventory covers the whole university; market-based scope 2 already zero.\"\n },\n \"42606a70-4d2b-5d7a-9b38-890e8d51fd11\": {\n  \"hash\": \"31ea3d1a3f7fa19e\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Demonstrated industrial-scale CO2-to-fuel conversion concept in Dunkirk; one completed demonstration project.\"\n },\n \"42658051-4a0b-5b11-b7fa-60450a0d3627\": {\n  \"hash\": \"669a4bfca21f2e08\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Small loan programme letting families trial cargo e-bikes; limited concrete reach.\"\n },\n \"428644b2-5fad-5e6b-82aa-7bbfad3f0331\": {\n  \"hash\": \"4884ddb83ccd167f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing mentoring line for green/circular entrepreneurs; concrete service, no scale stated.\"\n },\n \"42c5f3c8-7686-5af3-9f64-bfd2dcebdff7\": {\n  \"hash\": \"20cf9fc49f667632\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Co-founding role in Koopera network; operations counted under Koopera's own profile.\"\n },\n \"436bb896-7871-54c7-8f1f-edcd87b2948f\": {\n  \"hash\": \"753556a1d5fb19bc\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Adopted strategic framework for 2026-2029 structuring BC3's research and societal impact priorities.\"\n },\n \"43749a00-2216-5292-9687-3d209219a4eb\": {\n  \"hash\": \"848b3231f0b2aeef\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing incremental expansion of conventional street bike parking across neighbourhoods.\"\n },\n \"43e14191-f8a5-5e7a-88b9-38e87b2d65db\": {\n  \"hash\": \"421116d1ddd3a9d2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single flagship solar EV charging hub in Madrid; concrete but one-site scale.\"\n },\n \"442d427a-1a19-5fbb-8124-aad75ccba838\": {\n  \"hash\": \"58f8d0ebbafddb08\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"public_transport\",\n  \"partOf\": \"3662c3e3-4d50-5629-9074-741409765f4b\",\n  \"sameWorksAs\": [],\n  \"zone\": \"Illunbe\",\n  \"reason\": \"Seasonal fully-electric shuttle bus connecting Illunbe park-and-ride to the city centre in summer.\"\n },\n \"444b20a0-4c64-48e3-9525-2008d6569e10\": {\n  \"hash\": \"c7e74b02d5387d1f\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in EU beech-forest sustainable management project.\"\n },\n \"4494c73d-643c-4306-9ece-72395a53a6aa\": {\n  \"hash\": \"35472135258cf87c\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Long-established accredited analytical service (over 100,000 analyses/year) for the whole agro-forestry sector.\"\n },\n \"4535e749-27d5-5025-9039-0f3d9abf2d86\": {\n  \"hash\": \"46e8b333b327315d\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"biodiversidad\",\n  \"partOf\": \"82f2144b-8fa9-506a-8d7f-ba195cb8e48d\",\n  \"sameWorksAs\": [],\n  \"zone\": \"humedal de Ibaeta\",\n  \"reason\": \"Completed February 2026: wetland/vegetation restoration at Ibaeta, a single-site component within PluSSS.\"\n },\n \"45af2c63-cc17-4cac-9114-9665e4044c27\": {\n  \"hash\": \"86de9cf29cd89c12\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Verified GHG Protocol footprint reporting across scopes; monitoring, not mitigation.\"\n },\n \"47407d4c-5c2b-59d1-a73a-2e651cdaa465\": {\n  \"hash\": \"af4fb824db5142e0\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Project on biodiversity impacts of large solar plants begins imminently; committed, not yet under way.\"\n },\n \"475a5383-f65d-5d5f-ac39-8fc86f0f0be8\": {\n  \"hash\": \"696d1379ec63a7f8\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Artxanda, Otxarkoaga, Rekalde, Zorroza\",\n  \"reason\": \"Municipal gardens at four named sites; small scale citywide.\"\n },\n \"47bcaef8-53a0-5a9e-a5d0-8045fe12eb2a\": {\n  \"hash\": \"c5976581c2764b97\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Adopted grid-investment strategic plan; a strategy alone counts as L1 regardless of ambition.\"\n },\n \"47de7d7d-075e-5b3f-ac74-9d0150a5f446\": {\n  \"hash\": \"26c4a689c2c3a0de\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Awareness mural painted by students for mobility project; no concrete environmental change.\"\n },\n \"4807357f-287b-5f84-8ec3-7ef49b7588a4\": {\n  \"hash\": \"485e25040db01a71\",\n  \"level\": \"L1\",\n  \"status\": \"unclear\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Visitor awareness programme aiming to build environmental care ('maritime guardians'); timing unclear.\"\n },\n \"48350752-a670-45f4-99b5-d9324236d65c\": {\n  \"hash\": \"46785b91f0e858b5\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Recycling-awareness campaign reached 800 primary pupils, under 1% of the municipality's population.\"\n },\n \"4852c642-605e-5e8a-9a0c-7cf4be7124fe\": {\n  \"hash\": \"41390361352a1087\",\n  \"level\": \"L0\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Tourist-stay tax only initially approved July 2026; further processing needed before planned January 2027 start.\"\n },\n \"489cf816-686a-5e7f-99d3-69929842e7a2\": {\n  \"hash\": \"608942e855389229\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Next Generation-funded grant for sustainable local gastronomy sector; award confirmed, execution detail limited.\"\n },\n \"48b97d35-e2b3-5271-beba-b1c8e20dacf8\": {\n  \"hash\": \"d3c3a23702896375\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Structural technical secretariat serving the whole sustainable-municipality network.\"\n },\n \"48cb78ad-b2f9-4968-b970-13b5c397af4f\": {\n  \"hash\": \"b32ef228d96bab21\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Continuous monitoring of emissions, air, soil, water and noise since plant startup.\"\n },\n \"49096aa7-0a72-5f4f-9b6b-3bb11f05ed77\": {\n  \"hash\": \"771ce60a1984dc5b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single-municipality multi-agent pilot process; concrete but limited scope for the organisation.\"\n },\n \"49626f35-4fb2-492e-a47e-7dbaadb33a18\": {\n  \"hash\": \"8ff35ec3534665b7\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Over half of about 4,200 suppliers are local producers, a large local-sourcing share.\"\n },\n \"49762197-cc21-5cc7-aeb2-cd52d51c0d7c\": {\n  \"hash\": \"d44093b4856cda04\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"EU-funded viticulture-adaptation project tested across 30 pilot plots in four territories, with grower training.\"\n },\n \"49bd6594-3396-50d7-9017-0b5e9b623522\": {\n  \"hash\": \"e5eebbdb2f7ef572\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Pilot program helped tourism accommodations calculate carbon footprint using Ihobe tools; no participant numbers given.\"\n },\n \"4aeffcfe-01f9-5a1d-ab6a-a93bc8df335d\": {\n  \"hash\": \"5dc9bb21e7e0df25\",\n  \"level\": \"L0\",\n  \"status\": \"unclear\",\n  \"category\": \"biodiversity\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Monte Gazteluzarramendi\",\n  \"reason\": \"Forest intervention pending assessment; text explicitly says no positive balance verified yet.\"\n },\n \"4b22f6a8-3f65-4237-baf7-4e218bf2f677\": {\n  \"hash\": \"ff1f43e625441010\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Bus fleet electrification underway; 8% consumption cut despite service growth.\"\n },\n \"4b3439bc-87cc-51a3-a037-b50f62779865\": {\n  \"hash\": \"3965bd711206b450\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operates an Ibero-American education platform connecting many educational actors; a core recurring activity.\"\n },\n \"4b84ae05-7bd5-57ec-a2fc-efee7370160c\": {\n  \"hash\": \"15ac40e6ab44a987\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"street_redesign\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"82b875ec-3984-55ed-a84d-4ed4eea58d43\"\n  ],\n  \"zone\": \"Las Arenas\",\n  \"reason\": \"Three coordinated street transformations in Las Arenas (incl. ~600m new cycle path) favouring pedestrians/cyclists.\"\n },\n \"4b8f9a42-da29-5e2a-b72b-1a30c4c98002\": {\n  \"hash\": \"3c7ac3a0d89ab1e2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Free tool developed and offered; concrete but reach not stated.\"\n },\n \"4c5f1d51-8788-595b-88f7-b17eaf523097\": {\n  \"hash\": \"2a2d70e637a37de8\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Municipal food-waste prevention program (information, pledges, best practices) for households, retail, hospitality.\"\n },\n \"4cb1667f-3db8-5f36-8846-2e4ea4e57b55\": {\n  \"hash\": \"7fdf7d5da55dcc35\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring thermosolar engineering service line; no quantified company-wide share.\"\n },\n \"4d38a58d-89a1-5b7b-b7c6-993c42d450af\": {\n  \"hash\": \"e09e06b855235a21\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"municipal_renewables\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Albergue de Ulia\",\n  \"reason\": \"Started biomass/aerothermal heating conversion at Ulia hostel, complementing existing PV; single-building scale.\"\n },\n \"4d443bbb-16a5-545a-aace-03d786de13e9\": {\n  \"hash\": \"d6acf2612b024d65\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Four birdwatching routes established in Gorbeia Natural Park's northern sector.\"\n },\n \"4d57aa94-f0af-5004-91f2-c10e026f738e\": {\n  \"hash\": \"9ca5ba10130b5eb6\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"citizen_energy\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Management contract awarded for new energy advisory office; reach not yet established.\"\n },\n \"4de20c36-a7de-432c-952e-85f7d691e680\": {\n  \"hash\": \"88c0b77b8883bf24\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"last_mile_logistics\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Electric last-mile delivery extended city-wide; quantified CO2e cut is tiny.\"\n },\n \"4df3fd6a-7ff5-40b4-b894-878d001c328d\": {\n  \"hash\": \"f90561a6d6c23b3f\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Approved 23-measure energy plan; adopted strategy, measures not yet executed.\"\n },\n \"4e5245e3-2dcb-559c-ac79-b87fff84591d\": {\n  \"hash\": \"b82f1fb881d013a2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Supports energy-community models since 2020; number of communities unstated.\"\n },\n \"4e8156b4-65d0-4cc7-b3ba-9fbc303f300c\": {\n  \"hash\": \"84461b0622f9d2cc\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"20kW rooftop self-consumption PV at one school; no total-consumption share stated.\"\n },\n \"4ebdc903-3d87-479d-8bf6-51a1737dcdda\": {\n  \"hash\": \"8ed74efc41a892b4\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Dual-flush toilets; minor concrete water-saving fixture.\"\n },\n \"4ecb26a0-c1e9-59d2-8f71-93a3266ad4cb\": {\n  \"hash\": \"3ec062b6f3a5502c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": \"b9c4c700-0444-5f08-943d-88804b635ecb\",\n  \"sameWorksAs\": [],\n  \"reason\": \"Quality compost output from the plant can return to soil, closing the bio-waste material cycle.\"\n },\n \"4f0ce198-dd18-5b92-b55b-b1090efbc583\": {\n  \"hash\": \"f903e1c0add156e0\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operating pumped-storage portfolio being expanded; system-scale flexibility, no quantified share.\"\n },\n \"4f389c32-8173-4b13-8ba8-e45bc5aba3d4\": {\n  \"hash\": \"267b83828f482171\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Free educational visits on the water cycle, open to the public; participant numbers not given.\"\n },\n \"4f3a7a4e-a951-59fb-a567-225a15710e28\": {\n  \"hash\": \"45e1c48779c10854\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"street_redesign\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"calle Miramar, Algorta\",\n  \"reason\": \"Phase 3 of progressive pedestrianization of calle Miramar in Algorta; single-street redesign underway.\"\n },\n \"4fb2ad40-94a6-459d-9ec3-080524e725f0\": {\n  \"hash\": \"9688d6b501b279cd\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Co-financed ecosystem-management and local-leadership project in Bolivia's Chiquitania; no reach data.\"\n },\n \"4fc14f58-fd8d-517e-aaef-8f28aa4034cb\": {\n  \"hash\": \"41422c532820135a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring recycling-plant engineering service line; no quantified share of activity.\"\n },\n \"4fe1864a-a718-5ab8-9bf1-f427720c6c5f\": {\n  \"hash\": \"48835b5e38de73ce\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Approved 2024-2027 Local Agenda action plan (5 lines, 21 programs, 73 actions); per rules, plan is L1.\"\n },\n \"5003a554-bf29-43ac-bcc5-a0100a475c7d\": {\n  \"hash\": \"323eb0b23e91cfbd\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Solar panels installed at one market; consumption share not stated.\"\n },\n \"5043f4b6-4f96-484e-a2b6-c8e2ab983b6f\": {\n  \"hash\": \"3a9e09ef68ec1cf3\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Guided biodiversity visits to an arboretum with a university; visitor numbers not given.\"\n },\n \"50c03f9b-8544-47f5-861b-fcaefac01cea\": {\n  \"hash\": \"30ee720a3b66f309\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Campus-wide transdisciplinary sustainability programme running annually since 2017/18, outcomes not quantified.\"\n },\n \"5105fe6d-9988-5345-9f00-7f19fa8af790\": {\n  \"hash\": \"3a371ffb631b6cd1\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": \"f86fc858-d992-5490-9bbb-63f07df76921\",\n  \"sameWorksAs\": [],\n  \"reason\": \"Mechanical-biological treatment integrated within the residual-fraction management system; a component process.\"\n },\n \"510bf78d-4d31-5fad-8871-d5908356df86\": {\n  \"hash\": \"8b07461234fd654a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in the selective light-packaging treatment system, facilitating sorting and material recovery.\"\n },\n \"514dc7d5-980b-5153-9c00-9e39cb0879a9\": {\n  \"hash\": \"8c3f1292ca0a8f9f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Annual Green Week combining hands-on sustainability activities (birdwatching, composting, repair); recurring event.\"\n },\n \"51ceb35f-53d8-5118-b3f2-3c7de8783b9e\": {\n  \"hash\": \"fabe4fa83369e7cf\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Grant line for renewables and efficiency studies; aggregate reach not quantified.\"\n },\n \"51e0315d-1c87-457b-af9a-3f9e66925feb\": {\n  \"hash\": \"752a31f9f9ee9db1\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Rooftop PV installation registered for the school; capacity or share not stated.\"\n },\n \"522b1660-4eb3-5f2b-bec9-4f3b5fef5a7d\": {\n  \"hash\": \"5d1b4fc32191515c\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing research into large-scale hydrogen storage in salt caverns; one discrete project.\"\n },\n \"525fa06c-fa83-5302-8525-c3bbebfd318a\": {\n  \"hash\": \"4cc6275777c55375\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Joint-venture rapid-charging network across Iberia, ongoing rollout; no reach figures given.\"\n },\n \"526184ad-0d22-5c62-92ee-e8a6c2e1de1d\": {\n  \"hash\": \"956e648c15d4fedb\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Applied training for SMEs producing individual waste-prevention plans; reach not quantified.\"\n },\n \"52a17542-28b1-4e99-9e50-dcbd57601e5e\": {\n  \"hash\": \"bedfc0da6576f6d7\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Efficient VCV climate system yields around 20% building-wide energy savings.\"\n },\n \"52b68a71-7d72-43e7-ba9f-56a4532e4112\": {\n  \"hash\": \"9ca482a8c943c9c1\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Investor network for environmental businesses identified over 300 investment proposals in three years.\"\n },\n \"530d1f7e-a791-5a7f-b7d1-3df979041b57\": {\n  \"hash\": \"d945828b9e82b537\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"municipal_buildings_efficiency\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"2026 budget (>€400,000) for municipal-building energy-efficiency upgrades, distinct from the broader climate plan.\"\n },\n \"53424b98-a6bf-577f-817c-149f0315488c\": {\n  \"hash\": \"0c0447bb14222bf2\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing scientific and technical support tailored to artisanal fishing fleets across multiple data dimensions.\"\n },\n \"537d6129-c89f-49f3-916e-f2d22d1cb7e5\": {\n  \"hash\": \"c3c2a71cb4e1dd72\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Continued Basque-coast marine-litter monitoring with over 2000 citizen-science participants yearly.\"\n },\n \"53b4a3d9-7935-5777-bb1f-6dc72c61f9e3\": {\n  \"hash\": \"3715340a6b6c888c\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"biodiversity\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing multi-species conservation work across several named wetland and natural sites.\"\n },\n \"53e64ee4-7922-5d84-aed8-996b5e9e69a7\": {\n  \"hash\": \"51343c92c8cc720f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Continued age-adapted sustainability/Agenda 2030 education spaces for children and youth.\"\n },\n \"53ef2de5-8a8f-5709-a686-c90601534e73\": {\n  \"hash\": \"33953d02d4a046a1\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Develops ARIES models feeding UN global biodiversity indicators; research turned into international monitoring infrastructure.\"\n },\n \"5402778e-b05b-46f4-9ca2-62ee367e0bb5\": {\n  \"hash\": \"afb19d265fc7e398\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Sources regenerative organic cotton via a cooperative; scope across product range unstated.\"\n },\n \"544eca54-16a1-5c53-a2e8-ac7fff360f7a\": {\n  \"hash\": \"fed42c77b0b94e0c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operates a long-running wave power plant; capacity not stated.\"\n },\n \"545f32fa-dd5b-5495-8072-abbfdcfe7dc1\": {\n  \"hash\": \"5566615531519ad6\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Herriko Plaza-Landa Kalea / estación Euskotren-BiziPark\",\n  \"reason\": \"Bike-lane works started linking plaza, train station and bike park; single link.\"\n },\n \"54e0cc7a-4b59-4668-b689-3c27dd573a4f\": {\n  \"hash\": \"ae871856299a7a58\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Building supplies over 50% of the company's own energy from solar.\"\n },\n \"553ee17b-a995-4918-99b2-7049c0b3468d\": {\n  \"hash\": \"678be81be023292b\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"First port worldwide certified with an Environmental Product Declaration for all its services (2019).\"\n },\n \"555d11eb-ecb2-5fba-98ac-4e7635486782\": {\n  \"hash\": \"f44f0ba530389af7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed analysis linking circular economy to consumption-based emissions.\"\n },\n \"55608a4b-66d0-5a30-a3dd-53c9da0a38a4\": {\n  \"hash\": \"5a919d513163c024\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"category\": \"public_transport\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"corredor BEI\",\n  \"reason\": \"Structural high-capacity electric bus corridor, not a pilot; several million km operated.\"\n },\n \"55f306b6-dd55-5d6a-ab5a-ca4414d6606d\": {\n  \"hash\": \"a58c6082a6beac23\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing project improving hydrogen transport infrastructure safety and materials; one discrete contribution.\"\n },\n \"56193b2d-3720-43d6-ac4e-79a1f788dc4e\": {\n  \"hash\": \"2fefa7627973c671\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Core social/labour integration programme for people at risk of exclusion; no reach figures.\"\n },\n \"56915fae-aaaf-5ccc-8146-257086332534\": {\n  \"hash\": \"2ed27cc47231ce66\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Coordinates substantive components of a European project on justice-centred climate adaptation; leadership role.\"\n },\n \"56ad01f6-af1d-5137-935f-bad4b91b6b76\": {\n  \"hash\": \"5d49be40c317281a\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Eighth edition of a zero-marine-litter swim event announced; no participation numbers given.\"\n },\n \"56c9b838-532c-5774-8b9d-da4d497cc810\": {\n  \"hash\": \"ce5d5f488b6d4739\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing investment line for renewable self-consumption; reach not quantified.\"\n },\n \"574f49b8-8cc0-49a5-b794-061b20e0ee69\": {\n  \"hash\": \"227908fa4d39d105\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recycles and transforms construction waste as part of its valorisation services.\"\n },\n \"57ea31f7-726f-5979-a999-fe834c46f1ca\": {\n  \"hash\": \"48285c73a88434f7\",\n  \"level\": \"L0\",\n  \"status\": \"unclear\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"General self-reported description of research activity; no concrete specific action stated.\"\n },\n \"589e7e1c-9337-58a2-9909-f6ea2ac2f3e0\": {\n  \"hash\": \"8059cb5ef315034b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": \"29db1eaa-746d-55b8-893b-bcd351fd3c92\",\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed joint study with Ihobe on circular-economy positioning.\"\n },\n \"5973f3ee-91e3-4fa7-9227-ef23b7c17fdb\": {\n  \"hash\": \"66a4a541e114dd72\",\n  \"level\": \"L3\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Coordinated hydrogen programme: 62 projects, 74 organisations, ~€900M planned through 2030.\"\n },\n \"5a2a0cdf-9451-541b-a60a-5d7603bdd8c9\": {\n  \"hash\": \"a8033651df264061\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring participation in an awareness week; no concrete UN Etxea intervention described.\"\n },\n \"5b9108ee-a325-5e28-b53b-83c25ffd7ecb\": {\n  \"hash\": \"66b18d411a4aab23\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Practical workshops on urban gardening and composting; text notes no established community-garden network shown.\"\n },\n \"5bb86d51-5be0-5645-aaa3-05b4773f165c\": {\n  \"hash\": \"3aec23e7d060eac1\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Contactless system used across Bizkaia's whole public transport network, in daily use.\"\n },\n \"5c4bb151-d6fb-47c7-a142-93ab59883a1f\": {\n  \"hash\": \"7cd9d50f3f81d053\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"95% recycling rate, explicitly limited to the Irun plants only.\"\n },\n \"5c58a5f4-83a5-59c5-b971-65b9d16dcc67\": {\n  \"hash\": \"9b2126ef22675567\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"municipal_renewables\",\n  \"partOf\": \"80fc659f-d246-5ad1-baad-00dcd8d9b753\",\n  \"sameWorksAs\": [],\n  \"zone\": \"Polideportivo de Altza\",\n  \"reason\": \"Large PV installation at Altza sports complex plus a second shared self-consumption unit; single-facility scale.\"\n },\n \"5d591c70-d7c7-54dd-8a9c-8659f915f44c\": {\n  \"hash\": \"2008aa4aee3824ee\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"street_lighting\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Multi-year LED rollout still under 40% network coverage, more investment being prepared.\"\n },\n \"5d8fd9fc-639b-4c42-ba2d-ff5f455a0c28\": {\n  \"hash\": \"2278638477449b12\",\n  \"level\": \"L5\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"100% of the building's electricity is contracted as certified renewable energy.\"\n },\n \"5ddbef2d-daf3-556d-92fe-a88b00e1451e\": {\n  \"hash\": \"52f148c121fd1095\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Permanent research lab on energy/environment; concrete institutional resource, modest scale.\"\n },\n \"5e021c41-619e-5ea7-9094-c2c0bc93bca7\": {\n  \"hash\": \"48da8215bffdd061\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Mobile collection point rotating across neighbourhoods for special household waste.\"\n },\n \"5e32e004-18e9-4563-a433-9d5f9286e79c\": {\n  \"hash\": \"6fade6b87decb995\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Municipality-wide zero-food-waste scheme redistributes school-cafeteria surplus for free via vending machines.\"\n },\n \"5e38366f-5540-42f6-8f04-87c4376f2101\": {\n  \"hash\": \"3e82ebde7f91d30d\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed 770,691-euro geothermal installation decarbonising heating at NEIKER's Derio centre in 2025.\"\n },\n \"5eb6e3e8-baf6-5f86-a67b-a990a204dfb5\": {\n  \"hash\": \"1f309757fb839bd6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Maintains a technical knowledge repository; usage not quantified.\"\n },\n \"5edfbfc5-b2d5-5cf1-8037-3cce094a3689\": {\n  \"hash\": \"22e2c930a7a65ce5\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Launched a free multilingual training platform for ARIES tools; an operating product, reach unquantified.\"\n },\n \"5f1d4d09-3589-4b17-aeb9-5b204523334c\": {\n  \"hash\": \"e51037e2a0f67379\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"On-demand night-bus pilot enhancing safety and stops across the whole night service network.\"\n },\n \"5fc60c1f-f465-59f9-9f36-ee52428066b3\": {\n  \"hash\": \"a729ac2cf69558b9\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Sustainable-mobility activities in one municipality targeting mainly children; reach unquantified.\"\n },\n \"60776994-db8f-5fee-b47b-dea5409a75dd\": {\n  \"hash\": \"d34aa612934c7d10\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Programmatic line applying social innovation to Bizkaia's food-system sustainability; no numbers given.\"\n },\n \"60dd431b-6e72-4838-9c97-822fba4d9c90\": {\n  \"hash\": \"8e6e7b187fd9bce6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Programme for women entrepreneurs addressing gender gaps; no participant figures.\"\n },\n \"6192c261-6f51-46fb-b813-1071d132f3e2\": {\n  \"hash\": \"bbed198b46aaa69c\",\n  \"level\": \"L0\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Co-design process still at the planning stage for old-town greening; no concrete action yet.\"\n },\n \"61a7efd2-d648-53d1-a41a-2a49eed0fa07\": {\n  \"hash\": \"b9975573003f5f76\",\n  \"level\": \"L0\",\n  \"status\": \"in_progress\",\n  \"category\": \"climate_energy_plan\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Climate/energy plan still being drafted since 2025 with public participation; not yet adopted.\"\n },\n \"6232476e-32dc-4fc9-bdd9-5c84c309b244\": {\n  \"hash\": \"1cec9d58725a2eb5\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Permanent showroom of sustainable renovation products and systems.\"\n },\n \"62478217-f017-5913-b523-bf724e05e576\": {\n  \"hash\": \"733bb614e8b20bcc\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Planning tool developed with partners; usage and reach not quantified.\"\n },\n \"62ebb604-babb-53e8-b08d-c47366222672\": {\n  \"hash\": \"484eb14e7c96d693\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Built an experimental electrolyser test bench enabling third-party stack validation; discrete equipment.\"\n },\n \"6321e8b7-decc-4f57-82ea-2267a6f2f2dd\": {\n  \"hash\": \"045cfb6e3bc1d7e0\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Life-cycle footprint software covers over 20,000 steel references, basis for certified low-carbon products.\"\n },\n \"632874b9-d3c1-5878-ad23-325aca2ec929\": {\n  \"hash\": \"f39749ae98137dd7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Think Tank component producing foresight research on long-term challenges for Bizkaia.\"\n },\n \"635b04cc-ec0f-4689-b3ed-95cf405ffc8d\": {\n  \"hash\": \"405d02418d8d9943\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Long-running (since 2007) community water-source conservation model across two Peruvian regions.\"\n },\n \"636f3527-a498-5ef1-b820-0b87ecff3a2e\": {\n  \"hash\": \"26385340167bf2fe\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"public_transport\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Zabalgana\",\n  \"reason\": \"Tram extension formalised with renewed agreement, procurement underway, preconstruction stage.\"\n },\n \"63861b3c-53d0-5533-b047-25058432974d\": {\n  \"hash\": \"138672bdc9f98f2f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing local seed/variety conservation collaboration; concrete but narrow agricultural scope.\"\n },\n \"64c37166-8d47-41d0-b3f1-f3ffdf01e977\": {\n  \"hash\": \"a62dfbb3475b6038\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Environmental-action network expanding since 2021 across four Bizkaia comarcas, with UPV/EHU.\"\n },\n \"64e181c7-640c-5ad5-b56d-34b64eabc106\": {\n  \"hash\": \"3cfdb36b4e4a9041\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"public_transport\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Salburua\",\n  \"reason\": \"Completed tram extension connecting a major residential neighbourhood, in daily service.\"\n },\n \"64fb9b85-33b0-41da-aef3-9bac59b5d63a\": {\n  \"hash\": \"32ef3418e5e7ef2f\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single-street renaturalisation project converting asphalt to a green corridor; concrete but localized.\"\n },\n \"6506c572-0079-593b-a5f3-d649237d65cd\": {\n  \"hash\": \"27318d354cd71780\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Developed and evaluated shark/ray release devices with tuna-fleet partners; deployment scale not stated.\"\n },\n \"655bc33a-1881-4cb4-bbfd-f7e92c83a084\": {\n  \"hash\": \"577d69e7fb493f72\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Urban garden plots at Urarte for residents and nonprofits; concrete but modest, localized reach.\"\n },\n \"65ba34c3-8346-4a52-b835-d68513ac9fbc\": {\n  \"hash\": \"49c61ca14bcafbe1\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Store-wide biomass trigeneration; >25% electricity savings and quantified 178 t CO2/year cut.\"\n },\n \"671f9fe9-d902-4cae-b1d4-db797724ac70\": {\n  \"hash\": \"8481504a9ff4931d\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"100% renewable electricity with Guarantee of Origin eliminated scope-2, about 41% of 2019 baseline.\"\n },\n \"67498cae-2512-5589-b0d2-7e03fcb8e83e\": {\n  \"hash\": \"d2534f463aa2441f\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"low_emission_zone\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"zona central de Donostia\",\n  \"reason\": \"Low-emission zone operating since January 2025 with progressive restrictions in the central zone.\"\n },\n \"67a7daa5-1a07-4b1f-9c6c-9882bf9f8175\": {\n  \"hash\": \"8a808f8424ec1076\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Household recycling of paper, plastic, metal and oil; concrete but ordinary practice.\"\n },\n \"67e196bf-a8df-56b0-8e49-d66016919b34\": {\n  \"hash\": \"984e48965331c6bf\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"monitoring_data\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Las Arenas\",\n  \"reason\": \"Daily bathing-water-quality information system at Las Arenas beach since summer 2026; monitoring/transparency tool.\"\n },\n \"67eddfdc-cf64-58e4-b210-6c1c1d8f6c31\": {\n  \"hash\": \"da6a6b96db024aae\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Cristina Enea\",\n  \"reason\": \"Permanent environmental-education programming (workshops, visits) at Cristina Enea park; no attendance figures given.\"\n },\n \"681ed1bf-1058-5589-8e2b-f5192500e1e2\": {\n  \"hash\": \"b27ca132857e65ab\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Maintained multisectoral alliance (Kideak) accelerating Agenda 2030 in Bizkaia.\"\n },\n \"682e15d5-0c5a-5b02-849c-3ccfceabd1be\": {\n  \"hash\": \"fa729b0f69022aca\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Three-year partnership formalised; foreseen climate-education activities not yet described as under way.\"\n },\n \"6863c160-dc65-5cd7-a3cf-09af148e2144\": {\n  \"hash\": \"bf7ed64fc9854d5a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Supports modernising old residential electrical installations; scale unstated.\"\n },\n \"68938791-7418-4077-be45-b9ed53b15981\": {\n  \"hash\": \"3b20574edf2254aa\",\n  \"level\": \"L5\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Waste-to-energy plant treats essentially its full designed throughput; this is the org's whole activity.\"\n },\n \"68941930-8f35-43d0-b3b2-ea6fb31523b4\": {\n  \"hash\": \"ad7d93ef755304d5\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Uses cosmetics not tested on animals; concrete product-choice practice.\"\n },\n \"69aed256-fb87-40a8-b902-186067c09333\": {\n  \"hash\": \"546b689cb54da44c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"School garden/terrace naturalisation across three campuses; concrete but small combined area.\"\n },\n \"69b930de-9453-578e-b038-39b86aa93d64\": {\n  \"hash\": \"1566f5a3ec1dde21\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Long-running annual anchovy survey directly used in managing the whole fish stock.\"\n },\n \"69d31454-fe14-4a18-ac3f-f704bbcb06f7\": {\n  \"hash\": \"e6ca6d73e0de41a2\",\n  \"level\": \"L0\",\n  \"status\": \"unclear\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Vague statement of support for EkoPulse; no concrete action described.\"\n },\n \"6a0b91a2-dae9-49a8-afd0-752a179ab843\": {\n  \"hash\": \"e0083b17a04ef8a6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Plastic/metal recycling; concrete small-scale operational practice.\"\n },\n \"6a4a0c70-212c-54e8-938f-aefa2c142dea\": {\n  \"hash\": \"d65cd0d8c1a995db\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Mature green-ring infrastructure encircling the whole city, actively managed and expanded.\"\n },\n \"6b0b4ab9-6075-500f-8187-2695cdde0aeb\": {\n  \"hash\": \"dbf92aa7f85bb2ca\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Manufacturer spec sheet only; no installation or real-world reach data stated.\"\n },\n \"6bc90298-ec50-54b4-82f3-e0fbfcc25592\": {\n  \"hash\": \"177672559d5a732c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Regulation caps guided-tour groups at 25 people, bans amplification, requires annual operator declaration.\"\n },\n \"6c591b36-a9d8-4a7f-87c8-41bc50e4aa06\": {\n  \"hash\": \"0432412dbff8e1ae\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Climate-control parameters adapted to outdoor conditions cut gas use 30% and electricity 6%.\"\n },\n \"6c7381ce-87ec-45aa-a2ee-fc4c179e4a16\": {\n  \"hash\": \"876393e0af0877ce\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"98 bike spaces and 15 EV chargers serve a share of building occupants.\"\n },\n \"6ccda4c2-4ee8-4b83-83d5-ca0e34258377\": {\n  \"hash\": \"44583b245a43ff95\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Housing-matching social enterprise for loneliness/housing access; no participant numbers given.\"\n },\n \"6d3ec849-0192-532c-a8fd-abd4266c4979\": {\n  \"hash\": \"429c1934e76777b3\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"879e21f8-940b-498b-8816-c71763accd17\"\n  ],\n  \"reason\": \"Drafted and later approved the Vitoria-Gasteiz biodiversity strategy, commissioned by the CEA.\"\n },\n \"6d6125ba-f796-5323-9429-0eb7e5574f45\": {\n  \"hash\": \"3ecdc6d6dc781f65\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed project establishing a local net/rope recycling system, with transfer to operators and policy.\"\n },\n \"6dfcacfb-297b-5911-9740-1346fa1e46c7\": {\n  \"hash\": \"29efb31b63c28ba0\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Entrepreneurship support for migrant/returnee women in rural green/blue economy; no scale given.\"\n },\n \"6e2bb96a-bfb3-473d-a005-4aff5167f803\": {\n  \"hash\": \"7cd43e9dcc038c19\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"New arrangement to supply ash to an ecoaggregate plant from 2026; quantities not given.\"\n },\n \"6e4e2ee1-8b1a-40db-b330-4340bd6ad9b1\": {\n  \"hash\": \"acdda28019a73e9d\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Zero-waste shop with reusable containers; single small business, physical plus online.\"\n },\n \"6e6fcd42-056b-49c0-9f7e-1812703a361c\": {\n  \"hash\": \"ba431054e147adc2\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Weekly algae-monitoring network along the Basque coast with researchers and public bodies.\"\n },\n \"6ecc9b6b-d652-5e37-a79b-ec6d680c6034\": {\n  \"hash\": \"755597265c1fd495\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Supplies traced post-consumer textile waste to an initiative converting used polyamide into industrial materials.\"\n },\n \"6f2e093a-d731-57ac-a10a-b6506122fe9c\": {\n  \"hash\": \"7c86d3a06d0eb003\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"School programme with 616 families across 36 schools; sister programme cut school energy 5%.\"\n },\n \"6ffc43ae-1f83-4a0f-8c77-382571fd183b\": {\n  \"hash\": \"4060203ac0c93592\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Rooftop solar self-consumption since 2021; tiny quantified output (0.317 MWh/year).\"\n },\n \"70b0e923-9d02-4260-ae68-94de1dcc9978\": {\n  \"hash\": \"985cfa3a9187b00e\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"2025 waste management: 98% of non-hazardous and 70% of hazardous waste valorised organisation-wide.\"\n },\n \"70f7ce26-2d61-4e53-981c-e512dd4f7cbd\": {\n  \"hash\": \"030f474ce9ed67bf\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Funded R&D participation in aviation electric-propulsion decarbonisation project.\"\n },\n \"7113df89-e1de-5b29-b4e7-7ee6f04183ac\": {\n  \"hash\": \"06b743fe6c18a7d3\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Kuna Lab methodology structuring social-innovation projects for Bizkaia-relevant challenges.\"\n },\n \"7166dc57-606e-4be6-bdfc-6cffca374060\": {\n  \"hash\": \"44d2fec45b7c79c2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Funds R&D of inclusive exercise tools since 2005; no reach figures.\"\n },\n \"719ed0e4-8cd3-5355-8453-3679494547f1\": {\n  \"hash\": \"e2268bff67f7573d\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Marrus\",\n  \"reason\": \"Public organic urban gardens at Marrus since 2014, expanded 2025; demand exceeds available plots.\"\n },\n \"71a24de4-0c21-5a72-be23-ec168ced6741\": {\n  \"hash\": \"e5f0777a4908e464\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Strategic alliance signed for future sustainable-food and biotech collaboration; agreement stage only.\"\n },\n \"736cbce7-97d4-5bba-8f47-ded232f47252\": {\n  \"hash\": \"4ec010c70bf63504\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Preparing to host a European regional network meeting; concrete organising work for one event.\"\n },\n \"7376ad7b-c941-55f5-9ba8-2a8f385c56ec\": {\n  \"hash\": \"0b2621d8923733a8\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Audited occupational health and safety management system within the organisation.\"\n },\n \"73856b1a-9bb9-5325-9108-596c8da67b7c\": {\n  \"hash\": \"672f5bc237f6f5c1\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Targeted youth employment programme for a specific vulnerable age group.\"\n },\n \"73b962d6-cedd-505e-bfb7-d966a1ba7bc7\": {\n  \"hash\": \"ee564555999fb018\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"climate_energy_plan\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Adopted climate/energy plan (2022); counts as plan in its own right per rule.\"\n },\n \"743aa041-a7ad-4dd3-aea4-b6931ab70083\": {\n  \"hash\": \"66cadb17e865cb86\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Pioneering soil-monitoring network anticipating EU Soil Law; real but scale/coverage not quantified.\"\n },\n \"7441d1f9-8455-4814-9132-a74c91f6dbee\": {\n  \"hash\": \"294a49e8eef30693\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Avenida de Navarra\",\n  \"reason\": \"One-week temporary bike-lane pilot to test feasibility, not a lasting change.\"\n },\n \"74dcb765-113e-5128-b1ed-afbb2fbb364e\": {\n  \"hash\": \"2cbd9f0fb40e4de7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"participation\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Local Environmental Forum enables citizen input on climate-adaptation and 2030 agenda planning; ongoing mechanism.\"\n },\n \"751385b1-46f2-493f-8c01-a8d2a0aba895\": {\n  \"hash\": \"6388c0242f934d9b\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"85.10 kWp self-consumption PV at the school; sizable capacity, no consumption-share data.\"\n },\n \"7585e81f-f84d-47e1-af8a-cc1a49895fc8\": {\n  \"hash\": \"1fc1e01f44d22817\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Playground renovation tendered (€268k) for one school; works status not confirmed.\"\n },\n \"7585edbd-5781-5e4d-8506-4998c5ac0205\": {\n  \"hash\": \"66bee5c2c1cf0c80\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Los Herrán\",\n  \"reason\": \"Completed green boulevard replacing one traffic axis with SUDS and pedestrian space.\"\n },\n \"75a39bc9-e04f-54fe-88ed-abeb140aff7b\": {\n  \"hash\": \"f976c91eefe964d0\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"biodiversidad\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Larrañazubi\",\n  \"reason\": \"First butterfly-pollinator habitat created at Larrañazubi's Aula Ambiental; explicitly smaller scale than other actions.\"\n },\n \"75cb996c-66ab-53a6-b74b-dab8767398ea\": {\n  \"hash\": \"06fb7948788c9b6e\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"adaptation_plan\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Adopted 2022 climate-adaptation plan diagnosing risks and setting measures; per rules, plan itself is L1.\"\n },\n \"7638e940-b448-4fee-aea2-48eb5704eb60\": {\n  \"hash\": \"f3b7869554f87fed\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"First use of biodegradable paint and fully recyclable fibreboard in exhibition design, 2024.\"\n },\n \"7690b5a2-931f-5d89-bc9e-f8dcf8a149f8\": {\n  \"hash\": \"ef8f951e93f3bb1e\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"biodiversity\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing biodiversity measures across parks; scale not quantified.\"\n },\n \"76a5d8c7-d5d6-55ee-bc8b-1fc3994f3d37\": {\n  \"hash\": \"457f929f9edaa976\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"New awareness trail and art installation tied to a festival; symbolic.\"\n },\n \"76dfab61-3a5d-415b-81a2-316bcb705ed9\": {\n  \"hash\": \"728217de85e51901\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Avenida de Navarra y calle Mandoegi\",\n  \"reason\": \"Planned bike-lane segment and design for another; not yet built.\"\n },\n \"77498a55-8c41-5bb3-b50c-6cefc26e951e\": {\n  \"hash\": \"1e2c6f77edcb344b\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Helped 80 companies across 9 sectors complete diagnostics and roadmaps.\"\n },\n \"77a09d42-aea7-49b9-a800-692e1aa6367b\": {\n  \"hash\": \"b19dcccd0d44ab05\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"At least 20% recycled content in construction materials, a bounded one-off measure.\"\n },\n \"7887d623-55b3-5e7b-b653-64d614519777\": {\n  \"hash\": \"59fd95d66013d592\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Mendebaldea / Jundiz\",\n  \"reason\": \"Multi-phase park and ecological corridor near one industrial estate, sixth phase awarded.\"\n },\n \"78dc440c-52fd-573b-a97e-f512e86c9e8b\": {\n  \"hash\": \"c37b324017efac3a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"CARE programme combining SME improvement with sustainability content; no scale given.\"\n },\n \"79587b2a-269e-56f3-b734-fdc146ff185b\": {\n  \"hash\": \"5ca24908d7c02f61\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in a solar cooperative network; scale not quantified.\"\n },\n \"79c2f96c-1ab1-56bd-87e4-cd367c40c953\": {\n  \"hash\": \"454551e9208cee99\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Two training sessions delivered on Agenda 2030; concrete but small reach for the organisation.\"\n },\n \"79ed6733-0d1f-55ab-8cb4-ae736416e128\": {\n  \"hash\": \"dd3bf1e7eb6c3859\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in research facilitating transitions to sustainable, healthy diets; discrete scientific role.\"\n },\n \"7a25f33a-6154-43b6-b258-64a27340ca10\": {\n  \"hash\": \"477544896f3a50d6\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Adopted climate strategy; counts as L1 regardless of its ambition.\"\n },\n \"7c789083-a8c5-5811-a714-3a1ddb4ae717\": {\n  \"hash\": \"c92510ce58931342\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Measures surplus and channels food to prevent waste with pioneer shops, not the full network.\"\n },\n \"7c95a262-c02c-553b-a4fb-a262f50d8987\": {\n  \"hash\": \"1db747029e965428\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"biodiversidad\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Bolue\",\n  \"reason\": \"Ongoing conservation of ~12ha Bolue wetland, a key periurban biodiversity site; habitat protection and monitoring.\"\n },\n \"7d432589-7942-592e-abb3-fae6ad9523e9\": {\n  \"hash\": \"a9b4dbcadaa2a3e5\",\n  \"level\": \"L0\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Txomin Enea\",\n  \"reason\": \"Txomin Enea neighbourhood regeneration still at planning stage; sustainability features incorporated in design, not built.\"\n },\n \"7df29919-93ac-54ca-b085-dbf8ea9905a0\": {\n  \"hash\": \"9e8f18a86b8ea884\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"monitoring_data\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Public environmental information system providing mapping, inventories and monitoring data access.\"\n },\n \"7e4bbccc-677d-4754-be7f-fefe376c92a3\": {\n  \"hash\": \"a1733102ff520795\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"entorno del río Castaños / BEC\",\n  \"reason\": \"Localized river litter cleanups and tree plantings near the BEC; scale unquantified.\"\n },\n \"7e87be33-98a5-5cb4-a035-1cd009bdbc3c\": {\n  \"hash\": \"6728573a664fd80d\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Collaboration in a single one-off gathering; document published by the city, not UN Etxea.\"\n },\n \"7ea96882-e080-5e80-b663-caaaf8070bd2\": {\n  \"hash\": \"fb8513a6447e4145\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Specific training on gender rights and sustainability strengthening social organisations; reach not quantified.\"\n },\n \"7ebbedf2-a5ee-5196-91b5-b52f1382b206\": {\n  \"hash\": \"44b617042aacdb69\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Developed process improvements implemented in one company's tuna-canning operations; single-firm reach.\"\n },\n \"7ec515dc-52db-49c8-9de2-dfa572ca51ed\": {\n  \"hash\": \"65f3bf0f94249920\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Multi-level (local/national/EU) mobilization against offshore drilling; no achieved policy outcome yet.\"\n },\n \"7f45a263-0b08-56b2-99ca-4bfd34eba28a\": {\n  \"hash\": \"df21ce460e24498f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"monitoring_data\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Structured multi-year citizen-science biodiversity monitoring network with public participation.\"\n },\n \"7f5ee143-d290-4d3c-9009-60bcf691f9b1\": {\n  \"hash\": \"a1fa166d98f86881\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Water supply and sanitation project for one Peruvian rural community; approved 2024.\"\n },\n \"7f9aa26a-e83e-4b55-a015-735aca1539c9\": {\n  \"hash\": \"cc805860a9c2ff0f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Bulk-food shop with 100% compostable packaging; single small store, no volume data.\"\n },\n \"7fb26717-28d3-5124-b8b4-aef4c6ed9e80\": {\n  \"hash\": \"4c7630bf2c514ea2\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"water_management\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"2026 contracted works renewing municipal water/sewage networks; ~20-week duration, location detail limited.\"\n },\n \"7fbb0372-4c4f-55a5-9914-a9cc075df53f\": {\n  \"hash\": \"9adcaae86d1ad4ca\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Co-developed hiriZAIN, a tool integrating climate and health risk into urban planning decisions.\"\n },\n \"7fc6ea12-7c32-527e-a605-04bac5350052\": {\n  \"hash\": \"89c1005032f00618\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Validated NIR-based species-identification technology for marlin processing; deployment scale not stated.\"\n },\n \"7fc91e86-468d-5e63-b991-d7a8202b227d\": {\n  \"hash\": \"aa94a957f7b4ed3b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Stable line integrating culture into sustainable development via research, participation and cooperation.\"\n },\n \"80fc659f-d246-5ad1-baad-00dcd8d9b753\": {\n  \"hash\": \"e15563d0ad464168\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"municipal_renewables\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"53 municipal PV installations (2.8MW, Feb 2025) supplying 93 buildings via shared self-consumption; moderate scale.\"\n },\n \"82b875ec-3984-55ed-a84d-4ed4eea58d43\": {\n  \"hash\": \"3f9518d7d382d45c\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"low_emission_zone\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"4b84ae05-7bd5-57ec-a2fc-efee7370160c\"\n  ],\n  \"zone\": \"calle Mayor\",\n  \"reason\": \"LEZ rollout (access-control systems) plus green-boulevard transformation of calle Mayor; contracted installation underway.\"\n },\n \"82f2144b-8fa9-506a-8d7f-ba195cb8e48d\": {\n  \"hash\": \"9bd8766683953d93\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"water_management\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"PluSSS nature-based interventions across several sites (Miramón, Ibaeta, Morlans, Aiete); moderate multi-site reach.\"\n },\n \"82f2cd1e-c02f-460a-b408-96f3739d4a7b\": {\n  \"hash\": \"12d7478aeed968cb\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Faucet aerators reducing water flow; minor concrete fixture.\"\n },\n \"835ec0de-e9f6-57e5-94fd-ddd58ff89b64\": {\n  \"hash\": \"cabae56a9c8c1812\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"monitoring_data\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Municipal sustainability-indicator system tracking energy, mobility, waste and procurement over time; governance tool.\"\n },\n \"83c07b24-a882-441d-8165-65bbb15c987a\": {\n  \"hash\": \"6a9e40a80700df95\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Certified IGP beef feeds EROSKI's local range; contributed to 17.4% sales growth.\"\n },\n \"83fadd40-87fe-5433-90aa-b7fd9f709e41\": {\n  \"hash\": \"06087102ec131009\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Applied research line studying ecosystem recovery dynamics under climate pressures; discrete research.\"\n },\n \"842b018b-659b-5c36-9e1f-9fe738451782\": {\n  \"hash\": \"2b7168949fc4acb7\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Partner in a hydrogen ecosystem; own contribution scale unstated.\"\n },\n \"84407a13-69d1-5381-acf2-d3446a596066\": {\n  \"hash\": \"efd9b1fbdcff09b8\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Voucher scheme for reused clothing via Koopera outlets; concrete, modest scale.\"\n },\n \"845d93fa-c42e-580e-9093-58e88719ec42\": {\n  \"hash\": \"60bb003144d85fe9\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Publicly funded Lanbide-integrated orientation service; concrete, reach unquantified.\"\n },\n \"8460b549-4a47-5b92-b400-a68469a5fbe1\": {\n  \"hash\": \"26c8ad44c580eb12\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participated in an EU project recovering fish-industry nutrients into biofertilizers; concrete circular result.\"\n },\n \"846299aa-3c8a-4b7b-9896-ac841c900fac\": {\n  \"hash\": \"af15958aacf26319\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"SBTi-approved emissions targets (2024) are an adopted target, not yet a measure.\"\n },\n \"84c0efde-1f98-5a3c-9e2f-07ea4670ab70\": {\n  \"hash\": \"0ae2a3ab8ac59f44\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operates a training and knowledge hub; reach not quantified.\"\n },\n \"851d3364-e3d9-5527-aaac-852aee9e52c1\": {\n  \"hash\": \"3c1f98b2b8c96a53\",\n  \"level\": \"L0\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Manifesto signing only; no derived actions or results reported.\"\n },\n \"85ad64d8-c03c-407d-abb1-676a50947189\": {\n  \"hash\": \"4e8671a2ea18f9b3\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Offers mud-based/organic hair colouring as a service option.\"\n },\n \"85b60cfd-3975-4f83-9fcd-210cbe834f85\": {\n  \"hash\": \"5dc46255cdeb4196\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Committed 2025 budget line for mobility and low-emission-zone actions.\"\n },\n \"85fb18fd-1d41-4fb7-a25e-c71437ca5d2f\": {\n  \"hash\": \"4a29c176da87f2e5\",\n  \"level\": \"L0\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Envisions letting members invest in renewable projects; not yet a concrete scheme.\"\n },\n \"86379c81-6090-4285-bfb1-5e3b3dc6e7c4\": {\n  \"hash\": \"c1c2ed3c1356f5c0\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Forest management modified in 4 of 8 monitored watersheds to protect drinking-water supply, with daily sampling.\"\n },\n \"864c9480-4e5a-5abf-80d4-b87d4a0bf5b3\": {\n  \"hash\": \"0c79adee12f9cbd1\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One digital-twin tool for a single solar park; small reach for a company.\"\n },\n \"86fbb8b0-b26f-56ff-ad27-144b70daa05a\": {\n  \"hash\": \"acd6d1b00848c925\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Tourism carbon-footprint calculator with optional Soraluze reforestation offset; voluntary tool for visitors.\"\n },\n \"86ff87fe-cf56-5ae8-9539-7f3ae3c954b2\": {\n  \"hash\": \"8523bc4a7faeffe6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring PV engineering service line, explicitly not attributable per plant.\"\n },\n \"872887cc-49e6-4ca4-83a9-f66f426add0c\": {\n  \"hash\": \"51999c5a5d9335de\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"75 EV charging points installed; no share of fleet or users served stated.\"\n },\n \"8730ddc5-58ee-550b-ae43-8e6f8d6c557f\": {\n  \"hash\": \"221920fb5d61daa0\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Citywide secure bike-parking network in operation, growing use, complements street racks.\"\n },\n \"87973db5-6547-4abe-8491-a19f7c571419\": {\n  \"hash\": \"4b9cfa046cbd9302\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Continuous region-wide vector-surveillance programme; 2023 findings covered 95% of municipalities studied.\"\n },\n \"879e21f8-940b-498b-8816-c71763accd17\": {\n  \"hash\": \"fe8855292baa5eb6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"6d3ec849-0192-532c-a8fd-abd4266c4979\"\n  ],\n  \"reason\": \"Same Vitoria-Gasteiz biodiversity strategy as drafted/approved elsewhere; adopted plan.\"\n },\n \"87bcf4e9-d0f8-4d7e-b713-a6adcc6551c3\": {\n  \"hash\": \"790f63f2f09ebc5b\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Building-wide LED lighting, new BMS, efficient boilers and EV chargers installed; impact unquantified.\"\n },\n \"88249196-ff0d-5426-8168-f5e31cc57c96\": {\n  \"hash\": \"9375a47561a6bc9a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single R&D charging lab in Bilbao for testing equipment; concrete but small-scale.\"\n },\n \"88377bbd-3164-4405-9251-62a91449bc41\": {\n  \"hash\": \"a518d5e9fc731ba2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One CNG refuelling station for waste-transport trucks operating since 2022; modest reach.\"\n },\n \"88e81ba6-bab1-4aeb-8e73-ef9e573a14fd\": {\n  \"hash\": \"78f3a2210fb8c364\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"08100e54-10d1-4953-8e87-7891b6bef4ae\"\n  ],\n  \"reason\": \"Public transport is main mode, mainly for commuting; same underlying habit as no-car choice.\"\n },\n \"890af57f-f195-585d-a172-6eaa757157d9\": {\n  \"hash\": \"b5ed600e833b4eba\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"building_renovation_support\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Coronación\",\n  \"reason\": \"Completed district-scale deep envelope retrofit demonstrator in one neighbourhood, lasting benefits.\"\n },\n \"8926f461-324c-45e7-aa81-946aedb8a9cc\": {\n  \"hash\": \"14c1f92b17c304df\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Small turbine at a pumping station produced 1.73 GWh in 2022.\"\n },\n \"8951e833-4c86-5ded-82d0-fddf993843dc\": {\n  \"hash\": \"ec93f33782c96e01\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"flood_defence_river\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Punta Zorroza / Parque Txinbo\",\n  \"reason\": \"Flood-absorbing park under development; scale not quantified.\"\n },\n \"896cab15-64dd-51db-8701-7ba3542107ba\": {\n  \"hash\": \"4453802d7c0a20f7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Permanent workshops and awareness sessions on risk prevention for member companies.\"\n },\n \"899b7824-0c31-5d1d-9e67-fdab13fba928\": {\n  \"hash\": \"a39f21c97e807c17\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Casco Medieval\",\n  \"reason\": \"Greening interventions advancing from temporary to permanent in one historic district.\"\n },\n \"8a0f2ae9-c57e-5a52-a504-27a5609a4c46\": {\n  \"hash\": \"3391f4704410de8f\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Reuse preparation is the principal material destination for textile managed by Koopera; a core process.\"\n },\n \"8a4f4a5d-1769-51f9-84cb-6e2180182369\": {\n  \"hash\": \"0f94623e18341c24\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Emerging tool under development; no operational results reported yet.\"\n },\n \"8b5d0679-af38-4995-8802-033cb90721d9\": {\n  \"hash\": \"1e08bf94eab638dd\",\n  \"level\": \"L5\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"First net-zero-CO2 store; 65% energy cut, BREEAM/ISO 50001, exceeding the 40% L5 threshold.\"\n },\n \"8b8e46a8-0b8d-5ed8-8e6a-badf242331a3\": {\n  \"hash\": \"e5bea5eb7bfb13fc\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Individualised regularisation support programme; concrete but reach unquantified.\"\n },\n \"8c75f484-f90a-54b0-b20e-fd17a95e3745\": {\n  \"hash\": \"f32330a9b92d13bc\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operates a territorial network of waste transfer plants reducing collection-vehicle routes and costs.\"\n },\n \"8c7c2f13-2539-5f49-a5e8-6e7aecab4683\": {\n  \"hash\": \"27e1153d8acb44c2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Technical guidance tool for municipal waste-fee design.\"\n },\n \"8c8dfae4-c8fe-4e5b-aee3-a99cf74d4458\": {\n  \"hash\": \"f44ee12ea723b8d7\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"EMAS registration with an AENOR-validated annual Environmental Declaration.\"\n },\n \"8ccb0ae6-174e-5a88-973f-34bebb698181\": {\n  \"hash\": \"3a3b930eccc53a51\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Collaborative research project on Basque ecosystem services; outcomes and reach not yet described.\"\n },\n \"8d3d8e62-5ca8-5685-8445-1270dcd68975\": {\n  \"hash\": \"5bead6134f4c684c\",\n  \"level\": \"L0\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Announced promotional activity for local wool products; execution not confirmed.\"\n },\n \"8dfdf432-c192-5933-adcf-c3b084262282\": {\n  \"hash\": \"c18ee90996a7c936\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Promotes local commerce circuits; explicitly no measured environmental savings claimed.\"\n },\n \"8eaa4ae0-9096-4891-a275-a3f8cda35d29\": {\n  \"hash\": \"c7e6efe535c8a36a\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Restaurant certification programme for sustainable ocean-friendly practices; scale not stated.\"\n },\n \"8f71bf98-f360-5a2f-9c02-653f68b3fe44\": {\n  \"hash\": \"c888e2fa3a837863\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing Fair Trade promotion campaign (coffee/cacao) with local businesses; continued into 2026.\"\n },\n \"903c8388-67d3-4e37-81d5-b08118f6adb0\": {\n  \"hash\": \"8b468975fa65cc2a\",\n  \"level\": \"L0\",\n  \"status\": \"unclear\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Vague description of recycling/upcycling projects; no specifics on scope or status.\"\n },\n \"90e0a13b-64f0-4a00-97d0-b7d51c116471\": {\n  \"hash\": \"3c3c137bb9d3aa54\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"2023 biogenic-coal supply deal decarbonises only part of furnace coal use.\"\n },\n \"90ef93b3-649a-53ee-8047-05303efb0af5\": {\n  \"hash\": \"4b92f5cb8279bb04\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"circular_economy\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Adopted circular-economy strategy with active 2024-2027 action plan and follow-up.\"\n },\n \"91c27168-bc83-53eb-82bc-5faface550f5\": {\n  \"hash\": \"6ac5ee9cbe5a9ff9\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Adopted integrated urban roadmap coordinating other actions; treated as plan, L1.\"\n },\n \"91ed7bbd-2ee5-5e26-8482-cb5c865e7d77\": {\n  \"hash\": \"a7f591a3a86ac14b\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Siting model and framework adopted; specific projects not yet realized.\"\n },\n \"923e9f39-8c3c-4184-ac0c-9a5d052d8ced\": {\n  \"hash\": \"98d08f8708e85d73\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"At least 20% regionally sourced materials, a bounded one-off construction measure.\"\n },\n \"92632d89-c333-5319-9a6c-eaa075acec53\": {\n  \"hash\": \"f1c93ffb1d8c1f77\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Adopted sustainability strategy aligned with the SDGs, integrated into corporate planning.\"\n },\n \"9269ad24-586b-59bc-85ee-be26d73b6cd7\": {\n  \"hash\": \"9b2fafb42c6ac1b1\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"municipal_renewables\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"ETAP Araka\",\n  \"reason\": \"Single facility solar-plus-storage installation at one water treatment plant.\"\n },\n \"9333d03c-d378-5c68-bd6d-b95fa9ff1299\": {\n  \"hash\": \"05fc87e9e078f667\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Joint reforestation campaign of a former landfill site; scale not quantified.\"\n },\n \"93360883-17a3-432f-b161-0749758cbaa0\": {\n  \"hash\": \"333f793f683baac1\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Erasmus+ project (2024-2027) building sustainable-habits teaching resources for hospitality students.\"\n },\n \"9341ec4a-0695-5520-b5d5-b8927ed75f13\": {\n  \"hash\": \"d828869a0ab5bdd1\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Curated list of seven environmental podcasts recommended by MATER; a recommendation only.\"\n },\n \"936460fd-2bab-51cc-9d18-45f026517c4c\": {\n  \"hash\": \"3cb5f17daa2261a2\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"climate_shelters\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"94 climate shelters citywide (July 2026 update) for heat protection, plus green routes and public communication.\"\n },\n \"940cdfea-0e3b-5560-a8fa-1dfa18c466a7\": {\n  \"hash\": \"b4683eb562f2da0c\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participation in international network producing educational materials and seminars; reach unstated.\"\n },\n \"9415451f-50d4-5a9e-9ca7-06c65141f2fc\": {\n  \"hash\": \"d029dce51d85347c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Intxaurrondo, Loiola, Martutene\",\n  \"reason\": \"Scheduled bulky-waste pickup; Emaús-partnered home collection in Intxaurrondo, Loiola and Martutene.\"\n },\n \"94180208-97ff-595d-a229-ca81f72e057a\": {\n  \"hash\": \"b83fd9fcac361fd6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Monitored food-production learning experience for schools; number of participating schools not quantified.\"\n },\n \"9423c60a-a642-5269-b53f-a3f5009972b3\": {\n  \"hash\": \"cbcf7db07d76988e\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"City-wide selective collection network for main household fractions (organic, packaging, glass, etc.); daily service.\"\n },\n \"9479cff8-b87c-5f6f-90bc-f4f98815c222\": {\n  \"hash\": \"eddae47f7419b316\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"low_emission_zone\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Approved low-emission-zone ordinance; phased implementation not yet started.\"\n },\n \"9481ba51-6746-5f18-af46-2e56e57575d7\": {\n  \"hash\": \"45cbfa98c1f66a80\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"street_redesign\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"2023-2027 program pedestrianizing several central points (Plaza Gipuzkoa, Garibai-Misericordia, Kursaal bridge).\"\n },\n \"94eeaadc-3247-5fca-ac63-ea08e3684184\": {\n  \"hash\": \"7703b9e03078c872\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Work-integration contracts embedded across all operations, linking circular growth to social-inclusion capacity.\"\n },\n \"950ceca2-7260-489b-bd36-a5b756346d05\": {\n  \"hash\": \"540fcf313478df79\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One steel supplier >80% recycled content; not the whole product's material mix.\"\n },\n \"954064b2-48a2-4f6e-97c4-22e03c0ac5fa\": {\n  \"hash\": \"9b178aa495971ea8\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Household composting of non-animal waste in a garden composter.\"\n },\n \"95426d07-95ec-4304-9a36-48fb0b0de63f\": {\n  \"hash\": \"d33eaef6fc8a1113\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Circularity criteria embedded in a 358-dwelling energy retrofit; substantial neighbourhood scope, still executing.\"\n },\n \"9693272e-26bd-5a86-8f8c-f836e5733e22\": {\n  \"hash\": \"239440e333c1e308\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Authorised placement agency; concrete service, reach not quantified.\"\n },\n \"97182fc3-e5a1-4ea9-971f-3658dd8653c2\": {\n  \"hash\": \"d25fc2d24fc490fb\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"66 additional people in supported-employment programme in 2024; incremental figure.\"\n },\n \"97198dfd-ce4f-474e-b935-65b41b8a110b\": {\n  \"hash\": \"f135e3127145859f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"22 PV installations across campuses; no consumption-share figures given.\"\n },\n \"97405786-6dec-54d4-ab54-4d7ef70f6e72\": {\n  \"hash\": \"6071b15cc9606f72\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing research validating sustainable synthetic fuels for maritime transport; one discrete project.\"\n },\n \"9755a3dd-1361-513c-a2c5-1acac76acab8\": {\n  \"hash\": \"22b5e229f738db98\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Comprehensive multi-fraction waste collection network in daily use city-wide.\"\n },\n \"976d28a0-cfb5-4ed4-930d-7abf83c1b8a6\": {\n  \"hash\": \"5be25d8f65b60479\",\n  \"level\": \"L0\",\n  \"status\": \"unclear\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Permit request under public consultation only; not approved, works not confirmed.\"\n },\n \"97fd9238-4880-41a6-a521-8ba3c3dbad6b\": {\n  \"hash\": \"59b853d21dd750b6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Digital-fabrication support for inclusive sports equipment; reach not quantified.\"\n },\n \"98532f5f-f99a-4c43-b8c0-7776156567be\": {\n  \"hash\": \"806533afad149ad2\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Bird-ringing scientific station at Bolue wetland since 2009, monitoring migratory birds.\"\n },\n \"989acba0-588d-5082-aa0c-137703047c78\": {\n  \"hash\": \"05fedca0b028ff65\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Vehicle's entire propulsion is battery-electric with zero tailpipe emissions; lifecycle caveat noted.\"\n },\n \"98f6bbe5-1fc3-505c-a11a-c800ed50a782\": {\n  \"hash\": \"406a3a17402bb6ca\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Studies coffee-grounds valorisation pathways within a circular chain; still at research stage.\"\n },\n \"994c4c73-10bb-58d6-9614-548455420a18\": {\n  \"hash\": \"3eb78d726349579a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring technical support service to municipalities; reach not quantified.\"\n },\n \"99966a6e-2350-57ca-97d8-2d1d90bd202d\": {\n  \"hash\": \"bea342e7af0a8885\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"International battery-storage portfolio for grid flexibility; scale unclear, lower-confidence source.\"\n },\n \"99a364a4-9de8-5b49-84c6-d01d0573e306\": {\n  \"hash\": \"da0859f141a33c23\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"monitoring_data\",\n  \"partOf\": \"7c95a262-c02c-553b-a4fb-a262f50d8987\",\n  \"sameWorksAs\": [],\n  \"zone\": \"Bolue\",\n  \"reason\": \"Bird-ringing monitoring station at Bolue (2007-2021: 180 days, 3,000+ birds, 51 species); long-term ecological data.\"\n },\n \"9a26b890-cf3e-57f9-84e6-a02de7c6a3ed\": {\n  \"hash\": \"0781254076403f7d\",\n  \"level\": \"L0\",\n  \"status\": \"unclear\",\n  \"category\": \"public_transport\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Preliminary public consultation to shape a future collective-transport service; no service exists yet.\"\n },\n \"9aebb0aa-6dd7-5fdd-a6fc-b5e40a6d673a\": {\n  \"hash\": \"7331f50648249492\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Citywide financial incentive rewarding household organic-waste separation or home/community composting participation.\"\n },\n \"9b2fc3ce-3166-4924-8911-4a33e202115b\": {\n  \"hash\": \"ffdde4a5851967a8\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Oil waste recycling; concrete small-scale operational practice.\"\n },\n \"9b87e56a-b490-464c-824b-5c6776ce8230\": {\n  \"hash\": \"f44ee741b08e9365\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Composting of all non-animal organic waste; concrete small-scale practice.\"\n },\n \"9bc5845a-3b5c-5ed9-bd63-29538f3f8efb\": {\n  \"hash\": \"7c39e4d4bf8daf10\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participated in developing European fisheries-management tools; AZTI's specific contribution/reach unclear.\"\n },\n \"9bef7bee-88c2-4236-8caa-dc2bce3d32fa\": {\n  \"hash\": \"8ade5f8773f63814\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Demonstration e-fuel plant (2,000 t/yr) under construction; small relative to refinery scale.\"\n },\n \"9c0a00a6-570e-535f-bde7-c2ce694a9648\": {\n  \"hash\": \"977c56438974ad59\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in recovering polyester fibres from textile waste for new resin manufacturing; discrete R&D.\"\n },\n \"9c3f3242-a1b6-4164-985d-76148f7a22a4\": {\n  \"hash\": \"d9c85cca46328dcb\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Sustainability criteria applied to exhibition openings and the annual dinner since 2023.\"\n },\n \"9c4b7820-f132-401d-a65a-a4e904703925\": {\n  \"hash\": \"bedcf83463bffd00\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring climate observatory reports; monitoring and dissemination, not direct action.\"\n },\n \"9ca3f6bb-8671-5158-a7db-337ef679e9af\": {\n  \"hash\": \"b9abf1ee85c82e17\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed digitalisation of Spain's whole distribution grid via smart meters and automation.\"\n },\n \"9cccf671-ebe5-50f5-b1eb-ae950e2461cf\": {\n  \"hash\": \"de3e49848cba71db\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"flood_defence_river\",\n  \"partOf\": \"dddfb70b-37bf-59e9-8074-0d217dc42154\",\n  \"sameWorksAs\": [],\n  \"zone\": \"Fadura\",\n  \"reason\": \"URA-led Fadura intervention created controlled flood-storage areas and replaced bridges on the Gobela.\"\n },\n \"9cf73f36-8216-5c69-92d5-8c5e4bcaa6f5\": {\n  \"hash\": \"9d9cdd0826711bfb\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Long-running citywide school sustainability education programme reaching students across centres.\"\n },\n \"9daa9de4-ce22-5e14-aa35-c03fc2f2c5ad\": {\n  \"hash\": \"33a213e2e783fd04\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Applies integrated climate modelling to support Basque Government policy decisions; concrete but discrete.\"\n },\n \"9e1a1770-1267-4bc7-aa24-273f5e1f6fc6\": {\n  \"hash\": \"b7a88bdd5eeb69f9\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"EMAS-certified environmental management system with annual Declarations published (2023, 2024).\"\n },\n \"9ee05fd1-1c81-44dc-94bf-787d15c3d045\": {\n  \"hash\": \"1f31d3acb6512133\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Personal vehicle switched to electric, recharged mostly with own solar power.\"\n },\n \"9ef50799-63af-58b4-b451-9b53d911eb7a\": {\n  \"hash\": \"e031884047e66db8\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operational programme changing recurring public procurement procedures Basque-wide.\"\n },\n \"9efdb133-78c5-49de-b4d7-fd17ec810f05\": {\n  \"hash\": \"ee37c688ab953247\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Industrial blade-sharpening service enabling reuse; complements manufacturing, no volume data.\"\n },\n \"9f05d6ff-a819-47b6-a8ba-0f6f3208c333\": {\n  \"hash\": \"c6c4b75538eb1fde\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed multi-line citizen-activation programme in the old town with broad stakeholder participation.\"\n },\n \"9f0a04da-781d-5bff-b35b-d4e1a3a9a556\": {\n  \"hash\": \"810640ed7f8c3c06\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Grant programme for local-administration energy audits; reach not quantified.\"\n },\n \"9f223226-77b3-5e32-b7b5-b4e77a4bd9f4\": {\n  \"hash\": \"cc1b1cb9a286c3a4\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"188 MW wind and solar complex under development, 59 million euro investment.\"\n },\n \"a09aaa97-2045-586f-91ad-6ee585dea592\": {\n  \"hash\": \"1763c96bb7c50d76\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Internal mobility and road-safety procedure; concrete but limited to internal risk management.\"\n },\n \"a1197954-dcc6-4256-8716-0fe290eea4f1\": {\n  \"hash\": \"ad2dd233ab1c2d07\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Free open repository of technical aid designs; usage not quantified.\"\n },\n \"a18b1879-5962-442a-b9b9-9aa760bcf5a3\": {\n  \"hash\": \"d7830d17408b7013\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Publishes full annual GHG inventory since 2019; transparency practice, not itself a reduction.\"\n },\n \"a1f2f0cb-d8f5-5b43-9771-e564e5f4f958\": {\n  \"hash\": \"0c4a56a5746c9f7d\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operational fiscal-incentive instrument; uptake not quantified.\"\n },\n \"a20fd8a9-8b2e-558e-9a8f-0298bd8b3aed\": {\n  \"hash\": \"ad437cb8f6800c37\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Overarching employment and solidarity-economy structure spanning the organisation's inclusion work.\"\n },\n \"a212776e-9848-55f1-bc84-1367c670d0ed\": {\n  \"hash\": \"a525117001fd2745\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": \"b9c4c700-0444-5f08-943d-88804b635ecb\",\n  \"sameWorksAs\": [],\n  \"reason\": \"Pruning and plant waste used as feedstock within the composting plant's process.\"\n },\n \"a29304bf-9309-58af-9edf-b93998149b46\": {\n  \"hash\": \"889621ad49645977\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"category\": \"circular_economy\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Jundiz\",\n  \"reason\": \"Central citywide waste-treatment plant recovering materials and cutting emissions from residual waste.\"\n },\n \"a2cd6524-7a3f-466e-a270-ae4a354c5b0e\": {\n  \"hash\": \"e0770aac5d14c228\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"School joined a district-wide cap/battery/pen collection; permanent bins now installed on site.\"\n },\n \"a318ea74-42dc-448f-b948-8e0577d79471\": {\n  \"hash\": \"82e47b34b3702c70\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring surplus-potato donations to a food bank since 2015; quantified benefit to 1,200+ families in 2023.\"\n },\n \"a34bd546-9471-4f0b-8436-6cf3403e7181\": {\n  \"hash\": \"c0b7dd0bcefb2489\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One-day experiment on water pollution for one grade, tied to a themed day.\"\n },\n \"a394aca1-3cf6-46a3-969c-230b57069b4e\": {\n  \"hash\": \"43590c8fc1e1e34d\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One-time ecological-footprint calculation as a baseline study for a later initiative.\"\n },\n \"a3c7d9c9-c7fa-511f-9c25-a5ad91922432\": {\n  \"hash\": \"dfbd151bdf85946f\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"organic waste fraction\",\n  \"reason\": \"Opt-in organic waste system; not confirmed as covering every household.\"\n },\n \"a4f41561-124b-59ef-aa97-688d30a2fa3d\": {\n  \"hash\": \"8d1e957bede62cda\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"climate_energy_plan\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Adopted 2018 climate framework; per rules an adopted plan itself counts as L1 regardless of ambition.\"\n },\n \"a565a9f5-b97c-54e9-ba8f-e8d3b4168507\": {\n  \"hash\": \"bd7164ecb8c8cc5b\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Zorrotzaurre\",\n  \"reason\": \"Substantial resilient urban regeneration, confined to one district.\"\n },\n \"a5b3a021-c495-4451-9701-7f870d2ea25d\": {\n  \"hash\": \"67642ea0f5d415fc\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Salon uses vegetable-based ingredients; single small business, no reach data.\"\n },\n \"a5c540fd-1361-5e9d-a696-c7bd0416976c\": {\n  \"hash\": \"a9b5c9a4dee4a995\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Multi-year tree-planting and restoration programme with accumulated but unquantified reach.\"\n },\n \"a6064ed1-959f-55d9-b454-588681659ae3\": {\n  \"hash\": \"4da6e6a1953ec6ef\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Basaldea\",\n  \"reason\": \"Operating agroecological business incubator providing land/infrastructure to new local ventures.\"\n },\n \"a628b0b8-aa5e-4f7a-8734-5ab001a48273\": {\n  \"hash\": \"62226f32f9f85cdd\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"1,252 lighting points converted to telemanaged LED, cutting related energy/emissions by 54.5%.\"\n },\n \"a67c2d0a-5970-447b-a74a-a280503658ed\": {\n  \"hash\": \"1946766a5d58d260\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"711 solar panels cover up to 10% of the venue's annual energy consumption.\"\n },\n \"a6b840fd-0df5-412a-87bb-6f76d5f04d65\": {\n  \"hash\": \"a4db3ce3b8251bc9\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"New MSW mineralisation plant entered operation in 2026; throughput not quantified.\"\n },\n \"a6e562d4-d262-58db-996b-7bfda6944e9f\": {\n  \"hash\": \"06d509d247088598\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Adopted air-quality action plan currently in force during 2026; counts as plan.\"\n },\n \"a8152bb7-237c-4d6a-9a12-4a8a7539acda\": {\n  \"hash\": \"3081885204c344aa\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Offers vegetable/mud-based hair colouring as a service option.\"\n },\n \"a893f382-79c5-5e30-9c0b-85a3ea97f3cd\": {\n  \"hash\": \"1ac4df505a7b3d64\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Municipal selective waste collection with separate fractions and special-item service underlying other waste programs.\"\n },\n \"a910e345-c8ea-56be-84b7-55f07412b83d\": {\n  \"hash\": \"f926d034ef98aa3c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring sustainability training content within a dual-education programme.\"\n },\n \"a931c1ef-8756-4c7d-8c69-b09b9d4c4868\": {\n  \"hash\": \"e599ccbf5ba57ca8\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Rooftop solar plant covers 21% of one treatment plant's electricity; largest in Bizkaia at completion.\"\n },\n \"a9536337-973b-407e-95f5-c46b328d0c05\": {\n  \"hash\": \"bcc21660683bc3ac\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Partner in a funded plastics-recycling R&D consortium; targeted results not yet verified.\"\n },\n \"a9911d16-aa5d-48e3-b816-803f1d2c150b\": {\n  \"hash\": \"c1b8dcad8cb2324c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Demonstration silvopastoral areas established at two pilot sites in 2023 to test climate resilience.\"\n },\n \"aa1d51e6-5a05-514d-a309-963c17171ccb\": {\n  \"hash\": \"4db1369d534e7300\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Training programme placed about 30 young professionals for a year.\"\n },\n \"aa26f311-e495-5a10-a2b4-568ce4117599\": {\n  \"hash\": \"196d2df58e2a0396\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Noise planning, strategic map and June 2026 awareness campaign; policy-level, no major physical works described.\"\n },\n \"aa521328-0994-5902-91bf-6228e54f0697\": {\n  \"hash\": \"aa27312c102dcc3c\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Active EU-funded food-system project to 2028; concrete but outcomes not yet quantified.\"\n },\n \"aa709eff-186a-5ddb-b21e-f22d17cd961c\": {\n  \"hash\": \"c3e71ca99ddd07a0\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Specific partnership project promoting outdoor engagement; concrete, modest reach.\"\n },\n \"ab7a9da8-5893-5e11-8754-2ab15537126b\": {\n  \"hash\": \"975197f9660fe028\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Longstanding waste-education programme at Campus Artigas; concrete but reach not quantified.\"\n },\n \"ab81c69f-8fb7-4441-9c00-feccb2d57951\": {\n  \"hash\": \"4f750e203fe0f2de\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"School project addressing the energy topic in school and valley; depth not detailed.\"\n },\n \"aba106b2-8395-4267-8b97-f87d633a0474\": {\n  \"hash\": \"7df94ec70bc15e07\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Dual-flush toilets installed on premises; minor concrete fixture.\"\n },\n \"abde1102-31b4-579a-b386-d7d960919255\": {\n  \"hash\": \"9492d01c17d6c484\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Citizen-science network for biodiversity monitoring, coordinated citywide with the municipal biodiversity unit.\"\n },\n \"ad043d1a-843e-4372-9c00-38f3d52d588e\": {\n  \"hash\": \"4fed87ba0e3eeea0\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Double-skin facade with heat recovery is a core whole-building passive design strategy.\"\n },\n \"ad751e0c-1e22-5941-b48d-01097acd19bd\": {\n  \"hash\": \"6e8570c539ed4933\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Youth professional-experience programme; reach not quantified.\"\n },\n \"ada274c0-2d41-54df-81d1-d4e16c2111c3\": {\n  \"hash\": \"052bdbc11c13841a\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Co-developed route-optimisation system aiming to cut tuna-fleet fuel and emissions; adoption unstated.\"\n },\n \"add6cd1d-6f84-4ba1-a9ca-cd363c82a766\": {\n  \"hash\": \"763066f0ff55c682\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Regenerative-grazing model deployed across multiple Basque farms with training; reach unquantified.\"\n },\n \"ae57b091-543a-53d3-98f9-7985386d0317\": {\n  \"hash\": \"121e70a8c72d8422\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"EU-funded carpooling pilot launched in 2023; concrete but limited to pilot scale.\"\n },\n \"ae74a07b-308c-5076-8ed1-fd95477b7f4a\": {\n  \"hash\": \"5d4ce36142290711\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participation in a funded EU project; Ihobe's own reach unstated.\"\n },\n \"ae78215c-1484-5b74-9cba-23f7163be4c9\": {\n  \"hash\": \"85be4bfc525ba705\",\n  \"level\": \"L0\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Announced festival transport optimisation; execution not confirmed by the source.\"\n },\n \"aea63f1f-a614-4036-be03-f54da4fe7022\": {\n  \"hash\": \"79cc3f7f2661364c\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"2024 call: 58 companies funded, €17.6M subsidy/€85.6M induced investment across sectors.\"\n },\n \"af33f230-18d9-5c82-8abc-9629b97b760f\": {\n  \"hash\": \"37773c677f5175a3\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Own low-impact HQ design; single office, modest share of global operations.\"\n },\n \"aff8b097-7f47-4f5d-8967-a27ec30a8bc1\": {\n  \"hash\": \"128c29bf313b924a\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Secure bike-parking network across 17 Bizkaia municipalities, 500-bike capacity.\"\n },\n \"b0243ac5-bdff-4f0f-8094-250da41922e5\": {\n  \"hash\": \"7a3f088db73245bb\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in EU project testing circular/recycled materials for e-motors.\"\n },\n \"b0382ed0-37b3-56bc-a6b7-e341a5489201\": {\n  \"hash\": \"f6c441137636501c\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Large-scale programme modernising industrial equipment and processes region-wide.\"\n },\n \"b0670ce1-f200-56e7-9f44-79b6410ca800\": {\n  \"hash\": \"10863f639e154294\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Guided tours promoting marine culture and environmental awareness, offered several days weekly.\"\n },\n \"b0915799-f914-44b9-a2a3-43fb7b9600af\": {\n  \"hash\": \"124161b508d6fdd6\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Presented a project enabling up to 30% recycled asphalt; not yet executed.\"\n },\n \"b1a1c169-60d3-4683-b8cb-44cb4ec7dcdf\": {\n  \"hash\": \"d327853da11ef9a2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Discounts near-expiry food as one food-waste prevention measure.\"\n },\n \"b1d4c7d0-02a9-4840-8908-cd6ad4b49e55\": {\n  \"hash\": \"80c8f25e4ded16cb\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Promotes its own insertion companies across sectors; no reach figures given.\"\n },\n \"b1e5db82-77a2-4cb1-aff8-a6d6108f2a67\": {\n  \"hash\": \"5f787c31d1b5b555\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Multi-organisation manifesto with policy demands for water quality; a declaration, no concrete action.\"\n },\n \"b231e468-1dbb-5e69-a5c5-e87c78c16eec\": {\n  \"hash\": \"fea20fea7343f007\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Substantive role in an R&D pre-commercial procurement programme.\"\n },\n \"b3d07b13-9903-5a02-97f6-46c1321f2a8a\": {\n  \"hash\": \"f03c3f3eea7889ba\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Delivered one commercial tyre-recycling plant; single project for IDOM.\"\n },\n \"b416299a-c400-47d7-9c20-70d085fa6c5b\": {\n  \"hash\": \"90397a7fd33f85ae\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Long-running environmental education programme reaching schoolchildren across Bizkaia.\"\n },\n \"b45fb764-c595-4886-b45c-2c4d1bfab615\": {\n  \"hash\": \"a0363c6f93a32081\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"17kWp solar with 15kWh storage covering a large share of household's own electricity use.\"\n },\n \"b497965e-89df-5431-acb6-3439e151f4cc\": {\n  \"hash\": \"7d176d660a028e79\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Flagship long-term research initiative coordinating many actors across the region.\"\n },\n \"b5499cc7-65ed-4c96-acc4-db23ef1c26d3\": {\n  \"hash\": \"a82d468979944d2a\",\n  \"level\": \"L3\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Citywide cycle-network completion project underway, gradually executed, reaching the whole municipality.\"\n },\n \"b55abdad-7696-5e7a-8cc7-44daa376e1e3\": {\n  \"hash\": \"f1fa9f1f16c7990b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"building_renovation_support\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing municipal subsidies for private residential energy-efficiency upgrades; no uptake figures given.\"\n },\n \"b579abc8-8734-4c04-9a30-e5de53c25f15\": {\n  \"hash\": \"41782d588e77b0bd\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Facilitates client hair donations to a solidarity hair-donation charity.\"\n },\n \"b5b56315-3327-535e-930d-0706ac3656a8\": {\n  \"hash\": \"fe942d4af74940bc\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": \"29db1eaa-746d-55b8-893b-bcd351fd3c92\",\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed joint analysis with Ihobe of European Green Deal impacts.\"\n },\n \"b6007271-086b-5f26-8823-6f663393fc5e\": {\n  \"hash\": \"82b2cf07c5a9bd47\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"City-wide vertical-mobility infrastructure (lifts, ramps, escalators) easing daily pedestrian trips on steep terrain.\"\n },\n \"b624ae15-c783-5c57-a4b7-90c430980ece\": {\n  \"hash\": \"3819d460521a17d4\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Carpooling platform for industrial estates with tens of thousands of accumulated trips.\"\n },\n \"b628549a-f1ec-405d-9440-c4bfd1a82780\": {\n  \"hash\": \"3c52505d5df00405\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ecodesign applied to one product line (2023); scope limited to that range.\"\n },\n \"b695ff44-3418-594a-9e88-8b3e624945a8\": {\n  \"hash\": \"a5650b009ab39c06\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"municipal_buildings_efficiency\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Efficiency measures in municipal buildings; scale relative to city small.\"\n },\n \"b7c407c2-8caa-5827-a7b6-39256fbc000a\": {\n  \"hash\": \"f198d0a78921bdc1\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"building_renovation_support\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Plan-included renovation and efficiency measures; execution not confirmed.\"\n },\n \"b7ee4d7a-a64f-5089-b833-6554210ba787\": {\n  \"hash\": \"253c3adb86e03b7a\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Renewed research collaboration on fiscal instruments; results not stated.\"\n },\n \"b83824d8-839a-5c74-bf03-44395feca561\": {\n  \"hash\": \"fa8007e0d67bbb05\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"flood_defence_river\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Martutene\",\n  \"reason\": \"Decade-long river works reduced historic flood risk in Martutene neighbourhood; precursor to further upstream works.\"\n },\n \"b8575106-4315-40d4-ab5d-7224e3525794\": {\n  \"hash\": \"4c786542566c0d5e\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Long-running (20+ yr) volunteer waste-sorting collaboration at a festival; modest own effort.\"\n },\n \"b8832385-5612-5757-93cc-6091f087b4f0\": {\n  \"hash\": \"e815c488c1fc0e21\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing specialised training offering; reach not quantified.\"\n },\n \"b9076235-27ee-4c5a-8c4b-622faf56e970\": {\n  \"hash\": \"193ad07fc58cba8c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Green-roofed underground parking; single modest heat-island mitigation feature.\"\n },\n \"b9640f20-f6a0-5a7b-83aa-52b64a734353\": {\n  \"hash\": \"369d708e1d9547c7\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Supported 104 local fairs across 74 municipalities in 2024.\"\n },\n \"b9c4c700-0444-5f08-943d-88804b635ecb\": {\n  \"hash\": \"54605467b632eef2\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operates Biscay's provincial composting plant processing biowaste and plant material; stable core infrastructure.\"\n },\n \"b9c5f2d9-e478-4679-b7e4-d86e41ee1318\": {\n  \"hash\": \"d1e23c62cb11f280\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Parque Barandiarán y Parque de Arramendi\",\n  \"reason\": \"New/improved green spaces at two named parks; works underway, localized scope.\"\n },\n \"b9dec58e-4522-514f-9c41-b36cd0ac983f\": {\n  \"hash\": \"cb79757dddec1c1c\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Only announces and streams another organiser's event; no concrete action by UN Etxea itself.\"\n },\n \"b9e80f32-3a43-5f9a-b695-fc34f955e46c\": {\n  \"hash\": \"894cc391ce685598\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Transversal innovation support for social enterprises; reach not quantified.\"\n },\n \"ba9566db-73cd-56c2-8e3e-af63b9e631b9\": {\n  \"hash\": \"cf0f377a0e963d67\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Finances integral energy audits for SMEs; reach not quantified.\"\n },\n \"bb266071-3991-4f23-b6c0-1abd6f52f3c1\": {\n  \"hash\": \"dd447c69de9c863e\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"EMAS-registered environmental management system since 2014 with annual public Environmental Declarations.\"\n },\n \"bb4018e7-21e5-5d88-9c36-d380b810b89c\": {\n  \"hash\": \"4466b1b110f14328\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Works with worn garments via creative upcycling activities with Caritas, extending the reuse hierarchy.\"\n },\n \"bc68243e-97d1-4eb9-b001-05685767af2a\": {\n  \"hash\": \"aa9121b73c196303\",\n  \"level\": \"L5\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Whole-store overhaul: 100% renewable energy, 50% energy cut, LEED Gold, >80% waste valorized.\"\n },\n \"bcdfc278-027b-4e27-9005-e07cf80d44bd\": {\n  \"hash\": \"f7256aef92de18d2\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"All luminaires converted to LED (2015-2021): -25% electricity, 335 t CO2/yr avoided.\"\n },\n \"bd16c47c-da31-53ef-8e8a-c495c1984b51\": {\n  \"hash\": \"58b39cf4d19cecb3\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"flood_defence_river\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Okendotegi-Errekabeltza\",\n  \"reason\": \"New ~300m flood-protection project formalized July 2026, designed for a 100-year event; execution 2026-2027.\"\n },\n \"bd4810d7-51c2-513a-b124-6660343f8d29\": {\n  \"hash\": \"9d16dd7d3738349c\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"flood_defence_river\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"corredor del Zadorra\",\n  \"reason\": \"Multi-phase river flood-defence programme; early phases and a retention basin completed.\"\n },\n \"bd6949eb-b283-47a7-b77f-a317fcc1cda3\": {\n  \"hash\": \"c0adcd18ff1093f5\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"citizen_energy\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Radio de 2 km en torno a Portalea, Virgen de Arrate y Amaña\",\n  \"reason\": \"Shared community solar on three roofs benefits about 246 households, roughly 2% of municipality.\"\n },\n \"be31408c-eb28-5729-a6b9-3fab6c13a0ec\": {\n  \"hash\": \"ed42eb159b6dc26f\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": \"4f0ce198-dd18-5b92-b55b-b1090efbc583\",\n  \"sameWorksAs\": [],\n  \"reason\": \"Single hybrid pumped-storage plant commissioned; concrete but modest scale for global operations.\"\n },\n \"be5e9f90-a7b1-52ca-bb29-0d3de1705e78\": {\n  \"hash\": \"a95e7b841691c369\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Strategic alliance combining AI and biotech with AZTI's sustainable-food expertise; partnership formalised.\"\n },\n \"be61227c-5155-4a08-b2eb-71a5cb6d4090\": {\n  \"hash\": \"e03ef44e2cc2bc29\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Describes staff's extensive public-transport use; no organisational programme stated.\"\n },\n \"bf7794b1-20e6-5c54-b832-bcff476f771d\": {\n  \"hash\": \"de59f39d499d3be6\",\n  \"level\": \"L0\",\n  \"status\": \"unclear\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"General, unverified description of BBK Kuna's multisectoral social-innovation model; no concrete results.\"\n },\n \"bfc3362f-c8bf-5d09-be8c-0881dfc9c4cb\": {\n  \"hash\": \"e61b7b307594d91f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring onshore-wind engineering service line; no quantified company-wide share.\"\n },\n \"c026b24c-0497-4e52-952f-7d90e8546a32\": {\n  \"hash\": \"54a7012d3f3174df\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"First edition of social-inclusion water-sports and environmental-education programme in Donostia.\"\n },\n \"c046c1d7-95dc-5f68-9af7-f61756f6ddbd\": {\n  \"hash\": \"7b1828a2937e76c0\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"circular_economy\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Registers households/communities doing home or community composting, diverting organic waste; no scale figures given.\"\n },\n \"c080686c-be2b-4f23-9d7b-e5ae7cfa0f65\": {\n  \"hash\": \"5f51a57aaf24f069\",\n  \"level\": \"L3\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"0ca728c2-aaa7-5018-ab2d-56c980b10bed\"\n  ],\n  \"reason\": \"Agenda 21 Escolar dynamisation programme reaches over 200 schools; same broader programme.\"\n },\n \"c0847bdb-9067-5046-ac3c-05bc0030a0b7\": {\n  \"hash\": \"81630757e7c1d3eb\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"municipal_fleet\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"New street-cleaning service uses a high share of electric machinery citywide, operating daily.\"\n },\n \"c0cac10e-da61-506b-9752-79d9f46b78be\": {\n  \"hash\": \"9593c232729f9620\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing materials testing for hydrogen-blended gas networks; one discrete technical contribution.\"\n },\n \"c0da8b30-8cac-43cc-bc9e-6f0d5decc9cf\": {\n  \"hash\": \"706a02f08b0c93d0\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Regenerative braking recovers up to 8% of train energy, avoiding about 2,200 t CO2/yr.\"\n },\n \"c1844a4c-2638-5165-9454-faef6a559b84\": {\n  \"hash\": \"9ab5a78bcb8efca5\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operates a collaborative innovation space on waste and circular economy for multiple stakeholder groups.\"\n },\n \"c2091285-18f4-4fce-bad5-59543e23ccf5\": {\n  \"hash\": \"a1cf578fb2fc71eb\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Long-term seed-network project recovering traditional agricultural varieties; concrete but narrow scope.\"\n },\n \"c2639cde-0593-4a39-8652-a239ba42c11b\": {\n  \"hash\": \"a6d2448033e84837\",\n  \"level\": \"L5\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"100% of Sidenor's steel is made via scrap-fed EAF, ~900,000 t/yr recycled; its core production model.\"\n },\n \"c27782ec-d6a9-4f3e-b2f2-41caea6fa85a\": {\n  \"hash\": \"ec9617f9e8f41b5e\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"municipal_renewables\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Teatro Coliseo\",\n  \"reason\": \"120 panels fully power one municipal theatre; single-building scope.\"\n },\n \"c2c1d5fd-073e-5e7c-9162-b33c9c8bd63d\": {\n  \"hash\": \"53b2180d0760afe1\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One-day citizen awareness event on the SDGs in Bilbao.\"\n },\n \"c31bf4b9-46b8-5698-9cc4-139dae4ed93c\": {\n  \"hash\": \"13fe60059fceb6a2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"biodiversidad\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing invasive-plant control campaigns (esp. cortaderia) near Bolue, Larrañazubi and other sensitive sites.\"\n },\n \"c343a599-b30d-589d-9dab-60d9242cd8f9\": {\n  \"hash\": \"0e76982c8a321f60\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed project demonstrating conversion of incineration slag into construction raw materials.\"\n },\n \"c35a5a01-216e-4d16-bc60-199b73570a2e\": {\n  \"hash\": \"1d5440521f8b1a03\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Proprietary tool measuring social/environmental impact of financed businesses since 2025.\"\n },\n \"c3607de8-a20c-5959-8fa2-0c7ffee83550\": {\n  \"hash\": \"0109d479e7939450\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Feasibility and engineering for one industrial-park microgrid project.\"\n },\n \"c363bccc-ea9b-57ee-864f-a95f45595634\": {\n  \"hash\": \"d027e4b143f60a4e\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Leads a 23-organisation European project; substantial coordination role.\"\n },\n \"c397ffbe-a85e-5667-975d-9c74056d6bf7\": {\n  \"hash\": \"ac688c67fb6fdf3d\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Bizkaibizi bike-share network expanding in Getxo; new 2026 stations bring the local total to 20.\"\n },\n \"c3c63cf7-f216-4dc0-9525-a08d397ae4a0\": {\n  \"hash\": \"b23f2815f03c25e0\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Two postal/maintenance vehicles replaced with EVs plus two chargers; very small scale.\"\n },\n \"c3e6b758-9ca6-582a-8df8-cab61c673b2d\": {\n  \"hash\": \"5fd0f2d6acbefaa6\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Large-scale operating global renewable portfolio, core generation line, but no share figure given.\"\n },\n \"c411b84d-5937-57ed-8cc4-b01ef7610228\": {\n  \"hash\": \"f818bcc2291df3da\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Symbolic charter signature in 2000; no concrete measure described.\"\n },\n \"c487a578-73a4-4128-bdcb-dbde8f457dca\": {\n  \"hash\": \"45e4859d06e747cc\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Volunteer cleanups collected about 125 kg of litter across several natural sites in 2025.\"\n },\n \"c4b7925f-4c5b-4601-bf26-321f3348f8fc\": {\n  \"hash\": \"d8f0738f7355ac57\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Solar at one market covers 42% of its own consumption, single site.\"\n },\n \"c5083d82-b46b-4e29-88ae-f71334088aa5\": {\n  \"hash\": \"06a4108f3605edd0\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"2025 waste: 2,912 t collected, 33.5% recycled, with detailed organic and material valorisation.\"\n },\n \"c715afed-5bed-4e09-9e43-933d5feba9a5\": {\n  \"hash\": \"1e7186566c3a2a89\",\n  \"level\": \"L0\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Annual awareness campaign visibilising cigarette-butt impact; no concrete action described.\"\n },\n \"c71d84ed-a0f4-4591-8d2e-eede7212d4a7\": {\n  \"hash\": \"0fd4b0f32b630eb9\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed citywide urban tree-canopy mapping; concrete decision-support tool covering the whole city.\"\n },\n \"c7cd974a-865e-45ac-a2c6-e88cc3ee61df\": {\n  \"hash\": \"0b17f0911aec67bd\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Forest-certified wood used only in specific handle families, not company-wide.\"\n },\n \"c87ca3ea-d673-44cb-9353-860cf28c6342\": {\n  \"hash\": \"ab8d5bec06f3ef6e\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Bike-lane network expanded to 22.5 km and bike-share to 28 stations, plus more e-bikes.\"\n },\n \"c8bc368e-51df-519a-8d37-996c020c690a\": {\n  \"hash\": \"ce8543f83eb2c7a9\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"sustainable_food\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Operating network of municipal and associative urban community gardens across the city.\"\n },\n \"c8c9148a-4d52-4861-a027-4359b9595f9b\": {\n  \"hash\": \"3a191bffc1ee1c13\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"AENOR-verified annual GHG inventory (ISO 14064) covering the whole organisation since 2012.\"\n },\n \"c99383b4-fc49-503b-bbfd-91b3e60192f1\": {\n  \"hash\": \"a745a42b4a418e65\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"municipal_renewables\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Expanding photovoltaics on municipal buildings; capacity not quantified.\"\n },\n \"c9b053ec-0bec-4931-b826-7307e0f382b5\": {\n  \"hash\": \"cf877ca0f362f623\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Co-creates and openly shares 3D-printed assistive products; reach not quantified.\"\n },\n \"c9cd77b9-98c7-4699-9dc9-c9f9d3f63e17\": {\n  \"hash\": \"aabc28f7f5ee19a4\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"First phase, 4 of 12 planned projects (€4.6M), partly finished; small share of modernisation.\"\n },\n \"ca264a82-e582-54b8-9d5d-6c118af8f383\": {\n  \"hash\": \"b747784b5ee79d44\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Channels part of recovered textile to social distribution for people and families in need.\"\n },\n \"ca541ea7-8a04-51c1-bf38-e167e5f17211\": {\n  \"hash\": \"c81dfa6bf44e6e3d\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing business assistance service; scale not quantified in text.\"\n },\n \"ca954108-5a37-53dc-86db-a37031b6d48a\": {\n  \"hash\": \"2dff9e0cdd308001\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Stable network of second-hand stores reintroducing recovered garments into consumption; scale unspecified.\"\n },\n \"caa2b86c-a81b-44ec-a33e-50dd490db2e3\": {\n  \"hash\": \"9d6f489f51d08f35\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"100% of concrete removed during construction was recycled, a complete one-off diversion.\"\n },\n \"cb1f6765-9ddd-4ea5-8b4e-f7da977070a2\": {\n  \"hash\": \"eead003dd501e061\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Adopted 2024-2028 efficiency programme with budget for 10 projects; counts as a plan.\"\n },\n \"cb39b9ba-f9ae-46fa-9acd-f8511ae32d62\": {\n  \"hash\": \"0d89f3a1a5d95d75\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Small catering cooperative; works with 30+ local producers, no reach data.\"\n },\n \"ccc04a86-d4a9-5e58-87f0-def62b4b5499\": {\n  \"hash\": \"157b77bef049cdc9\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"EnergyLOOP awarded blade-recycling from one major repowering; concrete but a single defined volume.\"\n },\n \"cce3d716-a4c1-4ce3-a6cb-bd46724881a4\": {\n  \"hash\": \"4797ae2ecfff6b07\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"International FACT! programme improving sustainable food habits; reach not quantified.\"\n },\n \"cd0dfe56-14e2-52a1-aaef-05b15e04c94f\": {\n  \"hash\": \"6e748514d9c27a30\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring equity-stake mechanism mobilising renewables, grid and storage projects.\"\n },\n \"cd4016ea-4277-51ec-bc8f-cdad63171148\": {\n  \"hash\": \"e8a6e194fa87c67a\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One-day internal workshop reflecting on SDGs; awareness only, no concrete change.\"\n },\n \"cda69f9d-6b7f-431c-9b9f-2fb45ae352f8\": {\n  \"hash\": \"13d0f72008e1c614\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One-off free photo exhibition on urban wildlife; awareness event.\"\n },\n \"ce16ff39-2170-5d01-9ae7-1c862fddfc90\": {\n  \"hash\": \"54a7365c99291f27\",\n  \"level\": \"L0\",\n  \"status\": \"in_progress\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"PGOU urban-plan revision is incorporating environmental/climate criteria; revision still in process, not yet approved.\"\n },\n \"ce6d4c88-ff32-5e2b-9e51-e3db6f4ef3a6\": {\n  \"hash\": \"5c57087e53516b98\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Adopted mobility plan (2010-11); concrete strategy, no execution evidence given.\"\n },\n \"cec3baba-b601-47f2-8698-b66c46d2c206\": {\n  \"hash\": \"168299909293e8f4\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Food-donation pilot in Bizkaia, explicitly still at pilot scale.\"\n },\n \"cf4de57d-adc0-4eb7-8db5-6c0ccd601631\": {\n  \"hash\": \"06796d999d649f6a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Lights only where presence is needed; concrete behavioural energy-saving habit.\"\n },\n \"d03b4537-f0e7-4ce4-bdb3-ebfb0de05dbe\": {\n  \"hash\": \"8c8a4db4b3b50089\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Purchased electricity is fully renewable, but covers only ~0.2% of refrigerant-dominated emissions.\"\n },\n \"d0d831ff-c4ea-432c-8a4a-986af80ca4e2\": {\n  \"hash\": \"acbf87b7ddafcb7a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Replaced one van and one forklift with electric versions in 2025; tiny footprint effect.\"\n },\n \"d0dc6230-65d3-5fdf-94c9-ec31f5756614\": {\n  \"hash\": \"7ce36a4bbca01f80\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Managed regional EV subsidy programme; reach not quantified in text.\"\n },\n \"d0f8679c-1512-5bf3-9c28-bc1bdc41cbb8\": {\n  \"hash\": \"5f5752a77a665e6f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Separate budget block for renewable thermal electrification; scale unstated.\"\n },\n \"d0fe6ac4-a13e-5c4b-8dfe-10484a0bfccb\": {\n  \"hash\": \"dfe06fb8c1061630\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": \"e47f88c6-5a6f-5cc6-b2b9-966fdb700062\",\n  \"sameWorksAs\": [\n   \"27f74c4f-e488-5cd1-be81-69998e2c5cf3\"\n  ],\n  \"reason\": \"Reuse circuits within part of the Garbigune network recover usable items before treating them as waste.\"\n },\n \"d237982e-3c45-4387-b94d-62208ad70b1d\": {\n  \"hash\": \"a67bd9fcd9c213b4\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Agreement to raise awareness for wheelchair tennis; sensitisation-focused per its own description.\"\n },\n \"d25212f7-090a-5197-b05a-6fd8ed624195\": {\n  \"hash\": \"b8c549e4a4cb5348\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"flood_defence_river\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Zorrotzaurre\",\n  \"reason\": \"Built flood-water storage infrastructure; protection extent not quantified.\"\n },\n \"d31c613a-0b4d-5c17-a8d4-61b514e87fc2\": {\n  \"hash\": \"404672dc6cba1d36\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"citizen_energy\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing property-tax incentive for solar self-consumption; uptake not quantified.\"\n },\n \"d398eda9-411d-49d1-a431-0d92b65f8867\": {\n  \"hash\": \"93318cff49eaea4d\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Sustainable-event certification applied to several major fairs since 2022, not all events.\"\n },\n \"d39cfac0-adce-5e1f-8260-66c474eb0721\": {\n  \"hash\": \"f6e03b62fcae6cb0\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Leads deep building-renovation technology development within a European project; one discrete R&D line.\"\n },\n \"d47818f3-cb78-4959-b27f-ef42a21a9bae\": {\n  \"hash\": \"be0a240bd1d3ce6d\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"3bd3ac87-aad1-4fc3-95cf-d83d3bfa0161\"\n  ],\n  \"reason\": \"Surplus solar exported to grid; same installation as the solar-consumption record.\"\n },\n \"d4f5f8f5-2cd6-5d08-a40b-9b070e298c3d\": {\n  \"hash\": \"3f6e70badd168699\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"public_transport\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ten electric buses so far, one line electrified; small share of ordinary fleet.\"\n },\n \"d5c1c626-b2ce-50e5-9d9d-4c01532e68df\": {\n  \"hash\": \"73d224d765033ba1\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Fellowship for one young person per round in international biosphere science; small direct reach.\"\n },\n \"d5fd5122-c7a1-5ee7-8909-3148fb8054ae\": {\n  \"hash\": \"18f8c54ef43fd321\",\n  \"level\": \"L0\",\n  \"status\": \"unclear\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Vague awareness actions on climate change; no concrete measures, scope or date given.\"\n },\n \"d62c7a2c-4fe5-595c-8981-1c7e41914500\": {\n  \"hash\": \"438ccd93344c096d\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": \"29db1eaa-746d-55b8-893b-bcd351fd3c92\",\n  \"sameWorksAs\": [],\n  \"reason\": \"Specific green-budgets research project within the Ihobe alliance.\"\n },\n \"d765b7a3-ecff-454f-8954-bd8858058f6f\": {\n  \"hash\": \"031f60d7e153974a\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Electronic fittings cut whole-building potable water demand by 40%.\"\n },\n \"d78ca069-3819-5290-8593-0297c5ac0e0b\": {\n  \"hash\": \"3450a08dcee63982\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Winter 2025-26 campaign planted 861 new trees and worked on 4,400+ existing ones citywide; recurring program.\"\n },\n \"d79c38b7-1e7c-4080-96e5-05a6f585edca\": {\n  \"hash\": \"12ecae84ab047a82\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Core microcredit service for underserved microenterprises; no volume figures given.\"\n },\n \"d866f28d-da2c-4dd5-9930-43af0157f229\": {\n  \"hash\": \"c4a4d5ee56b0e24a\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Canal de Dunboa\",\n  \"reason\": \"Committed budget for canal renaturalisation; works not confirmed executed.\"\n },\n \"d8f9e065-9be5-4b00-8a1b-e8c9a17ee640\": {\n  \"hash\": \"7514593bc939d435\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Designed a food-waste certification system for agri-food and hospitality businesses.\"\n },\n \"d92eed73-7e82-5bc5-9711-259915642cb8\": {\n  \"hash\": \"5ecc8b43616eb645\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Structural free psychological service for people lacking private access.\"\n },\n \"d94e9a48-fcf6-4e4a-ac46-6d9183830428\": {\n  \"hash\": \"c2ca697283f5cfdb\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"First out-of-season bathing-water quality study in Barcelona; single-city monitoring project.\"\n },\n \"d96f0d17-feb1-5be3-9d7b-18926a902edd\": {\n  \"hash\": \"c904268a3e266b62\",\n  \"level\": \"L0\",\n  \"status\": \"committed\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Collaborative blue-economy/surf-tourism space; only a first sectoral roundtable held in 2025, still programmatic.\"\n },\n \"da77e8d5-18b1-5be1-9740-75de25360115\": {\n  \"hash\": \"52f2e63ade3356e0\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in a multi-partner post-fire reforestation project; role unquantified.\"\n },\n \"daa90995-a85a-5f5d-a367-9cf7b5eb1adb\": {\n  \"hash\": \"a579d2c324232fe5\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Waste-collection vehicle electrification underway; final 2026 unit count not yet confirmed by documentation.\"\n },\n \"dab21a94-1f66-4f94-b90f-de3593299d0b\": {\n  \"hash\": \"d031501a23e9de95\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing reforestation-site stewardship with 60+ volunteers: nest boxes, monitoring, pollinator habitat.\"\n },\n \"db271006-6b15-5d2b-ae8a-0bd5c5ed8ebb\": {\n  \"hash\": \"03ea413aff870fb8\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Single assembly attendance by one representative; no ongoing programme described.\"\n },\n \"db3e203f-3a14-52db-a34f-7cb564d40c0b\": {\n  \"hash\": \"d00a3a5c303c3825\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Internal analysis and staff training on Agenda 2030; no external concrete measure.\"\n },\n \"db42da97-b0a0-4b66-ac28-3a75ef5214e5\": {\n  \"hash\": \"5fd4e978ce324e96\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Partner in a funded biochar R&D consortium; expected results not yet verified.\"\n },\n \"dbf9d937-dcad-404b-aeed-1834a404f4e5\": {\n  \"hash\": \"dcee4eebd1c264dc\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed energy retrofit (boiler, heat pump, insulation) across the school's own building.\"\n },\n \"dc41e178-6172-59fb-82df-5d0a100c6504\": {\n  \"hash\": \"9bc43822e9430646\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Stable SME circularity framework; reach not quantified.\"\n },\n \"dc45332d-698b-4af2-8684-a37114d507b9\": {\n  \"hash\": \"f2679b4d4d0fda81\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Cooperative, assembly-based governance with a declared equality commitment.\"\n },\n \"dc4f6ab1-73fd-4f4e-981b-824f3c20cf7d\": {\n  \"hash\": \"059b1455186fc0b0\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One flagship market fully retrofitted: water cooling, solar and battery, 30% self-consumption.\"\n },\n \"dc573cf9-634a-4457-8d47-360c94ef63de\": {\n  \"hash\": \"24e2fc909b26bcbc\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Reported 27% carbon-footprint cut since 2023 via renewables and efficiency actions.\"\n },\n \"dcb8ec26-9911-4da1-8497-a8236a596d92\": {\n  \"hash\": \"16e545fe3c7c63aa\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"EMAS-validated ~32% energy and 23% GHG cut at one of Egibide's five campuses.\"\n },\n \"dcbc58a2-c2ba-51d4-8fae-4712df5b1196\": {\n  \"hash\": \"7ac737acfe3c46b1\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing multi-city research on participatory urban climate adaptation; one discrete applied project.\"\n },\n \"dcf709f9-76c8-568a-9789-eb6e73c89a47\": {\n  \"hash\": \"b40c9e6fdb88222e\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"municipal_renewables\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Created renewable-energy entity BilboEner; capacity not quantified.\"\n },\n \"dd350a4b-4111-5289-9606-9d05d8c4e710\": {\n  \"hash\": \"7ac483be44f21ffd\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": \"09bdc163-d69f-54a1-8cfd-58a0da888892\",\n  \"sameWorksAs\": [],\n  \"reason\": \"Applied accelerator component selecting and coaching circular startups from Spain and Portugal.\"\n },\n \"ddb047a7-c425-4375-84d1-54826a5b8e59\": {\n  \"hash\": \"4429ef6329c1ccfa\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Incorporates SDGs into management system covering stakeholders, environment, people, results.\"\n },\n \"ddde2777-5dc1-5108-814f-9cf8207ac35a\": {\n  \"hash\": \"8dc423b43d6495c7\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"low_emission_zone\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Abando\",\n  \"reason\": \"Operating low-emission zone restricting polluting vehicles in one district.\"\n },\n \"dddfb70b-37bf-59e9-8074-0d217dc42154\": {\n  \"hash\": \"6962fe9e38d885a8\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"category\": \"flood_defence_river\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"río Gobela\",\n  \"reason\": \"Comprehensive ~4.3km Gobela river hydraulic and ecological restoration reducing flood risk; major river-corridor scale.\"\n },\n \"ddf1485d-5c57-5913-99fd-53086f89465f\": {\n  \"hash\": \"78e8337d446d9e93\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Permanent, continuously renewed public environmental education programme at major facilities.\"\n },\n \"de56174b-b106-59ed-8054-d3ce05823cf9\": {\n  \"hash\": \"4e743987691d12e1\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Stable ongoing programme reaching public and private agents; broad but unquantified scope.\"\n },\n \"de6f747a-58cb-5bba-8e3a-4e5bb1a3437c\": {\n  \"hash\": \"61a66011c444aa74\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One-day dissemination event on forest-pathogen research results; a gesture/event, no new concrete action.\"\n },\n \"dee25261-3b55-4095-8d94-88ffe6d6fc60\": {\n  \"hash\": \"d6b7787407f72de7\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Hosted the fourth transnational CLYMACT event in Bizkaia on waste and climate mitigation.\"\n },\n \"df345945-2f8e-52be-b32f-67940068159f\": {\n  \"hash\": \"8a17d13cc42f048b\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in a cross-border project applying the Pyrenean climate strategy; one discrete project.\"\n },\n \"df4da6fd-a3dc-4c0f-bd3d-7dc254eaa7da\": {\n  \"hash\": \"3347eb1f3e36cc5e\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Donated marine-litter collection gear to three maritime rescue bases; modest material contribution.\"\n },\n \"dfe27df5-cec5-5466-8179-c2a931f5a9a3\": {\n  \"hash\": \"d6b6e61f5e5f8078\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Insertion company creating real jobs; stable economic infrastructure, not just training.\"\n },\n \"e007f302-89ce-5c09-bec6-7faee2743511\": {\n  \"hash\": \"ddd0ed6f5af04d70\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"monitoring_data\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Periodic municipal sustainability/climate report (2025 edition, June 2026); concrete accountability tool, minor reach.\"\n },\n \"e0928cbd-c7e8-476f-8930-efffc2824e1c\": {\n  \"hash\": \"afaf7697006cdd2a\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"All garments made exclusively from 100% recycled materials; whole product line converted.\"\n },\n \"e0a57b32-1786-50ab-a2c5-619f4cbae027\": {\n  \"hash\": \"f3e9be22c7094cfe\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operating circular-economy chain for fishing-gear recovery, connecting ports, crews and industry.\"\n },\n \"e0a66db1-2647-4c60-a0ac-f6031f8a0204\": {\n  \"hash\": \"b630a32a17d02da4\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Greywater/rainwater reuse system serves toilets across most of the building's floors.\"\n },\n \"e0b3b290-0b80-54f4-985c-e04a5eceb936\": {\n  \"hash\": \"5c679427248ba82a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed study quantifying the food system's territorial footprint; informational, no action yet.\"\n },\n \"e0eb9839-4a8e-55af-801d-c9036a72c588\": {\n  \"hash\": \"8075e58f8d1ea4e1\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Manufacturer spec only; no installation data; explicitly no additional Impact beyond the inverter.\"\n },\n \"e0f0c543-5a4a-4bb4-9c8e-aee1c4cd7251\": {\n  \"hash\": \"fbddcbd107768f48\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Core business designs and installs industrial self-consumption solar; no scale metrics.\"\n },\n \"e1a7fae3-6fd6-5918-84cf-19339394d617\": {\n  \"hash\": \"51401851aad7649f\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Employment resource centre distinct from the placement agency; concrete, modest.\"\n },\n \"e1d18ca6-d272-586e-99af-65629133d4cd\": {\n  \"hash\": \"caaa9adb1004abc3\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"R&D support for marine renewable prototypes; scale not quantified.\"\n },\n \"e202451a-6586-53ff-965f-acfdd1e79375\": {\n  \"hash\": \"57b40dacd8816f65\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Nationwide public EV charging network in permanent operation across Spain, real service uptake.\"\n },\n \"e252953a-ff1b-5948-a163-dd727b94ccf0\": {\n  \"hash\": \"9471d4a71891b3ee\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Transversal research line structuring the institute's sustainable-competitiveness agenda.\"\n },\n \"e25943f3-2fce-5140-94dc-a7046efdcb1e\": {\n  \"hash\": \"3c5272f1c477afff\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Engineering contribution to Medellín transit lines; system-wide savings not IDOM's alone.\"\n },\n \"e284a661-57a1-57ff-af31-b40ac7aeb3f9\": {\n  \"hash\": \"5ef954704c799d41\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Green-financing mechanism supporting eligible investments; a governance tool, not a physical measure.\"\n },\n \"e342fe10-cd33-53b6-950d-04d0481567b1\": {\n  \"hash\": \"45f1c92b5ec4d1c2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Own certified HQ building; one office among many for a global engineering firm.\"\n },\n \"e3ab31ad-de12-5a3a-926f-8793c67c11bd\": {\n  \"hash\": \"165bfd193f343171\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Concrete student participation in a formal water-education programme and workshop; small reach.\"\n },\n \"e3e3c089-f7ad-4490-b67d-8ce70d006fe1\": {\n  \"hash\": \"76c43fef25c09068\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Largest PV roof in Bizkaia supplies about 18% of BEC's own consumption.\"\n },\n \"e47f88c6-5a6f-5cc6-b2b9-966fdb700062\": {\n  \"hash\": \"6fb13bb7b93f6cac\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operates the province-wide Garbigune clean-point network in regular use across Bizkaia for waste segregation.\"\n },\n \"e4d002c5-8fac-5853-9217-9140bf86297a\": {\n  \"hash\": \"4f883303d6c51778\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Corporate procurement sustainability policy; adopted governance, no quantified climate impact stated.\"\n },\n \"e5672920-b11f-5348-9663-0287aca580d9\": {\n  \"hash\": \"154eee45a7bda09b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Manages bulky-waste collection and treatment circuits, diverting some items from direct disposal.\"\n },\n \"e57f57a1-4065-54b2-86d7-e2be4dfada35\": {\n  \"hash\": \"49a5283f91c9a088\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operates ARIES, a natural-capital modelling infrastructure underpinning multiple downstream BC3 products.\"\n },\n \"e59a6fc1-efcd-5a6d-a718-a290811a238b\": {\n  \"hash\": \"8a1c0d184f0c1873\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Educational programmes for all school stages plus general-public activities; reach not quantified.\"\n },\n \"e5b35f27-d57c-4029-a314-8a2dc7d69759\": {\n  \"hash\": \"a835127601577a95\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Larger 10 MW electrolyzer under construction, dedicated hydrogen supply for port plant and refinery.\"\n },\n \"e5c31b3b-51d8-54f8-a2af-5f18afc864a6\": {\n  \"hash\": \"8eae72b06ca34536\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing programme letting Basque youth train at the UN Human Rights Council; small cohort reach.\"\n },\n \"e5cbde59-55f7-4214-b452-4b6a6510178b\": {\n  \"hash\": \"ac8c21e2b1a6c5ca\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Core circular-economy waste collection and reuse service; scale of reach not quantified.\"\n },\n \"e634c006-c32a-57b4-8850-1d35dd7701d6\": {\n  \"hash\": \"077a7d8bd0e4fc8a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"participation\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing grants and support for sustainability initiatives; reach not quantified.\"\n },\n \"e67d724e-29de-455c-b0f1-330fd7634cb1\": {\n  \"hash\": \"efa16dba5aaa12a2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Job-orientation NGO mainly for women across CAV; no beneficiary numbers stated.\"\n },\n \"e6c2b876-ee41-407f-ad3d-b5f06aed8afd\": {\n  \"hash\": \"bfe9ee715b5124ef\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"City-wide e-bike service, 1,250 bikes/66 stations, open to whole municipality, daily use.\"\n },\n \"e72e109a-db69-4f38-8b80-924e7e2deac1\": {\n  \"hash\": \"cde855ffe393f9f6\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"External theatre campaign on recycling habits delivered to two grade levels only.\"\n },\n \"e73563fb-c1ec-41f5-a79a-378d07c5aa69\": {\n  \"hash\": \"1e4596a49010e933\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Average 925 people integrated via industrial activity and disability employment model.\"\n },\n \"e796a53e-8eac-4793-afa6-92e0547a0a76\": {\n  \"hash\": \"8915ddd2eb84d812\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"School has rooftop PV and a geothermal system; no capacity figures given.\"\n },\n \"e79771b4-0010-523a-8dcf-82c0b7c22178\": {\n  \"hash\": \"7210b5f86e4cdd09\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"50% stake in an infrastructure alliance deploying charging and H2 refuelling.\"\n },\n \"e7a6610a-0595-5486-9eae-d0933d1efbda\": {\n  \"hash\": \"3ea430d643233f11\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Established eDNA biodiversity-monitoring service spanning fisheries, aquaculture, conservation and infrastructure.\"\n },\n \"e7af04f4-a027-5d9b-bde5-66df1a5448d7\": {\n  \"hash\": \"4424e47ac10509ec\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Specific energy-role contribution within a larger project it does not lead.\"\n },\n \"e7f17c0f-b0c9-559d-a7a0-4a2dfa84e4c2\": {\n  \"hash\": \"31c00bc87a189b43\",\n  \"level\": \"L4\",\n  \"status\": \"in_progress\",\n  \"category\": \"water_management\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Digitalisation of the entire municipal water cycle, capture through sanitation, via IoT/digital twin.\"\n },\n \"e854970d-21b6-532a-9a4d-b81817c97a40\": {\n  \"hash\": \"7dfc95470dedcf0e\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"City-wide bike-parking network complementing Dbizi and cycling network; moderate, distributed daily-use infrastructure.\"\n },\n \"e879107b-34ed-482f-9485-3bc2f53a1c06\": {\n  \"hash\": \"55f97fbd6c5e8eac\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"2013 study of the garden dormouse in Vitoria-Gasteiz, with habitat survey and monitoring network.\"\n },\n \"e87f1484-b348-401a-a996-464e2579539b\": {\n  \"hash\": \"e2f140b543ff2112\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"participation\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Participatory budget allocation for cycling and climate-adaptation works.\"\n },\n \"e89d7f6b-3ca2-400f-9e32-5afc63117cb9\": {\n  \"hash\": \"ae321c733af0875b\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Joint school contest inviting work on local park preservation; status completed.\"\n },\n \"e935541e-dfe0-50f7-b846-9b8745d4c7c4\": {\n  \"hash\": \"9194f7ec1bcc4569\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Digital-plus-sustainability training itinerary for creative SMEs; sustainability weight shared.\"\n },\n \"ea1f9319-ae0f-5e6a-92e0-c4ce08e7291e\": {\n  \"hash\": \"fb1b9d69025c68e5\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Leads and provides secretariat for a recognised regional network of educational and social organisations.\"\n },\n \"eae8419a-7416-4ebc-9e67-e40a48e869cc\": {\n  \"hash\": \"c4313fc232515044\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Students restarted the school's disused compost bin with weekly sorting routines.\"\n },\n \"eb16fa61-1477-55d3-863c-5a600316fb05\": {\n  \"hash\": \"189d294c6003305d\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"2026 beach-use rules ban smoking and unamplified sound devices on municipal beaches; regulatory measure.\"\n },\n \"eb2fa954-f276-508c-808c-d707ad1bc8d5\": {\n  \"hash\": \"3367e60c790a6674\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Circularity targets set across renewable equipment portfolio; a target/policy counts as L1.\"\n },\n \"eb74f53e-12fc-55a1-a3a8-996848bd13c1\": {\n  \"hash\": \"8a5d511a21891700\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Citywide bike-share with ~700,000 trips in H1 2026, mostly electric; daily-use service across the city.\"\n },\n \"eb90614b-8247-4544-a0bd-481fca47553f\": {\n  \"hash\": \"43c0a7aeaa7507f3\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One electric vehicle and chargers at four centres; small fleet decarbonisation step.\"\n },\n \"eb942305-e36a-51a3-a5dd-6809121f5efd\": {\n  \"hash\": \"01b0eb0ff6deb86b\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"street_redesign\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"City-wide sustainable mobility strategy including a general 30 km/h limit.\"\n },\n \"ece9be89-5bb2-5d7f-b6d7-b0e90deaff5a\": {\n  \"hash\": \"f50f634bb8266122\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"General technical support to the municipal mobility plan via studies and pilots; details unspecified.\"\n },\n \"ed1d052d-4706-5608-aed2-81a40e694fa7\": {\n  \"hash\": \"a09f17f5679ffc4f\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Early-stage soil-restoration living lab with co-creation workshops and experimental actions.\"\n },\n \"ed2510eb-76fc-4501-b8a9-417de6dbf7b7\": {\n  \"hash\": \"f31d8f215b462760\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Established annual outreach programme (about 20 sessions/year) transferring knowledge sector-wide.\"\n },\n \"ed44fd05-d35c-457b-947d-6ebb30126c9d\": {\n  \"hash\": \"51bb71fe7b039cd2\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Four ultra-fast EV charging bays installed, one for trucks; modest capacity.\"\n },\n \"ed79bbe8-d8d0-5014-a46f-2441915c851c\": {\n  \"hash\": \"9db63ba0ee8edd62\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in a Horizon project on innovative crop rotations as nature-based adaptation solutions.\"\n },\n \"edc79621-d617-5307-b2de-a15cb6a15639\": {\n  \"hash\": \"6fac226ee29b2dc2\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in a hybrid coastal-infrastructure demonstrator project; concrete but pilot scale, partner role.\"\n },\n \"edd3d8c5-eb6b-58a9-8acf-5652ec62ec33\": {\n  \"hash\": \"219c3b0462a10fa2\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Aixerrota-La Galea\",\n  \"reason\": \"New cycle connection from Aixerrota to La Galea seafront designed, including a junction upgrade; not yet built.\"\n },\n \"ee02682d-c502-5b59-aafe-125d10ed7b0c\": {\n  \"hash\": \"0de8f929a2087b0a\",\n  \"level\": \"L0\",\n  \"status\": \"in_progress\",\n  \"category\": \"adaptation_plan\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Getxo joins regional BIK 2050 adaptation project (modelling, digital twin); no local measures identified yet.\"\n },\n \"ef99e8c3-8a60-45bf-8d20-a69b24586fe7\": {\n  \"hash\": \"06051535b65a0728\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Students co-designed school-area safety improvements, completed with a mural event.\"\n },\n \"f0c341f3-a9e2-5590-804b-8a927c8a95f6\": {\n  \"hash\": \"8bd4c4081b844d9e\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"municipal_fleet\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Progressive electrification of municipal mobility and services; extent unstated.\"\n },\n \"f11eac0b-958c-473b-b876-937100aa8ff2\": {\n  \"hash\": \"bce7f0be9a2d17ed\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Rooftop solar self-consumption at six centres complements renewable supply; scale unquantified.\"\n },\n \"f1e28578-2d86-549b-ba28-349d5c11d453\": {\n  \"hash\": \"bc97edcdf13ef51a\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Mobilising youth consultations across three territories for a global process; scale not quantified.\"\n },\n \"f1f9788c-a12a-5e3d-bf45-7e7af1de18ab\": {\n  \"hash\": \"f81a60cbef81621a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Certified environmental management system covering the organisation's operations.\"\n },\n \"f21c393a-c2c1-5e71-8a9e-431f1a4f87d2\": {\n  \"hash\": \"2cbe7c13c93d1483\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Structured technical visits to treatment facilities connect the public with waste-management operations.\"\n },\n \"f231fae7-6ba2-5eb3-a2f7-12d10c827e6d\": {\n  \"hash\": \"7d7d10eb1da44783\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"climate_energy_plan\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"EU climate governance instrument with recognised Mission Label; counts as plan, L1.\"\n },\n \"f235879f-8543-41d6-8a3a-dd40a53260e0\": {\n  \"hash\": \"2d8cbd4ce9a2621a\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Nature-based transformation of six school playgrounds underway, with direct works management.\"\n },\n \"f2486673-0576-4e7a-ab86-a2f411eb3bd7\": {\n  \"hash\": \"c955d032644f40ec\",\n  \"level\": \"L0\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Collaborative sustainability project (EkoPulse) under development; no concrete outcomes stated yet.\"\n },\n \"f34881fb-37ff-56a3-b93c-c5f4e1406573\": {\n  \"hash\": \"44f100db15b1d6e4\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"climate_energy_plan\",\n  \"partOf\": null,\n  \"sameWorksAs\": [\n   \"37543b4f-012b-5498-8f6d-0b5519504af5\"\n  ],\n  \"zone\": \"shared\",\n  \"reason\": \"PACES 2030 framework; an adopted plan counts as L1 in its own right.\"\n },\n \"f377449a-1133-5231-8ec5-40c1ddb5674e\": {\n  \"hash\": \"b8623662e8004025\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"education_awareness\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"2025 hands-on ecological horticulture training delivered across several Donostia neighbourhoods; no numbers given.\"\n },\n \"f38e1567-ce9d-5af2-960d-e00d4e48bf5c\": {\n  \"hash\": \"25b5fcbb080b2aa3\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Uses NIR and Fibersort technology to sort textiles by composition, improving recycling routing precision.\"\n },\n \"f3bda508-c27c-4b13-a382-e00b4a86ae55\": {\n  \"hash\": \"7c209941eb203ecb\",\n  \"level\": \"L4\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"100% of purchased electricity is renewable, per the 2025 sustainability report.\"\n },\n \"f4180dd6-156c-5f83-85f0-994264e9ad4f\": {\n  \"hash\": \"100e3361eb7e57fd\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operating footprint-calculation tool for food products, sector-wide applicability, adoption scale unstated.\"\n },\n \"f418dcba-4dd7-5fcd-88d7-5e477291670f\": {\n  \"hash\": \"e28b17c99f48e094\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Door-to-door organic collection for high-volume commercial generators, narrower than universal service.\"\n },\n \"f4a989ec-1e60-49e7-be37-bf9509e25885\": {\n  \"hash\": \"7495c26cd44d3e59\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ten solar installations across three campuses being rolled out, ~2,250 MWh/yr estimated.\"\n },\n \"f57c659f-c741-471c-9517-a1ab57890dbd\": {\n  \"hash\": \"6f0877f42d0042aa\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Multiple steel-waste valorisation streams cut landfill 8% and raised the valorisation rate 17% in ten years.\"\n },\n \"f5801c3e-baa1-5e3b-966e-37b116a5dce6\": {\n  \"hash\": \"3a2a21d41accf99a\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Adopted corporate decarbonisation strategy and plan for TECNALIA's own operations and supply chain.\"\n },\n \"f66bfd8d-b3c2-4c8d-9a02-3a44ab1514b1\": {\n  \"hash\": \"1b1e3b0d6c1eac28\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Sustained environmental-control and public-service contract across Vitoria-Gasteiz's Green Belt since 2012.\"\n },\n \"f677b3db-4395-4f95-a75e-a36df9175fbd\": {\n  \"hash\": \"009ec361a5808f56\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"One environmental-education and leisure park; concrete single facility.\"\n },\n \"f68ea616-5591-59a8-b87d-b746eb1cf958\": {\n  \"hash\": \"df8d07b2c7ef8e6c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Local/circular sourcing for one hostelry school's menu; concrete but small-scale practice.\"\n },\n \"f71f6ae3-482b-56e1-9ac3-a5a8080bd46f\": {\n  \"hash\": \"c040313438265f64\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"biodiversidad\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Ulia\",\n  \"reason\": \"Small habitat ponds created and managed on Ulia for amphibians, with monitoring and outreach activities.\"\n },\n \"f78e3cfe-1885-5807-a0a8-422ccafb72f7\": {\n  \"hash\": \"2a354a13389fbd62\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Leads a project recovering mixed textiles from municipal waste destined for landfill; discrete initiative.\"\n },\n \"f7fecb99-fc6e-56ef-96f2-0e9ae37d85c0\": {\n  \"hash\": \"520db7cfdbdf40bd\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Joint industrial plant recycling wind-blade materials, launched; single-facility circular outlet.\"\n },\n \"f83d2cae-3008-5ed2-b1a7-51976d4c7054\": {\n  \"hash\": \"637c25abbee03e36\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participation in one mobility-data ecosystem project; outcomes not yet confirmed.\"\n },\n \"f851d8a9-11a3-57e6-a4cc-c3f8b695dbf2\": {\n  \"hash\": \"09ad1ab81ea71942\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"public_transport\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Ongoing Dbus fleet electrification; Next Generation program added 24 electric buses plus chargers/pantographs.\"\n },\n \"f86fc858-d992-5490-9bbb-63f07df76921\": {\n  \"hash\": \"4c6ca3a1cd5f7107\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Manages the residual waste fraction across Bizkaia, recovering materials and preparing the rest for treatment.\"\n },\n \"f88377dc-5bb2-4287-a373-f7a37a5f0d79\": {\n  \"hash\": \"2a1cd0805527403c\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Aerothermal heating replaces the old system at one centre (Gernika) only.\"\n },\n \"f8aeed57-f086-4f49-bf9a-b3eaf2758a30\": {\n  \"hash\": \"e36429b05f1e80a7\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ten renewable thermal installations across three campuses being rolled out for heating and cooling.\"\n },\n \"f8e837f1-a47c-4f47-92c8-e9f71f4b93af\": {\n  \"hash\": \"d06f67ccac67d556\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Recurring recycled-paper notebook activity for pupil participation on sustainability.\"\n },\n \"f90a53c3-1b6a-4aed-8f11-075b184962c4\": {\n  \"hash\": \"e7a7f70af9ce0ed6\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"waste_collection\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Budgeted equipment purchase for organic waste collection; not yet executed.\"\n },\n \"f93404b0-1d76-464d-b075-97954de518e2\": {\n  \"hash\": \"c8baa8eaa7b4674a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Neighbourhood-level climate vulnerability diagnosis report with the city council.\"\n },\n \"f96331dc-f7e5-54a4-bc46-e33cf6f760d7\": {\n  \"hash\": \"8a2faa721ba41c3a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"muro de Ondarreta\",\n  \"reason\": \"Repaired/reinforced Ondarreta seawall (finished June 2026) after 2025 storm damage; single-structure coastal resilience.\"\n },\n \"f9960f81-019c-48e9-b203-26581908f752\": {\n  \"hash\": \"f7fa52758cb036dc\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"municipal_renewables\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Colegios Sansaburu, San Andrés y Azitain\",\n  \"reason\": \"Self-consumption PV launched at three schools covering 25-50% of their own use.\"\n },\n \"f9e4be0b-cdc7-4d6f-8a3c-cd04d4895c5b\": {\n  \"hash\": \"528c9eb44ccce9ba\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Local-supplier preference in sourcing criteria; no quantified proportion given.\"\n },\n \"fa0160b5-83f2-54e0-ae78-5aa258692857\": {\n  \"hash\": \"389e014ddb4eb7c5\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"category\": \"monitoring_data\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"2026 update improved geodata viewer/Open Data links; concrete but modest-scale platform improvement.\"\n },\n \"fa7c63f8-fd9e-57d8-9713-2713f4451fb0\": {\n  \"hash\": \"ecb57f829c0ebd5e\",\n  \"level\": \"L1\",\n  \"status\": \"unclear\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Research line on transformative urban adaptation; evidence recovered is less complete than similar projects.\"\n },\n \"fa88a0dc-3f59-4fed-a05c-2953316b24a6\": {\n  \"hash\": \"103b250859a0c18a\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Hazardous cleaning products removed; quantified 11.8% consumption cut, narrow scope.\"\n },\n \"faaa3cca-1972-43c3-b794-457e8b66c0b1\": {\n  \"hash\": \"30f1afab78b07bed\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"biodiversity\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Presa de Ereñotzu y canal, río Urumea\",\n  \"reason\": \"Removes one dam/canal barrier for fish and species passage; concrete but a single site.\"\n },\n \"fadfdb25-8979-521f-b393-f52a49d18086\": {\n  \"hash\": \"98e63ad89068633a\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Informational article explaining the MSC sustainable-fishing certificate; awareness content only.\"\n },\n \"faf60fc8-c21b-595f-a9bf-c25406e81ab5\": {\n  \"hash\": \"1bfeb048284dc565\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"category\": \"other\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Symbolic pledge adhesion since 2010; no concrete measure described.\"\n },\n \"fb407d77-f976-49b9-91d3-5d79b1130127\": {\n  \"hash\": \"ce45af4fbbcf5d91\",\n  \"level\": \"L0\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Members give talks/attend events on sustainability; awareness only, no operational change.\"\n },\n \"fb667085-a11b-4612-ac46-b6c54ef7b34a\": {\n  \"hash\": \"511523f67aa4ecf4\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operating interpretation centre at the Salburua wetlands providing ongoing environmental education.\"\n },\n \"fb8e51e7-cfdc-56c5-a11b-2db5a289a4a6\": {\n  \"hash\": \"94608da56d55dcdf\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Contributed to technology behind H2SITE and its first industrial-scale hydrogen membrane reactor.\"\n },\n \"fb8fa07c-ef65-49a6-b2d0-9d9f4c81d609\": {\n  \"hash\": \"4a6d0973cd76b8c7\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"category\": \"biodiversity\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Irugurutzeta\",\n  \"reason\": \"EU-funded environmental recovery works being finished at one specific site.\"\n },\n \"fba3f304-0694-5bc4-addb-9d964abcefff\": {\n  \"hash\": \"e0b8bf2b678f5d1e\",\n  \"level\": \"L0\",\n  \"status\": \"unclear\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Vague general claim of water/governance projects across three countries; no concrete detail.\"\n },\n \"fbd10aaf-5d26-5744-a332-32539f1e5a1b\": {\n  \"hash\": \"bc2d40ad6cf106c4\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Develops and provides carbon capture and utilisation technologies to industry; one discrete technology line.\"\n },\n \"fbf4340b-1f6f-4924-921e-12b9b6c01ec3\": {\n  \"hash\": \"d306b07b15086ae2\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Ongoing sectoral research and tool-adoption programme for potato crops; concrete but scale unquantified.\"\n },\n \"fc25d042-da50-4a23-bc19-82dcdc7b1ecd\": {\n  \"hash\": \"3a951f0a8f1644e2\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"category\": \"urban_greening\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Zamalanda y Serralta\",\n  \"reason\": \"Two new parks (~147,000 m2 total) opened in named neighbourhoods with green space and sport.\"\n },\n \"fc991137-aa28-51e9-867a-68fc4aacc341\": {\n  \"hash\": \"f81da9c22656ad49\",\n  \"level\": \"L1\",\n  \"status\": \"committed\",\n  \"category\": \"cycling\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"shared\",\n  \"reason\": \"Adopted cycling strategy; counts as L1 as an adopted strategy.\"\n },\n \"fcbaad9e-9a88-461c-b74c-de0b7d315cc0\": {\n  \"hash\": \"3d9c3d9f583bb40e\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Low-VOC materials used building-wide, improving indoor air quality for all occupants.\"\n },\n \"fcc4dc05-eae8-5411-8ab2-8709d27e035c\": {\n  \"hash\": \"024929baa695c028\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"EV100 commitment to progressively electrify the corporate fleet; concrete but reach unclear, dated source.\"\n },\n \"fdd381d6-3167-5f1f-9fd4-29e63b9d8287\": {\n  \"hash\": \"04f6d69beae9b299\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Operates open marine-energy test infrastructure; reach not quantified.\"\n },\n \"fdd7baa8-8b3b-4243-bcbc-8d95482ba630\": {\n  \"hash\": \"93517a3a5cd94caa\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Citizen-science programme collecting marine-litter data and raising public awareness.\"\n },\n \"fe0050b9-8b98-482e-aaf8-79a3a07c75a7\": {\n  \"hash\": \"c342043e4cc40829\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": \"abde1102-31b4-579a-b386-d7d960919255\",\n  \"sameWorksAs\": [],\n  \"reason\": \"Four new citizen-science biodiversity programmes launched across city sites in 2023.\"\n },\n \"fe8889ac-8805-5281-8fa5-4ec4c2b5cb9f\": {\n  \"hash\": \"78ebd8e384a7d6a7\",\n  \"level\": \"L2\",\n  \"status\": \"in_progress\",\n  \"category\": \"building_renovation_support\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"zone\": \"Zaramaga\",\n  \"reason\": \"Neighbourhood-scale deep residential retrofit; first buildings finished, others still advancing.\"\n },\n \"fee81022-34fb-51bb-a9a4-8ac27200d0d4\": {\n  \"hash\": \"bae91c754510fc1b\",\n  \"level\": \"L1\",\n  \"status\": \"in_progress\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Participates in an ESA project building open ecosystem-mapping tools; one discrete research contribution.\"\n },\n \"ff5d7ff3-9685-4ef4-8dcb-8c9b827e11a9\": {\n  \"hash\": \"72bd707a570b210c\",\n  \"level\": \"L2\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Completed process recovering over 90% nitrogen from tuna-cannery brine into biofertilizer; pilot-scale result.\"\n },\n \"ff6db9e5-b26c-4156-9548-3fe30a5a5040\": {\n  \"hash\": \"ef1dad00505987e3\",\n  \"level\": \"L1\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Small school garden and composting programme since receiving a composter in 2020.\"\n },\n \"ffe20aa4-f294-427a-ac00-9fb31113d817\": {\n  \"hash\": \"6f3960986168ee57\",\n  \"level\": \"L3\",\n  \"status\": \"implemented\",\n  \"partOf\": null,\n  \"sameWorksAs\": [],\n  \"reason\": \"Whole foral estate's energy use cut 6% overall, with some segments down 15-30%.\"\n }\n};\n",
    "lib/profile-score-explanations.ts": "import type { ImpactV2Result } from '@/lib/profile-impact-v2';\n\n/** Shared presentation copy. Per-item facts come from the same engine as the score. */\nexport const IMPACT_HELP = 'Valora las mejoras sostenibles admitidas por el modelo y las relaciones relevantes. Las evidencias respaldan la Fiabilidad; no añaden puntos de Impacto directo.';\nexport const IMPACT_V2_SUMMARY = 'Modelo provisional: cada mejora se valora según su alcance para este tipo de perfil, de simbólica a transformadora. El mismo trabajo cuenta una sola vez. Los aportes se combinan con rendimientos decrecientes y se suman las relaciones relevantes. La evidencia añade 0 puntos de Impacto directo. No mide toneladas ni el impacto neto.';\nexport const IMPACT_REVIEW_HELP = 'Los niveles de alcance más altos requieren confirmación humana; hasta entonces se valoran de forma conservadora. Las mejoras nuevas o editadas usan un nivel provisional hasta que se revisa su texto actual.';\nexport const MUNICIPAL_OVERLAP_HELP = 'En la escala municipal también se limita el solapamiento entre mejoras que actúan sobre el mismo ámbito.';\nexport const PRACTICE_RELIABILITY_HELP = 'Esta mejora también forma parte de la cobertura de Fiabilidad. Retirarla puede aumentar o reducir esa cobertura; el efecto se calcula con el resto de los datos del perfil.';\nexport const PRACTICE_CURVE_HELP = 'El efecto en puntos depende del conjunto de mejoras y de la curva de rendimientos decrecientes; no hay una cantidad fija de puntos por nivel.';\nexport const IMPACT_LEVEL_HELP = {\n  L0: 'L0 indica un alcance simbólico.', L1: 'L1 indica un alcance menor.',\n  L2: 'L2 indica un alcance moderado.', L3: 'L3 indica un alcance significativo.',\n  L4: 'L4 indica un alcance importante.', L5: 'L5 indica un alcance transformador.',\n} as const;\nexport const PRACTICE_SCALE_HELP = {\n  municipality: 'El alcance se valora respecto al conjunto del municipio.',\n  organisation: 'El alcance se valora respecto a las operaciones de la organización y a las personas o el territorio a los que llega su actividad.',\n  person: 'El alcance se valora respecto a la vida y el hogar de la persona y a las personas o proyectos a los que llega su actividad.',\n  product: 'El alcance se valora respecto al producto o servicio y a sus usuarios o territorio.',\n} as const;\n\nexport function impactV2PracticeExplanation(result: ImpactV2Result, id: string): string[] {\n  const item = result.contributions.find(row => row.id === id);\n  if (!item) return [];\n  const parts: string[] = [PRACTICE_SCALE_HELP[result.scale]];\n  if (!item.read) {\n    parts.push(`Esta mejora no tiene una valoración vigente de su texto actual. Por ahora cuenta como ${item.countedLevel}.`);\n  } else if (item.held) {\n    parts.push(`Alcance asignado: ${item.level}. Pendiente de confirmación humana; por ahora cuenta como ${item.countedLevel}.`);\n  } else {\n    parts.push(`Nivel de alcance aplicado: ${item.countedLevel}.`);\n  }\n  parts.push(IMPACT_LEVEL_HELP[item.level]);\n  if (item.sameWorkAs) parts.push('Esta mejora describe el mismo trabajo que otra del perfil y no se suma de nuevo. Al retirarla se vuelve a calcular el grupo.');\n  else if (result.sameWorkCount > 0) parts.push('Las mejoras que describen el mismo trabajo se agrupan y cuentan una sola vez, con el nivel aplicado más alto.');\n  if (result.scale === 'municipality') parts.push(MUNICIPAL_OVERLAP_HELP);\n  parts.push(PRACTICE_CURVE_HELP, PRACTICE_RELIABILITY_HELP);\n  return parts;\n}\n",
    "lib/profile-reliability-presentation.ts": "import type { ProfileScoreResult } from '@/types/profile-score';\nimport { RELIABILITY_TIERS, RELIABILITY_V13_CONFIG, gradeOf, type ReliabilityTermName } from '@/lib/profile-reliability-v13';\n\nexport function reliabilityMaximum(freshnessAvailable = RELIABILITY_V13_CONFIG.freshnessAvailable) {\n  const score = Math.max(...RELIABILITY_TIERS.filter(tier => !tier.requiresFreshness || freshnessAvailable).map(tier => tier.ceiling));\n  return { score, grade: gradeOf(score) };\n}\n\nexport const RELIABILITY_TERM_LABELS: Record<ReliabilityTermName, string> = {\n  coverage: 'Cobertura de prácticas', depth: 'Diversidad de fuentes', foundation: 'Fundamento documental',\n  independence: 'Independencia de fuentes', corroboration: 'Corroboración', completeness: 'Información del perfil',\n  relationships: 'Relaciones propias', ecosystem: 'Respaldo del ecosistema',\n};\nconst TIER_LABELS = {\n  T0: 'Respaldo documental insuficiente', T1: 'Documentación parcial', T2: 'Documentado, principalmente con fuentes propias',\n  T3: 'Corroboración independiente', T4: 'Corroboración sólida y vigente',\n};\n\n/** Presentation only: expose the current model's returned terms, never rescore. */\nexport function reliabilityPresentation(score: ProfileScoreResult) {\n  if (score.status !== 'scored' || score.reliability.modelVersion !== '1.3' || !score.reliability.v13) return null;\n  const { reliability } = score;\n  const detail = reliability.v13!;\n  const tier = RELIABILITY_TIERS.find(row => row.id === detail.tier);\n  if (!tier) return null;\n  return {\n    grade: reliability.grade, score: reliability.score, modelVersion: reliability.modelVersion,\n    maximum: reliabilityMaximum(),\n    tierLabel: TIER_LABELS[detail.tier], floor: tier.floor, ceiling: tier.ceiling,\n    covered: detail.measures.coveredCount, practices: detail.measures.practiceCount,\n    rows: (Object.keys(RELIABILITY_TERM_LABELS) as ReliabilityTermName[]).map(key => ({\n      key, label: RELIABILITY_TERM_LABELS[key], points: detail.contributions[key],\n    })),\n  };\n}\n"
  }
}
