AWS Builder Center
Building SaansSafe: a Strands agent that tells Indian schools if kids can go outside

Building SaansSafe: a Strands agent that tells Indian schools if kids can go outside

How I turned CPCB air-quality and heat forecasts into go/no-go decisions for school assembly, recess and PE, built an AI advisor on the AWS Strands Agents SDK that answers in Hindi or English, and what fought back along the way.

Series: WeMakeDevs (1 article)

  1. 1
    Building SaansSafe: a Strands agent that tells Indian schools if kids can go outside This article
Final Year Computer Engineering Student at VCET
Every winter, Delhi's AQI crosses 300 for weeks. Every summer, schools across India face afternoons that feel like 45°C. And every school morning, a principal makes the same call: do we hold assembly outside? Do we cancel PE? Can the kids with asthma go out at recess?
Most of the time that decision comes from a single AQI number on a news app. That number is often on the US scale, often from a station 10 km away, and it says nothing about the hours children are actually outdoors. Heat and air are checked separately, if at all, and parents get no clear message.
For the WeMakeDevs Environmental Hacks hackathon (Air and Heat & Water tracks), I built SaansSafe (saans means "breath" in Hindi) to answer one question properly: can the kids go outside today?

What it does

You type a school, locality or city (or use GPS), and SaansSafe:
  1. Pulls the hourly forecast of PM2.5, PM10, temperature, humidity and UV for that exact spot.
  1. Converts it to India's official CPCB AQI, not the US scale.
  1. Judges each part of the school day on its own time window: assembly 7–9, recess 10–12, PE 9–14, dismissal 13–15.
  1. Gives a plain verdict for each one: Go · Go with care · Limit · Move indoors · Stay home, with what to do instead and whether air or heat is the reason.
  1. Writes a ready-to-forward circular for the parents' WhatsApp group in English and Hindi: what the school will do, and what to send with the child (water, a cap, an N95 mask, an inhaler).
  1. Has an AI advisor that answers free-form questions like "Can we hold the football match tomorrow at 11?" or "कल बच्चों को पीटी करा सकते हैं?"
There's also an asthma mode with stricter thresholds and a 4-day outlook for planning sports days. The whole UI switches between English and हिंदी, and everything runs on a basic phone with no login.

The stack

Plain text
Browser (HTML/CSS/JS, mobile-first, EN/HI)
│ fetches the forecast itself, then POSTs it
▼
FastAPI (Python)
├── rules.py ─ CPCB AQI · IMD-colour heat alerts · per-activity decisions
├── data.py ─ Open-Meteo (Copernicus CAMS) + OpenStreetMap geocoding, cache
└── agent.py ─ Strands Agents SDK advisor with 3 tools
model: Groq free tier (gpt-oss-120b → 20b fallback),
switchable to Amazon Bedrock
It ships as one Docker image, deployed on Render's free tier. Total cost: ₹0.

The rule engine: CPCB AQI, hour by hour

The core is deliberately not AI. A principal needs the same answer every time, so the decisions come from a small, unit-tested rule engine.
CPCB's National AQI maps each pollutant's concentration onto a band, scales it linearly inside that band, and takes the worst pollutant as the AQI:
Python
PM25_BREAKPOINTS = [ # (conc_lo, conc_hi, aqi_lo, aqi_hi)
(0, 30, 0, 50), (30, 60, 51, 100), (60, 90, 101, 200),
(90, 120, 201, 300), (120, 250, 301, 400), (250, 500, 401, 500),
]
def _sub_index(conc, table):
for c_lo, c_hi, i_lo, i_hi in table:
if conc <= c_hi:
return round(i_lo + (i_hi - i_lo) * (max(conc, c_lo) - c_lo) / (c_hi - c_lo))
return 500
def indian_aqi(pm25, pm10):
subs = {"PM2.5": _sub_index(pm25, PM25_BREAKPOINTS), "PM10": _sub_index(pm10, PM10_BREAKPOINTS)}
dominant = max(subs, key=subs.get)
return {"aqi": subs[dominant], "dominant": dominant}
Then each activity takes the worst hour inside its own window, and the result is mapped to a verdict level for both air and heat. The worse of the two wins. Strenuous PE steps up one level, and asthma mode uses stricter cut-offs. Heat labels follow IMD's colour code (Green / Yellow / Orange / Red), applied to the "feels like" temperature so humid coastal heat counts too.
I chose hourly values over CPCB's 24-hour average on purpose: a daily average hides the morning spike at exactly the time assembly happens.

Where AWS fits: the Strands advisor

