fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
223
geo-service/utils/license_checker.py
Normal file
223
geo-service/utils/license_checker.py
Normal file
@@ -0,0 +1,223 @@
|
||||
"""
|
||||
License Checker Utility
|
||||
Validates data source licenses and generates attribution
|
||||
"""
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
import structlog
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class LicenseType(Enum):
|
||||
"""Supported data source license types."""
|
||||
ODBL = "odbl" # OpenStreetMap
|
||||
COPERNICUS = "copernicus" # Copernicus DEM
|
||||
CC_BY = "cc-by" # Creative Commons Attribution
|
||||
CC_BY_SA = "cc-by-sa" # Creative Commons Attribution-ShareAlike
|
||||
CC0 = "cc0" # Public Domain
|
||||
PROPRIETARY = "proprietary" # Not allowed
|
||||
|
||||
|
||||
class DataSource(Enum):
|
||||
"""Known data sources."""
|
||||
OPENSTREETMAP = "openstreetmap"
|
||||
COPERNICUS_DEM = "copernicus_dem"
|
||||
OPENAERIAL = "openaerial"
|
||||
WIKIMEDIA = "wikimedia"
|
||||
GOOGLE = "google" # FORBIDDEN
|
||||
BING = "bing" # FORBIDDEN
|
||||
APPLE = "apple" # FORBIDDEN
|
||||
HERE = "here" # FORBIDDEN
|
||||
|
||||
|
||||
# License information for allowed sources
|
||||
ALLOWED_SOURCES = {
|
||||
DataSource.OPENSTREETMAP: {
|
||||
"license": LicenseType.ODBL,
|
||||
"attribution": "© OpenStreetMap contributors",
|
||||
"url": "https://www.openstreetmap.org/copyright",
|
||||
"commercial": True,
|
||||
"derivative_allowed": True,
|
||||
},
|
||||
DataSource.COPERNICUS_DEM: {
|
||||
"license": LicenseType.COPERNICUS,
|
||||
"attribution": "© Copernicus Service Information",
|
||||
"url": "https://spacedata.copernicus.eu/",
|
||||
"commercial": True,
|
||||
"derivative_allowed": True,
|
||||
},
|
||||
DataSource.OPENAERIAL: {
|
||||
"license": LicenseType.CC_BY,
|
||||
"attribution": "© OpenAerialMap contributors",
|
||||
"url": "https://openaerialmap.org/",
|
||||
"commercial": True,
|
||||
"derivative_allowed": True,
|
||||
},
|
||||
DataSource.WIKIMEDIA: {
|
||||
"license": LicenseType.CC_BY_SA,
|
||||
"attribution": "Wikimedia Commons",
|
||||
"url": "https://commons.wikimedia.org/",
|
||||
"commercial": True,
|
||||
"derivative_allowed": True,
|
||||
},
|
||||
}
|
||||
|
||||
# Forbidden sources
|
||||
FORBIDDEN_SOURCES = {
|
||||
DataSource.GOOGLE: "Google Maps ToS prohibit derivatives and offline use",
|
||||
DataSource.BING: "Bing Maps has restrictive licensing",
|
||||
DataSource.APPLE: "Apple Maps prohibits commercial use",
|
||||
DataSource.HERE: "HERE requires paid licensing",
|
||||
}
|
||||
|
||||
|
||||
class LicenseChecker:
|
||||
"""
|
||||
Utility for validating data source licenses and generating attributions.
|
||||
|
||||
Ensures DSGVO/GDPR compliance and proper licensing for educational use.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def is_source_allowed(source: DataSource) -> bool:
|
||||
"""Check if a data source is allowed for use."""
|
||||
return source in ALLOWED_SOURCES
|
||||
|
||||
@staticmethod
|
||||
def get_forbidden_reason(source: DataSource) -> Optional[str]:
|
||||
"""Get the reason why a source is forbidden."""
|
||||
return FORBIDDEN_SOURCES.get(source)
|
||||
|
||||
@staticmethod
|
||||
def validate_url(url: str) -> tuple[bool, str]:
|
||||
"""
|
||||
Validate if a URL is from an allowed source.
|
||||
|
||||
Returns:
|
||||
Tuple of (is_allowed, message)
|
||||
"""
|
||||
url_lower = url.lower()
|
||||
|
||||
# Check for forbidden sources
|
||||
forbidden_patterns = {
|
||||
"google": DataSource.GOOGLE,
|
||||
"googleapis": DataSource.GOOGLE,
|
||||
"gstatic": DataSource.GOOGLE,
|
||||
"bing.com": DataSource.BING,
|
||||
"virtualearth": DataSource.BING,
|
||||
"apple.com/maps": DataSource.APPLE,
|
||||
"here.com": DataSource.HERE,
|
||||
}
|
||||
|
||||
for pattern, source in forbidden_patterns.items():
|
||||
if pattern in url_lower:
|
||||
reason = FORBIDDEN_SOURCES.get(source, "Not allowed")
|
||||
return False, f"FORBIDDEN: {source.value} - {reason}"
|
||||
|
||||
# Check for allowed sources
|
||||
allowed_patterns = {
|
||||
"openstreetmap": DataSource.OPENSTREETMAP,
|
||||
"tile.osm": DataSource.OPENSTREETMAP,
|
||||
"copernicus": DataSource.COPERNICUS_DEM,
|
||||
"openaerialmap": DataSource.OPENAERIAL,
|
||||
"wikimedia": DataSource.WIKIMEDIA,
|
||||
}
|
||||
|
||||
for pattern, source in allowed_patterns.items():
|
||||
if pattern in url_lower:
|
||||
info = ALLOWED_SOURCES[source]
|
||||
return True, f"ALLOWED: {source.value} ({info['license'].value})"
|
||||
|
||||
# Unknown source - warn but allow with custom attribution
|
||||
return True, "UNKNOWN: Verify license manually"
|
||||
|
||||
@staticmethod
|
||||
def get_attribution_for_sources(
|
||||
sources: list[DataSource],
|
||||
) -> list[dict]:
|
||||
"""
|
||||
Get attribution information for a list of data sources.
|
||||
|
||||
Args:
|
||||
sources: List of data sources used
|
||||
|
||||
Returns:
|
||||
List of attribution dictionaries
|
||||
"""
|
||||
attributions = []
|
||||
|
||||
for source in sources:
|
||||
if source in ALLOWED_SOURCES:
|
||||
info = ALLOWED_SOURCES[source]
|
||||
attributions.append({
|
||||
"name": source.value.replace("_", " ").title(),
|
||||
"license": info["license"].value.upper(),
|
||||
"attribution": info["attribution"],
|
||||
"url": info["url"],
|
||||
"required": True,
|
||||
})
|
||||
|
||||
return attributions
|
||||
|
||||
@staticmethod
|
||||
def generate_attribution_html(sources: list[DataSource]) -> str:
|
||||
"""
|
||||
Generate HTML attribution footer for web display.
|
||||
|
||||
Args:
|
||||
sources: List of data sources used
|
||||
|
||||
Returns:
|
||||
HTML string with attribution
|
||||
"""
|
||||
attributions = LicenseChecker.get_attribution_for_sources(sources)
|
||||
|
||||
if not attributions:
|
||||
return ""
|
||||
|
||||
parts = []
|
||||
for attr in attributions:
|
||||
parts.append(
|
||||
f'<a href="{attr["url"]}" target="_blank" rel="noopener">'
|
||||
f'{attr["attribution"]}</a>'
|
||||
)
|
||||
|
||||
return " | ".join(parts)
|
||||
|
||||
@staticmethod
|
||||
def generate_attribution_text(sources: list[DataSource]) -> str:
|
||||
"""
|
||||
Generate plain text attribution.
|
||||
|
||||
Args:
|
||||
sources: List of data sources used
|
||||
|
||||
Returns:
|
||||
Plain text attribution string
|
||||
"""
|
||||
attributions = LicenseChecker.get_attribution_for_sources(sources)
|
||||
|
||||
if not attributions:
|
||||
return ""
|
||||
|
||||
return " | ".join(attr["attribution"] for attr in attributions)
|
||||
|
||||
@staticmethod
|
||||
def check_commercial_use(sources: list[DataSource]) -> tuple[bool, list[str]]:
|
||||
"""
|
||||
Check if all sources allow commercial use.
|
||||
|
||||
Returns:
|
||||
Tuple of (all_allowed, list_of_issues)
|
||||
"""
|
||||
issues = []
|
||||
|
||||
for source in sources:
|
||||
if source in FORBIDDEN_SOURCES:
|
||||
issues.append(f"{source.value}: {FORBIDDEN_SOURCES[source]}")
|
||||
elif source in ALLOWED_SOURCES:
|
||||
if not ALLOWED_SOURCES[source]["commercial"]:
|
||||
issues.append(f"{source.value}: Commercial use not allowed")
|
||||
|
||||
return len(issues) == 0, issues
|
||||
Reference in New Issue
Block a user