개방형 데이터 API 설계: 공개 데이터 게시 및 소비
공공행정 오픈데이터 API 설계 및 구현 방법: DCAT-AP_IT, CKAN, 표준 페이지 매김 및 캐싱이 포함된 REST API, 개방형 데이터 포털, 데이터 세트 품질 모범 사례.
맥락: 공공 인프라로서의 개방형 데이터
이탈리아 공공행정의 공개 데이터는 단순한 투명성 원칙이 아니라 의무입니다. 규정(EU 공개 데이터 지침 2019/1024를 대체하여 입법령 200/2021에 의해 개정된 입법령 36/2006), 경제 혁신의 원동력이자 생태계의 핵심 구성 요소입니다. 디지털 플랫폼 국가 데이터(PDND).
국내 포털 data.gov.itAgID가 관리하는 는 5,000개 이상의 데이터 세트를 수집하고 색인화합니다. 이탈리아 공공행정. 공개된 모든 데이터 - 대중교통 일정부터 결과까지 대기질 데이터부터 지방자치단체 결의안까지 선거는 다음과 같은 기준을 준수해야 합니다. 정확한 메타데이팅, 품질 및 접근성.
개발자에게 있어 정부 공개 데이터의 과제는 두 가지입니다. 출판하다 데이터 다른 한편으로는 실제로 재사용이 가능합니다(기관 사이트의 원시 CSV로는 충분하지 않습니다). 소비하다 다양한 표준과 품질을 지닌 다양한 소스의 이질적인 데이터. 이 문서에서는 두 가지 차원을 모두 다룹니다.
무엇을 배울 것인가
- DCAT-AP_IT 표준: 카탈로그 구조, 데이터 세트, 배포 및 필수 메타데이터
- CKAN: 데이터 세트 관리를 위한 구성, 이탈리아어 확장 및 REST API
- 개방형 데이터를 위한 REST API 설계: 페이지 매김, 필터링, 버전 관리 및 캐싱
- 배포 형식: CSV, JSON-LD, RDF, GeoJSON, Parquet
- 데이터 품질: 검증, 프로파일링 및 DCAT 품질 지표
- 개방형 데이터 소비: 강력한 클라이언트, 오류 처리 및 정규화
- PDND 및 상호 운용성: 국가 플랫폼을 통해 PA API 게시 및 사용
DCAT-AP_IT 표준: 공개 데이터 메타데이팅
이탈리아 프로필 DCAT-AP (데이터 카탈로그 어휘 애플리케이션 프로필), DCAT-AP_IT는 데이터 세트 메타데이터 게시의 표준입니다. 이탈리아 PA에서. AgID에 의해 정의되며 W3C DCAT 사양을 기반으로 하며 특정 확장이 포함되어 있습니다. 이탈리아어 맥락(지리, 라이센스, EUROVOC 테마 등).
DCAT-AP_IT의 주요 구조에는 세 가지 기본 엔터티가 포함됩니다.
- 카탈로그(dcat:카탈로그): PA의 데이터 세트 카탈로그입니다. 조직에 대한 메타데이터가 포함되어 있습니다. 데이터, 기본 라이선스, 홈페이지 및 업데이트 기간을 게시합니다.
- 데이터세트(dcat:Datasets): 정보 자원. 제목, 설명, 주제, 빈도 포함 업데이트 날짜, 생성/수정 날짜, 작성자, 시간 및 지리적 참조, 특정 라이센스.
- 배포(dcat:Distribution): 데이터 세트를 사용할 수 있는 특정 형식 (CSV, JSON, RDF, 쉐이프파일 등). 다운로드 URL, 형식, 크기, 마지막 수정 날짜가 포함됩니다.
# Generatore di metadati DCAT-AP_IT in Python
# Produce RDF/Turtle compatibile con dati.gov.it
from rdflib import Graph, Literal, URIRef, Namespace
from rdflib.namespace import DCAT, DCT, FOAF, RDF, XSD
from datetime import datetime, date
# Namespace italiani
DCATAPIT = Namespace("http://dati.gov.it/onto/dcatapit#")
VCARD = Namespace("http://www.w3.org/2006/vcard/ns#")
SKOS = Namespace("http://www.w3.org/2004/02/skos/core#")
def create_dcat_ap_it_metadata(
catalog_uri: str,
dataset_id: str,
title_it: str,
description_it: str,
publisher_name: str,
publisher_uri: str,
themes: list,
license_uri: str,
distributions: list
) -> str:
"""
Genera metadati DCAT-AP_IT completi in formato Turtle.
"""
g = Graph()
g.bind("dcat", DCAT)
g.bind("dct", DCT)
g.bind("foaf", FOAF)
g.bind("dcatapit", DCATAPIT)
# Definisci il Dataset
dataset_uri = URIRef(f"{catalog_uri}/dataset/{dataset_id}")
g.add((dataset_uri, RDF.type, DCAT.Dataset))
g.add((dataset_uri, RDF.type, DCATAPIT.Dataset))
# Metadati obbligatori
g.add((dataset_uri, DCT.title, Literal(title_it, lang="it")))
g.add((dataset_uri, DCT.description, Literal(description_it, lang="it")))
g.add((dataset_uri, DCT.modified, Literal(datetime.utcnow().date().isoformat(), datatype=XSD.date)))
g.add((dataset_uri, DCT.accrualPeriodicity, URIRef("http://publications.europa.eu/resource/authority/frequency/MONTHLY")))
g.add((dataset_uri, DCT.license, URIRef(license_uri)))
# Publisher (obbligatorio in DCAT-AP_IT)
publisher = URIRef(publisher_uri)
g.add((publisher, RDF.type, DCATAPIT.Agent))
g.add((publisher, FOAF.name, Literal(publisher_name, lang="it")))
g.add((publisher, DCT.identifier, Literal(publisher_uri)))
g.add((dataset_uri, DCT.publisher, publisher))
# Temi dal vocabolario EU EUROVOC
for theme_uri in themes:
g.add((dataset_uri, DCAT.theme, URIRef(theme_uri)))
# Distribuzione per ogni formato
for i, dist in enumerate(distributions):
dist_uri = URIRef(f"{dataset_uri}/distribution/{i}")
g.add((dist_uri, RDF.type, DCAT.Distribution))
g.add((dist_uri, RDF.type, DCATAPIT.Distribution))
g.add((dist_uri, DCAT.accessURL, URIRef(dist["url"])))
g.add((dist_uri, DCT.format, URIRef(f"http://publications.europa.eu/resource/authority/file-type/{dist['format']}")))
g.add((dist_uri, DCT.license, URIRef(license_uri)))
if "bytes" in dist:
g.add((dist_uri, DCAT.byteSize, Literal(dist["bytes"], datatype=XSD.decimal)))
g.add((dataset_uri, DCAT.distribution, dist_uri))
return g.serialize(format="turtle")
# Esempio di utilizzo
rdf_metadata = create_dcat_ap_it_metadata(
catalog_uri="https://dati.comune.milano.it",
dataset_id="qualità-aria-2024",
title_it="Qualità dell'Aria - Rilevazioni 2024",
description_it="Dataset con le rilevazioni orarie dei sensori di qualità dell'aria nel Comune di Milano",
publisher_name="Comune di Milano",
publisher_uri="http://spcdata.digitpa.gov.it/browse/page/Amministrazione/agid",
themes=["http://publications.europa.eu/resource/authority/data-theme/ENVI"],
license_uri="https://creativecommons.org/licenses/by/4.0/",
distributions=[
{"url": "https://dati.comune.milano.it/dataset/aria-2024.csv", "format": "CSV"},
{"url": "https://dati.comune.milano.it/dataset/aria-2024.json", "format": "JSON", "bytes": 45230000},
]
)
CKAN: 이탈리아 PA의 개방형 데이터 포털
CKAN (종합 지식 아카이브 네트워크)는 가장 널리 퍼진 오픈 소스 플랫폼입니다. 정부 오픈데이터 포털 관리를 위해 data.gov.it 자체는 CKAN을 기반으로 구축되었으며 동일합니다. 건축물은 수십 개의 이탈리아 지방 자치 단체, 지역 및 부처에서 사용됩니다.
확장 ckanext-dcatapit, GeoSolutions와 트렌토 자치주가 개발한, DCAT-AP_IT 프로필에 대한 전체 지원을 추가하여 CKAN이 호환 메타데이터를 노출하고 사용할 수 있도록 합니다. 이탈리아 및 유럽 표준에 따릅니다.
# Utilizzo delle API CKAN di dati.gov.it
# Le API CKAN sono REST con risposta JSON standardizzata
import httpx
import asyncio
from typing import Optional, List
class CKANClient:
def __init__(self, base_url: str, api_key: Optional[str] = None):
self.base_url = base_url.rstrip("/")
self.headers = {"Content-Type": "application/json"}
if api_key:
self.headers["Authorization"] = api_key
async def search_datasets(
self,
query: str,
filters: Optional[dict] = None,
rows: int = 20,
start: int = 0
) -> dict:
"""
Cerca dataset nel catalogo CKAN con filtraggio avanzato.
L'API usa Solr internamente per la ricerca full-text.
"""
params = {
"q": query,
"rows": rows,
"start": start,
}
# Filtri Solr per raffinamento
if filters:
fq_parts = [f"{k}:{v}" for k, v in filters.items()]
params["fq"] = " AND ".join(fq_parts)
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/api/3/action/package_search",
params=params,
headers=self.headers,
timeout=30.0
)
response.raise_for_status()
result = response.json()
if not result.get("success"):
raise ValueError(f"CKAN API error: {result.get('error')}")
return {
"total": result["result"]["count"],
"datasets": result["result"]["results"],
"page": start // rows + 1,
"per_page": rows
}
async def get_dataset(self, dataset_id: str) -> dict:
"""Recupera un dataset specifico con tutte le sue distribuzioni."""
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/api/3/action/package_show",
params={"id": dataset_id},
headers=self.headers,
timeout=30.0
)
response.raise_for_status()
result = response.json()
if not result.get("success"):
raise ValueError(f"Dataset not found: {dataset_id}")
return result["result"]
async def create_dataset(self, dataset_metadata: dict) -> dict:
"""Pubblica un nuovo dataset (richiede API key con permessi di scrittura)."""
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/api/3/action/package_create",
json=dataset_metadata,
headers=self.headers,
timeout=60.0
)
response.raise_for_status()
return response.json()["result"]
# Utilizzo pratico
async def main():
client = CKANClient("https://www.dati.gov.it")
# Cerca dataset ambientali aggiornati
results = await client.search_datasets(
query="qualità aria",
filters={"res_format": "CSV", "groups": "ambiente"},
rows=10
)
print(f"Trovati {results['total']} dataset")
for ds in results["datasets"]:
print(f"- {ds['title']} ({ds['num_resources']} risorse)")
asyncio.run(main())
오픈 데이터를 위한 REST API 설계
PA가 (CKAN뿐만 아니라) REST API를 통해 데이터를 게시하는 경우 설계 원칙을 따라야 합니다. 가용성, 안정성, 확장성을 보장합니다. 그만큼 상호 운용성 지침 PA 기술 AgID는 공공 서비스에 따라야 할 REST 패턴을 정의합니다.
페이지 매김 및 필터링
# FastAPI: REST API per open data con paginazione conforme AgID
from fastapi import FastAPI, Query, HTTPException
from fastapi.responses import JSONResponse
from typing import Optional, List
from datetime import date
import math
app = FastAPI(
title="Open Data API - PA Example",
description="API per la pubblicazione di dati aperti - conforme Linee Guida AgID",
version="1.0.0"
)
@app.get("/api/v1/datasets/air-quality",
summary="Rilevazioni qualità aria",
tags=["Environmental Data"],
response_model=dict)
async def get_air_quality(
# Paginazione standard AgID: page + page_size
page: int = Query(default=1, ge=1, description="Numero pagina (da 1)"),
page_size: int = Query(default=100, ge=1, le=1000, description="Elementi per pagina (max 1000)"),
# Filtraggio
station_id: Optional[str] = Query(default=None, description="ID stazione di rilevamento"),
pollutant: Optional[str] = Query(default=None, description="Inquinante (PM2.5, PM10, NO2, O3)"),
date_from: Optional[date] = Query(default=None, description="Data inizio (ISO 8601)"),
date_to: Optional[date] = Query(default=None, description="Data fine (ISO 8601)"),
# Ordinamento
sort_by: str = Query(default="timestamp", description="Campo di ordinamento"),
sort_order: str = Query(default="desc", regex="^(asc|desc)$"),
# Formato output
format: str = Query(default="json", regex="^(json|csv|geojson)$")
):
"""
Restituisce le rilevazioni di qualità dell'aria con paginazione e filtraggio.
Supporta output in JSON, CSV e GeoJSON per compatibilità massima.
Conforme a DCAT-AP_IT e Linee Guida interoperabilità AgID.
"""
# Query al database con parametri
offset = (page - 1) * page_size
records, total_count = await air_quality_service.get_records(
station_id=station_id,
pollutant=pollutant,
date_from=date_from,
date_to=date_to,
sort_by=sort_by,
sort_order=sort_order,
limit=page_size,
offset=offset
)
total_pages = math.ceil(total_count / page_size)
# Risposta con metadati di paginazione (pattern AgID)
response_body = {
"data": records,
"meta": {
"total_count": total_count,
"page": page,
"page_size": page_size,
"total_pages": total_pages,
"has_next": page < total_pages,
"has_prev": page > 1,
},
"links": {
"self": f"/api/v1/datasets/air-quality?page={page}&page_size={page_size}",
"first": f"/api/v1/datasets/air-quality?page=1&page_size={page_size}",
"last": f"/api/v1/datasets/air-quality?page={total_pages}&page_size={page_size}",
"next": f"/api/v1/datasets/air-quality?page={page+1}&page_size={page_size}" if page < total_pages else None,
"prev": f"/api/v1/datasets/air-quality?page={page-1}&page_size={page_size}" if page > 1 else None,
},
"dataset": {
"id": "aria-qualità-2024",
"title": "Qualità dell'Aria",
"license": "CC BY 4.0",
"publisher": "Comune di Milano",
"last_updated": "2024-12-01"
}
}
# Content negotiation: JSON vs CSV vs GeoJSON
if format == "csv":
return Response(
content=records_to_csv(records),
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=aria-qualità.csv"}
)
elif format == "geojson":
return Response(
content=records_to_geojson(records),
media_type="application/geo+json"
)
return JSONResponse(content=response_body)
개방형 데이터 API의 캐싱 및 성능
공개 데이터는 본질적으로 예측 가능한 빈도(일일, 주간, 월간)로 변경됩니다. 이 공격적인 캐싱 전략에 이상적인 후보가 됩니다. 잘 설계된 개방형 데이터 API는 HTTP 헤더를 사용합니다. 캐싱 표준이며 데이터베이스를 건드리지 않고도 대부분의 요청을 처리할 수 있습니다.
# Strategia di caching per open data API con Redis
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
import redis.asyncio as redis
import json
import hashlib
from datetime import timedelta
class OpenDataCacheMiddleware:
"""
Middleware di caching per API open data.
Usa Redis come cache layer con TTL basato sulla frequenza di aggiornamento del dataset.
"""
# TTL per tipo di dataset (in secondi)
DATASET_TTL = {
"realtime": 60, # Dati real-time (qualità aria, traffico)
"daily": 86400, # Aggiornamento giornaliero
"weekly": 604800, # Aggiornamento settimanale
"monthly": 2592000, # Aggiornamento mensile
"static": 31536000, # Dati statici (confini amministrativi)
}
def __init__(self, app, redis_url: str):
self.app = app
self.redis = redis.from_url(redis_url)
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
request = Request(scope, receive)
# Cache solo GET requests
if request.method != "GET":
await self.app(scope, receive, send)
return
# Genera cache key da URL + query params (ordinati per consistenza)
cache_key = self._generate_cache_key(str(request.url))
# Cerca nella cache
cached = await self.redis.get(cache_key)
if cached:
response_data = json.loads(cached)
response = Response(
content=response_data["body"],
status_code=response_data["status_code"],
headers={
**response_data["headers"],
"X-Cache": "HIT",
"Cache-Control": "public, max-age=3600"
}
)
await response(scope, receive, send)
return
# Esegui la request e intercetta la response
response_body = []
async def send_wrapper(message):
if message["type"] == "http.response.body":
response_body.append(message.get("body", b""))
await send(message)
await self.app(scope, receive, send_wrapper)
# Salva in cache
if response_body:
body = b"".join(response_body)
await self.redis.setex(
cache_key,
self.DATASET_TTL["daily"], # Default: aggiornamento giornaliero
json.dumps({"body": body.decode(), "status_code": 200, "headers": {}})
)
def _generate_cache_key(self, url: str) -> str:
"""Genera cache key stabile dall'URL."""
return f"opendata:{hashlib.sha256(url.encode()).hexdigest()[:16]}"
데이터 품질: 검증 및 프로파일링
기술적으로 접근 가능하지만 품질이 낮은 데이터 세트는 실제로 "개방형"이 아닙니다. 용어가 유용합니다. AgID는 2024-2026년 3개년 ICT 계획에서 특정 기능을 정의했습니다. 품질 지표 ISO/IEC 25012 표준의 품질 차원에서 영감을 받은 PA 데이터 세트의 경우:
| 품질 차원 | 정의 | 실용적인 측정법 | AgID 최소 임계값 |
|---|---|---|---|
| 완전성 | 누락된 값 없음 | 전체 중 NULL 필드 % | 필수 필드에 < 5% NULL |
| 정확성 | 현실과의 대응 | 신뢰할 수 있는 소스에 대한 검증 | 도메인에 따라 다릅니다. |
| 일관성 | 데이터 세트의 내부 일관성 | 참조 제약 조건, 범위 확인 | 제약 조건 위반 0% |
| 적시 | 선언된 빈도에 따라 업데이트된 데이터 세트 | 마지막 업데이트 이후 일수와 빈도 비교 | 명시된 기간의 2배 이내에 업데이트됨 |
| 규정 준수 | 표준 준수(DCAT-AP_IT) | SHACL 메타데이터 검증 | 100% 필수 메타데이터 존재 |
# Data Quality Profiler per dataset PA
import pandas as pd
import numpy as np
from dataclasses import dataclass, field
from typing import List, Dict, Any
@dataclass
class QualityReport:
dataset_id: str
total_rows: int
total_columns: int
quality_score: float # 0-100
issues: List[dict] = field(default_factory=list)
column_stats: Dict[str, Any] = field(default_factory=dict)
class DataQualityProfiler:
"""
Profiler di qualità per dataset open data PA.
Calcola metriche ISO/IEC 25012 e produce un report strutturato.
"""
REQUIRED_FIELDS = ["id", "timestamp", "value", "station_code"]
def profile(self, df: pd.DataFrame, dataset_id: str) -> QualityReport:
report = QualityReport(
dataset_id=dataset_id,
total_rows=len(df),
total_columns=len(df.columns),
quality_score=100.0
)
# 1. Completezza: campi obbligatori
for field_name in self.REQUIRED_FIELDS:
if field_name not in df.columns:
report.issues.append({
"severity": "critical",
"dimension": "completeness",
"field": field_name,
"message": f"Campo obbligatorio '{field_name}' mancante"
})
report.quality_score -= 20
else:
null_pct = df[field_name].isna().sum() / len(df) * 100
if null_pct > 5:
report.issues.append({
"severity": "warning",
"dimension": "completeness",
"field": field_name,
"message": f"{null_pct:.1f}% valori NULL nel campo '{field_name}'",
"null_count": int(df[field_name].isna().sum()),
"null_percentage": null_pct
})
report.quality_score -= min(10, null_pct)
# 2. Consistenza: duplicati
duplicate_count = df.duplicated().sum()
if duplicate_count > 0:
dup_pct = duplicate_count / len(df) * 100
report.issues.append({
"severity": "warning",
"dimension": "consistency",
"message": f"{duplicate_count} righe duplicate ({dup_pct:.1f}%)",
"duplicate_count": int(duplicate_count)
})
report.quality_score -= min(15, dup_pct * 2)
# 3. Statistiche per colonna
for col in df.columns:
col_stats = {
"dtype": str(df[col].dtype),
"null_count": int(df[col].isna().sum()),
"null_percentage": df[col].isna().sum() / len(df) * 100,
"unique_count": int(df[col].nunique()),
}
if df[col].dtype in [np.float64, np.int64]:
col_stats.update({
"min": float(df[col].min()),
"max": float(df[col].max()),
"mean": float(df[col].mean()),
"median": float(df[col].median()),
})
report.column_stats[col] = col_stats
report.quality_score = max(0.0, report.quality_score)
return report
오픈 데이터 소비: 강력한 클라이언트
소비 측면에서 공개 데이터 세트에는 특별한 주의가 필요한 특성이 있습니다. 비정기적인 업데이트, 일시적으로 사용이 불가능할 수 있으며, 버전마다 형식이 다를 수 있습니다. 동일한 데이터세트이므로 데이터의 품질이 보장되지 않습니다. 강력한 클라이언트는 이 모든 것을 처리해야 합니다. 이러한 상황.
# Client robusto per consumare open data PA
import httpx
import asyncio
from typing import AsyncIterator
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
class RobustOpenDataClient:
"""
Client per consumo open data con retry, streaming e validazione.
"""
def __init__(self, timeout: int = 60):
self.timeout = timeout
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=4, max=60),
retry=retry_if_exception_type((httpx.HTTPError, httpx.TimeoutException))
)
async def fetch_dataset(self, url: str) -> dict:
"""Scarica un dataset con retry automatico in caso di errore."""
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(url, follow_redirects=True)
response.raise_for_status()
return response.json()
async def stream_large_csv(self, url: str) -> AsyncIterator[dict]:
"""
Streama CSV di grandi dimensioni senza caricare tutto in memoria.
Utile per dataset che possono essere di centinaia di MB.
"""
import csv
import io
async with httpx.AsyncClient(timeout=self.timeout) as client:
async with client.stream("GET", url) as response:
response.raise_for_status()
buffer = ""
headers = None
async for chunk in response.aiter_text(chunk_size=8192):
buffer += chunk
lines = buffer.split("\n")
# Mantieni l'ultima linea incompleta nel buffer
buffer = lines[-1]
complete_lines = lines[:-1]
if headers is None and complete_lines:
headers = list(csv.reader([complete_lines[0]]))[0]
complete_lines = complete_lines[1:]
if headers:
for line in complete_lines:
if line.strip():
row = list(csv.reader([line]))[0]
if len(row) == len(headers):
yield dict(zip(headers, row))
# Utilizzo: import dati ISTAT da API REST
async def import_istat_data():
client = RobustOpenDataClient()
# API ISTAT SDMX REST
istat_url = "https://esploradati.istat.it/SDMXWS/rest/data/IT1,DCSC_POPRES1_EV,1.0/A.IT.9.0?startPeriod=2020"
try:
data = await client.fetch_dataset(istat_url)
# Normalizza il formato SDMX
return normalize_sdmx_response(data)
except httpx.HTTPStatusError as e:
raise RuntimeError(f"ISTAT API error {e.response.status_code}: {e.response.text}")
PDND: 국가 디지털 데이터 플랫폼
La 국가디지털데이터플랫폼(PDND)PagoPA S.p.A.에서 관리하는 인프라입니다. PA가 안전하고 통제된 방식으로 데이터를 공유할 수 있도록 하는 국가 상호 운용성 시스템 추적 가능. 순수한 공개 데이터(누구나 접근 가능한 공개 데이터)와 달리 PDND는 기관 간 합의에 따라 공유가 승인된 민감한 데이터입니다.
개발자에게 PDND 통합은 다음을 의미합니다.
- PDND에 가입하세요 interop.pagopa.it 포털을 통해 사용자 또는 공급자로서
- API 게시 AgID 상호 운용성 지침(OpenAPI 3.1 필수, PDND 설명자를 포함한 e-서비스)
- 인증하다 각 데이터 요청에 대해 X.509 인증서로 서명된 JWT 토큰을 통해
- 이용권을 존중해주세요: 특정 API에 대한 액세스를 승인하는 엔터티 간의 디지털 계약
결론 및 다음 단계
양질의 정부 공개 데이터에는 기관 사이트에 CSV 파일을 게시하는 것 이상의 것이 필요합니다. 표준 메타데이팅(DCAT-AP_IT), 페이지 매김 및 캐싱 기능을 갖춘 잘 설계된 REST API, 지속적인 검증이 필요합니다. 데이터 품질, CKAN 및 PDND와 같은 국가 인프라와의 통합.
이 시리즈의 다음 기사에서는 WCAG 2.1 AA(규제 요구 사항)에 따라 PA에 대한 액세스 가능한 UI를 다룰 것입니다. 공개 데이터와 함께 공공 디지털 서비스를 진정으로 포괄적으로 만드는 데 기여하는 것도 똑같이 중요합니다.
유용한 리소스
- data.gov.it - 국가 오픈 데이터 포털 이탈리아
- DCAT-AP_IT 지침 - AgID 메타데이터 표준
- ckanext-dcatapit - DCAT-AP_IT용 CKAN 확장
- PDND 문서 - 상호 운용성 플랫폼
이 시리즈의 관련 기사
- 정부 기술 #00: 디지털 공공 인프라 - 빌딩 블록 및 아키텍처
- 고브테크 #04: GDPR-by-Design - 공공 서비스를 위한 아키텍처 패턴
- 고브테크 #06: 정부 API 통합 - SPID, CIE 및 pagoPA
- 고브테크 #07: GovStack 빌딩 블록 - 디지털 정부를 위한 모듈







