Skip to main content
Ahmed Salama

Sustainability · geospatial · social platform

GeoFika

Everything happening to the planet on one live map: incidents drawn from world reporting, marks left by members, a research library, initiatives to join, groups to belong to, and a feed of the people you follow.

Context

Geofika starts from a conviction and a difficulty. The conviction, which is the platform’s own, is that sustainability touches every part of life and that people deserve plain information about their surroundings without having to be academics or technologists to get it. The difficulty is that news of the world arrives from everywhere at once and almost none of it turns up sorted or trusted. What sits between the two is a live world map, with a curation pipeline behind it and a community around it. The map layers events, incidents and accidents drawn from news and agency reporting over marks placed by members, initiatives looking for people, and research published to the library, all of it narrowed by sector, category and event type, by country and locality, and by date. Two records sit beneath the map and a reader can switch between them: the one the platform curates and vouches for, where a language model classifies, summarises and scores every candidate before a person approves it, and the one the community writes freely, where nobody asks permission. The rest is a social platform in earnest, because sustained attention to slow problems needs one. Members keep profiles, follow one another and read a feed built from whoever they follow. They publish research, projects and articles, start initiatives, join groups, post thoughts, and support, comment, repost and share across all of it. Language and theme belong to the member rather than the browser, in five languages, and travel with them between devices.

The problem

A map of world events is only worth opening if a reader can trust what is on it, and nothing arriving here is trustworthy by default. Agency reporting has to be read, categorised and condensed before it becomes a point on a map. Community submissions arrive with no provenance at all. Both feed the same surface, so a reader who cannot tell a sourced incident from an unverified mark has no reason to believe either. Doing that reading by hand does not scale to a world feed, and handing it to a language model instead simply relocates the problem: a model that is confidently wrong about an earthquake is worse than no entry at all. The platform therefore has to be fast enough to keep up with the news and slow enough to be right.

The solution

The answer is to hold two standards at once rather than one. Community publishing stays open, because a platform whose members cannot post is not a community. The verified record is where the bar sits, and there the model drafts and never publishes. An LLM pipeline runs over everything inbound as a series of passes rather than one call, with different models chosen per task: it classifies an item into the event taxonomy the map filters on, summarises the source reporting into the entry a reader sees, scores credibility, and produces the forecasting the platform offers. What comes out is a proposal. A person approves it, and only then does it enter the verified record. That rule is what lets the platform run at the speed of a news feed without staking its credibility on a model being right, and it applies to the verified record alone: nothing gates a member posting to the feed. The two corpora stay visibly separate rather than being merged: the curated database and the user database are different views a reader switches between, and every entry in the curated one carries the source it came from. Records are filed against a layered taxonomy rather than a flat tag. A sector such as Land Systems, Energy or Politics sits above a category such as Earthquakes and Seismic Disasters, International Policy and Cross-Border Relations or Election Campaigns, with an event type such as Accident or Affairs beneath that, then a country and a named locality. Twenty-six countries appear across a single page of results, from Greece and Norway to Sudan, Japan and Bolivia. Around the record sits the social product. A profile is the unit of identity: it carries what a member has published, who they follow and who follows them, the preferences they read under, and it is what every piece of content resolves back to. Language and light or dark theme are held against the member rather than the browser, so the platform follows them between devices, and it keeps them even for a visitor who declines cookies. The feed is assembled from that follow graph, mixing thoughts, newly published library content, initiatives and events into one stream, with a trending rail alongside it. Every object in the system, a map entry, a library item, a feed post, carries the same four engagements: support, comment, repost and share. The map composites four layers over one coordinate space, News, User Marks, Initiatives and Library, so a reader sees the verified record, the community’s marks, active initiatives and published research together or one at a time. A filter bar narrows by event type, sort order and a start and end date, and a guided tour and a set of map guidelines carry the rules of the surface to a first-time contributor.

Architecture

