[{"data":1,"prerenderedAt":1275},["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,"projet-courant-en-\u002Fprojets\u002Fglobe-satellites":127,"projets-navigation-en":270,"i-lucide:arrow-right":1273},{"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":128,"title":129,"architecture":130,"body":143,"cover":247,"description":248,"extension":249,"featured":118,"gallery":250,"highlights":251,"interface":118,"meta":255,"navigation":124,"path":256,"rang":257,"repo":71,"role":258,"seo":259,"site":260,"status":261,"stem":262,"tags":263,"type":267,"year":268,"__hash__":269},"projets_en\u002Fprojets\u002Fglobe-satellites.md","Globe Satellites",[131,134,137,140],{"label":132,"description":133},"Sources","TLEs refreshed every 2h from Celestrak, with automatic failover to a backup proxy when the network blocks them.",{"label":135,"description":136},"Compute","Vectorised SGP4 propagation (SatrecArray + NumPy) with Skyfield — every satellite recomputed in a single call every 60 seconds.",{"label":138,"description":139},"Delivery","One shared computation broadcast over WebSocket to every connected client, in parallel.",{"label":141,"description":142},"Rendering","3D globe (Globe.gl \u002F Three.js), a single InstancedMesh for all satellites, ARM64 Docker image on a Raspberry Pi 4.",{"type":144,"value":145,"toc":239},"minimark",[146,151,155,158,162,191,195,207,211,224,228],[147,148,150],"h2",{"id":149},"the-project","The project",[152,153,154],"p",{},"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.",[152,156,157],{},"Globe Satellites shows that ballet on a 3D globe, refreshed every sixty seconds.",[147,159,161],{"id":160},"features","Features",[163,164,165,173,179,185],"ul",{},[166,167,168,172],"li",{},[169,170,171],"strong",{},"Filtering by category"," — starlink, oneweb, communications, navigation,\nstations, weather, Earth observation, science, other — clickable in the HUD,\nwith \"Show all\" \u002F \"Hide all\" buttons",[166,174,175,178],{},[169,176,177],{},"Hover tooltip"," — name, altitude and category for each satellite",[166,180,181,184],{},[169,182,183],{},"Logarithmic radial scale"," — visibly separates low, medium and\ngeostationary orbits instead of stacking them on top of each other",[166,186,187,190],{},[169,188,189],{},"Automatic WebSocket reconnection"," (exponential backoff), with a watchdog\nthat catches a frozen connection with no clean TCP close",[147,192,194],{"id":193},"computing-on-the-server","Computing on the server",[152,196,197,198,201,202,206],{},"The structural choice is to ",[169,199,200],{},"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 (",[203,204,205],"code",{},"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.",[147,208,210],{"id":209},"network-resilience","Network resilience",[152,212,213,214,217,218,223],{},"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 ",[203,215,216],{},"\u002Fhealth"," endpoint, which pushes best-effort\n",[219,220,222],"a",{"href":221},"\u002Fen\u002Fprojects\u002Fvigie","Vigie"," monitoring for the refresh cycle.",[147,225,227],{"id":226},"the-hardware-constraint","The hardware constraint",[152,229,230,231,234,235,238],{},"The project is built for a ",[169,232,233],{},"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 ",[203,236,237],{},"InstancedMesh"," rather\nthan one object per satellite, and broadcasting one shared state rather than\ncomputing per connected client.",{"title":71,"searchDepth":240,"depth":240,"links":241},2,[242,243,244,245,246],{"id":149,"depth":240,"text":150},{"id":160,"depth":240,"text":161},{"id":193,"depth":240,"text":194},{"id":209,"depth":240,"text":210},{"id":226,"depth":240,"text":227},"\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.","md",[],[252,253,254],"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,"Full-stack design and development",{"title":129,"description":248},"https:\u002F\u002Fglobe.szmygiel.com","Live","projets\u002Fglobe-satellites",[16,264,26,25,265,51,266],"Globe.gl","WebSocket","Raspberry Pi","Web",2026,"TS8Sx0gdo3anp3sYPh3mls3nfLiVHR3E-exVBbiOAZM",[271,348,490,560,733,838,903,1050],{"id":272,"title":273,"architecture":274,"body":287,"cover":71,"description":333,"extension":249,"featured":118,"gallery":334,"highlights":335,"interface":124,"meta":339,"navigation":124,"path":340,"rang":341,"repo":71,"role":258,"seo":342,"site":343,"status":261,"stem":344,"tags":345,"type":267,"year":346,"__hash__":347},"projets_en\u002Fprojets\u002Fcatan.md","Catan",[275,278,281,284],{"label":276,"description":277},"State","Game state lives entirely on the server, never in the client.",{"label":279,"description":280},"Validation","Every action is checked server-side — turn, resources, legality — before it is applied.",{"label":282,"description":283},"Broadcast","The new state is pushed back to every participant over WebSocket.",{"label":285,"description":286},"Persistence","Games and move history kept in PostgreSQL, environment started with a single docker compose command.",{"type":144,"value":288,"toc":328},[289,291,294,298,304,307,310,314,325],[147,290,150],{"id":149},[152,292,293],{},"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.",[147,295,297],{"id":296},"architecture","Architecture",[152,299,300,303],{},[169,301,302],{},"Stack",": Vue 3 · FastAPI · PostgreSQL · WebSocket",[152,305,306],{},"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.",[152,308,309],{},"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.",[147,311,313],{"id":312},"what-the-game-teaches","What the game teaches",[152,315,316,317,320,321,324],{},"Catan is an excellent modelling exercise. The board is a hexagonal tiling where\nsettlements sit on ",[169,318,319],{},"vertices"," and roads on ",[169,322,323],{},"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.",[152,326,327],{},"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":240,"depth":240,"links":329},[330,331,332],{"id":149,"depth":240,"text":150},{"id":296,"depth":240,"text":297},{"id":312,"depth":240,"text":313},"A multiplayer web adaptation of the board game Catan, playable together in a browser in real time.",[],[336,337,338],"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,{"title":273,"description":333},"https:\u002F\u002Fcatan.szmygiel.com","projets\u002Fcatan",[16,26,25,34,265,51],2025,"uC6d3QfDReLfb9V3QMHz_nksiLKIELYYau75-kASmt4",{"id":349,"title":350,"architecture":351,"body":363,"cover":463,"description":464,"extension":249,"featured":124,"gallery":465,"highlights":475,"interface":124,"meta":479,"navigation":124,"path":480,"rang":481,"repo":71,"role":258,"seo":482,"site":71,"status":483,"stem":484,"tags":485,"type":267,"year":346,"__hash__":489},"projets_en\u002Fprojets\u002Festimap.md","Estimap",[352,355,358,360],{"label":353,"description":354},"Frontend","Nuxt 3 + MapLibre GL (PMTiles) — map, property sheet, back office.",{"label":356,"description":357},"Backend","FastAPI + SQLAlchemy, Celery for long-running work, two separate queues (imports\u002Fmutations).",{"label":31,"description":359},"PostgreSQL \u002F PostGIS (~100M rows) for the spatial layer, Redis as broker and cache.",{"label":361,"description":362},"Infra","Docker Compose, workers split by queue.",{"type":144,"value":364,"toc":455},[365,369,372,375,379,382,392,394,416,427,434,438,441,445,448,452],[147,366,368],{"id":367},"the-problem","The problem",[152,370,371],{},"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.",[152,373,374],{},"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.",[147,376,378],{"id":377},"the-product-goal","The product goal",[152,380,381],{},"A property price estimation engine — the map is the exploration surface, but the\npoint is to feed a model with genuinely cross-referenced features.",[152,383,384,387,388,391],{},[169,385,386],{},"Phase 1"," (weighted kNN comparables, progressive radii, plausibility guards)\nis in production and answers 100% of estimation requests today. ",[169,389,390],{},"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.",[147,393,297],{"id":296},[163,395,396,401,406,411],{},[166,397,398,400],{},[169,399,353],{},": Nuxt 3 + MapLibre GL, PMTiles — map, property sheet, back office",[166,402,403,405],{},[169,404,356],{},": FastAPI + SQLAlchemy, with Celery for long-running work",[166,407,408,410],{},[169,409,31],{},": PostgreSQL \u002F PostGIS for the spatial layer, Redis as broker and cache",[166,412,413,415],{},[169,414,361],{},": docker-compose, workers split by queue",[152,417,418,419,422,423,426],{},"Workers are split across two queues: ",[203,420,421],{},"imports"," for source ingestion, and\n",[203,424,425],{},"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.",[152,428,429,430,433],{},"An ",[203,431,432],{},"\u002Fadmin"," back office drives 15 source connectors and tracks import state.",[147,435,437],{"id":436},"what-the-map-covers","What the map covers",[152,439,440],{},"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.",[147,442,444],{"id":443},"method","Method",[152,446,447],{},"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.",[147,449,451],{"id":450},"what-i-took-away","What I took away",[152,453,454],{},"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":240,"depth":240,"links":456},[457,458,459,460,461,462],{"id":367,"depth":240,"text":368},{"id":377,"depth":240,"text":378},{"id":296,"depth":240,"text":297},{"id":436,"depth":240,"text":437},{"id":443,"depth":240,"text":444},{"id":450,"depth":240,"text":451},"\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.",[466,469,472],{"url":467,"caption":468},"\u002Fprojets\u002Festimap-fiche-bien.png","Property sheet — sale, energy rating, official hazard exposure, context and nearby amenities.",{"url":470,"caption":471},"\u002Fprojets\u002Festimap-tableau-de-bord.png","Back office — service health, sales tiles and live job tracking.",{"url":473,"caption":474},"\u002Fprojets\u002Festimap-sources.png","Back office — driving the 15 data-source connectors.",[476,477,478],"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":350,"description":464},"Completed","projets\u002Festimap",[486,20,26,487,35,37,488],"Nuxt 3","Celery","LightGBM","r1KLUD8K6yLxger7FFaaA_6RvAUCkR2_Jt1TMYJEcQM",{"id":128,"title":129,"architecture":491,"body":496,"cover":247,"description":248,"extension":249,"featured":118,"gallery":555,"highlights":556,"interface":118,"meta":557,"navigation":124,"path":256,"rang":257,"repo":71,"role":258,"seo":558,"site":260,"status":261,"stem":262,"tags":559,"type":267,"year":268,"__hash__":269},[492,493,494,495],{"label":132,"description":133},{"label":135,"description":136},{"label":138,"description":139},{"label":141,"description":142},{"type":144,"value":497,"toc":548},[498,500,502,504,506,524,526,532,534,540,542],[147,499,150],{"id":149},[152,501,154],{},[152,503,157],{},[147,505,161],{"id":160},[163,507,508,512,516,520],{},[166,509,510,172],{},[169,511,171],{},[166,513,514,178],{},[169,515,177],{},[166,517,518,184],{},[169,519,183],{},[166,521,522,190],{},[169,523,189],{},[147,525,194],{"id":193},[152,527,197,528,201,530,206],{},[169,529,200],{},[203,531,205],{},[147,533,210],{"id":209},[152,535,213,536,217,538,223],{},[203,537,216],{},[219,539,222],{"href":221},[147,541,227],{"id":226},[152,543,230,544,234,546,238],{},[169,545,233],{},[203,547,237],{},{"title":71,"searchDepth":240,"depth":240,"links":549},[550,551,552,553,554],{"id":149,"depth":240,"text":150},{"id":160,"depth":240,"text":161},{"id":193,"depth":240,"text":194},{"id":209,"depth":240,"text":210},{"id":226,"depth":240,"text":227},[],[252,253,254],{},{"title":129,"description":248},[16,264,26,25,265,51,266],{"id":561,"title":562,"architecture":563,"body":574,"cover":712,"description":713,"extension":249,"featured":124,"gallery":714,"highlights":721,"interface":124,"meta":725,"navigation":124,"path":726,"rang":240,"repo":71,"role":258,"seo":727,"site":728,"status":261,"stem":729,"tags":730,"type":267,"year":268,"__hash__":732},"projets_en\u002Fprojets\u002Fpyro-radar.md","Pyro Radar",[564,566,569,572],{"label":132,"description":565},"Six satellite feeds aggregated — NASA FIRMS, GOES-18\u002F19, Meteosat MTG, Himawari-9, Sentinel-3.",{"label":567,"description":568},"Ingestion","One ingester per source (CSV, NetCDF, HDF5) normalises into a shared schema of geolocated detections.",{"label":570,"description":571},"Storage","Burned areas estimated as polygons directly in PostGIS, in batches.",{"label":141,"description":573},"MapLibre GL vector tiles, real-time delivery over Server-Sent Events.",{"type":144,"value":575,"toc":706},[576,580,583,586,590,664,667,671,677,683,689,695,701,703],[147,577,579],{"id":578},"the-idea","The idea",[152,581,582],{},"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.",[152,584,585],{},"Pyro Radar merges those feeds into a single worldwide map, refreshed continuously.",[147,587,589],{"id":588},"the-sources","The sources",[591,592,593,609],"table",{},[594,595,596],"thead",{},[597,598,599,603,606],"tr",{},[600,601,602],"th",{},"Source",[600,604,605],{},"Coverage",[600,607,608],{},"Cadence",[610,611,612,624,635,645,655],"tbody",{},[597,613,614,618,621],{},[615,616,617],"td",{},"NASA FIRMS (VIIRS \u002F MODIS)",[615,619,620],{},"Worldwide",[615,622,623],{},"Orbital passes",[597,625,626,629,632],{},[615,627,628],{},"GOES-18 \u002F 19",[615,630,631],{},"Americas",[615,633,634],{},"10 min",[597,636,637,640,643],{},[615,638,639],{},"Meteosat MTG (LSA SAF)",[615,641,642],{},"Europe \u002F Africa",[615,644,634],{},[597,646,647,650,653],{},[615,648,649],{},"Himawari-9",[615,651,652],{},"Asia \u002F Oceania",[615,654,634],{},[597,656,657,660,662],{},[615,658,659],{},"Sentinel-3 SLSTR (Copernicus)",[615,661,620],{},[615,663,623],{},[152,665,666],{},"Geostationary satellites bring the frequency, polar orbits bring the resolution.",[147,668,670],{"id":669},"the-technical-work","The technical work",[152,672,673,676],{},[169,674,675],{},"Ingestion."," CSV for FIRMS, NetCDF for GOES, HDF5 for MTG: one ingester per\nsource normalises into a shared schema of geolocated detections.",[152,678,679,682],{},[169,680,681],{},"Burned areas."," Point detections become estimated polygons directly in\nPostGIS, in batches — loading every zone at once was taking Postgres down.",[152,684,685,688],{},[169,686,687],{},"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.",[152,690,691,694],{},[169,692,693],{},"Real time."," Server-Sent Events: no polling, and no bidirectional WebSocket\nwhere a one-way stream is enough.",[152,696,697,700],{},[169,698,699],{},"Mapping."," MapLibre GL, OpenFreeMap basemap and Esri satellite view — no API\nkey, so no quota and no surprise bill.",[147,702,451],{"id":450},[152,704,705],{},"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":240,"depth":240,"links":707},[708,709,710,711],{"id":578,"depth":240,"text":579},{"id":588,"depth":240,"text":589},{"id":669,"depth":240,"text":670},{"id":450,"depth":240,"text":451},"\u002Fprojets\u002Fpyro-radar-carte.png","Real-time wildfire tracking worldwide, built from six satellite sources aggregated onto a single vector map.",[715,718],{"url":716,"caption":717},"\u002Fprojets\u002Fpyro-radar-points-chauds.png","Hotspots — individual detections with confidence and per-click detail.",{"url":719,"caption":720},"\u002Fprojets\u002Fpyro-radar-historique.png","30-day history — cumulative fire count and estimated burned area.",[722,723,724],"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":562,"description":713},"https:\u002F\u002Fpyroradar.com","projets\u002Fpyro-radar",[16,20,28,35,34,731],"SSE","_v_G5VhIz3MViZGbQOxvjVqVhYkR5cKTu99Y43u2FR8",{"id":734,"title":735,"architecture":736,"body":748,"cover":71,"description":822,"extension":249,"featured":118,"gallery":823,"highlights":824,"interface":124,"meta":828,"navigation":124,"path":829,"rang":830,"repo":71,"role":831,"seo":832,"site":71,"status":483,"stem":833,"tags":834,"type":56,"year":268,"__hash__":837},"projets_en\u002Fprojets\u002Frootine.md","Rootine",[737,739,742,745],{"label":285,"description":738},"Habits and check-offs stored locally with SwiftData, with no external dependency.",{"label":740,"description":741},"Check-off","A Yesterday \u002F Today toggle to check off retroactively, without breaking streaks.",{"label":743,"description":744},"Visualisation","Monthly heatmap calendar where colour intensity reflects the completion rate.",{"label":746,"description":747},"Interface","Spring animations and haptic feedback on every check, in SwiftUI.",{"type":144,"value":749,"toc":816},[750,756,758,774,776,782,788,794,800,804,807,810,813],[751,752,753],"blockquote",{},[152,754,755],{},"Habits are the roots of who you become.",[147,757,150],{"id":149},[152,759,760,761,765,766,769,770,773],{},"Rootine — a play on ",[762,763,764],"em",{},"root"," and ",[762,767,768],{},"routine"," — is a habit tracker built around one\nsimple principle: ",[169,771,772],{},"consistency beats intensity",". Ten minutes of exercise every\nday is worth more than one two-hour session a week.",[147,775,161],{"id":160},[152,777,778,781],{},[169,779,780],{},"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.",[152,783,784,787],{},[169,785,786],{},"Routines"," — creating, editing and deleting the habits you track.",[152,789,790,793],{},[169,791,792],{},"Calendar"," — a monthly heatmap where colour intensity reflects that day's\ncompletion rate. Consistency becomes visible at a glance.",[152,795,796,799],{},[169,797,798],{},"Statistics"," — current streaks, completion rate, trends over time.",[147,801,803],{"id":802},"the-stance","The stance",[152,805,806],{},"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.",[152,808,809],{},"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.",[147,811,302],{"id":812},"stack",[152,814,815],{},"SwiftUI and SwiftData, with no external dependency. All data stays on the device.",{"title":71,"searchDepth":240,"depth":240,"links":817},[818,819,820,821],{"id":149,"depth":240,"text":150},{"id":160,"depth":240,"text":161},{"id":802,"depth":240,"text":803},{"id":812,"depth":240,"text":302},"A minimal, forgiving iOS habit tracker — today's checklist, a heatmap calendar and consistency statistics.",[],[825,826,827],"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":735,"description":822},"projets\u002Frootine",[59,60,61,835,836],"Swift","Charts","-jczwI2MfId-BcdlMwAxDkg111FvQlpAM7nKRbRdCv8",{"id":839,"title":840,"architecture":841,"body":852,"cover":71,"description":889,"extension":249,"featured":118,"gallery":890,"highlights":891,"interface":124,"meta":895,"navigation":124,"path":896,"rang":897,"repo":71,"role":831,"seo":898,"site":71,"status":483,"stem":899,"tags":900,"type":56,"year":268,"__hash__":902},"projets_en\u002Fprojets\u002Fsofa.md","Sofa",[842,845,847,850],{"label":843,"description":844},"Search","TMDB API queries for title search and detail sheets.",{"label":570,"description":846},"Library and watch history persisted with SwiftData, readable offline.",{"label":848,"description":849},"Enrichment","Available streaming platforms pulled from TMDB.",{"label":746,"description":851},"Dark design system inspired by cinema auditoriums, in native SwiftUI.",{"type":144,"value":853,"toc":883},[854,856,859,861,868,871,875,878,880],[147,855,150],{"id":149},[152,857,858],{},"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.",[147,860,302],{"id":812},[152,862,863,864,867],{},"A ",[169,865,866],{},"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.",[152,869,870],{},"SwiftData handles local persistence, which makes the library readable offline —\nonly search and detail refreshes need the network.",[147,872,874],{"id":873},"design","Design",[152,876,877],{},"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.",[147,879,451],{"id":450},[152,881,882],{},"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":240,"depth":240,"links":884},[885,886,887,888],{"id":149,"depth":240,"text":150},{"id":812,"depth":240,"text":302},{"id":873,"depth":240,"text":874},{"id":450,"depth":240,"text":451},"A native iOS watchlist app for films and series — TMDB search, personal library, watch tracking and ratings.",[],[892,893,894],"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":840,"description":889},"projets\u002Fsofa",[59,60,61,901,835],"TMDB API","L9BjW5Ev8RnMN90W7J3oAsl5_rNEZpLJ4xhveeWZqgw",{"id":904,"title":905,"architecture":906,"body":917,"cover":1031,"description":1032,"extension":249,"featured":118,"gallery":1033,"highlights":1034,"interface":118,"meta":1038,"navigation":124,"path":1039,"rang":1040,"repo":71,"role":1041,"seo":1042,"site":71,"status":483,"stem":1043,"tags":1044,"type":31,"year":346,"__hash__":1049},"projets_en\u002Fprojets\u002Fthanos.md","Thanos",[907,909,912,915],{"label":31,"description":908},"Data Manager — fetching and caching historical series per exchange and timeframe.",{"label":910,"description":911},"Indicators","Custom Indicators — technical indicators implemented by hand rather than imported.",{"label":913,"description":914},"Backtest","Backtest Analysis — performance and risk metrics across five strategy families.",{"label":743,"description":916},"Plot Analysis — entries, exits and equity curves.",{"type":144,"value":918,"toc":1025},[919,921,924,928,982,985,989,1015,1019,1022],[147,920,150],{"id":149},[152,922,923],{},"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\".",[147,925,927],{"id":926},"strategies-implemented","Strategies implemented",[591,929,930,940],{},[594,931,932],{},[597,933,934,937],{},[600,935,936],{},"Strategy",[600,938,939],{},"Principle",[610,941,942,950,958,966,974],{},[597,943,944,947],{},[615,945,946],{},"SMA",[615,948,949],{},"Simple moving average crossover",[597,951,952,955],{},[615,953,954],{},"EMA",[615,956,957],{},"Exponential moving average crossover",[597,959,960,963],{},[615,961,962],{},"Momentum (RSI)",[615,964,965],{},"Entries on oversold \u002F overbought zones",[597,967,968,971],{},[615,969,970],{},"SMA-EMA combined",[615,972,973],{},"Cross-confirmation between the two moving average families",[597,975,976,979],{},[615,977,978],{},"TRIX",[615,980,981],{},"Triple exponential smoothing oscillator",[152,983,984],{},"They all share the same interface, which makes it possible to evaluate them on\nidentical ground — same data, same fees, same metrics.",[147,986,988],{"id":987},"modules","Modules",[163,990,991,997,1003,1009],{},[166,992,993,996],{},[169,994,995],{},"Data Manager"," — fetching and caching historical series per exchange and timeframe",[166,998,999,1002],{},[169,1000,1001],{},"Custom Indicators"," — technical indicators implemented by hand rather than imported",[166,1004,1005,1008],{},[169,1006,1007],{},"Backtest Analysis"," — performance and risk metrics",[166,1010,1011,1014],{},[169,1012,1013],{},"Plot Analysis"," — visualising entries, exits and equity curves",[147,1016,1018],{"id":1017},"the-real-lesson","The real lesson",[152,1020,1021],{},"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.",[152,1023,1024],{},"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":240,"depth":240,"links":1026},[1027,1028,1029,1030],{"id":149,"depth":240,"text":150},{"id":926,"depth":240,"text":927},{"id":987,"depth":240,"text":988},{"id":1017,"depth":240,"text":1018},"\u002Fprojets\u002Fthanos-cover.svg","A research, backtesting and analysis platform for trading strategies on cryptocurrency markets.",[],[1035,1036,1037],"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":905,"description":1032},"projets\u002Fthanos",[25,1045,1046,1047,1048],"Pandas","NumPy","Backtesting","Data analysis","dSBxOYef84ONZdN6irpkgiOWjftugwl2o8W9AqvRZUY",{"id":1051,"title":222,"architecture":1052,"body":1063,"cover":1248,"description":1249,"extension":249,"featured":124,"gallery":1250,"highlights":1260,"interface":124,"meta":1265,"navigation":124,"path":1266,"rang":1267,"repo":71,"role":258,"seo":1268,"site":1269,"status":261,"stem":1270,"tags":1271,"type":267,"year":268,"__hash__":1272},"projets_en\u002Fprojets\u002Fvigie.md",[1053,1056,1058,1060],{"label":1054,"description":1055},"API","AdonisJS v6, TypeScript, Lucid — business logic and endpoints.",{"label":31,"description":1057},"PostgreSQL 17, progressive rollup (fine → hourly → daily) to keep retention affordable.",{"label":746,"description":1059},"Vue 3, Vite, Pinia — dashboard and public status page.",{"label":1061,"description":1062},"Exposure","Docker Compose on a VPS (Caddy, GitHub Actions), plus a read-only MCP server to query monitoring from the editor.",{"type":144,"value":1064,"toc":1240},[1065,1067,1070,1073,1077,1109,1112,1114,1161,1181,1185,1188,1202,1206,1213,1231,1233],[147,1066,368],{"id":367},[152,1068,1069],{},"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.",[152,1071,1072],{},"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.",[147,1074,1076],{"id":1075},"what-vigie-does","What Vigie does",[163,1078,1079,1085,1091,1097,1103],{},[166,1080,1081,1084],{},[169,1082,1083],{},"Visit analytics"," with no cookie and no persistent identifier — no consent banner required",[166,1086,1087,1090],{},[169,1088,1089],{},"Uptime monitoring",": regular HTTP probes with history and uptime calculation",[166,1092,1093,1096],{},[169,1094,1095],{},"Scheduled-job supervision",": a cron that hasn't checked in within its window raises an alert",[166,1098,1099,1102],{},[169,1100,1101],{},"Error tracking"," for the application errors reported by the connected projects",[166,1104,1105,1108],{},[169,1106,1107],{},"A public status page",", fed by the same data as the internal dashboard",[152,1110,1111],{},"Every monitored project has its own API key, and never sees another project's data.",[147,1113,297],{"id":296},[591,1115,1116,1126],{},[594,1117,1118],{},[597,1119,1120,1123],{},[600,1121,1122],{},"Layer",[600,1124,1125],{},"Choice",[610,1127,1128,1135,1143,1150],{},[597,1129,1130,1132],{},[615,1131,1054],{},[615,1133,1134],{},"AdonisJS v6, TypeScript, Lucid",[597,1136,1137,1140],{},[615,1138,1139],{},"Database",[615,1141,1142],{},"PostgreSQL 17",[597,1144,1145,1147],{},[615,1146,746],{},[615,1148,1149],{},"Vue 3, Vite, Pinia, TypeScript",[597,1151,1152,1155],{},[615,1153,1154],{},"Deployment",[615,1156,1157,1158],{},"Docker Compose on a VPS, Caddy in front, GitHub Actions on ",[203,1159,1160],{},"main",[152,1162,1163,1164,1167,1168,1167,1171,1167,1174,1167,1177,1180],{},"Everything runs in containers — ",[203,1165,1166],{},"postgres",", ",[203,1169,1170],{},"redis",[203,1172,1173],{},"api",[203,1175,1176],{},"worker",[203,1178,1179],{},"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.",[147,1182,1184],{"id":1183},"the-client-side-agent","The client-side agent",[152,1186,1187],{},"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.",[152,1189,1190,1191,765,1194,1197,1198,1201],{},"It wraps ",[203,1192,1193],{},"pushState",[203,1195,1196],{},"replaceState"," to catch navigations in a single-page\napp, then restores the original methods: nothing is permanently altered for the\nhost application. A ",[203,1199,1200],{},"@vigie\u002Fjs"," plugin hooks straight into the Vue 3 router\ninstead of guessing from history events.",[147,1203,1205],{"id":1204},"an-mcp-server-to-stay-in-the-editor","An MCP server, to stay in the editor",[152,1207,1208,1209,1212],{},"What's broken? Is this error new? Did last night's cron run? Vigie exposes six\nread-only tools over ",[169,1210,1211],{},"MCP"," to answer those questions without opening the\ndashboard.",[152,1214,1215,1216,1219,1220,1167,1223,1226,1227,1230],{},"Two deliberate choices: ",[169,1217,1218],{},"no writes at all"," — an assistant that reads gets the\ndiagnosis wrong, an assistant that writes gets production wrong; and ",[169,1221,1222],{},"internal\ncodes translated on the way out",[203,1224,1225],{},"cause: \"silent agent\""," rather than\n",[203,1228,1229],{},"reason: 5",", because a model facing an unknown code doesn't stop, it guesses.",[147,1232,451],{"id":450},[152,1234,1235,1236,1239],{},"The hard part wasn't collection but ",[169,1237,1238],{},"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":240,"depth":240,"links":1241},[1242,1243,1244,1245,1246,1247],{"id":367,"depth":240,"text":368},{"id":1075,"depth":240,"text":1076},{"id":296,"depth":240,"text":297},{"id":1183,"depth":240,"text":1184},{"id":1204,"depth":240,"text":1205},{"id":450,"depth":240,"text":451},"\u002Fprojets\u002Fvigie-dashboard.png","Unified monitoring for my web projects — cookieless analytics, uptime checks, scheduled-job tracking and a public status page.",[1251,1254,1257],{"url":1252,"caption":1253},"\u002Fprojets\u002Fvigie-erreurs.png","Application errors — grouped by fingerprint, with latest occurrences and stack trace.",{"url":1255,"caption":1256},"\u002Fprojets\u002Fvigie-taches.png","Scheduled jobs — state, run history and an incident when one fails.",{"url":1258,"caption":1259},"\u002Fprojets\u002Fvigie-disponibilite.png","Uptime — URL checks with response time and 30-day history.",[1261,1262,1263,1264],"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":222,"description":1249},"https:\u002F\u002Fvigie.watch","projets\u002Fvigie",[28,16,18,34,37,51,1211],"RiBHY7jRZ-V3wJoap5kgVKpqBMn-pw7QB4wK5yabSNI",{"left":116,"top":116,"width":117,"height":117,"rotate":116,"vFlip":118,"hFlip":118,"body":1274},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14m-7-7l7 7l-7 7\"\u002F>",1788530204319]