Skip to main content
Glama

IrradianceCrew Brief

An MCP server and Bun CLI for rooftop solar installers: give it tomorrow's booked jobs as a CSV and it returns a ranked crew schedule (Markdown + JSON). For every job it geocodes the address, reads the roof's pitch and azimuth from the Google Solar API, checks each shift hour of the Google Weather API hourly forecast against crew-safety thresholds, estimates irradiance on that roof plane, and says KEEP, MOVE (new start the same day) or SLIP, naming the rule that decided it.

Decision support only, not a safety authority. The thresholds are examples written for this lab, the irradiance figures are a documented estimate (not a measurement or a forecast product), and forecasts can be wrong. The person in charge on site decides whether work goes ahead.

CI License

The problem

An install dispatcher's evening question is "which of tomorrow's roofs can we work, from when, and which jobs slip?". The answer needs three things joined per address: the hourly forecast (gusts, rain, thunder, temperature), the roof itself (which way it faces and how steep it is, for commissioning work that wants strong sun on the array), and the crew's own limits. IrradianceCrew Brief does that join in one read-only tool call and prints the reason next to every decision: "J-102 MOVE to 11:00-17:00: wind_gust_kmh 52 km/h at 08:00 breaks gust".

Related MCP server: RoofMeasure MCP

Quickstart

bun install --frozen-lockfile
bun run demo                 # keyless: 9 synthetic jobs -> ranked schedule (reports/example-crew-brief.md)
bun run irradiancecrew profiles
cp .env.example .env         # add GOOGLE_MAPS_API_KEY (Geocoding + Solar + Weather), optional GEMINI_API_KEY
bun run irradiancecrew brief --jobs my-jobs.csv --format both --out briefs/   # live, tomorrow by default
bun run irradiancecrew brief --jobs my-jobs.csv --source nws                  # keyless US forecast, lat/lng rows

jobs.csv (header names, any order; job_id, date and an address or lat+lng are required):

job_id,date,address,crew,task,start,duration_hours,panels,priority
J-101,2026-11-03,"100 Example Ave, Sampletown",crew-a,install,07:00,6,,2
J-104,2026-11-03,"55 Mockingbird Way, Sampletown",crew-b,commission,09:00,2,12,