The backend is a modular monolith: one Laravel deployment divided internally into modules, each owning its own database rather than sharing one schema, so a module can be lifted into its own service later as a deployment change instead of a data migration. A Next.js web application built with Tailwind carries every public surface: the map, both databases, the library of researches, projects and articles, the community initiatives and groups, and the Thoughts feed. An admin dashboard sits alongside it for the people running the platform. Both talk to a Laravel API over MySQL, and the API is where the interesting sequencing lives. Inbound material, whether drawn from agency reporting or submitted by a member, goes to the LLM pipeline before it goes anywhere else, and comes back as a draft rather than a record. Human approval is the gate between that draft and the verified record, which makes it the most load-bearing component in the system. It sits on one path only: member posts, marks, articles and initiatives publish without it. The map itself is Leaflet in the browser, with Google Maps behind it, and sign-in is federated over OAuth rather than resting on passwords the platform holds. Redis fronts the reads that repeat, which on a map filtered by the same handful of sectors and date ranges is most of them, and a queue carries the work that must not sit on a request, the LLM passes and notification fan-out among it. Media sits in S3 behind CloudFront so image bytes never travel through the API, which matters for a map whose entries carry photographs. Firebase Cloud Messaging carries notifications out to members, since a platform built on people supporting and commenting on each other only works if it can tell them something happened. The whole thing runs on AWS on an Amazon EKS cluster. Three things are not recorded: whether the platform also ships a native mobile application alongside the web app, which models are used for which task in the pipeline, and where the module boundaries actually fall behind the database-per-module split.

System diagram: GeoFika topologyNext.js and Tailwind, with the map rendered by Leaflet. Carries every public surface: the interactive map with its event, user-mark, initiative and library layers, both databases, the library, community initiatives and groups, member profiles with their following and follower lists and their language and theme preferences, and the Thoughts feed with its supports, comments, reposts and shares.CLIENTNext.js web appWhere the platform is run: moderation, the event taxonomy, the curated record and the people using it.CLIENTAdmin dashboardNews and agency reporting feeding the curated record. It is entered and checked by hand: staff read the agency source and mark the record verified, which is slow and is exactly what makes the verified badge mean that a person looked.EXTERNALAgency reportingA modular monolith, one deployment divided into modules with a database each. Single entry point for both surfaces and both inbound paths.SERVICELaravel APISits behind the Leaflet map. It supplies the base tiles Leaflet renders, turns the place names members type into coordinates, and completes locations as they are searched for. The map is the product, so this is a hard dependency of it rather than a convenience.EXTERNALGoogle MapsGoogle, Apple and Facebook, alongside an ordinary email and password for anyone who would rather not bring an account with them.EXTERNALOAuth sign-inFronts the reads that repeat, which on a map filtered by the same handful of sectors and date ranges is most of them.CACHERedisCarries what must not sit on a request: the LLM passes over inbound material, and notification fan-out.QUEUEQueueMedia for map entries, library content and member posts, kept off the API path.DATAAmazon S3Cloud Messaging, carrying notifications to members. A platform built on people supporting and commenting on each other has to be able to tell them something happened.EXTERNALFirebaseSeparate passes rather than one call, with models chosen per task: classify into the event taxonomy, summarise the source reporting, score credibility, forecast. Produces a proposal, never a published record.SERVICELLM pipelineDelivers that media to readers so image bytes never travel through the API.EDGECloudFrontThe gate in front of the verified record only. Nothing the pipeline produces is published until a person approves it. Member posts, marks, articles and initiatives do not pass through here at all.CLIENTHuman approvalOne database per module rather than a shared schema, holding both corpora side by side: the verified record with a source on every entry, and the openly published user one. Where the module boundaries fall is not recorded.DATAMySQLmarks, posts, contentmoderation, taxonomysourced reportingtiles, place lookupmember sign-inrepeated readsdeferred workclassify, summarise,scoredraft for approvalapproved and publishedreadsmediaoriginnotifications
Read this diagram as text
Next.js web appClient
Next.js and Tailwind, with the map rendered by Leaflet. Carries every public surface: the interactive map with its event, user-mark, initiative and library layers, both databases, the library, community initiatives and groups, member profiles with their following and follower lists and their language and theme preferences, and the Thoughts feed with its supports, comments, reposts and shares.
Admin dashboardClient
Where the platform is run: moderation, the event taxonomy, the curated record and the people using it.
Agency reportingExternal
News and agency reporting feeding the curated record. It is entered and checked by hand: staff read the agency source and mark the record verified, which is slow and is exactly what makes the verified badge mean that a person looked.
Laravel APIService
A modular monolith, one deployment divided into modules with a database each. Single entry point for both surfaces and both inbound paths.
Google MapsExternal
Sits behind the Leaflet map. It supplies the base tiles Leaflet renders, turns the place names members type into coordinates, and completes locations as they are searched for. The map is the product, so this is a hard dependency of it rather than a convenience.
OAuth sign-inExternal
Google, Apple and Facebook, alongside an ordinary email and password for anyone who would rather not bring an account with them.
RedisCache
Fronts the reads that repeat, which on a map filtered by the same handful of sectors and date ranges is most of them.
QueueQueue
Carries what must not sit on a request: the LLM passes over inbound material, and notification fan-out.
Amazon S3Data
Media for map entries, library content and member posts, kept off the API path.
FirebaseExternal
Cloud Messaging, carrying notifications to members. A platform built on people supporting and commenting on each other has to be able to tell them something happened.
LLM pipelineService
Separate passes rather than one call, with models chosen per task: classify into the event taxonomy, summarise the source reporting, score credibility, forecast. Produces a proposal, never a published record.
CloudFrontEdge
Delivers that media to readers so image bytes never travel through the API.
Human approvalClient
The gate in front of the verified record only. Nothing the pipeline produces is published until a person approves it. Member posts, marks, articles and initiatives do not pass through here at all.
MySQLData
One database per module rather than a shared schema, holding both corpora side by side: the verified record with a source on every entry, and the openly published user one. Where the module boundaries fall is not recorded.