The dashboard answers "what's the verdict?" But principals ask messier questions: "We have the inter-house final at 11 tomorrow, can we keep it?" That's where I used the AWS open-source Strands Agents SDK.
What I liked straight away: a tool is just a Python function. Strands builds the tool schema from the type hints and the docstring.
Python
from strands import Agent, tool
@tool
def school_conditions(lat: float, lon: float, place: str = "",
sensitive_children: bool = False, day: int = 0) -> str:
"""Get air quality (Indian AQI), heat stress and activity verdicts for school hours.
Args:
lat: Latitude of the school.
lon: Longitude of the school.
place: Human-readable name of the place.
sensitive_children: True when advising for children with asthma.
day: 0 = today, 1 = tomorrow, up to 3.
"""
...
The agent gets three tools,  find_place ,  school_conditions  and  parent_circular , and a system prompt with one hard rule: always call a tool before answering, and never override the rule engine's verdicts. The LLM explains decisions; it never invents AQI numbers.
Python
agent = Agent(
model=model,
tools=[find_place, school_conditions, parent_circular],
system_prompt=SYSTEM_PROMPT,
callback_handler=None,
)
answer = agent("Can we hold sports day tomorrow?")
Strands' agent loop handles the multi-step part on its own: find the school, fetch tomorrow's conditions, then answer. I wrote no orchestration code.
Strands is also model-agnostic, which is what let me build this for free. I used its  OpenAIModel  provider pointed at Groq's OpenAI-compatible endpoint. Switching to Amazon Bedrock is one environment variable ( LLM_PROVIDER=bedrock ), because the same code path already creates a  BedrockModel .

What fought back

This is the part I learned the most from.
1. My server got rate-limited by a free API. Locally everything worked. Deployed, every forecast failed with  429 Too Many Requests . Free hosts share outbound IP addresses, and the weather API was throttling the shared IP. The fix was architectural: the browser now fetches the forecast itself (Open-Meteo allows CORS) and POSTs the raw data to my API, which runs the rules. Each visitor uses their own quota. The server-side fetch stays as a fallback.
2. The AI advisor "froze". Groq's free tier allows 8,000 tokens per minute per model, and each question was using about 3,800. The third question in a minute got throttled, and Strands' default retry strategy waited (4s, 8s, 16s…) before giving up, so the page looked frozen. Three fixes:
  • I made the tool output compact (about 277 tokens instead of about 1,175).
  • I capped retries with  ModelRetryStrategy(max_attempts=2, initial_delay=2, max_delay=4) .
  • On  ModelThrottledException , I fall back to a second model that has its own quota.
Python
for model_id in [None, "openai/gpt-oss-20b"]:
agent = Agent(model=_model(model_id), tools=TOOLS, system_prompt=prompt,
callback_handler=None,
retry_strategy=ModelRetryStrategy(max_attempts=2, initial_delay=2, max_delay=4))
try:
return str(agent(question))
except ModelThrottledException:
continue
raise AdvisorBusy()
3. Hindi questions got English answers. I first added "[Reply in Hindi]" after the user's question. The model ignored it, because the tool results were all in English and outweighed one short line. Moving the language rule into the system prompt, with Hindi names for each verdict, fixed it every time. Lesson: put instructions where the model actually gives them weight.
4. "Kolhapur" isn't one place. Several villages share the name, and one in Wardha district had an AQI of 148 while Kolhapur city was at 54. Picking the wrong suggestion would mean giving a school the wrong air. Results are now ranked by population, duplicates are merged, and every suggestion shows its district.
5. "Why is your AQI different from the website?" A tester compared my numbers with a popular app. The same air reads about 54 on India's CPCB scale and about 108 on the US scale most apps use. That turned into a design principle: Indian standards only (CPCB for air, IMD for heat). The app also says honestly that the data is a ~40 km model forecast, not a ground sensor.
6. The parent notice read like a robot. My first version was a list of staff instructions pasted into a message. A real teacher wouldn't send that to parents. I rewrote it as a school circular: "Dear Parents", what the school will do, what to send with the child, when to keep them home, signed by the Principal, with dates in DD-MM-YYYY. It's built from the same rules, with no LLM involved, so it's always consistent with the dashboard.

What I'd build next

  • A 6 am alert by SMS or WhatsApp for subscribed schools, using Amazon EventBridge Scheduler and Amazon SNS.
  • Live CPCB station readings blended in wherever a monitoring station is close to the school.
  • School-specific timetables and more regional languages.
  • Moving the advisor to Amazon Bedrock for production use, which is a config change thanks to Strands.

Takeaways

  • The hard part wasn't the AI. It was turning air and heat science into a decision a principal can act on in five seconds.
  • Keep decisions deterministic and let the agent explain them. Strands tools made that split clean: the rules decide, the model talks.
  • Free tiers are a design constraint, not an afterthought. Rate limits shaped the architecture twice.
  • Build for the real user. Hindi, WhatsApp, DD-MM-YYYY and Indian standards mattered more to usability than any animation did.
If you run or teach at a school in India, try it on your own area and tell me what's wrong:  https://saanssafe.onrender.com 
SaansSafe is a guidance tool, not a medical or government order. Always follow your state's education department and GRAP circulars.

Series: WeMakeDevs (1 article)

  1. 1
    Building SaansSafe: a Strands agent that tells Indian schools if kids can go outside This article
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article