[{"data":1,"prerenderedAt":1225},["ShallowReactive",2],{"profil-header-en":3,"profil-footer-en":93,"i-lucide:menu":115,"i-simple-icons:github":120,"i-simple-icons:linkedin":122,"i-lucide:mail":125,"profil-projets-en":127,"projets-liste-en":149},{"id":4,"accroche":5,"apropos":6,"competences":11,"cv":71,"email":72,"extension":73,"localisation":74,"meta":75,"nom":76,"reseaux":77,"stem":90,"titre":91,"__hash__":92},"profil_en\u002Fen\u002Fprofil.yml","I design and build web applications end to end — from the database to the pixel — and the AI tooling that plugs into them. Vue and Nuxt on the interface, Python and Node on the server, Docker and a VPS to ship it.",[7,8,9,10],"I'm a full-stack developer, and I build whole products rather than pieces of products: data model, API, interface, deployment and monitoring. That wide view is what interests me — understanding how a schema decision ripples all the way out to the user experience.","My personal projects tend to revolve around geographic data and real time: mapping wildfires from satellite feeds, merging public real-estate sources, tracking satellites in orbit. I like subjects where the raw data is thankless and the work consists of making it readable.","I work in Docker end to end, I host on my own servers, and I document what I build — because a project you can't pick back up six months later isn't really finished.","I didn't come to development through computing, but through objects: industrial product design first, then real-time 3D. That's where I picked up a taste for hard constraints and geometry, and the habit of starting from a brief rather than from a technology. Code showed up as the most direct way to automate whatever was slowing me down — and never left.",[12,21,30,39,47,55,62],{"categorie":13,"icon":14,"items":15},"Front-end","lucide:layout-dashboard",[16,17,18,19,20],"Vue 3","Nuxt","TypeScript","Tailwind CSS","MapLibre GL",{"categorie":22,"icon":23,"items":24},"Back-end","lucide:server",[25,26,27,28,29],"Python","FastAPI","Node.js","AdonisJS","PHP",{"categorie":31,"icon":32,"items":33},"Data","lucide:database",[34,35,36,37,38],"PostgreSQL","PostGIS","MongoDB","Redis","SQLite",{"categorie":40,"icon":41,"items":42},"AI & Data science","lucide:brain-circuit",[43,44,45,46],"TensorFlow","Keras","Vertex AI","pandas",{"categorie":48,"icon":49,"items":50},"Infrastructure","lucide:container",[51,52,53,54],"Docker","nginx","Caddy","GitHub Actions",{"categorie":56,"icon":57,"items":58},"Mobile","lucide:smartphone",[59,60,61],"SwiftUI","SwiftData","iOS",{"categorie":63,"icon":64,"items":65},"Method & product","lucide:git-branch",[66,67,68,69,70],"GitHub Flow","Testing","Documentation","Product Ownership","Agile","","rs.szmygiel@gmail.com","yml","Bordeaux, France",{},"Ronan Szmygiel",[78,82,86],{"nom":79,"url":80,"icon":81},"GitHub","https:\u002F\u002Fgithub.com\u002Fnanro22","simple-icons:github",{"nom":83,"url":84,"icon":85},"LinkedIn","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fszmygiel-ronan\u002F","simple-icons:linkedin",{"nom":87,"url":88,"icon":89},"Email","mailto:rs.szmygiel@gmail.com","lucide:mail","en\u002Fprofil","AI & Web Developer \u002F Product Owner","ZIzTObRSPg6iBvcjNxFpN9tVpN-w_PoqFkwZVJ9VFb8",{"id":4,"accroche":5,"apropos":94,"competences":95,"cv":71,"email":72,"extension":73,"localisation":74,"meta":110,"nom":76,"reseaux":111,"stem":90,"titre":91,"__hash__":92},[7,8,9,10],[96,98,100,102,104,106,108],{"categorie":13,"icon":14,"items":97},[16,17,18,19,20],{"categorie":22,"icon":23,"items":99},[25,26,27,28,29],{"categorie":31,"icon":32,"items":101},[34,35,36,37,38],{"categorie":40,"icon":41,"items":103},[43,44,45,46],{"categorie":48,"icon":49,"items":105},[51,52,53,54],{"categorie":56,"icon":57,"items":107},[59,60,61],{"categorie":63,"icon":64,"items":109},[66,67,68,69,70],{},[112,113,114],{"nom":79,"url":80,"icon":81},{"nom":83,"url":84,"icon":85},{"nom":87,"url":88,"icon":89},{"left":116,"top":116,"width":117,"height":117,"rotate":116,"vFlip":118,"hFlip":118,"body":119},0,24,false,"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 5h16M4 12h16M4 19h16\"\u002F>",{"left":116,"top":116,"width":117,"height":117,"rotate":116,"vFlip":118,"hFlip":118,"body":121},"\u003Cpath fill=\"currentColor\" d=\"M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\"\u002F>",{"left":116,"top":116,"width":117,"height":117,"rotate":116,"vFlip":118,"hFlip":118,"body":123,"hidden":124},"\u003Cpath fill=\"currentColor\" d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037c-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85c3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.06 2.06 0 0 1-2.063-2.065a2.064 2.064 0 1 1 2.063 2.065m1.782 13.019H3.555V9h3.564zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0z\"\u002F>",true,{"left":116,"top":116,"width":117,"height":117,"rotate":116,"vFlip":118,"hFlip":118,"body":126},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"m22 7l-8.991 5.727a2 2 0 0 1-2.009 0L2 7\"\u002F>\u003Crect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"\u002F>\u003C\u002Fg>",{"id":4,"accroche":5,"apropos":128,"competences":129,"cv":71,"email":72,"extension":73,"localisation":74,"meta":144,"nom":76,"reseaux":145,"stem":90,"titre":91,"__hash__":92},[7,8,9,10],[130,132,134,136,138,140,142],{"categorie":13,"icon":14,"items":131},[16,17,18,19,20],{"categorie":22,"icon":23,"items":133},[25,26,27,28,29],{"categorie":31,"icon":32,"items":135},[34,35,36,37,38],{"categorie":40,"icon":41,"items":137},[43,44,45,46],{"categorie":48,"icon":49,"items":139},[51,52,53,54],{"categorie":56,"icon":57,"items":141},[59,60,61],{"categorie":63,"icon":64,"items":143},[66,67,68,69,70],{},[146,147,148],{"nom":79,"url":80,"icon":81},{"nom":83,"url":84,"icon":85},{"nom":87,"url":88,"icon":89},[150,239,384,512,685,790,855,1002],{"id":151,"title":152,"architecture":153,"body":166,"cover":71,"description":219,"extension":220,"featured":118,"gallery":221,"highlights":222,"interface":124,"meta":226,"navigation":124,"path":227,"rang":228,"repo":71,"role":229,"seo":230,"site":231,"status":232,"stem":233,"tags":234,"type":236,"year":237,"__hash__":238},"projets_en\u002Fprojets\u002Fcatan.md","Catan",[154,157,160,163],{"label":155,"description":156},"State","Game state lives entirely on the server, never in the client.",{"label":158,"description":159},"Validation","Every action is checked server-side — turn, resources, legality — before it is applied.",{"label":161,"description":162},"Broadcast","The new state is pushed back to every participant over WebSocket.",{"label":164,"description":165},"Persistence","Games and move history kept in PostgreSQL, environment started with a single docker compose command.",{"type":167,"value":168,"toc":213},"minimark",[169,174,178,182,189,192,195,199,210],[170,171,173],"h2",{"id":172},"the-project","The project",[175,176,177],"p",{},"A digital version of Catan, playable multiplayer in the browser: randomly\ngenerated board, settlement and road placement, resource production, trading\nbetween players, the robber and victory point counting.",[170,179,181],{"id":180},"architecture","Architecture",[175,183,184,188],{},[185,186,187],"strong",{},"Stack",": Vue 3 · FastAPI · PostgreSQL · WebSocket",[175,190,191],{},"Game state lives on the server, never in the client. Every action a player sends\nis validated server-side — is it their turn, do they have the resources, is the\nspot legal — before it is applied and pushed back to every participant.",[175,193,194],{},"It's the only defensible architecture for a multiplayer game: a client that\nworks out for itself what it's allowed to do is a client you can tamper with.",[170,196,198],{"id":197},"what-the-game-teaches","What the game teaches",[175,200,201,202,205,206,209],{},"Catan is an excellent modelling exercise. The board is a hexagonal tiling where\nsettlements sit on ",[185,203,204],{},"vertices"," and roads on ",[185,207,208],{},"edges"," — so three coordinate\nsystems coexist, and half the work is moving cleanly between them: which tiles\ntouch this vertex, which edges start here, which vertices are too close to take\na new settlement.",[175,211,212],{},"Once that geometry is laid down correctly, the game rules become almost trivial\nto implement. Laid down crooked, every rule becomes a special case.",{"title":71,"searchDepth":214,"depth":214,"links":215},2,[216,217,218],{"id":172,"depth":214,"text":173},{"id":180,"depth":214,"text":181},{"id":197,"depth":214,"text":198},"A multiplayer web adaptation of the board game Catan, playable together in a browser in real time.","md",[],[223,224,225],"Multiplayer games synchronised in real time over WebSocket","Game rules and move validation entirely server-side","Full stack up and running with a single docker compose command",{},"\u002Fprojets\u002Fcatan",5,"Full-stack design and development",{"title":152,"description":219},"https:\u002F\u002Fcatan.szmygiel.com","Live","projets\u002Fcatan",[16,26,25,34,235,51],"WebSocket","Web",2025,"uC6d3QfDReLfb9V3QMHz_nksiLKIELYYau75-kASmt4",{"id":240,"title":241,"architecture":242,"body":254,"cover":357,"description":358,"extension":220,"featured":124,"gallery":359,"highlights":369,"interface":124,"meta":373,"navigation":124,"path":374,"rang":375,"repo":71,"role":229,"seo":376,"site":71,"status":377,"stem":378,"tags":379,"type":236,"year":237,"__hash__":383},"projets_en\u002Fprojets\u002Festimap.md","Estimap",[243,246,249,251],{"label":244,"description":245},"Frontend","Nuxt 3 + MapLibre GL (PMTiles) — map, property sheet, back office.",{"label":247,"description":248},"Backend","FastAPI + SQLAlchemy, Celery for long-running work, two separate queues (imports\u002Fmutations).",{"label":31,"description":250},"PostgreSQL \u002F PostGIS (~100M rows) for the spatial layer, Redis as broker and cache.",{"label":252,"description":253},"Infra","Docker Compose, workers split by queue.",{"type":167,"value":255,"toc":349},[256,260,263,266,270,273,283,285,309,321,328,332,335,339,342,346],[170,257,259],{"id":258},"the-problem","The problem",[175,261,262],{},"French real-estate data is public and free. It is also scattered: transactions\nsit in the DVF sales register, parcel geometry in the cadastre, building\ncharacteristics in the BDNB registry, energy performance in the DPE dataset,\ndemographics at INSEE, hazard exposure somewhere else again — and plenty more:\nlocal amenities, transport, fibre coverage, schools, building permits,\ncondominiums.",[175,264,265],{},"Each has its own format, its own granularity and its own pivot identifier.\nCross-referencing \"the price per square metre of houses built before 1975 with\nan F or G energy rating in a flood-prone area\" therefore takes a considerable\namount of upfront merging.",[170,267,269],{"id":268},"the-product-goal","The product goal",[175,271,272],{},"A property price estimation engine — the map is the exploration surface, but the\npoint is to feed a model with genuinely cross-referenced features.",[175,274,275,278,279,282],{},[185,276,277],{},"Phase 1"," (weighted kNN comparables, progressive radii, plausibility guards)\nis in production and answers 100% of estimation requests today. ",[185,280,281],{},"Phase 2","\n(LightGBM, 27 features) is in final training before promotion, behind an\nautomatic gate that compares any new model against the one currently served.",[170,284,181],{"id":180},[286,287,288,294,299,304],"ul",{},[289,290,291,293],"li",{},[185,292,244],{},": Nuxt 3 + MapLibre GL, PMTiles — map, property sheet, back office",[289,295,296,298],{},[185,297,247],{},": FastAPI + SQLAlchemy, with Celery for long-running work",[289,300,301,303],{},[185,302,31],{},": PostgreSQL \u002F PostGIS for the spatial layer, Redis as broker and cache",[289,305,306,308],{},[185,307,252],{},": docker-compose, workers split by queue",[175,310,311,312,316,317,320],{},"Workers are split across two queues: ",[313,314,315],"code",{},"imports"," for source ingestion, and\n",[313,318,319],{},"mutations"," for rebuilds, enrichment, tile generation and model training. That\nseparation keeps a multi-hour import from blocking a tile rebuild that takes a\nfew minutes.",[175,322,323,324,327],{},"An ",[313,325,326],{},"\u002Fadmin"," back office drives 15 source connectors and tracks import state.",[170,329,331],{"id":330},"what-the-map-covers","What the map covers",[175,333,334],{},"Around 7 million property sales (18M raw rows in the sales register) shown as\npoints coloured by price per square metre or as a heatmap, filterable by type,\nprice, energy rating and pool. A click opens the property sheet: sale history,\nextended energy rating, building context (outbuildings, fibre, school with its\nsocial index, condominium, recent permits, parks, water, noise), live regulatory\nhazards, and nearby amenities that can be layered onto the map.",[170,336,338],{"id":337},"method","Method",[175,340,341],{},"The project rests on documentation in ten files — architecture, sources, data\nmodel, pipelines, API, frontend, back office, runbook, roadmap — kept current\nwith every significant change. The runbook in particular records the\ninfrastructure traps learned the hard way, which saves relearning them.",[170,343,345],{"id":344},"what-i-took-away","What I took away",[175,347,348],{},"On this kind of project the difficulty is never the algorithm: it's reconciling\nidentifiers across registries that were never designed to talk to each other. A\ncadastral parcel and a building record share no key — it has to be rebuilt\nspatially, and you have to accept an imperfect match rate. And a quality metric\n(a backtest median absolute percentage error, say) is only worth what its\nprotocol is worth: a temporal leak between comparables and the tested sale\nskewed the headline number for a long time.",{"title":71,"searchDepth":214,"depth":214,"links":350},[351,352,353,354,355,356],{"id":258,"depth":214,"text":259},{"id":268,"depth":214,"text":269},{"id":180,"depth":214,"text":181},{"id":330,"depth":214,"text":331},{"id":337,"depth":214,"text":338},{"id":344,"depth":214,"text":345},"\u002Fprojets\u002Festimap-carte.png","An interactive map of France merging 16 public real-estate data sources — sales records, cadastre, building registry, energy ratings, census, hazards, schools, transport — behind a price estimation engine.",[360,363,366],{"url":361,"caption":362},"\u002Fprojets\u002Festimap-fiche-bien.png","Property sheet — sale, energy rating, official hazard exposure, context and nearby amenities.",{"url":364,"caption":365},"\u002Fprojets\u002Festimap-tableau-de-bord.png","Back office — service health, sales tiles and live job tracking.",{"url":367,"caption":368},"\u002Fprojets\u002Festimap-sources.png","Back office — driving the 15 data-source connectors.",[370,371,372],"16 public data sources merged across ~7 million sales","Estimation engine in production (kNN comparables, LightGBM in the wings)","A 14-step enrichment pipeline driven from a back office",{},"\u002Fprojets\u002Festimap",3,{"title":241,"description":358},"Completed","projets\u002Festimap",[380,20,26,381,35,37,382],"Nuxt 3","Celery","LightGBM","r1KLUD8K6yLxger7FFaaA_6RvAUCkR2_Jt1TMYJEcQM",{"id":385,"title":386,"architecture":387,"body":400,"cover":494,"description":495,"extension":220,"featured":118,"gallery":496,"highlights":497,"interface":118,"meta":501,"navigation":124,"path":502,"rang":503,"repo":71,"role":229,"seo":504,"site":505,"status":232,"stem":506,"tags":507,"type":236,"year":510,"__hash__":511},"projets_en\u002Fprojets\u002Fglobe-satellites.md","Globe Satellites",[388,391,394,397],{"label":389,"description":390},"Sources","TLEs refreshed every 2h from Celestrak, with automatic failover to a backup proxy when the network blocks them.",{"label":392,"description":393},"Compute","Vectorised SGP4 propagation (SatrecArray + NumPy) with Skyfield — every satellite recomputed in a single call every 60 seconds.",{"label":395,"description":396},"Delivery","One shared computation broadcast over WebSocket to every connected client, in parallel.",{"label":398,"description":399},"Rendering","3D globe (Globe.gl \u002F Three.js), a single InstancedMesh for all satellites, ARM64 Docker image on a Raspberry Pi 4.",{"type":167,"value":401,"toc":487},[402,404,407,410,414,440,444,455,459,472,476],[170,403,173],{"id":172},[175,405,406],{},"Several thousand active satellites circle above our heads, and their orbits are\npublic. Celestrak publishes orbital elements in the TLE format (Two-Line\nElement), which is enough to compute the position of any object at any instant.",[175,408,409],{},"Globe Satellites shows that ballet on a 3D globe, refreshed every sixty seconds.",[170,411,413],{"id":412},"features","Features",[286,415,416,422,428,434],{},[289,417,418,421],{},[185,419,420],{},"Filtering by category"," — starlink, oneweb, communications, navigation,\nstations, weather, Earth observation, science, other — clickable in the HUD,\nwith \"Show all\" \u002F \"Hide all\" buttons",[289,423,424,427],{},[185,425,426],{},"Hover tooltip"," — name, altitude and category for each satellite",[289,429,430,433],{},[185,431,432],{},"Logarithmic radial scale"," — visibly separates low, medium and\ngeostationary orbits instead of stacking them on top of each other",[289,435,436,439],{},[185,437,438],{},"Automatic WebSocket reconnection"," (exponential backoff), with a watchdog\nthat catches a frozen connection with no clean TCP close",[170,441,443],{"id":442},"computing-on-the-server","Computing on the server",[175,445,446,447,450,451,454],{},"The structural choice is to ",[185,448,449],{},"compute server-side",". SGP4 orbital propagation\nfor several thousand objects, once a minute, is not work for the browser — all\nthe more so since every connected client shares exactly the same result. The\nserver computes once (",[313,452,453],{},"SatrecArray"," + NumPy, a single vectorised call rather\nthan a Python loop — roughly 24x faster), broadcasts to everyone over WebSocket\nin parallel, and the client only renders.",[170,456,458],{"id":457},"network-resilience","Network resilience",[175,460,461,462,465,466,471],{},"TLEs lose accuracy as the days pass — propagating from week-old elements drifts\nnoticeably — hence an automatic refresh every two hours. Loading follows a\nthree-tier chain: the full Celestrak group first, a fallback to 37 groups\ndownloaded in parallel if that fails or returns a 403, then a backup proxy if\nboth fail — a sign of a network block rather than an isolated incident. It\nreturns to the direct route on its own, with no manual action. Data freshness is\nexposed through a ",[313,463,464],{},"\u002Fhealth"," endpoint, which pushes best-effort\n",[467,468,470],"a",{"href":469},"\u002Fen\u002Fprojects\u002Fvigie","Vigie"," monitoring for the refresh cycle.",[170,473,475],{"id":474},"the-hardware-constraint","The hardware constraint",[175,477,478,479,482,483,486],{},"The project is built for a ",[185,480,481],{},"Raspberry Pi 4 on ARM64",". That constraint drove\nseveral decisions: a multi-architecture Docker image, vectorised NumPy\ncomputation rather than a Python loop, a single Three.js ",[313,484,485],{},"InstancedMesh"," rather\nthan one object per satellite, and broadcasting one shared state rather than\ncomputing per connected client.",{"title":71,"searchDepth":214,"depth":214,"links":488},[489,490,491,492,493],{"id":172,"depth":214,"text":173},{"id":412,"depth":214,"text":413},{"id":442,"depth":214,"text":443},{"id":457,"depth":214,"text":458},{"id":474,"depth":214,"text":475},"\u002Fprojets\u002Fglobe-satellites-carte.png","An interactive 3D globe showing the live position of active satellites in Earth orbit, built to run on a Raspberry Pi.",[],[498,499,500],"Positions recomputed every 60 seconds, vectorised SGP4 propagation (~24x faster than a loop)","Automatic failover to a backup proxy if Celestrak becomes unreachable","Every satellite rendered in a single draw call (Three.js InstancedMesh)",{},"\u002Fprojets\u002Fglobe-satellites",4,{"title":386,"description":495},"https:\u002F\u002Fglobe.szmygiel.com","projets\u002Fglobe-satellites",[16,508,26,25,235,51,509],"Globe.gl","Raspberry Pi",2026,"TS8Sx0gdo3anp3sYPh3mls3nfLiVHR3E-exVBbiOAZM",{"id":513,"title":514,"architecture":515,"body":526,"cover":664,"description":665,"extension":220,"featured":124,"gallery":666,"highlights":673,"interface":124,"meta":677,"navigation":124,"path":678,"rang":214,"repo":71,"role":229,"seo":679,"site":680,"status":232,"stem":681,"tags":682,"type":236,"year":510,"__hash__":684},"projets_en\u002Fprojets\u002Fpyro-radar.md","Pyro Radar",[516,518,521,524],{"label":389,"description":517},"Six satellite feeds aggregated — NASA FIRMS, GOES-18\u002F19, Meteosat MTG, Himawari-9, Sentinel-3.",{"label":519,"description":520},"Ingestion","One ingester per source (CSV, NetCDF, HDF5) normalises into a shared schema of geolocated detections.",{"label":522,"description":523},"Storage","Burned areas estimated as polygons directly in PostGIS, in batches.",{"label":398,"description":525},"MapLibre GL vector tiles, real-time delivery over Server-Sent Events.",{"type":167,"value":527,"toc":658},[528,532,535,538,542,616,619,623,629,635,641,647,653,655],[170,529,531],{"id":530},"the-idea","The idea",[175,533,534],{},"Fire detection data is public, free and abundant — NASA, ESA, NOAA and JAXA\nbroadcast it continuously. It also comes in heterogeneous formats, at different\ncadences, with geographic coverage that only partly overlaps.",[175,536,537],{},"Pyro Radar merges those feeds into a single worldwide map, refreshed continuously.",[170,539,541],{"id":540},"the-sources","The sources",[543,544,545,561],"table",{},[546,547,548],"thead",{},[549,550,551,555,558],"tr",{},[552,553,554],"th",{},"Source",[552,556,557],{},"Coverage",[552,559,560],{},"Cadence",[562,563,564,576,587,597,607],"tbody",{},[549,565,566,570,573],{},[567,568,569],"td",{},"NASA FIRMS (VIIRS \u002F MODIS)",[567,571,572],{},"Worldwide",[567,574,575],{},"Orbital passes",[549,577,578,581,584],{},[567,579,580],{},"GOES-18 \u002F 19",[567,582,583],{},"Americas",[567,585,586],{},"10 min",[549,588,589,592,595],{},[567,590,591],{},"Meteosat MTG (LSA SAF)",[567,593,594],{},"Europe \u002F Africa",[567,596,586],{},[549,598,599,602,605],{},[567,600,601],{},"Himawari-9",[567,603,604],{},"Asia \u002F Oceania",[567,606,586],{},[549,608,609,612,614],{},[567,610,611],{},"Sentinel-3 SLSTR (Copernicus)",[567,613,572],{},[567,615,575],{},[175,617,618],{},"Geostationary satellites bring the frequency, polar orbits bring the resolution.",[170,620,622],{"id":621},"the-technical-work","The technical work",[175,624,625,628],{},[185,626,627],{},"Ingestion."," CSV for FIRMS, NetCDF for GOES, HDF5 for MTG: one ingester per\nsource normalises into a shared schema of geolocated detections.",[175,630,631,634],{},[185,632,633],{},"Burned areas."," Point detections become estimated polygons directly in\nPostGIS, in batches — loading every zone at once was taking Postgres down.",[175,636,637,640],{},[185,638,639],{},"Wind."," GFS data is only decoded around active fires. The worldwide grid\nwould have weighed several gigabytes per cycle for information that only matters\nover a few dozen square kilometres.",[175,642,643,646],{},[185,644,645],{},"Real time."," Server-Sent Events: no polling, and no bidirectional WebSocket\nwhere a one-way stream is enough.",[175,648,649,652],{},[185,650,651],{},"Mapping."," MapLibre GL, OpenFreeMap basemap and Esri satellite view — no API\nkey, so no quota and no surprise bill.",[170,654,345],{"id":344},[175,656,657],{},"Adding a coverage area is one line of SQL. That design constraint, set early,\nkept the geographic logic from scattering across the application code.",{"title":71,"searchDepth":214,"depth":214,"links":659},[660,661,662,663],{"id":530,"depth":214,"text":531},{"id":540,"depth":214,"text":541},{"id":621,"depth":214,"text":622},{"id":344,"depth":214,"text":345},"\u002Fprojets\u002Fpyro-radar-carte.png","Real-time wildfire tracking worldwide, built from six satellite sources aggregated onto a single vector map.",[667,670],{"url":668,"caption":669},"\u002Fprojets\u002Fpyro-radar-points-chauds.png","Hotspots — individual detections with confidence and per-click detail.",{"url":671,"caption":672},"\u002Fprojets\u002Fpyro-radar-historique.png","30-day history — cumulative fire count and estimated burned area.",[674,675,676],"Six satellite sources aggregated, from polar orbits to geostationary","Burned-area estimates computed directly in PostGIS","Real-time delivery over Server-Sent Events, no polling",{},"\u002Fprojets\u002Fpyro-radar",{"title":514,"description":665},"https:\u002F\u002Fpyroradar.com","projets\u002Fpyro-radar",[16,20,28,35,34,683],"SSE","_v_G5VhIz3MViZGbQOxvjVqVhYkR5cKTu99Y43u2FR8",{"id":686,"title":687,"architecture":688,"body":700,"cover":71,"description":774,"extension":220,"featured":118,"gallery":775,"highlights":776,"interface":124,"meta":780,"navigation":124,"path":781,"rang":782,"repo":71,"role":783,"seo":784,"site":71,"status":377,"stem":785,"tags":786,"type":56,"year":510,"__hash__":789},"projets_en\u002Fprojets\u002Frootine.md","Rootine",[689,691,694,697],{"label":164,"description":690},"Habits and check-offs stored locally with SwiftData, with no external dependency.",{"label":692,"description":693},"Check-off","A Yesterday \u002F Today toggle to check off retroactively, without breaking streaks.",{"label":695,"description":696},"Visualisation","Monthly heatmap calendar where colour intensity reflects the completion rate.",{"label":698,"description":699},"Interface","Spring animations and haptic feedback on every check, in SwiftUI.",{"type":167,"value":701,"toc":768},[702,708,710,726,728,734,740,746,752,756,759,762,765],[703,704,705],"blockquote",{},[175,706,707],{},"Habits are the roots of who you become.",[170,709,173],{"id":172},[175,711,712,713,717,718,721,722,725],{},"Rootine — a play on ",[714,715,716],"em",{},"root"," and ",[714,719,720],{},"routine"," — is a habit tracker built around one\nsimple principle: ",[185,723,724],{},"consistency beats intensity",". Ten minutes of exercise every\nday is worth more than one two-hour session a week.",[170,727,413],{"id":412},[175,729,730,733],{},[185,731,732],{},"Home"," — the checklist of today's habits, with an animated checkbox (spring\nand haptic feedback), an overall progress bar and a congratulations message when\neverything is done. A Yesterday \u002F Today toggle allows checking off retroactively.",[175,735,736,739],{},[185,737,738],{},"Routines"," — creating, editing and deleting the habits you track.",[175,741,742,745],{},[185,743,744],{},"Calendar"," — a monthly heatmap where colour intensity reflects that day's\ncompletion rate. Consistency becomes visible at a glance.",[175,747,748,751],{},[185,749,750],{},"Statistics"," — current streaks, completion rate, trends over time.",[170,753,755],{"id":754},"the-stance","The stance",[175,757,758],{},"Most habit apps punish forgetting: broken streaks, lost badges, guilt-tripping\nnotifications. That lever motivates for a week, then produces the opposite\neffect — you uninstall rather than face the dashboard.",[175,760,761],{},"Rootine takes the opposite stance. You can check off yesterday, no streak breaks\nfor good, and the tone stays encouraging. The point is to keep going for months,\nnot to win a score.",[170,763,187],{"id":764},"stack",[175,766,767],{},"SwiftUI and SwiftData, with no external dependency. All data stays on the device.",{"title":71,"searchDepth":214,"depth":214,"links":769},[770,771,772,773],{"id":172,"depth":214,"text":173},{"id":412,"depth":214,"text":413},{"id":754,"depth":214,"text":755},{"id":764,"depth":214,"text":187},"A minimal, forgiving iOS habit tracker — today's checklist, a heatmap calendar and consistency statistics.",[],[777,778,779],"Retroactive check-off for yesterday, without the guilt trip","Monthly heatmap calendar and trend statistics","Spring animations and haptic feedback on every check",{},"\u002Fprojets\u002Frootine",8,"iOS design and development",{"title":687,"description":774},"projets\u002Frootine",[59,60,61,787,788],"Swift","Charts","-jczwI2MfId-BcdlMwAxDkg111FvQlpAM7nKRbRdCv8",{"id":791,"title":792,"architecture":793,"body":804,"cover":71,"description":841,"extension":220,"featured":118,"gallery":842,"highlights":843,"interface":124,"meta":847,"navigation":124,"path":848,"rang":849,"repo":71,"role":783,"seo":850,"site":71,"status":377,"stem":851,"tags":852,"type":56,"year":510,"__hash__":854},"projets_en\u002Fprojets\u002Fsofa.md","Sofa",[794,797,799,802],{"label":795,"description":796},"Search","TMDB API queries for title search and detail sheets.",{"label":522,"description":798},"Library and watch history persisted with SwiftData, readable offline.",{"label":800,"description":801},"Enrichment","Available streaming platforms pulled from TMDB.",{"label":698,"description":803},"Dark design system inspired by cinema auditoriums, in native SwiftUI.",{"type":167,"value":805,"toc":835},[806,808,811,813,820,823,827,830,832],[170,807,173],{"id":172},[175,809,810],{},"Sofa answers an everyday problem: knowing what to watch, and above all\nremembering what you meant to watch. The app lets you search for a film or\nseries through the TMDB API, add it to your library, mark what you've seen, rate\ntitles and see where to stream them.",[170,812,187],{"id":764},[175,814,815,816,819],{},"A ",[185,817,818],{},"100% native iOS"," app, written in SwiftUI with SwiftData for persistence. No\ncross-platform framework, no WebView: the animations, the gestures and the\nsystem behaviour are those of an iOS app, because that is precisely what\nseparates an app you keep from an app you delete.",[175,821,822],{},"SwiftData handles local persistence, which makes the library readable offline —\nonly search and detail refreshes need the network.",[170,824,826],{"id":825},"design","Design",[175,828,829],{},"A dark interface, inspired by the atmosphere of a cinema: deep backgrounds,\nposters given room to breathe, high-contrast typography. The design system is\ndefined once — colours, spacing, type — then applied consistently across every\nscreen.",[170,831,345],{"id":344},[175,833,834],{},"Coming from the web, SwiftUI asks you to unlearn a few reflexes. The layout\nsystem doesn't work like Flexbox, and state propagation follows its own rules.\nIn exchange, the integration with the system — widgets, sharing, dark mode,\naccessibility — is on a different level from anything you get in a web app.",{"title":71,"searchDepth":214,"depth":214,"links":836},[837,838,839,840],{"id":172,"depth":214,"text":173},{"id":764,"depth":214,"text":187},{"id":825,"depth":214,"text":826},{"id":344,"depth":214,"text":345},"A native iOS watchlist app for films and series — TMDB search, personal library, watch tracking and ratings.",[],[844,845,846],"Local persistence with SwiftData, usable offline","TMDB integration — search, detail sheets and streaming platforms","Dark design system inspired by cinema auditoriums",{},"\u002Fprojets\u002Fsofa",7,{"title":792,"description":841},"projets\u002Fsofa",[59,60,61,853,787],"TMDB API","L9BjW5Ev8RnMN90W7J3oAsl5_rNEZpLJ4xhveeWZqgw",{"id":856,"title":857,"architecture":858,"body":869,"cover":983,"description":984,"extension":220,"featured":118,"gallery":985,"highlights":986,"interface":118,"meta":990,"navigation":124,"path":991,"rang":992,"repo":71,"role":993,"seo":994,"site":71,"status":377,"stem":995,"tags":996,"type":31,"year":237,"__hash__":1001},"projets_en\u002Fprojets\u002Fthanos.md","Thanos",[859,861,864,867],{"label":31,"description":860},"Data Manager — fetching and caching historical series per exchange and timeframe.",{"label":862,"description":863},"Indicators","Custom Indicators — technical indicators implemented by hand rather than imported.",{"label":865,"description":866},"Backtest","Backtest Analysis — performance and risk metrics across five strategy families.",{"label":695,"description":868},"Plot Analysis — entries, exits and equity curves.",{"type":167,"value":870,"toc":977},[871,873,876,880,934,937,941,967,971,974],[170,872,173],{"id":172},[175,874,875],{},"Thanos is a complete environment for prototyping, testing and comparing trading\nstrategies across several exchanges and timeframes. The goal isn't to trade:\nit's to have an honest test bed for the question \"would this idea have worked,\nand at what cost in risk\".",[170,877,879],{"id":878},"strategies-implemented","Strategies implemented",[543,881,882,892],{},[546,883,884],{},[549,885,886,889],{},[552,887,888],{},"Strategy",[552,890,891],{},"Principle",[562,893,894,902,910,918,926],{},[549,895,896,899],{},[567,897,898],{},"SMA",[567,900,901],{},"Simple moving average crossover",[549,903,904,907],{},[567,905,906],{},"EMA",[567,908,909],{},"Exponential moving average crossover",[549,911,912,915],{},[567,913,914],{},"Momentum (RSI)",[567,916,917],{},"Entries on oversold \u002F overbought zones",[549,919,920,923],{},[567,921,922],{},"SMA-EMA combined",[567,924,925],{},"Cross-confirmation between the two moving average families",[549,927,928,931],{},[567,929,930],{},"TRIX",[567,932,933],{},"Triple exponential smoothing oscillator",[175,935,936],{},"They all share the same interface, which makes it possible to evaluate them on\nidentical ground — same data, same fees, same metrics.",[170,938,940],{"id":939},"modules","Modules",[286,942,943,949,955,961],{},[289,944,945,948],{},[185,946,947],{},"Data Manager"," — fetching and caching historical series per exchange and timeframe",[289,950,951,954],{},[185,952,953],{},"Custom Indicators"," — technical indicators implemented by hand rather than imported",[289,956,957,960],{},[185,958,959],{},"Backtest Analysis"," — performance and risk metrics",[289,962,963,966],{},[185,964,965],{},"Plot Analysis"," — visualising entries, exits and equity curves",[170,968,970],{"id":969},"the-real-lesson","The real lesson",[175,972,973],{},"The hard part of a backtest isn't making it run: it's stopping it from lying.\nBiases slip in easily — using data that wasn't yet available at decision time,\nignoring fees and slippage, or tuning parameters on the same period used to\nvalidate the strategy.",[175,975,976],{},"A strategy that posts a spectacular backtest result is almost always the sign of\na data leak somewhere, not of a discovery.",{"title":71,"searchDepth":214,"depth":214,"links":978},[979,980,981,982],{"id":172,"depth":214,"text":173},{"id":878,"depth":214,"text":879},{"id":939,"depth":214,"text":940},{"id":969,"depth":214,"text":970},"\u002Fprojets\u002Fthanos-cover.svg","A research, backtesting and analysis platform for trading strategies on cryptocurrency markets.",[],[987,988,989],"Five strategy families comparable on a single test bed","Custom technical indicators implemented by hand","Historical data cached across multiple exchanges and timeframes",{},"\u002Fprojets\u002Fthanos",6,"Design and development",{"title":857,"description":984},"projets\u002Fthanos",[25,997,998,999,1000],"Pandas","NumPy","Backtesting","Data analysis","dSBxOYef84ONZdN6irpkgiOWjftugwl2o8W9AqvRZUY",{"id":1003,"title":470,"architecture":1004,"body":1015,"cover":1200,"description":1201,"extension":220,"featured":124,"gallery":1202,"highlights":1212,"interface":124,"meta":1217,"navigation":124,"path":1218,"rang":1219,"repo":71,"role":229,"seo":1220,"site":1221,"status":232,"stem":1222,"tags":1223,"type":236,"year":510,"__hash__":1224},"projets_en\u002Fprojets\u002Fvigie.md",[1005,1008,1010,1012],{"label":1006,"description":1007},"API","AdonisJS v6, TypeScript, Lucid — business logic and endpoints.",{"label":31,"description":1009},"PostgreSQL 17, progressive rollup (fine → hourly → daily) to keep retention affordable.",{"label":698,"description":1011},"Vue 3, Vite, Pinia — dashboard and public status page.",{"label":1013,"description":1014},"Exposure","Docker Compose on a VPS (Caddy, GitHub Actions), plus a read-only MCP server to query monitoring from the editor.",{"type":167,"value":1016,"toc":1192},[1017,1019,1022,1025,1029,1061,1064,1066,1113,1133,1137,1140,1154,1158,1165,1183,1185],[170,1018,259],{"id":258},[175,1020,1021],{},"I had a dozen projects online and no overall view: did last night's cron run? Is\nthe API still answering? How many visitors this week? Every answer meant logging\ninto a different server.",[175,1023,1024],{},"Plugging a third-party service into each project would have multiplied the\naccounts, the costs and the cookie banners. I preferred to build the single tool\nI was missing.",[170,1026,1028],{"id":1027},"what-vigie-does","What Vigie does",[286,1030,1031,1037,1043,1049,1055],{},[289,1032,1033,1036],{},[185,1034,1035],{},"Visit analytics"," with no cookie and no persistent identifier — no consent banner required",[289,1038,1039,1042],{},[185,1040,1041],{},"Uptime monitoring",": regular HTTP probes with history and uptime calculation",[289,1044,1045,1048],{},[185,1046,1047],{},"Scheduled-job supervision",": a cron that hasn't checked in within its window raises an alert",[289,1050,1051,1054],{},[185,1052,1053],{},"Error tracking"," for the application errors reported by the connected projects",[289,1056,1057,1060],{},[185,1058,1059],{},"A public status page",", fed by the same data as the internal dashboard",[175,1062,1063],{},"Every monitored project has its own API key, and never sees another project's data.",[170,1065,181],{"id":180},[543,1067,1068,1078],{},[546,1069,1070],{},[549,1071,1072,1075],{},[552,1073,1074],{},"Layer",[552,1076,1077],{},"Choice",[562,1079,1080,1087,1095,1102],{},[549,1081,1082,1084],{},[567,1083,1006],{},[567,1085,1086],{},"AdonisJS v6, TypeScript, Lucid",[549,1088,1089,1092],{},[567,1090,1091],{},"Database",[567,1093,1094],{},"PostgreSQL 17",[549,1096,1097,1099],{},[567,1098,698],{},[567,1100,1101],{},"Vue 3, Vite, Pinia, TypeScript",[549,1103,1104,1107],{},[567,1105,1106],{},"Deployment",[567,1108,1109,1110],{},"Docker Compose on a VPS, Caddy in front, GitHub Actions on ",[313,1111,1112],{},"main",[175,1114,1115,1116,1119,1120,1119,1123,1119,1126,1119,1129,1132],{},"Everything runs in containers — ",[313,1117,1118],{},"postgres",", ",[313,1121,1122],{},"redis",[313,1124,1125],{},"api",[313,1127,1128],{},"worker",[313,1130,1131],{},"web"," —\nwith no Node required on the host machine. The worker handles probes and\nrollups outside the request cycle, which keeps the API responsive no matter how\nmany projects are being monitored.",[170,1134,1136],{"id":1135},"the-client-side-agent","The client-side agent",[175,1138,1139],{},"Collecting means putting code inside someone else's page: a bug here doesn't\ndegrade Vigie, it degrades the site being monitored. So the script stays under\ntwo kilobytes, with no cookie and no browser fingerprint — and the budget is\nchecked at build time, so the build fails rather than shipping something heavier.",[175,1141,1142,1143,717,1146,1149,1150,1153],{},"It wraps ",[313,1144,1145],{},"pushState",[313,1147,1148],{},"replaceState"," to catch navigations in a single-page\napp, then restores the original methods: nothing is permanently altered for the\nhost application. A ",[313,1151,1152],{},"@vigie\u002Fjs"," plugin hooks straight into the Vue 3 router\ninstead of guessing from history events.",[170,1155,1157],{"id":1156},"an-mcp-server-to-stay-in-the-editor","An MCP server, to stay in the editor",[175,1159,1160,1161,1164],{},"What's broken? Is this error new? Did last night's cron run? Vigie exposes six\nread-only tools over ",[185,1162,1163],{},"MCP"," to answer those questions without opening the\ndashboard.",[175,1166,1167,1168,1171,1172,1119,1175,1178,1179,1182],{},"Two deliberate choices: ",[185,1169,1170],{},"no writes at all"," — an assistant that reads gets the\ndiagnosis wrong, an assistant that writes gets production wrong; and ",[185,1173,1174],{},"internal\ncodes translated on the way out",[313,1176,1177],{},"cause: \"silent agent\""," rather than\n",[313,1180,1181],{},"reason: 5",", because a model facing an unknown code doesn't stop, it guesses.",[170,1184,345],{"id":344},[175,1186,1187,1188,1191],{},"The hard part wasn't collection but ",[185,1189,1190],{},"retention",": keeping years of measurement\npoints without blowing up the database. The answer was progressive rollup —\nfine-grained data ages into hourly averages, then daily ones.",{"title":71,"searchDepth":214,"depth":214,"links":1193},[1194,1195,1196,1197,1198,1199],{"id":258,"depth":214,"text":259},{"id":1027,"depth":214,"text":1028},{"id":180,"depth":214,"text":181},{"id":1135,"depth":214,"text":1136},{"id":1156,"depth":214,"text":1157},{"id":344,"depth":214,"text":345},"\u002Fprojets\u002Fvigie-dashboard.png","Unified monitoring for my web projects — cookieless analytics, uptime checks, scheduled-job tracking and a public status page.",[1203,1206,1209],{"url":1204,"caption":1205},"\u002Fprojets\u002Fvigie-erreurs.png","Application errors — grouped by fingerprint, with latest occurrences and stack trace.",{"url":1207,"caption":1208},"\u002Fprojets\u002Fvigie-taches.png","Scheduled jobs — state, run history and an incident when one fails.",{"url":1210,"caption":1211},"\u002Fprojets\u002Fvigie-disponibilite.png","Uptime — URL checks with response time and 30-day history.",[1213,1214,1215,1216],"Multi-project with strict data isolation per API key","Cookieless visit analytics, so no consent banner","Alerts and an automatically generated public status page","Read-only MCP server — monitoring you can query from the editor",{},"\u002Fprojets\u002Fvigie",1,{"title":470,"description":1201},"https:\u002F\u002Fvigie.watch","projets\u002Fvigie",[28,16,18,34,37,51,1163],"RiBHY7jRZ-V3wJoap5kgVKpqBMn-pw7QB4wK5yabSNI",1788530203852]