Connections

  • Next.js web app → Laravel API (marks, posts, content)
  • Admin dashboard → Laravel API (moderation, taxonomy)
  • Agency reporting → Laravel API (sourced reporting)
  • Next.js web app → Google Maps (tiles, place lookup)
  • Laravel API → OAuth sign-in (member sign-in)
  • Laravel API → Redis (repeated reads)
  • Laravel API → Queue (deferred work)
  • Queue → LLM pipeline (classify, summarise, score)
  • LLM pipeline → Human approval (draft for approval)
  • Human approval → MySQL (approved and published)
  • Laravel API → MySQL (reads)
  • Laravel API → Amazon S3 (media)
  • Amazon S3 → CloudFront (origin)
  • Laravel API → Firebase (notifications)

What makes it interesting

The parts a system like this is actually judged on.

  • Two publishing standards run on one platform, and keeping them apart is the design. Anyone can post a thought, an event, an article or an initiative with nothing in the way, while the verified record admits nothing a person has not approved. The same map shows both, so the entire value of the verified half rests on a reader being able to tell which is which.
  • The model drafts and a person publishes, and that ordering is what makes the verified record affordable. The platform can read a world news feed at machine speed while every entry it vouches for has had a human behind it, which turns the quality bar into a staffing question rather than a model-accuracy question.
  • There is no formal evaluation of the language model, and the honest reason is that the architecture does not depend on one. Confidence in the output comes from the approval step rather than from a benchmark, which is a legitimate answer for a public record of world events and a weaker one for anything that publishes automatically. The trade is explicit: no evaluation harness, no autonomous publishing.
  • Neither escape route from the two-corpus problem is open. Remove the user record and Mark on Map has nothing to write to, which is half the product. Fold it into the verified record and that record loses the only thing it claims, which is that its entries cite a source. Both have to stand at once, so everything rests on how firmly the boundary is held.
  • The automated rules have an easier target than they appear to. They do not have to be right about the arguable cases, only right about which cases are arguable, and that is the property that lets a small team put its name on more entries than it could read line by line.
  • A support count is a popularity signal and a source line is a provenance claim, and here they arrive in the same visual frame. Whether support influences ordering or what reaches the trending rail is what decides whether the social half of the product can move the factual half.
  • The pipeline runs as separate passes rather than one prompt returning everything, which costs more calls and buys the ability to see which step was wrong. Classification, summarisation, credibility scoring and forecasting fail in different ways and are worth failing separately.
  • A map reads by geographic extent, a feed reads by recency, and the library reads by topic, so the same underlying records are subject to three unrelated access patterns and cannot be indexed for one without considering the others.
  • A feed built on a follow graph forces the oldest decision in social software: fan out on write and pay per follower at publish time, or fan in on read and pay per viewer at every refresh. The first makes a member with many followers expensive to post as, the second makes every feed load a join across the graph, and whichever way it went it is the choice that decides how the product behaves at its own success.
  • Engagement counts and follower counts are derived state that everything reads and nothing owns. A support count is asked for on every card in every list, so it is either denormalised and kept in step with the thing it counts, or recomputed and paid for on every render. The same question applies to the follow graph, and the two answers do not have to match.
  • The feed mixes objects that are not alike: a thought from a member, a newly published article, an initiative asking to be joined, and an event the platform has verified. Ranking them against one another means weighing a popularity signal against a provenance claim, which is the two-corpus problem arriving somewhere it cannot be settled with a layer toggle.
  • The map is the product, which makes rendering it a first-order problem rather than a presentation detail. Leaflet drawing a world view has to answer what happens when a viewport contains far more points than it can usefully plot, and the answer is either clustering, viewport-bounded queries, server-side aggregation by zoom level, or an increasingly slow page. Four layers compositing over one another multiplies whatever that answer costs.
  • One taxonomy is absorbing categories that behave nothing alike. An earthquake is a point with a timestamp and a locality; an item filed under international policy or election campaigns is a period, a process, and often a whole country. Filing both against sector, category and event type keeps the filter bar simple and quietly asks the map to plot two different kinds of thing as the same kind of dot.
  • A database per module is a bet about the future rather than a convenience now. It costs the ability to join across modules, which is exactly what makes a boundary erode, and buys the option to extract one later without first untangling a shared schema.
  • Community features change the failure mode of a factual platform. Support, comments, reposts and initiatives make a wrong entry travel, so the cost of publishing something incorrect is not a correction, it is a correction that arrives after the thing has spread.

