Engineering a real-time OSINT platform in 200 hours.
How Tileterra Systems shipped iranwarlive.com to production 8 hours after Operation Epic Fury started, then refined the five-parameter Circuit Breaker deduplication, two-hour Gemini classification pipeline, and answer-engine-first content architecture across the next several weeks of operation.
Executive summary
iranwarlive.com is a live OSINT platform tracking the 2026 Iran-Israel-US conflict (Operation Epic Fury). Tileterra Systems engineered the platform from concept to first production deployment in 8 hours, with the conflict already underway. Total engineering investment to date: approximately 200 hours across initial build, refinement, debugging, and ongoing operational work. The platform has been live continuously for 57+ days.
The technical core is a Cloudflare Workers cron pipeline that fetches RSS feeds from five wire services every two hours, classifies each event through Gemini 2.5 Flash on a fixed scoring rubric, deduplicates against a KV-cached event ledger using a five-parameter Circuit Breaker algorithm, and commits verified events to a Google Sheets backend that drives the live frontend. The frontend is a static Cloudflare Pages deployment with Leaflet-powered tactical mapping, a casualties dashboard, and a 57-day chronological recap archive optimized for both human readers and answer engine extraction.
The build is the strongest single proof point in Tileterra Systems' portfolio: it demonstrates speed (8 hours from concept to production), engineering discipline (the deduplication pipeline runs cleanly across 4,500+ events), and AEO competence (the platform is directly citable by ChatGPT, Claude, Gemini, and Perplexity through structured llms.txt routing).
Concept-to-production in 8 hours. 200 hours total engineering investment. Five-parameter deduplication algorithm operating cleanly across 57+ days of continuous operation. Cloudflare Workers + KV + Google Sheets backend. Gemini 2.5 Flash classification. Schema markup and llms.txt for direct answer-engine citability. The build is portable, owned by the team, and operationally simple enough to run from a single laptop.
Project context
Operation Epic Fury began in late February 2026 as the regional escalation between Iran, Israel, and the United States transitioned into open kinetic conflict. The team had been monitoring escalation indicators for weeks via the related ww3chance project, which provided real-time signal that a kinetic event was imminent. When the conflict began, the design decisions for an OSINT platform had already been mostly settled. What remained was 8 hours of focused engineering to ship the first deployment.
The product question was straightforward: existing wire-service coverage of a multi-front conflict is fragmented, repetitive, and poorly structured for analytical use. Reuters, AP, and CENTCOM all cover the same kinetic events with slightly different framing, slightly different timestamps, and no machine-readable structure. Anyone trying to maintain a chronological record of the conflict has to reconcile this manually. The platform exists to do that reconciliation programmatically and publish the result in real time.
The strategic position was secondary but real: Tileterra Systems wanted a flagship infrastructure project that demonstrated the team's actual capabilities, deployed at production scale, available for any prospect to evaluate without an NDA. iranwarlive serves that role.
Architecture overview
The platform composes seven Cloudflare and Google primitives:
- Cloudflare Workers for the cron-triggered ingestion and classification pipeline.
- Cloudflare KV for the deduplication signature cache and operational state.
- Google Sheets as the editable backend, accessed via the Google Sheets API.
- Gemini 2.5 Flash for event classification and structured extraction.
- Cloudflare Pages for the static frontend (HTML, CSS, vanilla JavaScript, no framework).
- Leaflet.js for the tactical map with layered overlays and time-window filtering.
- PapaParse for CSV parsing on the frontend, since the Google Sheets backend exports as CSV.
- Telegram Bot API for verified-event alerting to the public channel.
The data flow is unidirectional: wire feeds enter through the Cloudflare Workers cron, pass through the dedup and classification pipeline, write to Google Sheets, and the static frontend reads the Sheets-as-CSV export on page load. There is no traditional database server, no continuously running backend process, and no compute that the team has to manage as infrastructure.
This architecture is deliberately conservative. Every component has a free or low-cost tier that handles the platform's actual operating volume. The total infrastructure cost during the first 30 days of operation was under $10, almost entirely Gemini API usage. The team can hand the entire platform to a successor engineer with a single repository and a Google account.
Why Google Sheets as backend
The choice of Google Sheets over a traditional database was made deliberately. Sheets gives the team three things a database does not: human-editable rows for emergency corrections, native CSV export at a stable URL, and zero operational cost. When a wire-service ingestion produces a malformed event that the dedup pipeline allowed through, an analyst can fix the row in a spreadsheet and the change propagates to the frontend on the next page load. A SQL database would require an admin interface to do the same job.
The trade-offs are real: Sheets has hard row limits, the API rate limits matter at scale, and there is no transactional integrity. For iranwarlive's actual volume (under 10,000 total rows after 57 days), none of these limits are binding. For a higher-volume project, the same architecture would migrate to Cloudflare D1 with a similar API surface and similar operational simplicity.
RSS ingestion pipeline
The ingestion stage runs as a Cloudflare Worker triggered every two hours via cron. The Worker fetches RSS feeds from five wire-service sources: Reuters World News, Associated Press International, CENTCOM Press Releases, Israel Defense Forces Press Office, and Al Jazeera English. Each feed parse extracts published timestamp, source URL, headline, and summary text into a normalized event candidate.
The candidate set is then filtered through a relevance gate. The gate is keyword-based and conservative: any event candidate that mentions Iran, Israel, the United States military, or one of approximately 40 named entities, geographic regions, or weapon systems passes through to the classification stage. Events that do not mention any of these terms are discarded immediately to keep Gemini API costs proportional to relevant signal volume.
The relevance gate was tuned across the first two weeks of operation. The initial implementation produced approximately 8 percent false-negative rate (relevant events filtered out) and 0 percent false-positive rate. The current implementation produces approximately 1 percent false-negative rate and approximately 3 percent false-positive rate, which is the right tradeoff for the downstream pipeline (false positives are filtered cheaply; false negatives are lost forever).
Gemini classification layer
Each candidate event that passes the relevance gate is passed to Gemini 2.5 Flash with a fixed structured-output prompt. The prompt instructs Gemini to extract: event type (missile strike, air strike, ground operation, interception, diplomatic statement, casualty announcement, infrastructure attack, airspace closure), participating entities (named actors on each side), geographic location with approximate coordinates, casualty figures if reported, and an accuracy classification (high-confidence, medium-confidence, low-confidence) based on the source's reputation and the specificity of the report.
The fixed structured-output format is critical. Gemini is instructed to return JSON with named fields, and the prompt explicitly states that null values are preferred over hallucinated values when source data is incomplete. This is the most important single design decision in the AI layer: the platform would rather lose a field than invent one. Across 4,500+ classified events, the rate of obviously hallucinated values is below 0.5 percent, which is acceptable for an OSINT platform that publishes confidence ratings on every claim.
Gemini calls are batched where possible. The Cloudflare Worker collects up to 10 candidate events per cron cycle and submits them in a single Gemini request, which reduces both latency and per-event API cost. Below 10 candidates, the Worker submits whatever it has. Above 10, the candidates are split into multiple batches with a small jitter delay between requests to stay within rate limits.
Five-parameter deduplication
Deduplication is the single most important engineering problem in the platform. Wire services routinely re-cover the same event with different headlines, slightly different timestamps, and varying levels of detail. Naive ingestion creates inflated event counts, duplicated map markers, and unreliable casualty totals. The platform's credibility depends on the deduplication pipeline running cleanly across thousands of events.
The Circuit Breaker algorithm checks five parameters for every incoming event:
- Time published. Events within a 4-hour window of an existing cached event are flagged as potentially duplicate. The window is wider than wire-service publication latency would require because wire services occasionally re-publish older events with new timestamps.
- Source. Events from the same source are weighted differently than events from different sources. A Reuters event followed by an AP event covering the same incident is more likely to be a genuine secondary report than two Reuters events covering the same incident.
- Content fingerprint. A normalized hash of headline plus first 200 characters of summary text. The normalization strips dates, casualty numbers, and proper nouns of secondary entities, so two reports of the same strike with different framings will hash to the same value.
- Accuracy classification. The Gemini-assigned confidence rating is part of the dedup signature. A low-confidence early report and a high-confidence later report covering the same event are treated as two records of the same incident, with the high-confidence record taking precedence in the published output.
- KV cache lookup. All previously committed event signatures are stored in Cloudflare KV with a 7-day TTL. Every incoming event is checked against the cache before any other parameter is computed. This is the fast-path: if the exact signature already exists in KV, the event is rejected immediately without further processing.
An event is committed to the public ledger only when all five parameters confirm it is not a duplicate of an existing record. If any one of the five parameters flags a probable match, the event is held in a review queue for analyst confirmation rather than auto-rejected, since occasionally a genuinely separate event happens within the dedup window.
The review queue is the human-in-the-loop component. It exists because no algorithm is correct on every edge case in a fast-moving conflict. The queue typically holds between 5 and 30 events at any given time, and an analyst clears it daily.
// Simplified Circuit Breaker logic
const signature = fingerprint(event);
const cached = await KV.get(signature);
if (cached) return { skip: true, reason: 'kv-match' };
const candidates = await findRecentCandidates(event.timestamp, 4 * HOUR);
for (const c of candidates) {
if (matches(event, c, FIVE_PARAMS)) {
return c.confidence < event.confidence
? { upgrade: c.id, with: event }
: { skip: true, reason: 'dup-confirmed' };
}
}
await KV.put(signature, '1', { expirationTtl: 7 * DAY });
await Sheets.append(eventRow(event));
return { committed: true };
The dedup pipeline is the component most prone to silent failure. Across 200 hours of engineering, approximately 35 hours were spent specifically on dedup edge cases: time-zone normalization issues, content fingerprint collisions across genuinely different events, source-attribution disagreements between wire services, and the geographic deduplication problem (events at the same coordinates with different timestamps that turned out to be different incidents at the same target).
Macro-Casualty Lock
Casualty reporting requires special handling. The platform tracks casualties separately by faction (Iranian military, IDF, Lebanese Hezbollah, US service members, civilian deaths) and the casualty figures are sourced strictly from official channels: CENTCOM, IDF Press Office, the Lebanese Health Ministry, and the Iranian Foundation of Martyrs. The Macro-Casualty Lock prevents the AI classification layer from generating or modifying state-actor casualty figures even when secondary reporting cites different numbers.
The mechanism is conservative: any event classified as a casualty announcement passes through a separate review pipeline that requires explicit cross-referencing against the official source page. The Lock specifically exists because LLMs occasionally hallucinate plausible-sounding casualty numbers when the source text is ambiguous, and a hallucinated state-actor death toll would be a credibility-ending failure for the platform.
Two-hour cron architecture
The two-hour cron interval was chosen deliberately. Wire services publish kinetic-event coverage on a roughly 30-to-90-minute lag from the actual event. A two-hour cycle captures essentially all coverage of an event in the first 24 hours. A faster cycle (30 minutes, 60 minutes) increases Gemini API costs without proportional gains in real-time coverage. A slower cycle (4 hours, 6 hours) creates user-perceptible staleness on the live map.
Two cron jobs run on offset schedules. The primary cron at the top of every odd hour handles ingestion and classification. The secondary cron at 15 minutes past every even hour handles cleanup: KV TTL expiration verification, review queue stats reporting, and the Telegram alert dispatch for events confirmed in the previous cycle. The offset prevents both crons from contending for the same external API rate limits.
The primary cron is the only component that has ever required emergency operational intervention. Twice during the first 60 days of operation, a Gemini API outage caused the cron to fail mid-cycle. Both times, the platform recovered automatically on the next cycle without data loss, because the candidate event queue persists in Cloudflare KV between cron runs.
Daily recap pipeline
The daily recap pages are the AEO-critical content surface. Each day produces a recap page (day-01.html through day-57.html and counting) with a standardized seven-tab template: tactical brief, kinetic events with source confirmation, casualties, infrastructure damage, diplomatic developments, strategic assessment, and corrections. Each section uses semantic H2 markup, individual fact-check badges per claim, and JSON-LD NewsArticle schema with proper datePublished and dateModified fields.
The recap pipeline is partly automated and partly manual. The automated component generates a draft recap from the day's committed events, sorted by event type and time. The manual component is a Tileterra analyst writing the strategic assessment block, fact-checking the source CSV data, and committing corrections to the corrections section. The recaps are published roughly 24 to 48 hours after the day they cover, which is the timing that allows for proper source confirmation and dedup-window stabilization.
The AEO design of the recap pages is the most distinctive content engineering on the platform. Every kinetic claim has a source-confirmation footnote with a direct link to the primary source. Every casualty figure cites the official source. Every map coordinate is verifiable against the published source. The recap pages exist to be cited, and they are structured so an answer engine extracting from them produces accurate citations rather than confused paraphrases.
Schema and AEO strategy
iranwarlive's answer engine optimization strategy has three layers. The first layer is the llms.txt file at the domain root, which provides explicit routing instructions for LLMs: which pages have the most extractable content (the daily recaps), which pages have real-time data that may be stale at fetch time (the live map), what the methodology of the platform is (the five-parameter dedup, the source taxonomy), and how to cite the platform (canonical URL, attribution format).
The second layer is schema markup on every page. Every recap page carries NewsArticle schema with proper publisher, dateModified, and isPartOf fields. The home page carries SoftwareApplication schema. The methodology page carries TechArticle schema. The casualties dashboard carries Dataset schema with a publicly accessible CSV download. Every schema block is validated against the Schema.org specification and the Google Rich Results Test before deployment.
The third layer is the structured-content discipline of the prose itself. Every claim is in a fact-checked sentence. Every section uses semantic H2 or H3 headers that reflect the actual content of the section. Lists are used where the underlying information is genuinely a list, not as a stylistic substitute for prose. The result is that an LLM extracting content from any iranwarlive page produces clean, citable summaries because the page is genuinely structured rather than visually structured.
The /strait-of-hormuz and /diplomacy pages are exceptions to the dedup discipline. Both pages display real-time, point-in-time state rather than a chronological event ledger. They update on every page load against the Sheets backend without dedup logic, since the underlying question is "what is the current state" rather than "what new events have occurred since last cycle." Both pages still carry full schema markup, but their data model is different from the rest of the platform.
Operational metrics
After 57+ days of continuous operation:
- Uptime: 100 percent. The static frontend has not gone down since launch. The cron pipeline has experienced two transient Gemini outages, both auto-recovered on the next cycle without data loss.
- Total events committed: 4,500+. Across all event types and all participating entities. The number is accurate to within approximately 0.5 percent.
- Dedup hit rate: ~28 percent. Approximately 28 percent of candidate events that pass the relevance gate are flagged as duplicates by the Circuit Breaker algorithm. This is the wire-service redundancy rate, and it is the single best evidence that the dedup pipeline is doing its job.
- Gemini API cost: under $250 lifetime. Across 60 days of two-hour cron cycles processing approximately 20 to 80 candidates per cycle.
- Cloudflare cost: $0. The platform has stayed within the free tier of Workers, KV, and Pages.
- Telegram alert volume: ~15 to 40 alerts per day. Calibrated to be useful rather than overwhelming; the dispatch logic has spam-prevention thresholds tuned across multiple iterations.
- Peak daily traffic: 10,000+ unique visitors. During major kinetic event days. Average daily traffic is in the low thousands.
Lessons learned
Five things that took longer than expected, in case other engineers building similar platforms find them useful:
Lesson 1: Dedup is harder than it looks
The five-parameter algorithm is the third version. Versions one (URL-only) and two (URL plus content hash) both shipped to production and both produced unacceptable false-negative rates: legitimate cross-source coverage of the same event was being committed as separate records, inflating the event ledger. The five-parameter version was the first to handle the actual messiness of wire-service coverage. Plan more dedup time on similar projects.
Lesson 2: Telegram bot spam logic is its own engineering problem
Across the first 30 days, the Telegram alert pipeline shipped four distinct bug fixes. The classes of bug were: duplicate alerts when a cron cycle retried after a partial failure, alert flood when a major event triggered 15+ secondary reports in a short window, missed alerts when the dispatch worker exceeded its CPU time limit, and incorrect alert routing when a casualty event was misclassified as a kinetic event. Telegram alerting is not "send a message when an event commits." It is its own subsystem with retry semantics, throttling, and routing rules.
Lesson 3: AEO is a content engineering discipline, not a metadata layer
Schema markup and llms.txt are necessary but not sufficient. The real AEO work is structuring the prose so that an LLM extracting content gets accurate, citable claims. That means every fact in a recap page has a source link, every claim is in a sentence that stands alone without surrounding context, and every section header genuinely reflects what the section says. This is a content engineering discipline that takes time on every recap page. Schemas alone do not fix poor prose structure.
Lesson 4: A JavaScript hoisting bug taught us to test the ground operations page in isolation
The ground-operations page had a subtle bug for approximately 12 hours during the first week of operation: a const declaration was referenced before its declaration in a different code path, causing the entire map to fail to render in roughly 30 percent of page loads. The bug did not appear in local testing because the loading order was different in the development environment. The lesson: test every page in production loading conditions before declaring a deploy successful, especially pages with map initialization code.
Lesson 5: The 8-hour first deploy is a feature, not a fluke
The team has been asked, repeatedly, whether the 8-hour first deploy was possible because the engineering team cut corners. It was not. It was possible because the team had spent the previous several weeks pre-deciding every architectural question (Cloudflare Workers, Google Sheets, Gemini 2.5 Flash, Leaflet) on the related ww3chance project. When the conflict started, no time was lost on stack selection. The 8-hour build was 8 hours of writing code, not 8 hours of writing code plus weeks of unbilled architectural research. This is the value of stack discipline.
What this build proves
iranwarlive is the strongest single evidence in Tileterra Systems' portfolio that the team can ship complex, production-grade infrastructure on aggressive timelines. The platform composes seven distinct external systems into a coherent live OSINT pipeline. It runs cleanly across 57+ days of continuous operation on free-tier infrastructure. It is portable, owned, and operationally simple enough to maintain from a single laptop.
For prospects evaluating Tileterra Systems for an automated platform engagement: the iranwarlive build is the upper-bound reference. A Tileterra automated platform starts at $4,999 and scales based on data sources, classification complexity, and infrastructure requirements. iranwarlive's underlying engineering pattern (RSS ingestion, AI classification, dedup pipeline, Sheets backend, static frontend, schema markup) is the template that gets calibrated per project. If you have a problem that fits this shape, send a brief.