116 lines
3 KiB
Python
116 lines
3 KiB
Python
|
|
import uuid
|
||
|
|
from datetime import datetime
|
||
|
|
from typing import Literal
|
||
|
|
from pydantic import BaseModel, computed_field
|
||
|
|
|
||
|
|
# GDPR maturity scale (CNIL/IAPP CMMI-aligned, adapted for third-party assessment)
|
||
|
|
GDPRMaturity = Literal["unknown", "non_compliant", "initial", "developing", "defined", "managed", "certified"]
|
||
|
|
|
||
|
|
# Services at these levels trigger a GDPR warning
|
||
|
|
GDPR_WARNING_LEVELS = {"unknown", "non_compliant", "initial"}
|
||
|
|
|
||
|
|
PricingModel = Literal["free", "paid", "freemium", "usage_based", "unknown"]
|
||
|
|
AuthType = Literal["api_key", "oauth", "cli", "none", "unknown"]
|
||
|
|
|
||
|
|
|
||
|
|
class TPSCCatalogCreate(BaseModel):
|
||
|
|
slug: str
|
||
|
|
name: str
|
||
|
|
provider: str | None = None
|
||
|
|
category: str | None = None
|
||
|
|
website_url: str | None = None
|
||
|
|
pricing_model: PricingModel = "unknown"
|
||
|
|
gdpr_maturity: GDPRMaturity = "unknown"
|
||
|
|
gdpr_notes: str | None = None
|
||
|
|
dpa_available: bool = False
|
||
|
|
tos_url: str | None = None
|
||
|
|
privacy_policy_url: str | None = None
|
||
|
|
data_processing_regions: list[str] | None = None
|
||
|
|
data_retention_notes: str | None = None
|
||
|
|
status: str = "active"
|
||
|
|
|
||
|
|
|
||
|
|
class TPSCCatalogRead(BaseModel):
|
||
|
|
model_config = {"from_attributes": True}
|
||
|
|
|
||
|
|
id: uuid.UUID
|
||
|
|
slug: str
|
||
|
|
name: str
|
||
|
|
provider: str | None
|
||
|
|
category: str | None
|
||
|
|
website_url: str | None
|
||
|
|
pricing_model: str
|
||
|
|
gdpr_maturity: str
|
||
|
|
gdpr_notes: str | None
|
||
|
|
dpa_available: bool
|
||
|
|
tos_url: str | None
|
||
|
|
privacy_policy_url: str | None
|
||
|
|
data_processing_regions: list[str] | None
|
||
|
|
data_retention_notes: str | None
|
||
|
|
status: str
|
||
|
|
created_at: datetime
|
||
|
|
updated_at: datetime
|
||
|
|
|
||
|
|
@computed_field
|
||
|
|
@property
|
||
|
|
def gdpr_warning(self) -> bool:
|
||
|
|
return self.gdpr_maturity in GDPR_WARNING_LEVELS
|
||
|
|
|
||
|
|
|
||
|
|
class TPSCEntryCreate(BaseModel):
|
||
|
|
service_slug: str
|
||
|
|
purpose: str | None = None
|
||
|
|
auth_type: str | None = None
|
||
|
|
endpoint_override: str | None = None
|
||
|
|
notes: str | None = None
|
||
|
|
|
||
|
|
|
||
|
|
class TPSCEntryRead(BaseModel):
|
||
|
|
model_config = {"from_attributes": True}
|
||
|
|
|
||
|
|
id: uuid.UUID
|
||
|
|
snapshot_id: uuid.UUID
|
||
|
|
catalog_id: uuid.UUID | None
|
||
|
|
service_slug: str
|
||
|
|
purpose: str | None
|
||
|
|
auth_type: str | None
|
||
|
|
endpoint_override: str | None
|
||
|
|
notes: str | None
|
||
|
|
# Denormalised from catalog for convenience
|
||
|
|
gdpr_maturity: str | None = None
|
||
|
|
gdpr_warning: bool = False
|
||
|
|
pricing_model: str | None = None
|
||
|
|
|
||
|
|
|
||
|
|
class TPSCIngestRequest(BaseModel):
|
||
|
|
repo_slug: str
|
||
|
|
source_file: str = "tpsc.yaml"
|
||
|
|
entries: list[TPSCEntryCreate]
|
||
|
|
|
||
|
|
|
||
|
|
class TPSCSnapshotRead(BaseModel):
|
||
|
|
model_config = {"from_attributes": True}
|
||
|
|
|
||
|
|
id: uuid.UUID
|
||
|
|
repo_id: uuid.UUID | None
|
||
|
|
snapshot_at: datetime
|
||
|
|
source_file: str | None
|
||
|
|
entry_count: int
|
||
|
|
entries: list[TPSCEntryRead] = []
|
||
|
|
|
||
|
|
|
||
|
|
class TPSCGDPRWarning(BaseModel):
|
||
|
|
repo_slug: str | None
|
||
|
|
service_slug: str
|
||
|
|
gdpr_maturity: str
|
||
|
|
purpose: str | None
|
||
|
|
pricing_model: str | None
|
||
|
|
|
||
|
|
|
||
|
|
class TPSCGDPRReport(BaseModel):
|
||
|
|
generated_at: datetime
|
||
|
|
total_services: int
|
||
|
|
warning_count: int
|
||
|
|
warnings: list[TPSCGDPRWarning]
|
||
|
|
by_maturity: dict[str, int]
|