feat: ingestion automatique toutes les 6h + indicateur fraîcheur UI

- Symfony Scheduler (MainSchedule) : cron 0,6,12,18h UTC
- IngestionScheduleMessage + IngestionScheduleHandler : pipeline complet
  ingestion → forecasts → impact scores pour toutes les zones Antilles
- messenger.yaml : transport scheduler_main + async Redis (rs971_async)
- entrypoint.sh : 2 workers en background (scheduler_main + async)
- GET /api/status : date/heure de la dernière observation
- SargassesMap : badge "Données du JJ/MM à HH:MM" en bas à gauche

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gwadaking
2026-04-03 22:31:58 -04:00
parent 55caca001c
commit 48e6e5060c
9 changed files with 223 additions and 0 deletions

View File

@@ -40,6 +40,9 @@ export const api = {
feedback: {
create: (data) => post('/feedback', data),
},
status: {
get: () => get('/status'),
},
push: {
vapidKey: () => get('/push/vapid-public-key'),
subscribe: (data) => post('/push/subscribe', data),

View File

@@ -1,3 +1,18 @@
/* ── Indicateur fraîcheur données ── */
.smap-freshness {
position: absolute;
bottom: 28px;
left: 10px;
background: rgba(255, 255, 255, 0.82);
backdrop-filter: blur(4px);
border-radius: 6px;
padding: 4px 10px;
font-size: 11px;
color: #555;
pointer-events: none;
z-index: 10;
}
/* ── Spot markers ── */
.smap-marker {
cursor: pointer;

View File

@@ -156,6 +156,18 @@ export default function SargassesMap({ onSpotSelect, selectedSpotId, activeStep,
const [viewState, setViewState] = useState(INITIAL_VIEW);
const [layerOpacity, setLayerOpacity] = useState(1);
const [mapStyle, setMapStyle] = useState(FALLBACK_STYLE);
const [dataDate, setDataDate] = useState(null);
// Fraîcheur des données
useEffect(() => {
api.status.get()
.then(s => {
if (s.lastObservation?.detectedAt) {
setDataDate(new Date(s.lastObservation.detectedAt));
}
})
.catch(() => {});
}, []);
// Load cartoon map style
useEffect(() => {
@@ -254,6 +266,14 @@ export default function SargassesMap({ onSpotSelect, selectedSpotId, activeStep,
>
<NavigationControl position="bottom-right" />
{dataDate && (
<div className="smap-freshness">
Données du {dataDate.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' })}
{' à '}
{dataDate.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}
</div>
)}
{obsGeoJSON && (
<Source id="observations" type="geojson" data={obsGeoJSON}>
<Layer {...OBS_STYLES.fill} />