# Solar renders for installers
Two systems that find buildings with a bare roof, put a real solar array on that exact roof in a real photo, and send it to the person who owns it. Everything here works anywhere in the world and every prompt is written out in full.
https://www.everestchris.com/guides/solar

## prompt 01 · step 2 · Decide if the roof is worth it

Any cheap text model. Paste the API answer in at the bottom.

You are screening roofs for a solar sales picture. You will be given the raw JSON answer from the Google Solar API buildingInsights endpoint for one building. Decide whether this roof is worth making a picture for.

Apply these rules in order and stop at the first one that fails:

1. REJECT if solarPotential.maxArrayPanelsCount is less than 8.
   Reason: "too small to be worth an installer's time".
2. REJECT if no entry in solarPotential.roofSegmentStats has stats.areaMeters2 of at least 20.
   Reason: "no single roof slope big enough for a tidy array".
3. Find the BEST SLOPE: of the slopes with areaMeters2 of 20 or more, pick the one whose azimuthDegrees is closest to {{EQUATOR_AZIMUTH}}.
   ({{EQUATOR_AZIMUTH}} is 180 if the building is north of the equator, 0 if it is south of it.)
4. REJECT if that best slope's azimuthDegrees is more than 90 degrees away from {{EQUATOR_AZIMUTH}}, measured the short way round the circle.
   Reason: "best roof slope faces away from the sun".
5. REJECT if that best slope's pitchDegrees is below 5 or above 45.
   Reason: "roof angle outside the straightforward install range".
6. REJECT if imageryDate is earlier than {{MIN_IMAGERY_YEAR}}.
   Reason: "photo too old to trust that the roof is still bare".

If nothing rejects it, ACCEPT.

Then choose how many panels to draw. From solarPotential.solarPanelConfigs, pick the LARGEST option whose panelsCount, multiplied by (panelHeightMeters * panelWidthMeters), is still no more than 80% of the best slope's areaMeters2. Never pick the biggest option in the list. If nothing qualifies, pick the smallest option.

Return ONLY this JSON object and nothing else. No explanation, no code fences.

{
  "decision": "accept" | "reject",
  "reason": "one short lowercase sentence",
  "best_slope": { "pitch_degrees": number, "azimuth_degrees": number, "area_m2": number },
  "chosen_panel_count": number,
  "system_kwp": number,
  "yearly_kwh": number,
  "confidence": "high" | "medium" | "low"
}

How to work out the numbers: system_kwp is chosen_panel_count times solarPotential.panelCapacityWatts divided by 1000, rounded to one decimal place. yearly_kwh is the yearlyEnergyDcKwh of the option you chose, rounded to a whole number. Never invent a number that is not in the JSON you were given. Set confidence to "low" if imageryQuality is not HIGH.

Here is the building:

{{BUILDING_INSIGHTS_JSON}}

## prompt 02 · step 5 · Put the panels on the house

gemini-3-pro-image or gpt-image-2. Send the real photo with it.

Photorealistic edit of this real street photograph of a house at {{ADDRESS}}. Add a real rooftop solar photovoltaic array onto the roof slopes that face the camera.

Use {{PANEL_COUNT}} standard all-black monocrystalline panels, each about {{PANEL_H}} m by {{PANEL_W}} m, laid in neat landscape rows flush and parallel to the existing roof pitch, mounted on low-profile black rails about 10 cm above the {{ROOF_MATERIAL}}, with a consistent gap from the ridge and from every roof edge, so no panel overhangs the eaves or the hips.

The panels must follow the exact perspective, vanishing lines and pitch of the real roof planes in this photo. Give the panel glass a slightly reflective dark blue-black finish that picks up the same sky and the same light already in the photograph, and cast soft panel shadows onto the roof in the same direction as every other shadow in the scene. Leave a clear walkway strip around the chimney and do not cover the chimney, the skylights or the roof vents.

Change nothing else at all. Keep the walls, the windows, the doors, the garden, the boundary wall, any parked vehicles, the sky, the exposure, the grain and the framing exactly as they are.

The result must look like an unretouched photograph of a house that already has solar panels, not like a rendering or a diagram. No text, no labels, no watermarks, no arrows, no overlays.

## prompt 03 · step 5 · Commercial version

Send the satellite tile with it, not a street photo

Photorealistic edit of this real top down satellite image of {{BUSINESS_TYPE}} at {{ADDRESS}}. Cover the large {{ROOF_DESCRIPTION}} in the centre of the frame with a commercial rooftop solar photovoltaic array.

Use dense rows of dark blue-black framed panels laid flat and flush with the existing roof bays, running parallel to the long axis of the building, in continuous blocks separated by clear walkway gangways about one metre wide every few rows, so a maintenance crew can walk the roof.

Keep a clear perimeter setback of about one and a half metres from every roof edge and around every existing rooflight strip, vent and plant unit already visible on the roof. Do not cover {{EXCLUSIONS}}.