task: commission (any task listed under the profile's irradiance.tasks) additionally needs the irradiance estimate on the roof plane to reach the threshold (example: 400 W/m2) every hour of the block. panels picks the first N panels of the Solar API layout to decide which roof segments the crew works on. Up to 25 jobs per brief; jobs booked on other dates are listed, not briefed.

MCP server

{
  "mcpServers": {
    "irradiancecrew": {
      "command": "bun",
      "args": ["/abs/path/irradiancecrew-brief/src/cli.ts", "serve"],
      "env": { "GOOGLE_MAPS_API_KEY": "" }
    }
  }
}

Add "--fixtures" to args for the keyless synthetic demo. Five read-only tools:

Tool

Input

Upstream calls

crew_brief

jobs_csv; optional date, profile, rules[], source (auto / google / nws), min_quality, format, include_hours, gemini

per distinct site: 1 geocode (addresses only) + 1 Solar + 1 Weather call per 24 forecast hours; or 2 NWS calls on fallback

job_windows

one job: address or lat+lng, task, start, duration_hours, panels, date

same, for one site

brief_method

none

none: formulas, references, attribution, caching terms

list_profiles

none

none

session_status

none

none

How a job is decided

Each shift hour (default 07:00-17:00 local) is checked against the profile's rules. A rule states what must hold for GO: broken = NO-GO, within its margin = MARGINAL, value missing = its on_missing (MARGINAL by default). Bundled example profile crew-example:

Rule

Example threshold

gust

wind_gust_kmh < 40 (MARGINAL within 8)

rain-chance

precip_probability_pct < 50 (MARGINAL within 20)

rain

precip_mm <= 0.2 per hour

thunder

thunderstorm_probability_pct <= 20 (MARGINAL within 10)

frost

temperature_c > 1 (MARGINAL within 2)

heat

feels_like_c < 35 (MARGINAL within 3)

irradiance (commission tasks)

estimate >= 400 W/m2 every hour of the block

Decision rules, first match wins:

Rule

Action

When

no-location

slip

the address does not geocode and the CSV has no lat/lng

no-forecast

slip

no hourly forecast for the job day (past, beyond 240 h, both sources failed, or no time zone)

planned-window-ok

keep

every hour of the planned block (start + duration) is GO or MARGINAL, and meets the irradiance threshold when the task needs it

moved-within-day

move

the planned block fails, but an equally long block inside the shift passes; the earliest one is proposed

no-irradiance-window

slip

safe blocks exist, but none meets the irradiance threshold (irradiance tasks only)

no-safe-window

slip

no block of the needed length without a NO-GO hour; the rule behind most NO-GO hours is named

Ranking: keep, move, slip; then CSV priority; then fewer MARGINAL rules in the block; then the higher irradiance estimate; then job id. Same-crew overlaps after moves are flagged for the dispatcher, not resolved. Override thresholds with --rule "wind_gust_kmh < 30 margin 5 id gust" (same id replaces), or --profile-file (CLI only).

Irradiance proxy (an estimate)

Neither API gives plane-of-array irradiance: the Weather API ForecastHour has cloudCover and uvIndex but no irradiance field, and the Solar API gives roof geometry and yearly sunshine, not hourly forecasts. The proxy per hour is:

  1. Sun position from NOAA's "General Solar Position Calculations" (fractional-year equation of time and declination).

  2. Clear-sky GHI = 1098 cos z exp(-0.059 / cos z) W/m2 (Haurwitz 1945; constants as in pvlib-python's clearsky.haurwitz).

  3. Plane of array with an assumed clear-sky diffuse fraction of 0.15: POA = DNI max(0, cos AOI) + DHI (1 + cos tilt) / 2, with tilt = Solar API pitchDegrees and azimuth = azimuthDegrees (0 = N, 90 = E, 180 = S).

  4. Cloud factor 1 - 0.75 (cloudCover/100)^3.4 (Kasten & Czeplak 1980).

  5. Mean of four samples per hour; several roof segments weighted by their share of the panels.

Not modelled: shading, aerosols, altitude, the shift to diffuse light under cloud, ground reflection. Use it to rank hours and roofs against each other, not as an energy forecast.

Measured

Produced by bun run report (scripts/report.ts) on darwin-x64, Bun 1.4.3: each row is one call through the MCP server (in-memory client, real tools, service, HTTP client, rules and irradiance model) with every upstream answered by the synthetic fixtures (invented sites, roofs and weather; now = 2026-11-02T18:00:00-05:00, brief date 2026-11-03). No network, no keys. Source: reports/irradiancecrew-brief.md and .json; full example brief: reports/example-crew-brief.md.

Scenario

Tool

Jobs

KEEP

MOVE

SLIP

Decided by

Weather code path (synthetic data)

Roof source

Upstream calls

demo CSV, source auto (Weather API path, NWS fallback)

crew_brief

8

3

2

3

planned-window-ok 3, moved-within-day 2, no-irradiance-window 1, no-location 1, no-safe-window 1

google 6, none 1, nws (fallback) 1

solar-panels 5, unavailable 2, solar-largest-segment 1

30

demo CSV, source google only

crew_brief

8

2

2

4

moved-within-day 2, planned-window-ok 2, no-forecast 1, no-irradiance-window 1, no-location 1, no-safe-window 1

google 6, none 2

solar-panels 5, unavailable 2, solar-largest-segment 1

28

demo CSV, inline rule gust < 25 margin 5

crew_brief

8

3

1

4

planned-window-ok 3, no-safe-window 2, moved-within-day 1, no-irradiance-window 1, no-location 1

google 6, none 1, nws (fallback) 1

solar-panels 5, unavailable 2, solar-largest-segment 1

30

demo CSV, --gemini with no key (gemini: skipped: no GEMINI_API_KEY)

crew_brief

8

3

2

3

planned-window-ok 3, moved-within-day 2, no-irradiance-window 1, no-location 1, no-safe-window 1

google 6, none 1, nws (fallback) 1

solar-panels 5, unavailable 2, solar-largest-segment 1

30

demo CSV, upstream budget 12

crew_brief

8

1

1

6

no-location 5, moved-within-day 1, no-safe-window 1, planned-window-ok 1

none 5, google 3

unavailable 5, solar-panels 3

12

one commission job by lat/lng (west roof, cloudy morning)

job_windows

1

0

1

0

moved-within-day 1

google 1

solar-panels 1

3

Demo CSV, per job (synthetic data; irradiance figures are the documented ESTIMATE, not measurements):

#

Job

Task

Action

Planned

Proposed

Decided by

Roof az/pitch (source)

Shift GO / MARGINAL / NO-GO h

Irradiance est. block Wh/m2

Peak est. W/m2

1

J-101

install

KEEP

07:00-13:00

07:00-13:00

planned-window-ok

180/25 + 95/25 (solar-panels)

10 / 0 / 0

3664

784

2

J-108

install

KEEP

08:00-13:00

08:00-13:00

planned-window-ok

150/18 (solar-panels)

10 / 0 / 0

3449

787

3

J-107

install

KEEP

07:00-13:00

07:00-13:00

planned-window-ok

180/0 (unavailable)

7 / 1 / 2

2694

624

4

J-102

install

MOVE

08:00-14:00

11:00-17:00

moved-within-day

200/30 (solar-panels)

6 / 0 / 4

4135

871

5

J-104

commission

MOVE

09:00-11:00

12:00-14:00

moved-within-day

265/22 + 85/22 (solar-panels)

10 / 0 / 0

1170

600

6

J-103

install

SLIP

07:00-14:00

-

no-safe-window

178/20 (solar-panels)

3 / 2 / 5

-

487

7

J-105

commission

SLIP

10:00-12:00

-

no-irradiance-window

2/35 (solar-largest-segment)

10 / 0 / 0

-

56

8

J-106

install

SLIP

-

-

no-location

180/0 (unavailable)

0 / 0 / 0

-

-

Live checks (bun run test:live, 2026-10-21, this machine, keys from dev-test-461117)

Check

Result

Geocoding v4 geocode/address (a public landmark address)

HTTP 200, granularity=ROOFTOP, within 0.02 deg of the expected point; test passes

Solar API buildingInsights:findClosest

HTTP 403 SERVICE_DISABLED (Solar API not enabled in that project); prints a skipped: line

Weather API forecast/hours:lookup

HTTP 403 SERVICE_DISABLED (Weather API not enabled in that project); prints a skipped: line

End to end: one lat/lng job tomorrow, source: auto

Weather API 403 -> NWS fallback, roof unavailable (Solar 403), 10 shift hours evaluated, 4 upstream calls; test passes

Gemini generateContent (optional wording)

HTTP 402 (RESOURCE_EXHAUSTED); prints a skipped: line, template wording kept

Without keys the Google tests print skipped: ... no GOOGLE_MAPS_API_KEY; with IRRADIANCECREW_LIVE=0 (CI) every live test prints a skipped: line. Live responses are not stored.

Sources (checked 2026-10-21)

Source

Used for

Reference

Geocoding API v4 GET https://geocode.googleapis.com/v4/geocode/address/{address} (X-Goog-FieldMask: results.placeId,results.formattedAddress,results.location,results.granularity)

job coordinates

Geocoding v4 overview

Solar API GET https://solar.googleapis.com/v1/buildingInsights:findClosest?location.latitude&location.longitude&requiredQuality

roofSegmentStats[].pitchDegrees/azimuthDegrees/stats.areaMeters2, solarPanels[].segmentIndex, imageryQuality, imageryDate

findClosest

Weather API GET https://weather.googleapis.com/v1/forecast/hours:lookup?...&unitsSystem=METRIC&hours&pageSize=24&pageToken

interval, temperature, feelsLikeTemperature, wind.speed/gust, precipitation.probability/qpf/snowQpf, thunderstormProbability, cloudCover, uvIndex, isDaytime, weatherCondition.type, timeZone.id

forecast.hours.lookup

NWS GET https://api.weather.gov/points/{lat},{lng} then the raw forecastGridData grid (keyless, US only)

fallback hourly gust, wind, precipitation probability and amount, thunder probability, sky cover, temperature, time zone

NWS API

Gemini POST .../v1beta/models/{model}:generateContent (optional)

wording of the brief from ids, rule ids, times and rounded numbers only; rejected if it adds a number

Gemini API

About "WeatherNext-powered". The idea text calls the Weather API "WeatherNext-powered". Google's Weather API FAQ (last updated 2026-10-20) describes its forecasts as built "with advancements from Google DeepMind's MetNet and WeatherNext AI models (2025)", combining AI and traditional forecasting systems, and says the raw WeatherNext models (GraphCast, GenCast) are a separate offering through Earth Engine and BigQuery. So the Weather API draws on WeatherNext alongside other systems; it does not expose WeatherNext output, and this tool makes no claim about which model produced a given hour (FAQ).

Attribution and caching

  • Live output prints, as the Weather, Solar and Geocoding policies require: Source: Includes weather data from Google, Source: Includes solar data from Google, Includes data from Google Maps (for the derived schedule and irradiance estimate) and Google Maps (geocoded addresses shown without a map), plus an NWS line when the fallback is used. Fixture runs print a SYNTHETIC note instead, because no Google data is involved.

  • Caching: the Google Maps Platform Service Specific Terms allow Weather API hourly forecast values to be cached for one hour, Solar API Building Insights for up to 30 days, and Geocoding lat/lng for up to 30 days. This tool caches nothing between runs (each site is fetched once per run, in memory) and commits no real responses. A written brief contains hourly forecast values: regenerate it rather than keeping it for reuse.

  • The Solar API terms allow use to assess, design or install an energy system; this tool reads roof geometry for installs that are already booked. The Weather API terms forbid recreating a weather service; this tool only derives crew decisions.

Limits

  • Not verified live in this build: Solar API and Weather API responses. Both APIs answer 403 SERVICE_DISABLED in the only Cloud project available here and were not enabled for this build. Their clients run only against synthetic fixtures built from the published references (the fixtures are checked against the documented field lists). The live path that ran end to end is Geocoding + NWS fallback.

  • Not verified: the Gemini wording path. The key answers HTTP 402, so --gemini was only exercised against mocked responses. The default model id gemini-3.1-flash-lite comes from SkilledDragonCorp/pollenbrief-mcp's record and was not confirmed from this build; override with IRRADIANCECREW_GEMINI_MODEL.

  • The irradiance figure is an estimate with an assumed diffuse fraction and no shading; the 400 W/m2 commissioning threshold is an example, not taken from any standard or equipment manual.

  • Formula sources: the NOAA sun-position equations and the Haurwitz constants (pvlib clearsky.py) were checked against the source documents during this build; the Kasten & Czeplak cloud factor is the widely cited form of the 1980 paper, which was not retrieved here.

  • Forecast wind is not given at roof height; treat it as a lower bound for wind at the eave.

  • Hours are whole local hours; blocks must start on the hour. The shift is one window per profile.

  • Moves stay within the same day; slipped jobs are not re-planned onto later days, and crew overlaps are flagged, not resolved.

  • NWS fallback: US only; no UV, no condition type; precipitation amounts arrive in multi-hour blocks and are spread evenly.

  • One Solar request per site at requiredQuality (default MEDIUM); a 404 leaves the job with a flat-roof proxy rather than retrying lower qualities.

  • The per-session call budget (default 60) is per process, not per key or per day.

Development

bun run check is the local gate and the only CI step: frozen install, skills layout, strict YAML (workflow + profiles), Biome, tsc --noEmit, unit + keyless e2e tests, live tests (skip cleanly), the report, bun audit, actionlint when installed, and gitleaks dir. Fixtures are regenerated with bun run fixtures.

Not affiliated

Not affiliated with, endorsed by, or sponsored by Google LLC or the US National Weather Service. Google Maps, Google Maps Platform, Solar API, Weather API and Gemini are trademarks of Google LLC.

Built by SkilledDragon LLC. Questions: hello@skilleddragon.com. Apache-2.0, see LICENSE and NOTICE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Provides free EagleView-style satellite roof measurements and modular Xactimate-style estimating from Google Solar API data, enabling contractors to generate reports and estimates from any address.
    3
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables users to check whether an Australian field-service job can be worked at a location on a given day for a specific trade, using live weather forecasts, daylight hours, and public holiday data.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables home solar energy planning by estimating PV output, validating consumption plans against generation and battery constraints, and automatically shifting flexible loads to ensure power availability during scheduled outages.
    -