"""Openness 2.0 — measurable dimensions first, a carefully labelled category second. Dimensions (each `True | False | None` = unknown): weights_available the weights can be downloaded (a checkpoint exists on a public hub / official download) source_code_available inference / model code is published training_code_available the training pipeline is published training_data_disclosed the composition of the training data is documented dataset_available the training data itself (or most of it) is downloadable commercial_use_allowed licence permits commercial use redistribution_allowed licence permits redistributing the weights derivatives_allowed licence permits fine-tunes / modifications Categories (`OPENNESS_CATEGORIES`) are derived, never asserted directly by a connector unless the source literally states it: open-source weights + code under an OSI-approved (or equivalently permissive) licence, commercial use and derivatives allowed open-weights weights downloadable under a permissive or Creative Commons licence allowing commercial use (code may be missing) restricted-weights weights downloadable but the licence restricts commercial use, hosting, derivatives or field of use proprietary weights not available (API / product only) unknown not enough evidence The historical single-value vocabulary (`open-weights | open-source | proprietary | restricted`) stays valid as input to `normalize_openness`. """ from __future__ import annotations from typing import Any from aiatlas.ontology.licenses import LICENSES, normalize_license OPENNESS_CATEGORIES = ("open-source", "open-weights", "restricted-weights", "proprietary", "unknown") OPENNESS_DIMENSIONS = ( "weights_available", "source_code_available", "training_code_available", "training_data_disclosed", "dataset_available", "commercial_use_allowed", "redistribution_allowed", "derivatives_allowed", ) _LEGACY = { "open-weights": "open-weights", "open_weights": "open-weights", "open weights": "open-weights", "openweights": "open-weights", "open": "open-weights", "open-source": "open-source", "open_source": "open-source", "open source": "open-source", "opensource": "open-source", "oss": "open-source", "proprietary": "proprietary", "closed": "proprietary", "closed-source": "proprietary", "closed_source": "proprietary", "closed source": "proprietary", "api-only": "proprietary", "api only": "proprietary", "commercial": "proprietary", "restricted": "restricted-weights", "restricted-weights": "restricted-weights", "restricted_weights": "restricted-weights", "gated": "restricted-weights", "research-only": "restricted-weights", "non-commercial": "restricted-weights", "unknown": "unknown", "": "unknown", } def normalize_openness(raw: Any) -> str | None: """Legacy single label → canonical category. Unknown strings → None (the writer keeps the raw label).""" if raw is None: return None s = str(raw).strip().lower() return _LEGACY.get(s) def openness_dimensions(*, weights_available: bool | None = None, license_key: str | None = None, license_raw: str | None = None, source_code_available: bool | None = None, training_code_available: bool | None = None, training_data_disclosed: bool | None = None, dataset_available: bool | None = None) -> dict[str, bool | None]: """Fill the licence-derived dimensions from the licence ontology; everything else is passed through (unknown stays None).""" key = license_key or normalize_license(license_raw) info = LICENSES.get(key) if key else None dims: dict[str, bool | None] = { "weights_available": weights_available, "source_code_available": source_code_available, "training_code_available": training_code_available, "training_data_disclosed": training_data_disclosed, "dataset_available": dataset_available, "commercial_use_allowed": info.commercial_use if info else None, "redistribution_allowed": info.redistribution if info else None, "derivatives_allowed": info.derivatives if info else None, } if info and not info.weights_downloadable and weights_available is None: dims["weights_available"] = False return dims def derive_openness(dims: dict[str, bool | None], *, license_key: str | None = None) -> str: """Category from dimensions (+ licence category). Conservative: a custom community licence is never 'open-source'.""" info = LICENSES.get(license_key) if license_key else None weights = dims.get("weights_available") if weights is False: return "proprietary" if weights is None: return "unknown" commercial = dims.get("commercial_use_allowed") derivatives = dims.get("derivatives_allowed") redistribution = dims.get("redistribution_allowed") restricted_by_license = bool(info and (info.category in ("community", "research-only", "responsible-ai") or info.hosting_restrictions)) if commercial is False or derivatives is False or redistribution is False or restricted_by_license: return "restricted-weights" if info and info.osi_approved and dims.get("source_code_available") is True and commercial is True and derivatives is True: return "open-source" if info and info.category in ("permissive", "creative-commons") and commercial is True and redistribution is True: return "open-weights" if info is None: # weights downloadable, licence unknown → we know the weights exist, not the terms return "open-weights" if commercial is None else "restricted-weights" return "open-weights" OPENNESS_LABELS = { "open-source": "Open source", "open-weights": "Open weights", "restricted-weights": "Restricted weights", "proprietary": "Closed / proprietary", "unknown": "Unknown", } OPENNESS_DEFINITIONS = { "open-source": "Weights and code published under an OSI-approved licence that allows commercial use and derivatives.", "open-weights": "Weights downloadable under a permissive or Creative Commons licence allowing commercial use; code or data may be missing.", "restricted-weights": "Weights downloadable, but the licence restricts commercial use, hosting, derivatives or field of use (community, research and RAIL licences).", "proprietary": "Weights are not available; the model is reachable only through an API or a product.", "unknown": "Not enough sourced evidence to classify.", } __all__ = ["OPENNESS_CATEGORIES", "OPENNESS_DEFINITIONS", "OPENNESS_DIMENSIONS", "OPENNESS_LABELS", "derive_openness", "normalize_openness", "openness_dimensions"]