Engineering challenges

  • Two publishing standards on one platform: open community posting alongside a verified record that admits nothing unapproved
  • Nothing arrives trustworthy: agency reporting needs reading and condensing, community submissions arrive with no provenance at all
  • A layered taxonomy filing point events and long-running processes as the same kind of record
  • Fast enough to follow a world news feed, slow enough to be right
  • A human approval gate that must not become the bottleneck that stops the platform publishing
  • Two corpora with different trust properties sharing one map without being merged
  • Language-model output that is confidently wrong is worse than a missing entry
  • Three unrelated read patterns over the same records: geographic extent, recency and topic
  • Community amplification means an incorrect entry spreads before it can be corrected
  • Feed assembly over a follow graph: fan out on write or fan in on read, with the cost landing somewhere either way
  • Engagement and follower counts are derived state, read on every card and owned by nothing
  • Ranking unlike objects in one stream: posts, articles, initiatives and verified events
  • Multilingual interface and light and dark theming held against the member rather than the browser, so preferences follow them between devices

Integrations

  • Agency and news reporting as a source for the curated record; which agencies, and whether intake is automated, is not recorded
  • Firebase Cloud Messaging: notification delivery to members
  • Google Maps: behind the Leaflet map; whether it serves tiles, geocoding or both is not recorded
  • OAuth: third-party sign-in for members; which providers are offered is not recorded

Technology

  • Laravel
  • MySQL
  • Redis
  • Next.js
  • Leaflet
  • Google Maps
  • OAuth
  • Tailwind CSS
  • Firebase Cloud Messaging
  • AWS
  • Amazon EKS
  • Amazon S3
  • Amazon CloudFront
  • Docker

These are the projects my confidentiality agreements allow me to publish, so this is a subset of the work rather than all of it. Every one of them comes from a full-time role, and I was on the project from its start through to its end. Freelance and consulting engagements are not published here.

The write-ups still describe systems rather than individual credit. Where the source material does not establish who did what inside a team, a case study says nothing rather than implying credit it cannot support.

Let’s talk

Have a product, platform or delivery challenge? Let’s talk about turning it into a structured, scalable solution.

Open to technical leadership, product delivery and senior engineering roles, and available for architecture consulting, technical reviews and mentorship. Engagements run as project-based work, contracts, consulting, freelance engagements, remote collaboration and long-term partnerships.

Based in Cairo, Egypt, working remotely with clients across the MENA region and internationally.

Also on LinkedIn (opens in a new tab)