Match the exact top down camera angle, the resolution, the satellite softness, the lighting and the colour balance of this image. Panels should read as a real installed array seen from a satellite, with subtle variation in reflection across the roof, not as a flat blue rectangle.

Change nothing else in the frame. The car park, the parked vehicles, the delivery yard, any stacked materials, the trees, the roads and every neighbouring building stay exactly as they are.

No text, no labels, no outlines, no highlight boxes, no arrows, no watermarks.

## prompt 04 · step 8 · Write the message that goes with it

Any text model

Write the message that goes with a solar picture being sent to the owner of a specific building. The picture does the selling. Your job is the few lines around it that make opening it feel normal rather than strange.

Facts you may use, and you may not use anything else:
- address: {{ADDRESS}}
- panels that fit: {{PANEL_COUNT}}
- system size: {{SYSTEM_KWP}} kWp
- estimated yearly production: {{YEARLY_KWH}} kWh
- where the roof measurement came from: {{DATA_SOURCE}}
- date of the aerial photo it was measured from: {{IMAGERY_DATE}}
- who is sending it: {{SENDER_NAME}} at {{SENDER_COMPANY}}

Rules:
- Under 60 words.
- All lowercase is fine and preferred. Plain everyday words.
- No em dashes. Use full stops and commas.
- Open by saying plainly what the picture is. Do not open with a greeting formula, a compliment, or a question about their energy bills.
- State the production figure once. Never turn it into money, a saving, a percentage or a payback period.
- Say where the roof measurement came from, in four words or fewer.
- Say clearly and briefly that the picture is an illustration, not a design.
- End with one easy question that can be answered in one word.
- Do not use: unlock, transform, revolutionise, game changer, exciting, solution, reach out, touch base, circle back, I hope this finds you well, act now, limited time, exclusive, no obligation.
- Do not claim you have visited the property, surveyed it, or been asked to look at it. Do not imply you already know them.

Return only the message. If it is an email, return a JSON object with keys "subject" and "body", where the subject is under 50 characters and does not contain the words free, save, or offer.

## prompt 05 · Set up the database

Paste into Claude Code, Cursor, or any AI coding tool, in your project folder

Set up Supabase for this project using the Supabase CLI, and write the SQL migration for me.

First, if it is not already set up: install the CLI, run `supabase init`, then `supabase link` to my project, and tell me exactly what to paste when it asks for my project ref and database password.

Then create a migration with three tables. Use uuid primary keys with gen_random_uuid() as the default, and give every table a created_at that defaults to now().

TABLE 1, prospects. One row per building I am considering.
  - owner_id, text, not null. This is my Clerk user id.
  - system, text, not null. Either 'residential' or 'commercial'.
  - name, text, nullable. The business name. Null for a house.
  - address, text, not null. lat and lng, both float8, not null.
  - place_id, text, unique, nullable. The Google Places id. Null for a house.
  - roof_area_m2, panel_count, system_kwp, yearly_kwh. The numbers from the Google Solar API.
  - imagery_date, date. When the aerial photo the measurement came from was taken.
  - raw, jsonb. The entire Solar API response, kept as is. Never throw this away.
  - status, text, not null, default 'new'.

TABLE 2, assets. One row per picture I make for a prospect.
  - prospect_id, references prospects, and delete it if the prospect is deleted.
  - before_url and render_url, text. Where the two pictures live in storage.
  - subject and body, text. The message that goes with it.
  - model, text, and prompt, text. Which image model made it and the exact prompt used, so that when the pictures get worse in three months I can prove what changed.
  - status, text, not null, default 'pending'. Only ever 'pending', 'approved' or 'rejected'.

TABLE 3, sends. One row every single time something goes out. Never updated, only inserted.
  - asset_id, references assets, and delete it if the asset is deleted.
  - channel, text. One of 'email', 'whatsapp', 'postcard'.
  - to_address, text, not null. provider_id, text, the id the provider gives back.
  - status, text, not null, default 'queued'. sent_at, timestamptz, nullable.

Now the security, and this part matters most. Turn on row level security on all three tables, then write policies so I can only ever see and change my own rows. prospects has owner_id on it directly, so compare that against the Clerk user id, which Supabase exposes as auth.jwt() ->> 'sub'. assets and sends have no owner column of their own, so their policies have to join back up to prospects to work out who the row belongs to.

Also create a private storage bucket called 'assets' for the pictures, with a policy that only lets me read files inside a folder named after my own user id.

Finally, add the indexes that actually matter for how I will use this. I will be listing prospects by owner and status, sorting them by roof area, looking up assets that are still pending, and looking up sends by the provider id when a delivery webhook comes back.

Explain in plain English what each policy does before you write it, run the migration with `supabase db push`, then show me one test query proving a second user cannot read my rows.
