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