Sprint Kickoff:
Building Your
AI-EO Application
From architecture to working code. You have your design from this morning and feedback from Factory 2026. Now, it is time to build. This session is your dedicated construction time with instructor support.
Sprint Methodology
Today we adopt an agile sprint approach to building your application. A sprint is a short, focused burst of development with clear goals and check-ins. This is how professional software teams work, and it is how you will build your AI-EO prototype today.1
Why Sprints Work for Prototyping
- Time-boxed focus: Two hours of dedicated building with clear start and end points
- Incremental delivery: Ship small pieces that work rather than one big piece that does not
- Rapid feedback: Test each feature as you build it, then correct course immediately
- Reduced scope creep: A sprint forces you to prioritize ruthlessly
Key Principle: The goal is not perfection. The goal is a working skeleton. Get the core feature running first, then improve it. A working demo with rough edges beats a polished mockup with no functionality.
Your Sprint Backlog
Prioritize these items in order. Each one builds on the previous:
- Map renders with base tiles and correct initial view
- Chat UI sends and displays messages
- AI responds to user queries via the Gemini API
- Map reacts to AI instructions (markers, layers, zoom)
- Polish: error handling, loading states, styling
Today's Goal: A Working Skeleton
By the end of this two-hour session, you should have a functional application skeleton where the core feature works end-to-end. This means a user can interact with your app and see meaningful results, even if the interface is rough.
Interactive Map
Leaflet map with base tiles, centered on your study area, responding to programmatic commands
Chat Interface
Input field and message display where users can type queries and see AI responses
AI Integration
Gemini API connection that processes user questions and returns structured responses
Connected Pipeline
User input triggers AI, which triggers map actions: the full loop working together
Scope Discipline: Resist the temptation to add features. Do not add satellite data overlays, multi-layer controls, or fancy animations today. Get the skeleton working first. You will have all of next week to add features and polish.
Work Format
This is individual or team work with floating instructor support. Work at your own pace. Raise your hand or post in the class channel when you get stuck. Check in at the milestone times on the schedule. Help each other: if you solved a problem, share the solution.
Template Repository Structure
Your project follows the architecture you designed this morning. Here is the standard folder structure for an AI-EO web application. Every file has a clear purpose and a single responsibility.2
Why This Structure?
- Separation of Concerns: Each module handles one thing. AI logic does not touch the DOM. Map logic does not call APIs.
- Testability: You can test each module independently
- Team-friendly: Two people can work on different modules without merge conflicts
- Scalability: Easy to add new features (e.g., a
satellite-service.js) without modifying existing code
Simplified Start: For today's sprint, you may start with a single index.html file containing everything inline. This reduces setup friction. You can refactor into separate files tomorrow or next week.
Starter Code: HTML + Leaflet + Chat Panel
Here is the complete HTML shell for your application. It provides a split-screen layout: a Leaflet map on the left and a chat sidebar on the right. Copy this as your starting point.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>My AI-EO App</title> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"> <style> body { margin: 0; display: flex; height: 100vh; font-family: sans-serif; } #map { flex: 1; } #sidebar { width: 400px; display: flex; flex-direction: column; background: #0d1224; color: #f9fafb; } #chat-messages { flex: 1; overflow-y: auto; padding: 1rem; } #chat-input-area { display: flex; padding: 0.5rem; gap: 0.5rem; border-top: 1px solid rgba(255,255,255,0.1); } #chat-input { flex: 1; padding: 0.75rem; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.05); color: white; border-radius: 8px; } .msg { padding: 0.75rem; margin: 0.5rem 0; border-radius: 12px; max-width: 85%; } .msg.user { background: #10b981; margin-left: auto; } .msg.ai { background: rgba(255,255,255,0.05); } </style> </head> <body> <div id="map"></div> <div id="sidebar"> <div id="chat-messages"></div> <div id="chat-input-area"> <input id="chat-input" placeholder="Ask about any location..."> <button onclick="sendMessage()">Send</button> </div> </div> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> </body> </html>
Copy this exactly. Save it as index.html, open it in your browser, and confirm you see a map on the left and a dark sidebar on the right. If that works, you have a foundation to build on.
AI Service Module
The AI service module handles all communication with the Gemini API. It encapsulates configuration, request formatting, response parsing, and error handling in one place.3
// ============================================ // AI SERVICE MODULE // Handles all Gemini API communication // ============================================ const AI_CONFIG = { apiKey: 'YOUR_API_KEY_HERE', model: 'gemini-2.0-flash', baseUrl: 'https://generativelanguage.googleapis.com/v1beta', }; // Build the API endpoint URL function getEndpoint() { return `${AI_CONFIG.baseUrl}/models/${AI_CONFIG.model}:generateContent?key=${AI_CONFIG.apiKey}`; } // Send a prompt to Gemini and get a response async function askGemini(userMessage, systemPrompt) { const body = { contents: [{ role: 'user', parts: [{ text: userMessage }] }], systemInstruction: { parts: [{ text: systemPrompt }] } }; try { const res = await fetch(getEndpoint(), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!res.ok) { throw new Error(`API Error: ${res.status}`); } const data = await res.json(); return data.candidates[0] .content.parts[0].text; } catch (err) { console.error('Gemini error:', err); return 'Sorry, I could not process that request. Please check your API key and try again.'; } }
Key Design Decisions
- Centralized config: API key and model name in one object; easy to change
- Error boundaries: try/catch ensures the app never crashes from a failed API call
- Single responsibility: This module only talks to the AI. It does not touch the DOM or the map.
Security Note: Never commit your API key to a public GitHub repository. For this class exercise, embedding the key in client-side code is acceptable. For production apps, always use a backend proxy or environment variables.
Map Service Module
The map service module wraps all Leaflet interactions. It initializes the map, manages markers and layers, and provides clean functions that other modules can call to update the map display.4
// ============================================ // MAP SERVICE MODULE // Manages Leaflet map and all geospatial UI // ============================================ let map; let markersLayer; // Initialize the map with default view function initMap(containerId, center, zoom) { map = L.map(containerId).setView(center, zoom); L.tileLayer( 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap' } ).addTo(map); // Layer group for dynamic markers markersLayer = L.layerGroup().addTo(map); return map; } // Add a marker with a popup function addMarker(lat, lng, popupText) { const marker = L.marker([lat, lng]) .bindPopup(popupText); markersLayer.addLayer(marker); return marker; } // Fly to a location with animation function flyTo(lat, lng, zoom = 10) { map.flyTo([lat, lng], zoom, { duration: 1.5 }); } // Clear all dynamic markers function clearMarkers() { markersLayer.clearLayers(); }
Leaflet Essentials to Remember
Container Height
The map container must have explicit height. Use height: 100vh or flex: 1.
Layer Groups
Use L.layerGroup() to manage markers. Makes it easy to add, remove, and clear.
flyTo vs setView
flyTo() animates the transition. setView() jumps instantly. Both take [lat, lng].
The Orchestrator: app.js
The app.js file is the conductor of your application. It does not contain business logic itself; instead, it wires the AI service, map service, and UI together into a coherent flow.
// ============================================ // APP ORCHESTRATOR // Connects AI, Map, and UI modules // ============================================ // System prompt: tells the AI how to behave const SYSTEM_PROMPT = `You are an Earth observation assistant. When the user asks about a location, respond with a JSON object containing: - "text": your explanation (1-2 paragraphs) - "location": { "lat": number, "lng": number } - "zoom": suggested zoom level (1-18) - "marker": text for the map marker popup Always respond in valid JSON only.`; // Initialize the map on page load initMap('map', [48.57, 7.75], 6); // Main message handler async function sendMessage() { const input = document .getElementById('chat-input'); const text = input.value.trim(); if (!text) return; // 1. Show user message addChatMessage(text, 'user'); input.value = ''; // 2. Call AI const raw = await askGemini(text, SYSTEM_PROMPT); // 3. Parse and act try { const data = JSON.parse(raw); addChatMessage(data.text, 'ai'); if (data.location) { flyTo(data.location.lat, data.location.lng, data.zoom); addMarker(data.location.lat, data.location.lng, data.marker); } } catch { addChatMessage(raw, 'ai'); } } // Simple chat message renderer function addChatMessage(text, role) { const div = document.createElement('div'); div.className = `msg ${role}`; div.textContent = text; const container = document .getElementById('chat-messages'); container.appendChild(div); container.scrollTop = container.scrollHeight; }
The JSON Pattern: By asking Gemini to respond in JSON, we get structured data we can parse programmatically. The try/catch around JSON.parse is essential because LLMs do not always produce valid JSON. The fallback displays the raw text.
Complete Starter Scaffold
Here is the complete, self-contained starter application. This single file includes the HTML layout, all styles, and the full JavaScript for AI integration, map control, and chat UI. Copy this and replace YOUR_API_KEY_HERE with your Gemini API key to get a working prototype.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>My AI-EO App</title> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"> <style> /* === LAYOUT === */ * { margin:0; padding:0; box-sizing:border-box; } body { display:flex; height:100vh; font-family: 'Outfit', sans-serif; } #map { flex:1; } #sidebar { width:400px; display:flex; flex-direction:column; background:#0d1224; color:#f9fafb; } #chat-header { padding:1rem; font-weight:700; border-bottom:1px solid rgba(255,255,255,0.1); font-size:1.1rem; } #chat-messages { flex:1; overflow-y:auto; padding:1rem; } #chat-input-area { display:flex; padding:0.5rem; gap:0.5rem; border-top:1px solid rgba(255,255,255,0.1); } #chat-input { flex:1; padding:0.75rem; border:1px solid rgba(255,255,255,0.1); background:rgba(255,255,255,0.05); color:white; border-radius:8px; font-family:inherit; font-size:0.9rem; } #send-btn { padding:0.75rem 1.25rem; background:#10b981; color:white; border:none; border-radius:8px; font-weight:600; cursor:pointer; } #send-btn:hover { background:#059669; } /* === MESSAGES === */ .msg { padding:0.75rem; margin:0.5rem 0; border-radius:12px; max-width:85%; line-height:1.5; font-size:0.9rem; } .msg.user { background:#10b981; margin-left:auto; } .msg.ai { background:rgba(255,255,255,0.05); } .msg.loading { opacity:0.6; font-style:italic; } </style> </head> <body> <div id="map"></div> <div id="sidebar"> <div id="chat-header">π AI Earth Observer</div> <div id="chat-messages"></div> <div id="chat-input-area"> <input id="chat-input" placeholder="Ask about any location..." onkeydown="if(event.key==='Enter') sendMessage()"> <button id="send-btn" onclick="sendMessage()">Send</button> </div> </div> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> <script> // ====== CONFIG ====== const API_KEY = 'YOUR_API_KEY_HERE'; const MODEL = 'gemini-2.0-flash'; const SYSTEM = `You are an Earth observation assistant. Respond in JSON: {"text":"...", "location":{"lat":0,"lng":0}, "zoom":10, "marker":"popup text"}. Always valid JSON.`; // ====== MAP ====== const map = L.map('map').setView([48.57,7.75],6); L.tileLayer('https://{s}.tile.openstreetmap.org' + '/{z}/{x}/{y}.png').addTo(map); const markers = L.layerGroup().addTo(map); // ====== AI ====== async function askAI(prompt) { const url = `https://generativelanguage.googleapis.com/v1beta/models/` + `${MODEL}:generateContent?key=${API_KEY}`; const r = await fetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ contents:[{role:'user', parts:[{text:prompt}]}], systemInstruction:{parts:[{text:SYSTEM}]} }) }); const d = await r.json(); return d.candidates[0].content.parts[0].text; } // ====== CHAT ====== function addMsg(text, role) { const el = document.createElement('div'); el.className = `msg ${role}`; el.textContent = text; const c = document.getElementById('chat-messages'); c.appendChild(el); c.scrollTop = c.scrollHeight; return el; } async function sendMessage() { const inp = document.getElementById('chat-input'); const q = inp.value.trim(); if(!q) return; addMsg(q, 'user'); inp.value = ''; const loading = addMsg('Thinking...', 'ai loading'); try { const raw = await askAI(q); loading.remove(); const data = JSON.parse( raw.replace(/```json?\n?/g,'') .replace(/```/g,'').trim() ); addMsg(data.text, 'ai'); if(data.location) { map.flyTo([data.location.lat, data.location.lng], data.zoom||10); L.marker([data.location.lat, data.location.lng]) .bindPopup(data.marker||'π') .addTo(markers).openPopup(); } } catch(e) { loading.remove(); addMsg('Error: '+e.message, 'ai'); } } </script> </body> </html>
This is your launchpad. Every student should have this running within the first 15 minutes. Everything you build from here is customization: your own system prompt, your own study area, your own unique features.
Work Session Structure
The remaining time is yours to build. We structure it as a series of short sprints with brief check-ins to maintain momentum and catch problems early.
| Time | Activity | Goal |
|---|---|---|
| 14:00 - 14:15 | Setup Sprint | Scaffold running, map visible, sidebar visible |
| 14:15 - 14:45 | Core Sprint 1 | AI responds to chat input with text |
| 14:45 - 14:50 | Check-in #1 | Show a working AI response to the class |
| 14:50 - 15:30 | Core Sprint 2 | AI responses drive map actions (flyTo, markers) |
| 15:30 - 15:35 | Check-in #2 | Demo the full chat-to-map pipeline |
| 15:35 - 15:55 | Polish Sprint | Error handling, loading states, custom system prompt |
| 15:55 - 16:00 | Wrap-up | Git commit, push, progress reflection |
The 15-Minute Rule: If you have been stuck on the same problem for 15 minutes, stop and ask for help. Do not waste sprint time debugging alone. The instructor and your classmates are resources; use them.
Getting Unstuck: Troubleshooting Guide
These are the most common issues students encounter during the first build session. Before asking for help, check this list. Most problems have a quick fix.
Check 2: Open the browser console (F12). Look for HTTP 400 or 403 errors.
Check 3: Are you hitting rate limits? The free tier has per-minute request limits (check your quota at Google AI Studio).
Check 4: Is the model name spelled correctly? Use
gemini-2.0-flash (not gemini-pro).
#map) have explicit height? It needs height: 100vh or flex: 1 inside a flex parent.Check 2: Is the Leaflet CSS loaded before the JS? CSS must come first.
Check 3: Check the tile URL. The
{s} subdomain placeholder is required.
raw.replace(/```json?\n?/g, '').replace(/```/g, '').trim()Also add a
try/catch fallback that displays raw text when parsing fails.
Check 2: Check the response: is
candidates[0].content.parts an array? Look for functionCall objects.Check 3: Your system prompt must clearly describe when to use each tool.
Pro Tip: Keep the browser console open at all times. Most errors appear there first. Use console.log() liberally during development; remove them before presenting.
Quick Check: Debug Skills
Q1. Your Leaflet map shows a grey area with no tiles. What is the most likely cause?
Q2. The Gemini API returns a 429 status code. What does this mean?
Q3. Why do we ask the AI to respond in JSON instead of plain text?
Mandatory Team Git Exercise
Before you write any project code, your team must complete this Git collaboration drill to prevent overwriting each other's code during the sprint:
- One person creates the GitHub repository and adds the others as Collaborators.
- Everyone clones the repository locally.
- Everyone creates their own branch (
git checkout -b feature-test). - Everyone edits the same line of the
README.mdfile, commits, and pushes their branch. - Open Pull Requests for all branches. The first one will merge cleanly. The others will have a Merge Conflict.
- Resolve the merge conflict together as a team on the GitHub UI or locally.
Using GitHub Copilot During the Sprint
GitHub Copilot (or any AI coding assistant) can dramatically accelerate your development during the sprint. The key is knowing when to trust it and when to verify.5
β Great Uses
- Boilerplate code: "Create a function to format a date string"
- CSS styling: "Style this button with a gradient and hover effect"
- Error handling patterns: "Add try/catch with user-friendly error messages"
- Documentation: "Add JSDoc comments to this function"
- Repetitive patterns: Multiple similar event handlers, form fields
β οΈ Verify Carefully
- API endpoints: Copilot may hallucinate URLs that do not exist
- Gemini API syntax: Always check against the official docs
- Leaflet methods: Verify method signatures against Leaflet docs
- Complex logic: Multi-step data transformations
- Security patterns: Authentication, key management
Copilot Tip: Write a clear comment describing what you want before you start typing code. Copilot generates better suggestions when it has a natural language description to work from. For example: // Parse the AI response JSON, extract lat/lng, and fly the map to that location
When to Debug Yourself vs. Ask for Help
- Debug yourself: Typos, missing semicolons, wrong variable names, CSS layout issues
- Ask Copilot: "Why is this function returning undefined?" (paste the function)
- Ask the instructor: Architecture questions, API design choices, "am I on the right track?"
Milestone Checkpoints
Use these checkpoints to gauge your progress. If you are behind, focus on the current milestone. If you are ahead, move to the next one. Do not skip ahead; each milestone builds on the previous.
By 14:30 (30 min in)
- β HTML template saved and open in browser
- β Map renders with tiles visible
- β Sidebar visible with chat input
- β API key inserted into config
By 15:00 (60 min in)
- β User messages display in chat
- β AI responds with text in chat
- β Loading indicator appears while waiting
- β Errors caught and displayed gracefully
By 15:30 (90 min in)
- β AI responses parsed as JSON
- β Map flies to locations from AI
- β Markers appear with popup text
- β Full loop: type question, see map react
By 16:00 (end)
- β Custom system prompt for your topic
- β Initial study area set for your project
- β At least one unique feature added
- β Code committed to Git
Success Metric: If you can type a location into the chat, get an AI response, and see the map fly to that location with a marker, you have achieved today's primary objective. Everything beyond that is a bonus.
Git Commit and Push Your Work
Before you leave today, commit and push your code. This is a non-negotiable professional habit. Your repository is your safety net and your progress tracker.6
# Stage all changes git add . # Commit with a descriptive message git commit -m "feat: initial scaffold with map + chat + AI integration" # Push to your remote repository git push origin main
Commit Message Best Practices
- Be specific: "Add AI service module with error handling" is better than "update code"
- Use conventional prefixes:
feat:for features,fix:for bugs,docs:for documentation - Reference what works: "feat: chat sends messages and displays AI responses"
Before pushing: Make sure your .gitignore excludes any file containing your API key. If you used an inline key in index.html, add a comment reminding yourself to move it to a config file or environment variable before making the repo public.
Progress Reflection
Take two minutes to answer these questions in your README or a notebook:
- What works? List the features that are functional right now.
- What is broken? Note any bugs or incomplete features.
- What is next? Your top 3 priorities for the next work session.
Looking Ahead: Next Week
You now have a working skeleton. Next week, we add muscle and skin. Here is what is coming:
Satellite Data APIs
Connect to real EO data sources: Sentinel Hub, Google Earth Engine, NASA FIRMS. Overlay actual satellite imagery on your map.
UX Polish
Responsive design, dark/light theme toggle, loading animations, accessibility improvements, mobile support.
Testing & Edge Cases
What happens when the API is down? When the user sends gibberish? When coordinates are invalid? Build resilience.
Final Presentations
Prepare a 5-minute demo of your working application. Show the problem, the solution, and a live walkthrough.
Weekend Challenge (Optional)
If you want a head start for next week, try these stretch goals:
- Multiple base maps: Add a layer switcher (satellite, terrain, street) using
L.control.layers() - Conversation history: Send the last 3 messages as context to Gemini for multi-turn conversations
- Custom markers: Use Leaflet's
L.divIconto create styled markers that match your app theme - GeoJSON overlay: Load a GeoJSON file of country or region boundaries and display them on the map
Recommended Reading: Review the Gemini Function Calling documentation before Monday. Next week we will use function calling to give the AI direct control over map operations.
Summary of Big Ideas
1. Working Software Over Perfect Plans
A functioning prototype with rough edges teaches you more than a detailed architecture document. Get code running, then improve it iteratively.
2. Separation of Concerns is Not Optional
AI logic, map logic, and UI logic should live in separate modules. This makes debugging possible and collaboration practical. When something breaks, you know exactly where to look.
3. Structured AI Output Enables Automation
Asking the LLM to respond in JSON transforms it from a chat tool into a programmable component. Structured output is the bridge between natural language and application logic.
4. Error Handling is a Feature, Not an Afterthought
Every external API call can fail. Every user input can be unexpected. Wrapping your code in try/catch blocks and providing graceful fallbacks is what separates a demo from a product.
5. Sprint Discipline Drives Delivery
Time-boxed work with clear milestones prevents scope creep and ensures you ship something usable. Prioritize ruthlessly: core feature first, polish later.
Glossary & References
Glossary of Key Terms
References & Resources
- [1] Schwaber, K. & Sutherland, J. (2020). "The Scrum Guide." Scrum.org. Available at: scrumguides.org
- [2] Osmani, A. (2023). "Learning JavaScript Design Patterns," 2nd ed. O'Reilly Media. ISBN: 978-1-098-13987-2. Available at: patterns.dev
- [3] Google. (2024). "Gemini API: Generate Content." Google AI for Developers. Available at: ai.google.dev/api/generate-content
- [4] Agafonkin, V. (2024). "Leaflet API Reference." Leaflet. Available at: leafletjs.com/reference.html
- [5] Peng, S., et al. (2023). "The Impact of AI on Developer Productivity: Evidence from GitHub Copilot." arXiv. DOI: 10.48550/arXiv.2302.06590
- [6] Chacon, S. & Straub, B. (2014). Pro Git, 2nd ed. Apress. Available at: git-scm.com/book
Useful Links
Margaret Hamilton
Software Engineer
She led the MIT team that developed the onboard flight software for the Apollo space program, coining the term 'software engineering'.
Global Data, Local Impact
Applying EO to Community Challenges
Earth Observation provides a macroscopic view of environmental trends, but its true power lies in downscaling this data to affect local policy and design, such as urban planning and sustainable workplaces.
Regional Decisions Scenario
Scenario: Designing for Placemaking at Work
As you build your architecture, you must consider the human element. How does your software enable meaningful connection in a remote or hybrid setting?
Your Task:
- Define the frontend architecture.
- Explain how the UI promotes a sense of 'Third Place'.
Big Ideas & Glossary
Summary of Big Ideas
- Data is only as valuable as its application.
- Space technology has direct terrestrial benefits.
Glossary of Terms
Auto-Graded Quiz
π Daily Reflection
What was your biggest takeaway from this session, and how does it apply to the TERRA project? Write your response below. Your instructor will review this to track your progress.