Centraline Ambientali - Bagnoli
POC (Proof of Concept) per il monitoraggio ambientale dell'area di Bagnoli (NA). Questa documentazione serve come brogliaccio per gli sviluppatori frontend e backend che implementeranno il sistema a microservizi.
Architettura Target
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FRONTEND β
β Angular 19 + SSR β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β
β βDashboard β β Meteo β βInquinantiβ β Admin β β
β βComponent β βComponent β βComponent β βComponent β β
β ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ β
β βββββββββββββββ΄βββββββββββββ΄ββββββββββββββ β
β β HttpClient β
β β + AuthInterceptor β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β REST API (JSON)
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β API GATEWAY β
β Django REST Framework β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β
β β Auth β βCentralineβ β Dati β β Export β β
β β Service β β Service β β Service β β Service β β
β ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ β
β β ββββββββββββββ΄ββββββββββββββ β
β β β β
β ββββββ΄ββββββ βββββββββ΄βββββββββ β
β β SQLite β β PostgreSQL β β
β β (users) β β (read-only) β β
β ββββββββββββ ββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Stack Tecnologico
| Layer | POC (attuale) | Target (produzione) |
|---|---|---|
| Frontend | HTML + Vanilla JS + Chart.js + Leaflet | Angular 19 + ng2-charts + ngx-leaflet |
| API | PHP vanilla (api/index.php) | Django REST Framework 3.15+ |
| Auth | JWT custom (PHP) | djangorestframework-simplejwt |
| DB Dati | PostgreSQL (schema bagnoli_ambiente_dev) - read only | |
| DB Auth | SQLite locale | SQLite o PostgreSQL dedicato |
| Deploy | Git push + webhook | Docker Compose (microservizi) |
Autenticazione
L'autenticazione usa JWT (JSON Web Token). Il flusso e':
- POST
/api/?action=logincon username/password - Ricevi token JWT
- Includi
Authorization: Bearer {token}in ogni richiesta protetta - Token scade dopo 24h
Credenziali default: admin / admin
Request Body
{
"username": "admin",
"password": "admin"
}
Response 200
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 1,
"username": "admin",
"role": "admin"
}
}
Equivalente Django
# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain'),
]
API - Centraline
Lista tutte le centraline con la localizzazione attiva.
Response
{
"data": [
{
"id": 1,
"name": "MMB 274 - Bagnoli - Interno SIN",
"alias": "1793",
"codice": "1793",
"tipo": "ISPRA",
"status": 1,
"colore_mappa": "#16a34a",
"is_manuale": false,
"lat": "40.8127060",
"lng": "14.1677380",
"nome_comune": "Bagnoli",
"nome_provincia": "NA"
}
]
}
Equivalente Django
# views.py
class CentralinaViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Centralina.objects.select_related('localizzazione_attiva').all()
serializer_class = CentralinaSerializer
# serializers.py
class CentralinaSerializer(serializers.ModelSerializer):
lat = serializers.DecimalField(source='localizzazione_attiva.lat')
lng = serializers.DecimalField(source='localizzazione_attiva.lng')
nome_comune = serializers.CharField(source='localizzazione_attiva.nome_comune')
class Meta:
model = Centralina
fields = '__all__'
Dettaglio singola centralina.
| Parametro | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
id | integer | Si | ID centralina |
API - Dati Ambientali
Dati aggregati da tutte le fonti (tabella dati_all). I valori sono separati da ;.
| Parametro | Tipo | Default | Descrizione |
|---|---|---|---|
id | integer | null (tutte) | Filtra per centralina |
from | date | -7 giorni | Data inizio (YYYY-MM-DD) |
to | date | oggi | Data fine |
limit | integer | 500 (max 2000) | Numero max record |
Equivalente Django
# views.py
class DatiAllViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = DatiAllSerializer
filterset_fields = ['id_centralina', 'fonte']
def get_queryset(self):
qs = DatiAll.objects.all()
from_date = self.request.query_params.get('from')
to_date = self.request.query_params.get('to')
if from_date:
qs = qs.filter(data_ora__gte=from_date)
if to_date:
qs = qs.filter(data_ora__lte=to_date + ' 23:59:59')
return qs.order_by('-data_ora')
API - Dati Meteo
Dati meteorologici dalla tabella meteostatnet_dati.
Campi response
| Campo | Tipo | Unita | Descrizione |
|---|---|---|---|
temp | numeric | °C | Temperatura |
dwpt | numeric | °C | Punto di rugiada |
rhum | numeric | % | Umidita relativa |
prcp | numeric | mm | Precipitazioni |
snow | numeric | mm | Neve |
wdir | numeric | ° | Direzione vento |
wspd | numeric | km/h | Velocita vento |
wpgt | numeric | km/h | Raffica max |
pres | numeric | hPa | Pressione atmosferica |
tsun | numeric | min | Durata sole |
coco | integer | - | Codice condizione meteo |
API - Dati ISPRA (Inquinanti)
Dati inquinanti dalle centraline ISPRA (tabelle opas_ispra_1793 e opas_ispra_1798).
Campi response
| Campo | Tipo | Unita | Descrizione |
|---|---|---|---|
so2 | numeric | µg/m³ | Biossido di zolfo |
o3 | numeric | µg/m³ | Ozono |
no2 | numeric | µg/m³ | Biossido di azoto |
co | numeric | mg/m³ | Monossido di carbonio |
pm2_5_fidas | numeric | µg/m³ | Particolato fine PM2.5 |
pm10_fidas | numeric | µg/m³ | Particolato PM10 |
benzene | numeric | µg/m³ | Benzene |
Soglie di legge (riferimento)
| Inquinante | Limite orario | Limite giornaliero | Limite annuale |
|---|---|---|---|
| NO&sub2; | 200 µg/m³ | - | 40 µg/m³ |
| PM10 | - | 50 µg/m³ | 40 µg/m³ |
| PM2.5 | - | - | 25 µg/m³ |
| O&sub3; | 180 µg/m³ (info) | - | - |
| SO&sub2; | 350 µg/m³ | 125 µg/m³ | - |
| CO | - | 10 mg/m³ (8h) | - |
| Benzene | - | - | 5 µg/m³ |
API - Statistiche
Statistiche aggregate (medie, min, max) per il periodo richiesto.
| Parametro | Default | Descrizione |
|---|---|---|
id | null | Filtra centralina ISPRA |
days | 30 (max 365) | Giorni indietro |
API - Export (Autenticato)
Esporta dati in JSON o CSV. Richiede token JWT.
| Parametro | Valori | Descrizione |
|---|---|---|
format | json, csv | Formato export |
Backend Django - Struttura Progetto
centraline-backend/
βββ manage.py
βββ requirements.txt
βββ docker-compose.yml
βββ Dockerfile
βββ config/
β βββ settings/
β β βββ base.py # Settings comuni
β β βββ dev.py # Dev settings
β β βββ prod.py # Prod settings
β βββ urls.py # URL router principale
β βββ wsgi.py
βββ apps/
β βββ auth_app/ # Microservizio Autenticazione
β β βββ models.py # User model custom
β β βββ serializers.py
β β βββ views.py
β β βββ urls.py
β βββ centraline/ # Microservizio Centraline
β β βββ models.py # Centralina, Localizzazione
β β βββ serializers.py
β β βββ views.py
β β βββ urls.py
β β βββ filters.py
β βββ dati/ # Microservizio Dati Ambientali
β β βββ models.py # DatiAll, MeteostatnetDati, OpasIspra
β β βββ serializers.py
β β βββ views.py
β β βββ urls.py
β β βββ filters.py
β βββ export/ # Microservizio Export
β βββ views.py
β βββ urls.py
β βββ renderers.py # CSV renderer custom
βββ tests/
βββ test_auth.py
βββ test_centraline.py
βββ test_dati.py
Django - Models
# apps/centraline/models.py
from django.db import models
class Centralina(models.Model):
name = models.CharField(max_length=255)
alias = models.CharField(max_length=100)
codice = models.CharField(max_length=50)
tipo = models.CharField(max_length=20) # ISPRA, meteostat, INVITALIA, manuale
external_id = models.CharField(max_length=100, null=True, blank=True)
url = models.CharField(max_length=200, null=True, blank=True)
status = models.IntegerField(default=1) # 1=attiva, 0=inattiva
config_json = models.JSONField(null=True, blank=True)
last_import = models.DateTimeField(null=True, blank=True)
colore_mappa = models.CharField(max_length=20, null=True, blank=True)
is_manuale = models.BooleanField(null=True, blank=True)
raggio_mappa = models.IntegerField(null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
managed = False # DB read-only
db_table = 'centralina'
class CentralinaLocalizzazione(models.Model):
id_centralina = models.ForeignKey(Centralina, on_delete=models.CASCADE,
db_column='id_centralina')
lat = models.DecimalField(max_digits=12, decimal_places=7, null=True)
lng = models.DecimalField(max_digits=12, decimal_places=7, null=True)
id_comune = models.CharField(max_length=20, null=True)
nome_comune = models.CharField(max_length=100, null=True)
id_provincia = models.CharField(max_length=20, null=True)
nome_provincia = models.CharField(max_length=100, null=True)
data_inizio = models.DateField()
data_fine = models.DateField(null=True, blank=True)
stato = models.IntegerField(default=1)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
managed = False
db_table = 'centralina_localizzazione'
# apps/dati/models.py
class DatiAll(models.Model):
id_centralina = models.ForeignKey('centraline.Centralina', on_delete=models.CASCADE,
db_column='id_centralina')
fonte = models.CharField(max_length=20)
valori = models.TextField()
data_ora = models.DateTimeField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
managed = False
db_table = 'dati_all'
class MeteostatnetDati(models.Model):
id_centralina = models.ForeignKey('centraline.Centralina', on_delete=models.CASCADE,
db_column='id_centralina')
time = models.DateTimeField()
temp = models.DecimalField(max_digits=6, decimal_places=2, null=True)
dwpt = models.DecimalField(max_digits=6, decimal_places=2, null=True)
rhum = models.DecimalField(max_digits=6, decimal_places=2, null=True)
prcp = models.DecimalField(max_digits=6, decimal_places=2, null=True)
snow = models.DecimalField(max_digits=6, decimal_places=2, null=True)
wdir = models.DecimalField(max_digits=6, decimal_places=2, null=True)
wspd = models.DecimalField(max_digits=6, decimal_places=2, null=True)
wpgt = models.DecimalField(max_digits=6, decimal_places=2, null=True)
pres = models.DecimalField(max_digits=8, decimal_places=2, null=True)
tsun = models.DecimalField(max_digits=6, decimal_places=2, null=True)
coco = models.IntegerField(null=True)
stato = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
managed = False
db_table = 'meteostatnet_dati'
class OpasIspra(models.Model):
"""Modello base per le tabelle ISPRA (1793 e 1798)"""
id_centralina = models.ForeignKey('centraline.Centralina', on_delete=models.CASCADE,
db_column='id_centralina')
so2 = models.DecimalField(max_digits=10, decimal_places=2, null=True)
o3 = models.DecimalField(max_digits=10, decimal_places=2, null=True)
no2 = models.DecimalField(max_digits=10, decimal_places=2, null=True)
co = models.DecimalField(max_digits=10, decimal_places=2, null=True)
pm2_5_fidas = models.DecimalField(max_digits=10, decimal_places=2, null=True)
pm10_fidas = models.DecimalField(max_digits=10, decimal_places=2, null=True)
benzene = models.DecimalField(max_digits=10, decimal_places=2, null=True)
stato = models.BooleanField(default=True)
data_ora = models.DateTimeField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class OpasIspra1793(OpasIspra):
class Meta:
managed = False
db_table = 'opas_ispra_1793'
class OpasIspra1798(OpasIspra):
class Meta:
managed = False
db_table = 'opas_ispra_1798'
Django - Serializers
# apps/centraline/serializers.py
from rest_framework import serializers
from .models import Centralina, CentralinaLocalizzazione
class LocalizzazioneSerializer(serializers.ModelSerializer):
class Meta:
model = CentralinaLocalizzazione
fields = ['lat', 'lng', 'nome_comune', 'nome_provincia', 'data_inizio', 'data_fine']
class CentralinaListSerializer(serializers.ModelSerializer):
localizzazione = serializers.SerializerMethodField()
class Meta:
model = Centralina
fields = '__all__'
def get_localizzazione(self, obj):
loc = obj.centralinalocalizzazione_set.filter(stato=1).first()
if loc:
return LocalizzazioneSerializer(loc).data
return None
# apps/dati/serializers.py
class MeteoSerializer(serializers.ModelSerializer):
class Meta:
model = MeteostatnetDati
fields = ['id', 'id_centralina', 'time', 'temp', 'dwpt', 'rhum',
'prcp', 'snow', 'wdir', 'wspd', 'wpgt', 'pres', 'tsun', 'coco']
class IspraSerializer(serializers.ModelSerializer):
class Meta:
model = OpasIspra1793 # Stessa struttura per entrambe
fields = ['id', 'id_centralina', 'data_ora', 'so2', 'o3', 'no2',
'co', 'pm2_5_fidas', 'pm10_fidas', 'benzene']
Django - Views / ViewSets
# apps/centraline/views.py
from rest_framework import viewsets, permissions
from django_filters.rest_framework import DjangoFilterBackend
class CentralinaViewSet(viewsets.ReadOnlyModelViewSet):
"""
list: GET /api/v1/centraline/
detail: GET /api/v1/centraline/{id}/
"""
queryset = Centralina.objects.all().order_by('id')
serializer_class = CentralinaListSerializer
permission_classes = [permissions.AllowAny]
# apps/dati/views.py
class MeteoViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = MeteoSerializer
permission_classes = [permissions.AllowAny]
filter_backends = [DjangoFilterBackend]
def get_queryset(self):
qs = MeteostatnetDati.objects.all()
from_date = self.request.query_params.get('from')
to_date = self.request.query_params.get('to')
if from_date:
qs = qs.filter(time__gte=from_date)
if to_date:
qs = qs.filter(time__lte=f"{to_date} 23:59:59")
return qs.order_by('-time')[:int(self.request.query_params.get('limit', 500))]
# apps/export/views.py
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
class ExportView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
format = request.query_params.get('format', 'json')
# ... query logic ...
if format == 'csv':
return self.render_csv(data)
return Response({'data': data})
Django - URLs
# config/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
router = DefaultRouter()
router.register(r'centraline', CentralinaViewSet)
router.register(r'meteo', MeteoViewSet, basename='meteo')
router.register(r'ispra', IspraViewSet, basename='ispra')
urlpatterns = [
path('api/v1/', include(router.urls)),
path('api/v1/token/', TokenObtainPairView.as_view()),
path('api/v1/token/refresh/', TokenRefreshView.as_view()),
path('api/v1/export/', ExportView.as_view()),
path('api/v1/stats/', StatsView.as_view()),
path('api/v1/latest/', LatestView.as_view()),
]
Frontend Angular - Struttura Progetto
centraline-frontend/
βββ angular.json
βββ package.json
βββ Dockerfile
βββ src/
β βββ app/
β β βββ app.config.ts
β β βββ app.routes.ts
β β βββ core/
β β β βββ services/
β β β β βββ api.service.ts # HttpClient wrapper
β β β β βββ auth.service.ts # Login, logout, JWT
β β β β βββ centraline.service.ts # CRUD centraline
β β β βββ interceptors/
β β β β βββ auth.interceptor.ts # JWT auto-inject
β β β βββ guards/
β β β β βββ auth.guard.ts # Route protection
β β β βββ models/
β β β βββ centralina.model.ts
β β β βββ meteo.model.ts
β β β βββ ispra.model.ts
β β βββ features/
β β β βββ public/
β β β β βββ dashboard/
β β β β β βββ dashboard.component.ts
β β β β β βββ dashboard.component.html
β β β β βββ meteo/
β β β β β βββ meteo-chart.component.ts
β β β β βββ inquinanti/
β β β β β βββ inquinanti-chart.component.ts
β β β β βββ mappa/
β β β β βββ mappa.component.ts
β β β βββ admin/
β β β β βββ config/
β β β β β βββ config.component.ts
β β β β βββ export/
β β β β β βββ export.component.ts
β β β β βββ raw/
β β β β βββ raw-explorer.component.ts
β β β βββ auth/
β β β βββ login/
β β β βββ login.component.ts
β β βββ shared/
β β βββ components/
β β β βββ navbar/
β β β βββ stat-card/
β β β βββ chart-panel/
β β β βββ data-table/
β β βββ pipes/
β β βββ date-format.pipe.ts
β βββ environments/
β β βββ environment.ts
β β βββ environment.prod.ts
β βββ styles.scss
Angular - Services
// src/app/core/services/centraline.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from '../../../environments/environment';
import { Centralina, MeteoData, IspraData, StatsData } from '../models';
@Injectable({ providedIn: 'root' })
export class CentralineService {
private http = inject(HttpClient);
private apiUrl = environment.apiUrl; // es: 'https://centraline.analist24.it.com/api/v1'
getCentraline(): Observable<Centralina[]> {
return this.http.get<{data: Centralina[]}>(`${this.apiUrl}/centraline/`)
.pipe(map(res => res.data));
}
getCentralina(id: number): Observable<Centralina> {
return this.http.get<{data: Centralina}>(`${this.apiUrl}/centraline/${id}/`)
.pipe(map(res => res.data));
}
getMeteo(from?: string, to?: string, limit = 500): Observable<MeteoData[]> {
let params = new HttpParams();
if (from) params = params.set('from', from);
if (to) params = params.set('to', to);
params = params.set('limit', limit.toString());
return this.http.get<{data: MeteoData[]}>(`${this.apiUrl}/meteo/`, { params })
.pipe(map(res => res.data));
}
getIspra(id?: number, from?: string, to?: string): Observable<IspraData[]> {
let params = new HttpParams();
if (id) params = params.set('id', id.toString());
if (from) params = params.set('from', from);
if (to) params = params.set('to', to);
return this.http.get<{data: IspraData[]}>(`${this.apiUrl}/ispra/`, { params })
.pipe(map(res => res.data));
}
getStats(id?: number, days = 30): Observable<StatsData> {
let params = new HttpParams().set('days', days.toString());
if (id) params = params.set('id', id.toString());
return this.http.get<{data: StatsData}>(`${this.apiUrl}/stats/`, { params })
.pipe(map(res => res.data));
}
}
// src/app/core/services/auth.service.ts
@Injectable({ providedIn: 'root' })
export class AuthService {
private http = inject(HttpClient);
private router = inject(Router);
login(username: string, password: string): Observable<{token: string, user: any}> {
return this.http.post<{token: string, user: any}>(
`${environment.apiUrl}/token/`,
{ username, password }
).pipe(
tap(res => {
localStorage.setItem('auth_token', res.token);
localStorage.setItem('auth_user', JSON.stringify(res.user));
})
);
}
logout(): void {
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_user');
this.router.navigate(['/login']);
}
isAuthenticated(): boolean {
return !!localStorage.getItem('auth_token');
}
getToken(): string | null {
return localStorage.getItem('auth_token');
}
}
// src/app/core/interceptors/auth.interceptor.ts
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = localStorage.getItem('auth_token');
if (token) {
req = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
}
return next(req);
};
Angular - Components (Esempio)
// src/app/features/public/dashboard/dashboard.component.ts
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [CommonModule, MappaComponent, MeteoChartComponent,
InquinantiChartComponent, StatCardComponent],
template: `
<div class="grid grid-4">
@for (card of statCards(); track card.label) {
<app-stat-card [value]="card.value" [label]="card.label" [type]="card.type" />
}
</div>
<app-mappa [centraline]="centraline()" />
<app-meteo-chart [data]="meteoData()" />
<app-inquinanti-chart [data]="ispraData()" />
`
})
export class DashboardComponent {
private service = inject(CentralineService);
centraline = signal<Centralina[]>([]);
meteoData = signal<MeteoData[]>([]);
ispraData = signal<IspraData[]>([]);
statCards = computed(() => this.buildStatCards());
constructor() {
this.loadData();
}
async loadData() {
const [centraline, meteo, ispra] = await Promise.all([
firstValueFrom(this.service.getCentraline()),
firstValueFrom(this.service.getMeteo()),
firstValueFrom(this.service.getIspra()),
]);
this.centraline.set(centraline);
this.meteoData.set(meteo);
this.ispraData.set(ispra);
}
}
Angular - Routing & Guards
// src/app/app.routes.ts
export const routes: Routes = [
{ path: '', component: DashboardComponent },
{ path: 'login', component: LoginComponent },
{
path: 'admin',
canActivate: [authGuard],
children: [
{ path: '', redirectTo: 'config', pathMatch: 'full' },
{ path: 'config', component: ConfigComponent },
{ path: 'export', component: ExportComponent },
{ path: 'raw', component: RawExplorerComponent },
]
},
];
// src/app/core/guards/auth.guard.ts
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isAuthenticated()) return true;
router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
return false;
};
Database - Schema ER
ββββββββββββββββββββββββ βββββββββββββββββββββββββββββββ
β centralina β β centralina_localizzazione β
ββββββββββββββββββββββββ€ βββββββββββββββββββββββββββββββ€
β PK id βββββββββ FK id_centralina β
β name β β lat, lng β
β alias β β nome_comune β
β codice β β nome_provincia β
β tipo β β data_inizio, data_fine β
β status β β stato β
β config_json β βββββββββββββββββββββββββββββββ
β colore_mappa β
β is_manuale β βββββββββββββββββββββββββββββββ
β last_import β β dati_all β
βββββββββ¬βββββββββββββββ βββββββββββββββββββββββββββββββ€
β β FK id_centralina β
ββββββββββββββββββββββββ fonte β
β β valori (text, sep=;) β
β β data_ora β
β βββββββββββββββββββββββββββββββ
β
β βββββββββββββββββββββββββββββββββββββββββ
β β meteostatnet_dati β
β βββββββββββββββββββββββββββββββββββββββββ€
βββββ FK id_centralina β
β β time, temp, dwpt, rhum, prcp β
β β snow, wdir, wspd, wpgt, pres β
β β tsun, coco β
β βββββββββββββββββββββββββββββββββββββββββ
β
β βββββββββββββββββββββββββββββββββββββββββ
β β opas_ispra_1793 / opas_ispra_1798 β
β βββββββββββββββββββββββββββββββββββββββββ€
βββββ FK id_centralina β
β data_ora β
β so2, o3, no2, co β
β pm2_5_fidas, pm10_fidas, benzene β
βββββββββββββββββββββββββββββββββββββββββ
Database - Dettaglio Tabelle
Database PostgreSQL: devbagnolicrm, schema: bagnoli_ambiente_dev
| Tabella | Record | Descrizione | Tipo dati |
|---|---|---|---|
centralina | 8 | Anagrafica centraline | ISPRA, meteostat, INVITALIA, manuale |
centralina_localizzazione | 3 | Coordinate GPS (storicizzate) | lat/lng, comune, provincia |
dati_all | 576 | Dati aggregati da tutte le fonti | Valori separati da ; |
meteostatnet_dati | 2.375 | Dati meteo orari | Temperatura, umidita, vento, pressione |
opas_ispra_1793 | 2.352 | Inquinanti centralina 1793 | SO2, O3, NO2, CO, PM2.5, PM10, benzene |
opas_ispra_1798 | 1.680 | Inquinanti centralina 1798 | Stessi campi di 1793 |
invitalia | 0 | Dati cantiere Invitalia | Parametri ambientali orari/giornalieri |
invitalia_dati | 0 | Campioni manuali | PM10, PM2.5, codici filtro |
Centraline attive
| ID | Nome | Tipo | Codice | Zona |
|---|---|---|---|---|
| 1 | MMB 274 - Bagnoli - Interno SIN | ISPRA | 1793 | Bagnoli (NA) |
| 2 | LM03 - Bagnoli - Citta della scienza | ISPRA | 1798 | Bagnoli (NA) |
| 3 | Napoli Capodichino | meteostat | METEO-16289 | Capodichino |
| 4 | CENTRALINA_MANUALE | manuale | 1122 | Bagnoli (NA) |
| 7-10 | AR_CO_ST.1/3/4/5 | INVITALIA | AR_CO_ST.* | Cantieri Bagnoli |
managed = False).
Il DB di autenticazione locale (SQLite) e' l'unico scrivibile.