================ ONE-SHOT BUILD CONTRACT (read first) ================ Build this in Google AI Studio "Build" in ONE shot — a complete, working app, no follow-up turns. These are hard rules, not suggestions: 1. TARGET = Full-Stack Web (Node server runtime, secrets, Firebase allowed). If you target Native Android instead, you MUST drop all server/DB/Workspace/ secrets and persist locally (Room / SharedPreferences) only. 2. PIN EVERY MODEL CALL — never let the agent auto-select (it downgrades on quota): - Reasoning / text -> gemini-3.5-flash (thinkingLevel: minimal|low|medium|high) - 4K image + legible text -> gemini-3-pro-image (image_size "4K", up to 14 refs) - High-volume image -> gemini-3.1-flash-image - Expressive TTS -> gemini-3.1-flash-tts-preview (inline tags e.g. [whispers]) - Realtime audio/video (WebSocket) -> gemini-3.1-flash-live-preview - Sandboxed agent -> antigravity-preview-05-2026 3. DIVISION OF LABOR — the model ONLY parses/extracts to a strict responseSchema. ALL math, money (store currency as integer minor units / cents), sorting, balancing and graph logic run in deterministic TypeScript/Python. The model must never compute totals, splits or balances itself. 4. responseSchema sanitation — no regex patterns, no fixed-length tuples, no format validators in the schema (they crash the OpenAPI engine). Enforce those in server-side code AFTER parsing the JSON. 5. responseSchema and google_search grounding are MUTUALLY EXCLUSIVE in one call. 6. CODEGEN — split large output into modular, single-responsibility files so no file is truncated by the output-token cap. 7. Every external call gets a graceful fallback (e.g. manual paste if a Workspace read fails). Never a silent dead end. 8. ROBUST STORAGE & CANVAS — Wrap all `localStorage`/`sessionStorage` operations (especially JSON parsing and writes) in `try-catch` blocks to prevent crashes in private windows or quota overflows. Canvas drawing elements must dynamically handle window resize and scale pixel density (`window.devicePixelRatio`) to avoid blurry graphics on retina displays. ===================================================================== # MUST OBEY — Mobile-first build requirements This app's PRIMARY surface is a mobile phone. Build it impeccably on mobile FIRST, then verify on tablet and desktop. Treat the rules below as non-negotiable hard constraints, not suggestions. ## Viewports to verify (every screen, every state) - 320 px, 360 px, 375 px, 390 px, 414 px, 480 px - 768 px, 834 px (iPad portrait / Pro 11) - 1024 px, 1280 px, 1440 px, 1920 px, 2560 px - Plus: 200% browser zoom, landscape orientation on every mobile width, iPhone with safe-area insets visible ## Hard layout rules - Mobile-first CSS. Default styles target mobile; `@media (min-width: ...)` for larger viewports. - Use `dvh` and `svh` instead of `vh` for full-height surfaces (iOS Safari URL-bar bug). - Use `clamp()` for fluid typography across all viewports. - Prefer container queries (`@container`) over media queries for component-level responsiveness. - Use `min(100%, ...)` widths so content never overflows. Zero horizontal overflow at any viewport. - Add `` to every page. - Apply `padding: max(safe-area-inset-X, fallback)` on every edge-bleeding container so notched iPhones in landscape never clip content. - Wide tables and code blocks scroll INSIDE their container (`overflow-x: auto`), never push the body. - Use `background-attachment: scroll` on mobile, not `fixed` (iOS Safari repaint bug). - Avoid `backdrop-filter` on animated elements. Use it sparingly on static surfaces only. - **Canvas Scaling**: Canvases must dynamically scale with window resize events and properly handle high-DPI screens (`window.devicePixelRatio`). Set physical dimensions (`canvas.width`/`canvas.height`) using pixel ratio and render relative to this grid, using CSS to control responsive viewport scaling. - **Robust Storage**: Every access to `localStorage`/`sessionStorage` (especially `JSON.parse` of loaded state or writes) MUST be wrapped in a `try-catch` block to handle disabled storage, private browsing mode, quota limits, or corrupted JSON gracefully. Fall back to a robust in-memory object store. ## Touch & accessibility - Tap targets ≥ 44 × 44 px on touch (Apple HIG). Increase to 48 px under `@media (hover: none) and (pointer: coarse)`. - All interactive controls reachable by keyboard with a visible focus ring; respect `:focus-visible`. - Color contrast ≥ 4.5:1 for body text, 3:1 for UI components. - All images have meaningful `alt`. Decorative images use `alt=""`. - Respect `prefers-reduced-motion: reduce` — zero animation durations under that query. - Forms validate inline; error messages are specific, not "Invalid input". - Modals: focus trap, `Esc` closes, `role="dialog"`, `aria-modal="true"`, focus restored on close. ## Performance bar (Lighthouse mobile, throttled 3G/4G) - LCP < 2.5 s · INP < 200 ms · CLS < 0.1 - JS bundle gzip < 200 KB mobile-first; lazy-load non-critical screens via `React.lazy` / dynamic imports. - No render-blocking resources above the fold. - Images: WebP/AVIF preferred, `loading="lazy"`, explicit `width`/`height` attributes (zero CLS), `srcset` for retina. - Videos: `preload="metadata"`, low-resolution poster, max 720p mobile fallback. Never autoplay with audio. - Fonts: `font-display: swap`; preload only the one used above the fold. - Smooth scroll honoured via CSS `scroll-behavior: smooth` with reduced-motion fallback. ## Pre-ship mobile checklist (the deployer MUST verify before declaring done) 1. Open at 375 px in DevTools — every screen scrolls vertically only; zero horizontal scroll. 2. Browser zoom 200% — layout reflows without overlap. 3. iPhone Safari with the URL bar visible AND landscape — no content under the home indicator; no notch clipping. 4. iPad portrait (768 px) and landscape (1024 px) — no awkward gaps; tablet-specific breakpoints land cleanly. 5. Tap every interactive element with a thumb at real-device size — every target is easy to hit. 6. `prefers-reduced-motion: reduce` — every transition / animation skips cleanly, scroll-behavior becomes instant. 7. Lighthouse mobile score ≥ 90 across all 4 categories. 8. Zero `console.error` and zero CLS shift in real-device testing on a mid-tier Android (e.g. Pixel 6a) and an iPhone SE. --- The original template starts below. All rules above apply on TOP of whatever this template specifies. --- # Site Diary ## 1. Project **Site Diary** is an end-of-day logbook for construction site managers — the one person on a job who has to know, by 18:00, what was done today, who was on site, what materials arrived, what hazards emerged, what weather got in the way, and how much of any of that is worth telling the client and the insurer before morning. The user walks the site for eight minutes at the end of the day with the phone in their pocket, presses one button per area, photographs what needs photographing, talks for thirty seconds per area into the same button, and the app produces a structured day-log: photos timestamped and geotagged, voice notes transcribed verbatim, per-area observations, materials on site, hazards flagged, weather captured from the on-site reading, hours worked by trade, and a print-ready PDF that is in the client's inbox and the insurer's inbox before the manager has finished locking the hoarding. This is the kind of app a project manager in Lagos opens at 17:42 on a Wednesday after walking a three-storey residential frame on the Lekki peninsula, because the client is in Atlanta and wants the day's log by morning Lagos time, the insurer needs the hazard register kept current to retain the policy, and the manager's own notebook is half-filled with rain. It is also the kind of app a PM in Manila opens on a Friday afternoon at a hospital-extension pour, because thirty-eight workers were on site, two scaffold ties were found loose at the noon walk, the ready-mix arrived ninety minutes late from QC delays, and the principal contractor in Hong Kong needs the photo evidence before the weekend's concrete sits. Same shape of day, different city, different hazard, different insurer's checklist. The single demo that proves the magic: at the end of a site walk through five areas — perimeter hoarding, ground-floor slab, first-floor block-work, scaffolding north elevation, site office — the user has captured 22 photos and five voice notes, one per area. Three minutes after pressing "finish day-log", the app shows a five-section site diary: each area with its photos in order with their original capture times preserved beside them, the voice note transcribed verbatim in the manager's own English-and-Tagalog code-switch, the structured observations extracted (3 hazards flagged, 4 materials delivered, weather "heavy rain 14:00–15:30", hours worked by trade), and a draft email to the client and the insurer with the day-log PDF attached. The manager reads the draft, adds one sentence about the late ready-mix to the client email, presses send. The day is logged. And in the harder cases — a near-miss on the scaffolding, a sub-contractor walking off site over wage arrears, a partial collapse of formwork that the insurer must hear about within 24 hours under the policy, a typhoon-warning shutdown that wipes out the second half of the day — the app holds onto the original photographs untouched with their EXIF intact, the voice note verbatim with every "actually" and "*sandali lang*" kept, the weather reading from the on-site rain gauge or the nearest station, and refuses to round up "60% complete" when the manager said "about half, maybe a bit more". Progress is always reported in the manager's own words; the app never claims a percentage the manager did not name. **Tagline:** _Walk the site, photograph what matters, talk for thirty seconds per area — the day-log is in the client's inbox before you've locked the gate. In any site, any trade, any weather._ ## 2. Target audience - Site managers and project managers running residential, commercial, and infrastructure builds in countries where the PM keeps the day-log themselves — Lagos, Nairobi, Lusaka, Accra, Manila, Hanoi, Karachi, Dhaka, Jakarta, Dubai, Riyadh, Cairo, Lima, São Paulo, Bogotá, Belfast, Aberdeen, Auckland - Foremen on smaller jobs (renovation, fit-out, fence-and-driveway crews) where the foreman is the manager and is also swinging a hammer until 16:00 - Independent owner-builders managing a self-build, especially those whose insurer requires a daily site log to retain cover - Sub-contractor leads (steelfix, M&E, roofing) whose principal contractor demands a daily report uploaded to a shared drive by morning - Quantity surveyors and contract administrators receiving these logs from twelve sites and needing them in one structured shape, not twelve PDF formats - Insurance loss-adjusters and brokers whose policy schedule names "daily site diary maintained" as a continuing condition of cover - Heritage and conservation site managers whose photo timestamps must survive intact for the heritage council's audit trail - Disaster-reconstruction and humanitarian-shelter project managers logging rebuilds in Aceh, Tacloban, Beira, Antakya, Mariupol, where the funder requires per-day evidence - Female site managers and PMs in male-defaulted construction software markets — the app's defaults must not assume a male user, an English-only crew, or a Western insurer ## 3. Core value propositions Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app. - **Photo timestamps are preserved exactly** — every photo's EXIF capture time, GPS coordinates, lens, and orientation are kept verbatim. The app never re-stamps a photo to "today" because the phone's clock was correct in the first place. The PDF day-log shows the capture time beside each photo in the same minute it was taken. The insurer can audit the chain of custody back to the original file. - **Voice notes are verbatim** — every "actually", "sandali lang", "ehn ehn", "yallah", "*más o menos*", every false start and self-correction is kept in the transcript exactly as spoken. The app does not "clean up" the manager's speech because the cleaned-up version is no longer evidence. The structured fields are extracted alongside the verbatim, never instead of it. - **Progress is never overstated** — if the manager said "about half, maybe a bit more", the structured progress field reads "about half, maybe a bit more" — never "55% complete" and never a clean percent the manager did not name. The hard rule that bends nothing: the app reports progress in the manager's own words. A separate %-complete field exists, but it is null unless the manager explicitly named a percentage. - **The site walks the manager, not the other way around** — the app's flow is one button per area. Press, photograph, talk, move on. No forms. No drop-downs. No mandatory hazard taxonomy. The taxonomy is extracted from what the manager said, not enforced before they could speak. - **Speak in any language; email in the client's** — record the memo in Tagalog, Yoruba, Igbo, Hausa, Pidgin English, Tamil, Hindi, Urdu, Punjabi, Bengali, Vietnamese, Cantonese, Mandarin, Khmer, Bahasa Indonesia, Bahasa Melayu, Arabic, Swahili, Amharic, Brazilian Portuguese, Mexican Spanish, French, Russian, or the manager's English. The structured day-log and the client email render in the language the client reads (commonly English, Arabic, French, Spanish, Mandarin); the verbatim source-language transcript is kept forever and is one tap away. - **Hazards have a register, not a folder** — every hazard the manager mentions in a voice note ("the north scaffold tie at level two looked loose at the noon walk, I made them re-tie it") becomes a row in the site's hazard register with its photo, its quoted sentence, its resolution status, and the date it was first observed. The insurer's continuing-condition-of-cover requirement is met by the register, not by good intentions. - **The weather is in the day-log, not the manager's memory** — the manager photographs the on-site rain gauge or thermometer or anemometer if there is one; otherwise the app pulls a reading from the nearest weather station and stores both the source and the timestamp. A wet day is recorded as wet; a heatwave is recorded with the temperature. - **The email writes itself** — at the end of the walk, the app drafts the client email and the insurer email in parallel. The two are not the same: the client email leads with progress and photos; the insurer email leads with hazards and hours worked. The manager reads, edits, sends. The PDF attached to both is structurally identical and bears the manager's signature block. ## 4. Features to build - One big "Start day-log walk" button on the home screen — initialises a walk session with start time and starting GPS - "New area" button during the walk — names the area in one tap from a list of the site's known areas, or by voice ("ground floor slab") - Per-area photo capture — multi-shot mode with EXIF preserved (capture time, GPS, focal length, orientation); never re-stamped, never re-compressed below 80% quality - Per-area voice note — single press to record, single press to stop; offline-first, queued for transcription on reconnect - Per-area materials capture — photograph the delivery dockets and the stacked materials; voice memo names the supplier, quantity, time of arrival - Hazard flag — a separate one-tap "hazard" button on every area screen; opens a 30-second voice note specifically logged into the hazard register - Weather reading capture — photograph the rain gauge / thermometer / anemometer if available; auto-fetch from nearest weather station as a fallback with both source and timestamp recorded - Hours-worked-by-trade capture — at the end of the walk, the app prompts "who was on site today?", parses the answer into trade × headcount × hours - Multimodal parse — voice + photos + on-site weather reading parsed in a single Gemini 3.5 Flash call per area, with the photos passed as multi-image inputs in capture order - Verbatim transcript preserved separately from structured fields — every diacritic, every code-switch, every "actually" preserved - Structured day-log generation — one site-wide JSON document per day, schema below, including every area, every hazard, every material, every trade, every photo URI - Progress-in-the-manager's-words — the structured field is the manager's verbatim sentence; the %-complete field stays null unless the manager named a number - Multi-day site memory — the app remembers yesterday's hazard register, yesterday's photos, yesterday's progress sentences, and pre-populates "still open" hazards into today's walk for resolution - Day-log PDF export — typeset with the original photographs, the verbatim transcripts in italics under each area, the structured observations as a sidebar, the hazard register at the back, the manager's signature block on the cover - Client email draft — leads with progress photos and observations; the day-log PDF attached - Insurer email draft — leads with hazards and hours worked; the day-log PDF attached - One-sentence search across the site's history — "the day the ready-mix was late" returns the right day in two seconds - Sub-contractor merging — same trade names across two voice notes ("the steelfixers", "Cebu Steel Works") fold to one record after explicit confirmation - Yearly export — every day-log, every hazard, every photo, every quote, in CSV + PDF + a ZIP of the original photographs with EXIF intact, for the client's project archive - Multi-site mode — a PM running three sites switches between them in the header; each site has its own day-log history, its own hazard register, its own team ## 4b. Required Gemini capabilities + backend services **This template's intelligence comes from the Gemini capabilities below. Wire them up explicitly — don't substitute generic LLM calls.** ### Gemini capabilities (the load-bearing intelligence) - **Multimodal image input** (Gemini 3.5 Flash) — reads the photographs of the day: scaffolding tie-detail close-ups, delivery dockets, stacked rebar, formwork cracks, finished tile work, on-site rain gauges, a written whiteboard headcount in the site office. One API call per area; each area's photos are submitted as a single multi-image call with explicit order ("photo 1 of 6 — overall area; photo 2 of 6 — scaffold tie-detail; …") so the model can sequence reliably. Submit via the Gemini Files API (`files/*` resource name) or `inlineData` (base64). Do NOT pass Firebase Storage public URLs directly to `generateContent`. - **Audio input + multilingual ASR** (Gemini 3.5 Flash) — accepts the raw memo as `inlineData` (m4a/aac) for memos under 90 seconds, or via the Files API for longer ones. Handles code-switching mid-sentence ("ground floor slab, *medyo problema yung* north corner, the formwork ties were *malabsa* at noon, *kailangan* re-do bukas") which is the dominant pattern in Manila, Lagos (Yoruba + English + Pidgin), Karachi (Urdu + English), Dubai (Arabic + Hindi + English + Tagalog crews). Produces a verbatim transcript in the source script first, then the structured Area object. - **Structured output / JSON Schema** — the response matches the `DayLog` schema below. Every field is typed; the Zod schema is converted to Gemini's `Schema` type via the SDK helper before passing to `responseSchema`. Numeric `min`/`max` inside the schema are documentation only; clamp server-side after parsing. - **Multilingual translation** (Gemini 3.5 Flash, low thinking) — for the client email body and the insurer email body when the client/insurer reads a different language from the manager's voice note. Preserves the manager's voice ("about half, maybe a bit more" stays approximate in the translated email, not "approximately 55% complete"). - **Long context (1M tokens)** — for the multi-day site memory call (yesterday's hazards + last week's progress sentences + today's walk), for the weekly client-summary roll-up, and for the search-across-the-site's-history call. A typical day-log is ~3,000-5,000 tokens after parse; a 12-week site (60 working days) sits around 250k tokens — comfortable. For sites running over a year (>240 working days), chunk by month before the cross-site history call. **Guardrail**: never load the original photographs into the long-context call; reference them by URI and metadata only. - **Search grounding** (Gemini 3.5 Flash) — for the weather fallback call ("nearest station to lat,lng at 14:30 local time on 2026-05-28") and for the materials-supplier resolution call ("Cebu Steel Works, Tacloban branch — phone number for the delivery query"). Weather and supplier lookups are the only calls that need grounding; the parse call does not. - **Gemini TTS** (`gemini-3.1-flash-tts-preview`) — for read-back of the day-log while the manager drives home in the truck. "Ground floor slab, formed and ready for pour. North scaffold tie at level two, hazard flagged at noon, resolved at 14:15. Ready-mix arrived 90 minutes late. 38 workers on site." The manager confirms with a tap or "send all". - **Nano Banana 2** (`gemini-3.1-flash-image`) — only for the optional cover image of the PDF day-log (a clean isometric thumbnail of the site for visual reference at the top of the client's inbox preview). Never for the photographs of the day — those are the user's photographs, untouched. - **Thinking levels** — `medium` for the primary per-area parse call (multilingual voice + multi-photo + materials extraction + hazard detection + weather inference). `low` for translation, sub-contractor merge proposals, materials-supplier resolution, and the email drafts. Omit `thinkingConfig` entirely on the TTS and Nano Banana 2 calls. ### Backend services - **Auth — Required.** Firebase Auth with Google sign-in (auto-provisioned by AI Studio Build). Phone-number sign-in is strongly recommended for PMs and foremen who don't habitually use email — this requires enabling the Phone provider in the Firebase console and a working SMS budget for their region. **Apple sign-in is optional but user-configured**: requires an Apple Developer account, Service ID, Key ID, and private key wired into the Firebase Auth console. **Magic-link email** (used for inviting a sub-contractor lead to read their portion of a day-log) requires the sender domain to be authorised in Firebase Auth. - **Database — Required.** Firestore for `users`, `sites`, `day_logs`, `areas`, `hazards`, `materials`, `sub_contractors`, `clients`, `insurers`, `weather_readings`, `signatures`. - **File storage — Required.** Firebase Storage for original photographs (preserved at upload resolution with EXIF intact, forever) and for the m4a/aac voice notes. **Storage is NOT auto-provisioned by AI Studio Build today** — the user must enable it in the Firebase console and wire the bucket name into the AIS Build project before the first photograph upload. Pre-signed URLs only; photographs are never publicly addressable. EXIF stripping is OFF; the chain of custody requires the original. - **Email — Required (transactional).** Client and insurer day-log emails via the user's domain (SendGrid or Postmark or Firebase Extensions "Trigger Email" pointed at a transactional provider). Sender-domain authorisation is the user's responsibility; the app will not send from a generic noreply address because the day-log must come from a verifiable source. Sub-contractor invitations via Firebase Auth magic links. - **Payments — Not needed for v1.** The app does not produce invoices; the day-log is operational reporting, not commercial billing. A future tier could pipe variation requests into a QS billing flow. - **External APIs:** Gemini API for all intelligence. Optional weather API (Open-Meteo for global free coverage; the user can configure a paid provider like Tomorrow.io if their insurer mandates a named source). Optional Mapbox or MapTiler for the per-day site-overview map; static fallback ships with the template. **Environment variables:** every secret (Gemini API key, Firebase service-account JSON, transactional-email API key, weather-API key, Mapbox token if used) lives in environment variables — never in client bundle. Include a `.env.example`. **Auth + data privacy reminders:** never log secrets · never store passwords in plain text · use HTTPS everywhere · honour 'delete my site' inside the UI · explicit opt-in for any analytics · the manager's voice notes and photographs are never sent to Gemini for model training (use the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms) · the manager's voice is the evidence and the app never alters it. **Read this first — prompt-craft rules that apply to every call in this template:** 1. **Name the model variant explicitly** in every Gemini API call. Do not let the agent pick the model. See the per-call matrix below. 2. **Pin `thinkingLevel` explicitly** per call. See the matrix. 3. **Seed the JSON Schema as a fenced TypeScript / Zod block** in the system instruction or `responseSchema` field. The literal schema is below. **Convert the Zod schema to Gemini's `Schema` type via the SDK helper** before passing to `responseSchema` — do NOT pass raw Zod. **Numeric `min`/`max` constraints are documentation only inside `responseSchema`; clamp on the server after the response arrives.** 4. **Pin the system instruction separately** from user input. Use the `systemInstruction` field for persona + behavioural rules; use `contents` for user input. Never concatenate. 5. **Pre-declare tools as an enable/disable list** per call. The matrix below names which tools are enabled per call. Tools NOT listed for a call should be disabled. 6. **State negative constraints explicitly** — they are listed below. They are NOT "be careful" suggestions; they are hard rules the model must follow. 7. **Grounded responses can wrap JSON in ```json fences or add prose preamble.** Server-side, strip fences and brace-extract: ```typescript function safeExtractJSON(raw: string): T { const clean = raw.replace(/```json\s*|```/gi, '').trim(); const s = clean.indexOf('{'); const e = clean.lastIndexOf('}'); if (s === -1 || e === -1) throw new Error('No JSON boundaries in grounded response'); return JSON.parse(clean.slice(s, e + 1)) as T; } ``` 8. **Strip unsupported Zod modifiers before passing to `responseSchema`** — Gemini's OpenAPI subset rejects `.regex()` / `pattern`, fixed-length `z.tuple()`, and other custom validators. Use a sanitizer that flattens tuples to length-2 arrays and removes regex patterns before serializing. Validate those constraints in middleware AFTER parsing. ### Per-call model + tools matrix | Call | Model | thinkingLevel | Tools enabled | |------|-------|---------------|---------------| | Parse per-area walk (voice + photos) → `Area` schema | `gemini-3.5-flash` | medium | (none) | | Translate manager's voice into client/insurer email language | `gemini-3.5-flash` | low | (none) | | Draft client email body | `gemini-3.5-flash` | low | (none) | | Draft insurer email body (hazards-first) | `gemini-3.5-flash` | low | (none) | | Resolve multi-day hazard register (yesterday → today) | `gemini-3.5-flash` | medium | (none) — long context over the site's history | | Resolve weather reading for site at time | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) | | Resolve materials supplier from docket photo + voice mention | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) | | Generate TTS read-back of day-log summary | `gemini-3.1-flash-tts-preview` | n/a | n/a | | Generate PDF cover thumbnail (optional) | `gemini-3.1-flash-image` | n/a | n/a | *Note for builders:* on TTS and Nano Banana 2 image-generation calls, omit `thinkingConfig` entirely — the field is not supported on those models. The `n/a` cells in this matrix are documentation only; do not serialise them into the request body. Where the matrix lists `google_search` grounding, do NOT also specify `responseSchema` on the same call — they are mutually exclusive in Gemini today; instruct the model to emit JSON in the text body and parse server-side, and read citation URLs from `response.groundingMetadata.groundingChunks[].web.uri`. ### Primary structured-output schema (seed this verbatim in the prompt) ```typescript import { z } from "zod"; const Photo = z.object({ photo_id: z.string(), storage_uri: z.string(), // gs:// reference; never re-stamped capture_time_iso: z.string(), // EXIF DateTimeOriginal, verbatim capture_gps: z.object({ lat: z.number(), lng: z.number(), accuracy_m: z.number().nullable(), }).nullable(), capture_camera: z.string().nullable(), // EXIF Model orientation: z.number().nullable(), // EXIF Orientation short_description: z.string(), // model-generated, concrete, no interpretation contains_text_visible: z.boolean(), // delivery docket, whiteboard headcount, etc. }); const VoiceNote = z.object({ voice_id: z.string(), storage_uri: z.string(), // gs:// reference; original m4a/aac duration_seconds: z.number(), source_language: z.string(), // BCP-47, "en-PH", "en-NG", "ar-AE" source_dialect_note: z.string().nullable(), // "Manila English with Tagalog code-switch" transcript_verbatim: z.string(), // every "actually", "sandali lang" preserved contains_multilingual_inserts: z.boolean(), }); const Material = z.object({ item_verbatim: z.string(), // "twelve lengths of 12 mm rebar" quantity: z.number().nullable(), unit: z.string().nullable(), // "lengths", "bags", "m3" supplier_verbatim: z.string().nullable(), // "Cebu Steel Works" supplier_resolved: z.string().nullable(), // resolved separately, never overwriting verbatim arrival_time_iso: z.string().nullable(), docket_photo_ids: z.array(z.string()), contextual_quote: z.string(), // verbatim sentence from the voice note }); const Hazard = z.object({ hazard_id: z.string(), description_verbatim: z.string(), // manager's own words, untranslated description_english: z.string().nullable(), // translation if source != en severity: z.enum([ "near-miss-no-injury", "first-aid-only", "lost-time-injury", "structural-concern", "environmental", "regulatory-breach", "security", "other", ]).nullable(), // nullable because manager may not have named it resolution_status: z.enum([ "open", "in-progress", "resolved-today", "carries-to-tomorrow", "escalated", ]), resolution_note: z.string().nullable(), photo_ids: z.array(z.string()), contextual_quote: z.string(), // verbatim first_observed_date_iso: z.string(), // today by default; multi-day hazards carry forward }); const TradeHeadcount = z.object({ trade_verbatim: z.string(), // "concreteworkers", "steelfixers", "Boys ko" trade_normalised: z.string().nullable(), // "concrete-workers", "steel-fixers" headcount: z.number().int().nullable(), hours_worked: z.number().nullable(), sub_contractor_verbatim: z.string().nullable(), contextual_quote: z.string(), }); const WeatherReading = z.object({ source: z.enum([ "on-site-photograph", "nearest-station-grounded", "manager-spoken", "none", ]), observed_time_iso: z.string().nullable(), rainfall_mm: z.number().nullable(), temperature_c: z.number().nullable(), wind_kmh: z.number().nullable(), conditions_verbatim: z.string().nullable(), // "heavy rain 14:00-15:30" source_attribution: z.string().nullable(), // station name and operator if grounded }); const Area = z.object({ area_id: z.string(), area_name_verbatim: z.string(), // "ground floor slab", "*sahig sa baba*" area_name_canonical: z.string().nullable(), // matched to site's known-areas list walk_start_time_iso: z.string(), walk_end_time_iso: z.string(), photos: z.array(Photo), voice_notes: z.array(VoiceNote), progress_in_managers_words: z.string(), // verbatim: "about half, maybe a bit more" progress_percent_named_explicitly: z.number().nullable(), // null unless manager said a number progress_percent_named_explicitly_quote: z.string().nullable(), observations: z.array(z.object({ description_verbatim: z.string(), contextual_quote: z.string(), })), materials_on_site: z.array(Material), hazards: z.array(Hazard), trades_observed: z.array(TradeHeadcount), weather_at_area: WeatherReading.nullable(), // usually one site-wide reading notes_from_yesterday_carried_forward: z.array(z.string()), reading_confidence: z.number().min(0).max(1), flagged_for_user_review: z.array(z.object({ field_path: z.string(), // "materials_on_site[2].quantity" reason: z.string(), })), }); const DayLog = z.object({ day_log_id: z.string(), site_id: z.string(), site_name: z.string(), // "Lekki Phase 2, Plot 47 — Adelaja residence" log_date_iso: z.string(), // YYYY-MM-DD, local log_date_timezone: z.string(), // IANA tz, "Africa/Lagos" manager_id: z.string(), manager_name: z.string(), walk_start_time_iso: z.string(), walk_end_time_iso: z.string(), walk_duration_minutes: z.number(), areas: z.array(Area), site_wide_weather: WeatherReading, site_wide_hazards_open_at_eod: z.array(z.string()), // hazard_ids site_wide_summary_in_managers_words: z.string(), // verbatim manager closing remark site_wide_progress_percent_named_explicitly: z.number().nullable(), client_email_draft: z.object({ to_recipients: z.array(z.string()), subject: z.string(), body_target_language: z.string(), body: z.string(), attached_pdf_uri: z.string(), }), insurer_email_draft: z.object({ to_recipients: z.array(z.string()), subject: z.string(), body_target_language: z.string(), body: z.string(), attached_pdf_uri: z.string(), }), manager_signature_block: z.string(), generated_at_iso: z.string(), audit_trail: z.array(z.object({ event: z.string(), // "photo captured", "voice note recorded" timestamp_iso: z.string(), actor: z.string(), })), }); type DayLog = z.infer; ``` ### Common failure modes (and how to avoid them) - Agent silently downgrades `thinkingLevel` on the the per-area parse call to save quota — pin `gemini-3.5-flash` with the matrix-specified `thinkingLevel` explicitly. Flash drops materials quantities, misreads delivery dockets handwritten in Yoruba or Tagalog, and confuses "twelve" for "two" on a noisy voice note. - Photos silently re-encoded or re-stamped — the EXIF capture time must survive. If the upload pipeline re-encodes via canvas, the original EXIF is lost. Upload the raw file bytes; perform parsing reads server-side from the original. - Manager said "about half, maybe a bit more"; structured field becomes "55%" — hard rule: `progress_in_managers_words` is the verbatim phrase; `progress_percent_named_explicitly` is null unless the manager named a specific number. Surface a unit test that the demo voice note's verbatim "*medyo malayo pa*" stays verbatim. - Code-switch flattened — "*medyo problema yung* north corner" silently translated to "the north corner has a problem". Preserve the code-switch in `transcript_verbatim`; translate separately into `description_english`. - Photo capture order lost — the model receives photos out of capture order and describes "photo 1" as a close-up when it was the wide shot. Pass photos in EXIF-capture-time order with explicit "photo 1 of 6" headers. - Hazard read as observation — the manager said "the scaffold tie at level two looked loose at noon". This is a hazard, not a passing remark. Specify in the system instruction: any sentence containing a structural concern, a near-miss, a regulatory term ("PPE", "permit", "safe system of work"), an injury, an environmental release, or an explicit "I'm flagging this" goes into `hazards[]` as well as `observations[]`. - Yesterday's open hazards silently dropped — every day-log inherits `site_wide_hazards_open_at_eod` from the previous day. The pre-load step is part of the parse call's input, not a separate post-process. If today's voice note does not resolve an open hazard, it carries forward with `resolution_status: carries-to-tomorrow`. - Weather invented when the manager never mentioned it — if no on-site reading, no grounded fallback, and no spoken description, `weather_at_area.source = "none"` and all numeric fields null. Do not estimate from the photo background ("looks bright, probably 28°C"). - Multipage voice notes (long walks where the manager talks while walking from area A to area B) split mid-sentence — if a single voice note exceeds 90 seconds, upload via the Files API. Do NOT submit as multiple short clips concatenated client-side; the model loses the cross-area context. - Materials supplier resolved from grounded search overwrites the verbatim — keep `supplier_verbatim` exactly as the manager said it ("CSW", "the steel guys from Cebu"); the resolved name goes into `supplier_resolved` separately. - Manager's name and signature block forged into client/insurer emails by hallucination — the signature block is loaded from the user's profile server-side and concatenated, never generated. ### Negative constraints (hard rules) - Do NOT alter photo EXIF metadata. Capture time, GPS, camera model, orientation are preserved exactly. Re-encoding for display thumbnails is fine; the original file in storage is sacrosanct. - Do NOT "clean up" voice transcripts. The verbatim is the verbatim. Filler words, false starts, code-switches, swearing, language mixes — all preserved. The structured fields are extracted in parallel, not by paraphrasing. - Do NOT overstate progress. Percent-complete is null unless the manager explicitly named a number. "Looks about 60% done from the photos" is forbidden output. - Do NOT translate the manager's voice into the client email's voice. The client email is a translation if needed, but the manager's choice of words on hedges and uncertainty must be carried across. "About half, maybe a bit more" stays "about half, maybe a bit more" in English, French, Arabic, Mandarin. - Do NOT silently drop yesterday's open hazards. The hazard register is multi-day. Every day-log inherits the open hazards and either resolves them, advances them, or carries them forward explicitly. - Do NOT auto-classify hazard severity if the manager did not name it. `severity` is nullable. The manager confirms or fills in. Insurers reject day-logs whose severity field was populated by the app rather than the manager. - Do NOT invent weather. If the manager did not speak about weather, no on-site photo of a gauge exists, and the grounded fallback fails, `source = "none"`. Empty is honest. - Do NOT use the manager's voice notes or photographs to train or fine-tune any model. Use the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. The capabilities-info panel says this in plain English. - Do NOT auto-send the client or insurer email. The manager reviews, edits, and sends. The draft is generated; the send action is always manual. - Do NOT publish the day-log publicly. Client and insurer have private access via signed download links that expire in 14 days. Sub-contractors see only the rows that name their trade. - Do NOT modify or remove censored areas (a child on site, a wage-arrears dispute named by name, a near-miss involving an identifiable worker). The manager redacts manually before sending; the original day-log is kept unredacted for the project archive. ### Per-call `systemInstruction` strings Use these as the literal `systemInstruction` field for each Gemini API call the built app makes. They complement the series-wide rules already uploaded as the global instructions file (`00-series-instructions.txt`). ### Call: Parse per-area walk (voice + photos) → `Area` schema Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You are a construction site assistant for a site manager doing the end-of-day walk. You receive, for ONE area on the site: - One or more photographs captured during the walk, in EXIF capture order, each with explicit "photo N of M — " preamble. - One voice note recorded during the walk, in the manager's spoken language. The voice note may code-switch — English with Tagalog, English with Yoruba, English with Pidgin, Hindi with English, Arabic with English and Tagalog (Dubai sites with Filipino and South Asian crews), Bahasa with English, Mandarin with Cantonese, Khmer with French, Brazilian Portuguese with Spanish, Swahili with Sheng, Amharic with English. Handle the code-switch; preserve it. - Optional: a list of open hazards from yesterday for this area or the wider site. These are pre-loaded so you can recognise whether today's walk resolves, advances, or fails to mention them. Multi-photo inputs are submitted as a SINGLE call with all photos in capture order. Upload each photo via the Gemini Files API (`files/*` resource name) or send as `inlineData` (base64). Do NOT pass Firebase Storage public URLs directly to `generateContent` — the API does not fetch them server-side. Include an explicit "photo 1 of 6 / photo 2 of 6" header at the start of each image's accompanying text so the model can sequence reliably. Output ONLY the Area JSON matching the provided schema. Hard rules: - Verbatim transcript. transcript_verbatim is exact: every "actually", "ah, sandali lang", "ehn ehn", "yallah", "*más o menos*", every false start and self-correction is preserved. Do NOT clean up the manager's speech. - Code-switch preserved. If the manager mid-sentence drops into Tagalog, Yoruba, Pidgin, Hindi, Arabic, Mandarin, or any second language, transcript_verbatim keeps the code-switch in the original scripts. contains_multilingual_inserts: true. - Progress in the manager's words is the LITERAL phrase the manager used. "About half, maybe a bit more" stays "about half, maybe a bit more". progress_percent_named_explicitly is null UNLESS the manager named a number explicitly (e.g. "we're at sixty-five percent on this slab"). If they did, also store the verbatim quote in progress_percent_named_explicitly_quote. - Hazards are a separate channel. Any sentence that mentions: structural concern (formwork ties loose, scaffolding sway, slab cracking, soil movement), near-miss or injury, regulatory term ("permit", "PPE", "method statement", "safe system of work", "exclusion zone"), environmental release (fuel spill, dust beyond hoarding, runoff), or the manager explicitly saying "I'm flagging this" — goes into hazards[]. The same sentence may also go into observations[]; the hazard channel is the insurer-visible channel. - Hazard severity is nullable. If the manager did not name a severity (and they often will not — they will say "have a look at the tie at level two" without naming severity), leave it null. The manager confirms severity in the UI. - Resolution status. If the manager's voice note resolves the hazard today ("I made them re-tie it at 14:15, all good now"), status: resolved-today. If they advanced it ("still working on it, will close tomorrow"), in-progress. If they did not mention a pre-loaded open hazard at all, carries-to-tomorrow. - Materials. "Twelve lengths of 12 mm rebar from CSW, arrived ten past three" parses into one Material with quantity 12, unit "lengths", item_verbatim "twelve lengths of 12 mm rebar", supplier_verbatim "CSW", arrival_time_iso "T15:10:00". Leave supplier_resolved null — that's another call. - Trades. "The steelfixers were six today, in from eight, out at four" parses into one TradeHeadcount with trade_verbatim "steelfixers", trade_normalised "steel-fixers", headcount 6, hours_worked 8. If the manager did not say headcount or hours, leave them null. - Photo descriptions. short_description is one concrete sentence per photo, no interpretation: "close-up of north scaffold tie at level two, looking south-east" not "scaffold tie that may be loose and needs attention". Interpretation is the manager's job. - Photo contains_text_visible: true if the photo is of a delivery docket, a whiteboard headcount, a permit notice, a meter reading, or any other significant on-site text. - Reading confidence below 0.7 on any field → add an entry to flagged_for_user_review with the field_path and a one-sentence reason. - Never invent details. If you cannot tell from the photos and the voice note, leave the field null. Do not extrapolate from the photo background ("the sky looks bright, probably 28°C"). No commentary. JSON only. ``` --- ### Call: Translate manager's voice into client/insurer email language Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You translate a site manager's end-of-day verbatim transcript and their per-area observations from their source language to the target language the client or insurer reads (commonly English, French, Arabic, Spanish, Mandarin, Portuguese, Hindi, Urdu, Tamil). The translation is for an email body, not for legal record. The verbatim transcript in the source language is kept separately and is the chain-of-custody artefact. Hard rules: - Preserve hedges and approximations. "About half, maybe a bit more" → "about half, maybe a bit more" — NOT "approximately 55%". "Medyo problema" → "a bit of a problem" — NOT "a critical issue". "Yallah, mish kteer mneeh" → "honestly, not great" — NOT "performance was below expectations". - Preserve uncertainty markers. "We might pour tomorrow if the rain holds off" stays conditional — never "We will pour tomorrow". - Preserve trade and material names in their domain form, with a parenthetical gloss only on first occurrence if the target language has a clearer equivalent. "Rebar" stays "rebar". "Formwork" stays "formwork". "P-trap" stays "P-trap". - Preserve people's names verbatim. Engineer Adekunle stays Adekunle. The supplier Cebu Steel Works stays Cebu Steel Works. Trade nicknames the manager used ("Boys ko", "the lads") translate to the natural equivalent ("my crew", "the lads") without naming individuals. - Preserve currency in the source if quoted. "₱2,800" stays "₱2,800". Add an optional parenthetical conversion only if the email body explicitly requests it via the user's setting. - Match register. A factual sentence stays factual. A flagged hazard stays a flagged hazard. A near-miss does NOT become a "potential concern". - Preserve sentence order. Do not reorder for target-language flow. The manager's rhythm matters when the client reads the email three timezones away. Output: the translation as a single string for the body block, or as the requested per-field map for the structured client-email draft. No commentary. ``` --- ### Call: Draft client email body Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You draft the body of a client email summarising the day's site walk. The email is from the site manager to the client (commonly the building owner, the developer's project director, the owner-builder's project committee, the humanitarian funder's country office). Lead with progress and photos. Follow with materials delivered, hours on site, weather. Hazards are mentioned briefly with a pointer to the attached day-log PDF for the full hazard register. Hard rules: - The opening sentence is a direct progress statement in the manager's own words. Do NOT open with "I hope this email finds you well." Do NOT open with "Please find attached." Open with the substance: "Today's pour on the ground-floor slab went in at 13:40, about half the area set by end of day. Photos attached." - One paragraph per area, in the order walked. Each paragraph names the area, the progress phrase (verbatim from the manager's voice), one specific observation, and a reference to the photo IDs that show it. - Materials paragraph names supplier, item, quantity, arrival time — exactly as the manager said. If the ready-mix was 90 minutes late, the email says so without dramatising and without apologising — it is a fact for the client to act on. - Hours-worked paragraph names trades and headcount, not individual names (privacy default). - Weather is one sentence, with source named if from a station ("Lagos NIMET station, 17:00 reading: 28°C, scattered showers 14:00-15:30"). - Hazards: one sentence pointing to the PDF — "Three items on the hazard register today; two resolved, one carries to tomorrow. Full detail in the attached day-log." Do NOT list the hazards inline — the client does not need them in the email body; the insurer does, and the insurer's email is separate. - Closing: one sentence with the next-day intent if the manager named it. If they did not, close with the manager's signature block, no further sentence. - Length: 180-280 words. Long enough to be substantive, short enough to read on a phone at 21:00 in another timezone. - Tone: direct, professional, no marketing. No "we are pleased to report". No "kindly find attached". No "as discussed". - Subject line: " — Day-log ". E.g. "Adelaja Residence — Day-log 2026-05-28 — Ground-floor pour, ready-mix late". Output: { subject: string, body: string }. No commentary. ``` --- ### Call: Draft insurer email body (hazards-first) Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You draft the body of an insurer email summarising the day's hazards and the active hazard register. The email is from the site manager (or the policyholder's named site contact) to the insurer's broker or the underwriter's risk-engineering team. Lead with hazards. Follow with hours worked (a continuing-condition of cover under most construction policies). Progress and weather are brief. Hard rules: - The opening sentence is a one-line summary of today's hazard status: "Three items on the hazard register today; two resolved, one carries to tomorrow." If the day had zero hazards, the opening sentence is "No new hazards or near-misses reported on site today; the hazard register is current as of 17:42 Lagos time." - One bullet per hazard, in the order observed. Each bullet contains: hazard ID, manager's verbatim description in parenthetical italics, severity if named (else "(severity not named by manager; please advise classification)"), resolution status. Do NOT classify severity if the manager did not. - Hours-worked paragraph names trades and total headcount on site. This satisfies the continuing-condition reporting under most CAR (contractors all risks) policies. - Progress: one sentence, manager's words. - Weather: one sentence, with source. The insurer cares about conditions that affect cover (storm exclusions, lightning, flood-watch warnings). - The day-log PDF is attached. Reference it once: "Full day-log with photographs and audit trail attached." - Closing: signature block. - Length: 150-250 words. Brokers triage; brevity is courtesy. - Tone: factual, no minimising, no exaggerating. The insurer reads this for risk; soft language is read as concealment. - Subject line: " — Day-log — Hazard summary". Output: { subject: string, body: string }. No commentary. ``` --- ### Call: Resolve multi-day hazard register (yesterday → today) Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none, long context over site history) ``` You receive the site's hazard register state as of yesterday's end of day, plus today's parsed Areas. Your task: produce the updated hazard register state, with each hazard's status correctly advanced. Hard rules: - A hazard from yesterday is RESOLVED today only if today's voice notes explicitly named the resolution ("re-tied the north scaffold ties this morning, all good now") — not inferred from a photo that looks fine. - A hazard from yesterday CARRIES forward if today's voice notes did not mention it at all. resolution_status: carries-to-tomorrow. Do NOT silently drop. - A hazard from yesterday is ADVANCED if today's voice notes mentioned progress without closure ("still working on the formwork at the north corner"). resolution_status: in-progress. - A new hazard today is added with first_observed_date_iso = today. - A duplicate of an open hazard (same description, same area, same severity if named) merges into the existing hazard with an appended contextual_quote, not a new hazard_id. - ESCALATED applies when the manager named an escalation explicitly ("notified the principal contractor", "called the structural engineer for tomorrow morning"). Do NOT escalate on your own reading. Output: the updated hazards[] array as a single JSON object. No commentary. ``` --- ### Call: Resolve weather reading for site at time Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding ``` You resolve the weather conditions for a site at a specific time on a specific date, using grounded search of public weather data. Given: - site name, country, lat, lng - date and approximate local time - preferred provider name if the user has configured one (Open-Meteo, NIMET, Met Office UK, PAGASA, Met Eireann, Hong Kong Observatory, NOAA, BMKG, etc.) Return: - conditions_verbatim (one short sentence: "heavy rain 14:00-15:30, clearing by 16:00") - rainfall_mm if available (today's total or the period total) - temperature_c at the named time - wind_kmh at the named time - observed_time_iso - source_attribution (full station name and operating authority, exactly as it appears on the provider's page) - citation URL (read from response.groundingMetadata, not from your own JSON output) Hard rules: - Prefer the nearest official meteorological station to the site coordinates. Do NOT use crowdsourced data (e.g. Weather Underground personal stations) for insurer-grade reporting. - If the nearest station is more than 25 km from the site, return the reading with `source_attribution` including the distance, so the manager can decide whether to override with their on-site reading. - If no station has data for the named time (e.g. the station reports hourly and the manager asked for 14:30), return the nearest hour and note "(at 14:00 reading)" in conditions_verbatim. - If grounded search fails to find any reading, return source attribution "no usable reading from grounded search" and leave all numeric fields null. The manager will note manually. Output the response as JSON in the text body (NOT via `responseSchema` — `responseSchema` and `google_search` cannot be combined in the same Gemini call today). Server-side: parse the JSON, then read citation URLs from the response's `groundingMetadata.groundingChunks[].web.uri` — do NOT ask the model to include URLs in the JSON body; it will hallucinate them. No commentary outside the JSON. ``` --- ### Call: Resolve materials supplier from docket photo + voice mention Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding ``` You resolve a materials supplier from: - A photograph of a delivery docket if one was captured. - The supplier name the manager spoke ("CSW", "Cebu Steel Works", "the steel guys from Cebu"). - The country and region of the site. Return: - supplier_resolved (canonical name as it appears on the supplier's own site or registered trade name) - supplier_address if visible on the docket or grounded - supplier_contact (phone, email, or website) for the manager to query late deliveries - citation URL from grounded search Hard rules: - Do NOT overwrite supplier_verbatim. The manager's verbatim name is the chain-of-custody record; the resolved name is a separate field for the email and the supplier-query flow. - If the docket photograph clearly shows the supplier's printed letterhead, prefer it over grounded search. - If grounded search returns multiple candidates with the same name (a chain with branches in multiple cities), return all candidates and let the manager confirm. Do NOT silently pick the one closest to the site coordinates — the closest is usually but not always correct. - If grounded search fails entirely, return supplier_resolved = null and let the manager type it. Output the response as JSON in the text body (NOT via `responseSchema` — `responseSchema` and `google_search` cannot be combined in the same Gemini call today). Read citation URLs from `groundingMetadata.groundingChunks[].web.uri`. No commentary outside the JSON. ``` --- ### Call: Generate TTS read-back of day-log summary Model: `gemini-3.1-flash-tts-preview` · n/a · n/a ``` Voice: calm, factual, unhurried. Pick the Gemini 2.5 Flash TTS voice whose `languageCode` matches the manager's preferred read-back language — pronunciation will follow that locale automatically. Prefer the same gender the manager has set in their profile; fall back to whichever is available rather than blocking. Pre-process the day-log summary text before sending it to TTS: - Read from a short generated summary string assembled server-side in the manager's preferred read-back language. It is a 90-120 word summary covering: site name, walk duration, areas walked, progress in the manager's own words, hazards count and status, materials delivered count and notable items, weather, total headcount on site. The summary is NOT the client email body rehashed — it is shorter and oriented to the manager driving home in the truck. - At each sentence boundary, insert a single ellipsis (`…`) so the TTS model produces a natural pause. At paragraph breaks (between the area-summary block and the hazard block), insert a blank line plus an em-dash (`—`). Gemini 2.5 TTS does not support SSML `` — these textual cues are how you signal pace. - Speak numbers naturally. "Three hazards" not "3 hazards"; "38 workers" can stay numeric or spelled out depending on the voice's native locale. Currency mentions are unusual in a day-log; if one appears, spell it out ("two thousand eight hundred pesos"). - Mid-call voice switching is not supported. If the manager's preferred read-back language differs from the verbatim transcript language, the read-back is entirely in the preferred language — the manager hears their own summary, not a switching mash-up. - Target rate: ~140 words per minute — driving-home pace, not podcast pace. Style direction: prepend ONE short directive sentence to the text input, exactly like: "Read calmly and factually, as a site manager would summarise the day's walk to themselves while driving home. …". There is no separate `style` API field on Gemini 2.5 TTS; the directive sentence inside the input is how style is conveyed. Phoneme overrides (Tagalog ng, Yoruba dotted vowels, Arabic emphatic consonants) are NOT exposed by Gemini 2.5 TTS — no SSML `` tag. Pronunciation comes from the chosen voice's native locale. ``` --- ### Call: Generate PDF cover thumbnail (optional) Model: `gemini-3.1-flash-image` · n/a · n/a ``` You generate ONE 1024×512 cover thumbnail for the top of the day-log PDF. The thumbnail is a clean, restrained, isometric illustration of the site at a glance — used as visual reference in the client's inbox preview, not as evidence. Constraints: - Restrained palette: warm neutrals (concrete grey, hi-vis amber, hoarding blue). No saturated cartoon colours. - Isometric line illustration with light fills. Not a render. Not a photograph (the photographs in the day-log are the user's, untouched). - No people, no logos, no flags, no text overlay. The PDF's title page renders the site name in typography; the thumbnail is purely visual. - One subject: the building under construction, with the surrounding hoarding suggested. Optionally one delivery truck or one crane if the site has one — never both. If image generation fails or returns an unacceptable result, omit the thumbnail; the PDF renders without a cover image. Output: a single 1024×512 image. No commentary. ``` ## 5. Use cases & content to include Build dedicated UI sections or flows for each of these — they tell you what content the app must support. - **The Lagos PM at 17:42.** A project manager finishing a walk on a three-storey residential frame on the Lekki peninsula. Five areas. 22 photos. Five voice notes. The client is in Atlanta and wants the day-log before they go to bed Eastern time. The app drafts the email, the manager edits one line, presses send. The hazard register is current; the insurer's broker in London gets a separate parallel email. - **The Manila pour day.** A PM on a hospital-extension pour, Friday afternoon, 38 workers on site, two scaffold ties found loose at noon, ready-mix arrived 90 minutes late from QC delays. The manager's voice note code-switches Tagalog and English. The structured log preserves "*medyo malayo pa*" verbatim and translates it as "still a way to go" in the client email — never as "approximately 65% remaining". - **The Dubai sub-contractor walk-off.** A PM on a Marina fit-out where the steel-fix sub-contractor's lead walked off site at 14:00 over a wage-arrears dispute that the manager will not name on the day-log. The hazard register records "loss of trade on-site, mid-job — see internal note", and the insurer email flags it without details. The manager's verbatim voice note is kept private to the site archive; the client email mentions only the schedule impact. - **The yesterday's hazards carried forward.** A PM in Karachi walks the site for the third day after monsoon rains. Yesterday's two open hazards (waterlogged excavation, soft formwork base) are pre-loaded into today's parse. Today's voice note mentions one ("the excavation drained overnight, we're back on it") which advances to resolved-today; the other is not mentioned, which carries forward with `carries-to-tomorrow` — the app does not silently drop it. - **The on-site rain-gauge.** A PM in Accra during the harmattan-to-rains transition photographs the on-site rain gauge at 16:30 every day as part of the walk. The app reads the gauge value via the multimodal parse, stores it as `WeatherReading.source: on-site-photograph`, and the insurer email cites it as the source rather than the nearest meteorological station. - **The heritage site audit.** A PM on a conservation-grade restoration in Belfast where the heritage council audits the photo chain of custody. Every photo's EXIF capture time, GPS, and camera model are preserved in the PDF day-log as a sidebar. A year later, when the auditor asks for the originals, the manager exports the full ZIP with EXIF intact. - **The typhoon-warning shutdown.** A PM in Tacloban whose site shuts down at noon because of PAGASA's Signal 2 warning. The walk happens at 11:30 instead of 17:30. The app records the shutdown reason in the day-log, the weather source ("PAGASA, Signal 2, Eastern Visayas region, issued 09:00"), and the insurer email leads with the cessation rather than progress. - **The Aceh humanitarian rebuild.** A PM on a 30-house build for tsunami-displaced families, funded by a Geneva-based donor. The donor's daily reporting requirement is satisfied by the day-log PDF; the PM's voice note is in Bahasa Indonesia with code-switches into Acehnese for trade names; the donor's monthly summary is built from the Friday roll-up across all 22 working days. - **The owner-builder's self-build.** A retired engineer in Auckland project-managing his own house build. The insurer's CAR policy requires a daily site diary as a continuing condition of cover. The app generates the diary even on the days only the foundations are curing and no work happens on site — a "no-work" day-log is still a log. - **The cross-timezone hand-off.** A PM in Dubai and a senior PM in London co-managing a tower project. The Dubai PM's day-log lands in the London inbox at 14:00 UK time; the London PM annotates it with one line of feedback before the Dubai PM's next morning. The annotation thread is preserved in the day-log audit trail. - **The sub-contractor's portion.** A roofing sub-contractor lead is invited to read the rows of the day-log that name "roofing" — they see their headcount, hours worked, and any hazards or observations tied to their trade, but never the other trades' data or the manager's verbatim voice note in full. - **The dispute mode.** A client queries on day 47 whether the steel-fix was actually complete on day 32. The PM searches "the day the rebar finished on the north slab" — the right day-log appears with the verbatim voice note ("rebar complete on the north slab, ready for inspection tomorrow morning"), the photos, and the hazard register that day. ## 6. Page structure Build the following screens / sections in this order. Adjust copy to fit the voice, but keep the structural intent. 1. **Welcome / sign-in.** A photographed-looking image of a site manager mid-walk on a residential frame at 17:30, phone in hand, hi-vis vest, hoarding behind, late-afternoon shadow. One paragraph: "Site Diary turns an eight-minute end-of-day walk into a structured log that's in your client's and insurer's inbox before you lock the hoarding." Single Google sign-in button; Apple sign-in next to it; Phone sign-in below (recommended for field users). "Try with the sample site" → loads the demo site in section 8a. 2. **Sites home.** A list of sites the user manages (a PM may manage three sites concurrently). Each site card: site name, today's walk status (✅ done, in progress, not yet, no-work day), open hazards count, last log sent. "Start day-log walk" is the primary action; "View past logs" is secondary. 3. **Start day-log walk.** A full-bleed screen. The site name. Today's date and timezone. The list of areas configured for this site, with checkmarks beside areas already walked today. A big "Start a new area" button. If yesterday's hazards are still open, a "Carry these forward" chip up top showing the count. 4. **Per-area walk screen.** Live camera viewfinder with a "capture photo" button (multi-shot mode). Above it, the area name (selected by tap or by voice). Below it, a "record voice note" button. A "flag a hazard" sub-button is permanently visible — opens a dedicated 30-second voice note logged specifically into the hazard register. A "next area" button finalises this area. 5. **End-of-walk summary.** Once the manager taps "finish day-log", a vertical list of areas walked, each with photo count, voice-note duration, hazards-flagged count. Below: a prompt for the site-wide closing remark ("anything for the day overall?") and a "who was on site today?" prompt for headcount-by-trade. The closing remark is recorded by voice; the headcount can be voice or tap. 6. **Day-log review.** The structured day-log rendered for review. Sticky header: site name, date, walk duration. Per area: the photos in capture order with capture times beside them, the verbatim transcript in italics, the parsed observations and materials as bullets, the hazards as red-bordered cards. The progress-in-managers-words is shown verbatim; the %-complete field, if null, is shown as a faint "(not named today)". The weather block is shown with source attribution. The hazard register sidebar lists all open hazards from today plus carried-forward from prior days. 7. **Email review.** Two side-by-side draft emails (stacked on mobile): the client email and the insurer email. Each can be edited inline. The PDF day-log is attached and previewable. The manager presses "Send both" or sends each individually. A "Save draft" option lets the manager send later. 8. **PDF preview.** A typeset preview of the day-log PDF as it will appear in the inboxes. Cover with site name, date, manager name, signature block. Per-area sections in walk order. Hazard register at the back. Photo capture times in a sidebar. EXIF audit trail in an appendix. Export options: download, share to cloud storage, copy signed link. 9. **Past logs.** Vertical timeline of all day-logs for the site. Each card: date, walk duration, photo count, hazards-opened/resolved/carried. Filter by month, by hazard severity, by sub-contractor on site that day. Tap a card → opens the day-log review for that day. 10. **Hazard register.** A site-wide view of every hazard ever logged. Filter by status (open, in-progress, resolved, escalated, carries-to-tomorrow), by severity, by area. Each hazard shows its first-observed date, every contextual quote across days, every photo, and its resolution history. 11. **Sharing & invitations.** Modal: "Invite a sub-contractor lead, the client, or the insurer's broker to receive day-logs." Three role presets: client (full PDF + email), insurer (PDF + hazards email), sub-contractor lead (only their trade's rows). Magic-link email. 12. **Settings.** Site setup (name, address, areas list, project type), sub-contractor list (with trade names and contact emails), client contacts, insurer contacts and policy reference, weather provider preference, manager profile (name, signature block, preferred read-back language, photo of company logo for the PDF cover), data export. 13. **Footer.** "For the manager who has to know, by 18:00, what happened today." Privacy: "Your photos and voice notes are yours. We never train on them." Capabilities `(i)` icon in header. ## 6b. First-visit onboarding Show a **first-visit onboarding** the first time a visitor lands on the app (detect via `localStorage` flag; do not show on return visits). Three slides, dismissible at any time. Persistent re-entry: a `?` icon in the header reopens it. **Slide 1 — What this is.** - Headline: "Welcome to Site Diary." - Subhead: "Walk the site, photograph what matters, talk for thirty seconds per area — the day-log is in the client's and insurer's inbox before you lock the gate." - One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic photo-capture or notes app: the photo timestamps and GPS are preserved exactly, the voice notes are kept verbatim as the chain-of-custody record, the hazard register is multi-day and never silently drops an open hazard, and progress is always reported in the manager's own words. - Visual: a small annotated illustration of a phone in a site manager's hand, with arrows pointing at "photo (EXIF preserved)", "voice note (verbatim)", "hazard flag (insurer register)" — not a generic notes-app icon. **Slide 2 — Try it now.** - One short prompt: "Try with the sample site". - A live demo input pre-loaded with three areas from the seed content in section 8a (the Adelaja residence in Lagos, day 47). - 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the Tagalog-English code-switch preserved verbatim; the scaffold-tie hazard auto-detected; the rain-gauge photo read as a weather source). **Slide 3 — How to remix this.** - Headline: "Make this yours." - Three short bullets: - "Swap the sample site in `/data/seed-site/` for your own." - "Adjust the prompts in `/server/prompts/` to fit your site's languages and trades." - "Wire up your Gemini API key and Firebase project via the env-var list in the capabilities panel." - Primary CTA: "Use this template" → links to AI Studio Build remix entry point. - Secondary: "Just exploring — close" (sets localStorage flag, never auto-shows again). **Accessibility:** focus trap, `Esc` closes, `role="dialog"`, `aria-modal="true"`, `aria-labelledby`, focus restored to trigger on close. Respect `prefers-reduced-motion`. **Don't:** - Don't gate content behind the modal. The page beneath must be fully usable. - Don't auto-reshow on return visits. Use `localStorage['onboarding-seen-v1']`. - Don't include unrelated CTAs (newsletter signup, social follow). Keep it about the template only. ## 6c. Capabilities info button (persistent in header) Add a persistent `(i)` icon in the top-right of the header (next to the primary nav). Click → opens a modal/panel titled **"What powers this app"**. **Panel contents (in this order):** **Gemini capabilities used (the hero list):** - **Gemini 3.5 Flash (multimodal — voice + photos)** — reads up to a dozen photos from one area in capture order, alongside the manager's voice note in their own language and code-switches. One API call per area; multi-photo inputs are submitted as a single multi-image call with explicit ordering. Submitted via the Gemini Files API (`files/*` resource names) or `inlineData` (base64). - **Gemini 3.5 Flash (multilingual ASR)** — handles English, Tagalog, Yoruba, Igbo, Hausa, Pidgin, Hindi, Urdu, Punjabi, Tamil, Bengali, Vietnamese, Cantonese, Mandarin, Khmer, Bahasa Indonesia, Bahasa Melayu, Arabic, Swahili, Amharic, Brazilian Portuguese, Mexican Spanish, French, Russian — and the code-switches between them that dominate Lagos, Manila, Karachi, Mumbai, Dubai sites. - **Gemini 3.5 Flash (long context)** — for the multi-day hazard register, the weekly client summary, and the one-sentence search across the site's history. A 12-week site sits comfortably under 250k tokens; year-long sites chunk by month. - **Gemini 3.5 Flash + grounded search** — for weather readings from the nearest meteorological station and for materials-supplier resolution from delivery dockets. - **Gemini TTS (2.5 Flash Preview)** — read-back of the day-log summary while the manager drives home. - **Nano Banana 2** — optional clean isometric thumbnail for the PDF cover. The photographs of the day are never AI-altered. - **Firebase Auth** — Google, Apple, and Phone sign-in (recommended for field users). - **Firestore** — stores sites, day-logs, hazards, materials, sub-contractors, clients, insurers, weather readings, signatures. - **Firebase Storage** — keeps the original photographs at upload resolution with EXIF intact, forever; keeps the original voice notes as m4a/aac. - **Cost note** — see the detailed breakdown in 6d. A typical day-log (5 areas, 22 photos, 5 voice notes) costs about $0.18 of Gemini API spend; a year of daily logs on one site sits around $45. - **Privacy note** — your photos, voice notes, and structured day-log are private to you and the people you invite (client, insurer, sub-contractor leads). This app uses the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. **Backend services this app depends on:** - Auth: see section 4b - Database: see section 4b - Storage: see section 4b (must be enabled by you in the Firebase console — not auto-provisioned by AIS Build) - Email: see section 4b (your sender domain must be authorised with the chosen transactional provider) - Payments: see section 4b (not used in v1) - External APIs: see section 4b **Environment variables you'll need to configure:** - `GEMINI_API_KEY` — your Google AI Studio API key - `FIREBASE_PROJECT_ID` — your Firebase project id - `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side only) - `EMAIL_PROVIDER_KEY` — SendGrid, Postmark, or similar - `WEATHER_API_KEY` — optional, if you want a paid provider over Open-Meteo - `MAPBOX_TOKEN` — optional, only if you want richer base-map tiles than the included static **Cost + privacy notes:** - One short paragraph per cost-sensitive capability: long-context calls are billed per token of input — a 12-week site history with hazard-register resolution costs about $0.40 per resolution run (default: once per day). - One short paragraph on privacy: where the data lives (your Firebase project, your transactional-email provider), how to delete it (Settings → "Delete this site forever" — gone in 60 seconds, with a 14-day soft-delete retention for accidental clicks), what is never sent for training. **Documentation links:** - AI Studio Build docs - Gemini API multimodal, multilingual, long-context, TTS, image-generation docs - Firebase Auth, Firestore, Firebase Storage docs - A short note on EXIF preservation through Firebase Storage upload pipelines (the canvas re-encode trap) **Accessibility:** same standards as the onboarding modal — focus trap, `Esc`, ARIA, restored focus. **Behaviour:** - Always available — single click from anywhere in the app. - Tooltip on the `(i)` icon: "How this app is built". - Mobile: opens as a full-screen sheet that slides up. - Should be the most honest part of the app — never hand-wave service requirements; never say "AI" without naming the specific Gemini model and capability. ## 6d. Detailed cost breakdown (deployer reads this BEFORE shipping) - **Per-area parse (Gemini 3.5 Flash, medium thinking)** — typical area = 4-6 photos + 30-60s voice note ≈ 4-6 image inputs + ~2,000 audio-equivalent tokens + ~1,500 output tokens. ~$0.035/area. A 5-area walk: ~$0.18. - **Translate to client/insurer language (Gemini 3.5 Flash, low thinking)** — only when source language differs from target. Typical day-log body ~600 words, both ways ≈ ~$0.008/email. Two emails: ~$0.016/day. - **Client email draft (Gemini 3.5 Flash, low thinking)** — ~$0.006/draft. - **Insurer email draft (Gemini 3.5 Flash, low thinking)** — ~$0.006/draft. - **Multi-day hazard register resolution (Gemini 3.5 Flash, medium thinking, long-context)** — runs once at end of walk. ~$0.40 for a 12-week site; ~$0.80 for a 6-month site; chunked above 12 months. - **Weather grounded lookup (Gemini 3.5 Flash + grounding)** — ~$0.001/call. Runs once per day per site. - **Materials-supplier resolution (Gemini 3.5 Flash + grounding)** — ~$0.001/supplier. A typical day has 0-3 deliveries; runs only when supplier_resolved is null. - **TTS read-back (Gemini 2.5 Flash TTS)** — billed per output token (~$10/M output tokens), ~$0.000003/character. A 110-word summary ≈ $0.002/day. Cached per day-log. - **Nano Banana 2 PDF cover (optional)** — ~$0.03/image. Generated once per site at setup, reused thereafter. Off by default to keep first-walk cost lean. - **Expected per-day cost on a steady site:** ~$0.65/day for a 5-area walk with two emails, one hazard-register resolution, one weather lookup, one read-back. **Per-month:** ~$15-18. **Per-year on one site:** ~$200; a single PM running three sites: ~$600/year of Gemini API spend. - **Image storage:** Firebase Storage standard tier, ~$0.026/GB/month. A 12 MP photo at original quality ≈ 4 MB; a year's worth on one site (≈ 22 photos/day × 250 working days) ≈ 22 GB ≈ $0.57/month. - **Voice-note storage:** ~50 KB/s for m4a/aac. ~5 minutes/day × 250 days ≈ 750 MB/year ≈ negligible. - **Email sending:** SendGrid or Postmark, ~$15/month for the volume of one PM's daily client + insurer emails across three sites. ## 7. Design language - **Mood:** A worksite logbook that lives on the manager's phone. Not a SaaS dashboard. Not a consumer photo app. The hi-vis vest at 17:42, the late-afternoon shadow across the slab, the dust on the screen, the truck door open, the radio still on. Clean, fast, no flourish, the same hand-feel as a tide-table or a port-call schedule. - **Typography:** Clean grotesque for app chrome and the structured day-log fields (Inter or Geist). A serif for the verbatim voice-note transcripts and for the manager's own closing remark — to mark them visually as words spoken, not data fields (Source Serif Pro or Iowan Old Style). Mono for the EXIF audit trail and for any code-like field reference (JetBrains Mono or SF Mono). - **Palette:** Hi-vis amber `#F5A623` only for live action and one-press buttons; never for body. Hoarding blue `#1F3A5F` for the primary headings and the manager's signature block. Concrete grey `#6E6E73` for secondary text. Soft chalk `#F4F4F0` for the page background — so the photographs and the PDF preview read as paper-bright above it. Hazard red `#C03A2B` only for the hazard register and the open-hazard chips; never for routine UI. Sub-contractor sage `#5C7A6E` for trade tags. Borrowed from the colour palette of a clipboard, not from SaaS design systems. - **Imagery:** The manager's photographs are the hero. Never replace them; never crop them tighter than the manager did. EXIF metadata is shown beside the photograph as a small mono-font caption ("17:32:14 · 6.4567°N 3.4123°E · iPhone 14 Pro"). Period maps for the per-day site overview use a restrained light grey base with the site outlined. - **Hand-feel touches:** A barely-visible paper grain on the day-log PDF preview background. The "play voice note" button shows a small waveform of the actual audio, not a generic icon. Hover on a hazard chip reveals the verbatim contextual quote; never animates aggressively. - **Spacing:** consistent 4-px base. Generous whitespace at the area-detail level — the manager needs to scan a day-log in 30 seconds while driving (read at a stoplight). - **Radius:** consistent token set (6 / 12 / 20 px). Day-log cards use 6; the email preview uses 12; the welcome card uses 20. - **Shadows:** subtle, layered, cool-grey tinted. Avoid heavy drop-shadows. Hazard cards have a 1-px hazard-red border, no shadow, to read as urgent without shouting. - **Motion:** purposeful — entrance fades, hover lifts, page transitions. Respect `prefers-reduced-motion`. No bouncing splash animations. No theatrical hero animations. The "send email" confirmation slides in with a 120-ms easing; reduced-motion users see an instant transition. - **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons not spinners where possible. Empty states have helpful next-action guidance ("Start your first walk — press the big amber button to log the perimeter"). ## 8. Content generation rules - Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'. - Invent plausible names, dates, addresses, sub-contractors, materials, voice-note phrases that fit the domain (use the seed content in section 8a as a starting point). When inventing, lean on twenty-first-century construction patterns — Lagos high-density residential, Manila hospital extension, Karachi commercial frame, Belfast heritage restoration — but never claim that a fictional day-log corresponds to a real site or a real PM. - Tone: direct, factual, free of corporate language. This template is for a working PM at 17:42, not a sales deck for a property-tech vendor. - Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'. - Body copy: short paragraphs (2-4 sentences). Use lists where appropriate. - Plain language. Avoid jargon — except where the user already speaks the jargon (the PM wants to see "RFI", "snag", "PC sum", "BCAR submission", "CAR cover"; the insurer broker wants to see "continuing condition of cover"). - Where the app outputs AI-generated content (the email drafts, the parsed observations), never label it as "AI says" — let it speak naturally. Use small uncertainty cues only where epistemic honesty requires them (a low-confidence transcript word shows as a faintly underlined word; tapping it reveals the alternates the model considered). ## 8a. Seed content (use these specific examples) Anchor every generated copy + sample data point in the concrete content below. Use these names, numbers, dates, and snippets verbatim where helpful, or generate close variants that sit in the same world. **Sample sites (sidebar):** - "Adelaja Residence, Lekki Phase 2, Plot 47" — Lagos, Nigeria; three-storey residential frame; PM is Ngozi Adekunle; client is Tunde Adelaja (in Atlanta); insurer is Leadway Assurance (broker in London); 47 working days in. Languages on site: English, Yoruba, Igbo, Pidgin. - "St Therese Hospital Extension, Block C" — Manila, Philippines; new 4-storey ward; PM is Maricel Dela Cruz; principal contractor in Hong Kong; insurer is Pioneer Insurance Manila (broker in Makati); 92 working days in. Languages on site: Tagalog, English, Cebuano. - "Marina Walk Fit-Out, Tower 3 Levels 24-26" — Dubai, UAE; commercial fit-out; PM is Ahmed Al-Mansoori; client is Emaar Properties; insurer is AXA Gulf; 31 working days in. Languages on site: Arabic, English, Hindi, Tagalog. - "Mount Stewart Estate Office Restoration" — Belfast, Northern Ireland; heritage conservation; PM is Siobhán McAleer; client is National Trust; insurer is RSA Heritage; 156 working days in. Languages on site: English, Irish (some trade names). **Sample day-log in detail view (this is what the demo should show):** - **Site:** Adelaja Residence, Lekki Phase 2, Plot 47 - **Log date:** 2026-05-28 (Wednesday) - **Timezone:** Africa/Lagos - **Manager:** Ngozi Adekunle - **Walk start:** 17:28:11 - **Walk end:** 17:42:47 - **Walk duration:** 14 minutes 36 seconds - **Areas walked:** Perimeter hoarding, Ground-floor slab, First-floor block-work, Scaffolding north elevation, Site office - **Area: Ground-floor slab** - Photos (5): wide shot at 17:31:02, formwork at north corner at 17:31:24, rebar coverage detail at 17:31:48, the pour edge at the day's stop line at 17:32:11, the on-site rain gauge at 17:32:39 - Voice note (38 seconds, English with Yoruba code-switch): "Ground floor slab — we got the pour in at thirteen-forty after the ready-mix finally arrived, *o gba akoko*, about half the area set by end of day. The north corner formwork ties looked a bit *malabsa* at the noon walk, I made the steelfix lead re-do them at fourteen-fifteen, all good now. Slab coverage on the rebar looks adequate; we'll check again at the inspection tomorrow. *Òjò ti dáhùn,* heavy rain from fourteen to fifteen-thirty, lost some time on the formwork strip." - Progress in manager's words (verbatim): "about half the area set by end of day" - Progress percent named explicitly: null (manager did not name a number) - Materials: ready-mix concrete from Dangote Cement Lagos depot, arrived 13:40 (90 minutes late), quantity ≈ 18 m³ (inferred from voice note; manager confirms) - Hazards: (1) North-corner formwork ties loose at noon — resolved-today at 14:15 by steelfix lead — severity not named (manager confirms in UI: "structural-concern") - Trades observed: steelfixers (6 on site, in 08:00 out 16:00), concrete crew (8 on site, in 06:00 out 17:00), formwork crew (4 on site, in 07:00 out 17:30) - Weather at area: WeatherReading.source = "on-site-photograph" (rain gauge at 17:32:39 reads 18 mm), with grounded fallback also captured from NIMET Ikeja station (14:00 reading: 28°C, scattered showers 14:00-15:30) - Reading confidence: 0.94 - **Site-wide closing remark (verbatim):** "Overall, a wet day, pour went in late but we made the cut-off, formwork hazard at the north corner resolved, scaffolding tie at level two still flagged from yesterday — engineer coming in the morning to sign off. Tomorrow: strip formwork on the south side, second pour at thirteen-hundred if the rain holds off." - **Site-wide progress percent named explicitly:** null - **Open hazards at end of day:** 1 (the scaffold tie at level two — carries-to-tomorrow, escalated to structural engineer) - **Total headcount on site:** 38 **Sample client email draft (subject + body, ~220 words):** - Subject: "Adelaja Residence — Day-log 2026-05-28 — Ground-floor pour, ready-mix late" - Body: "Tunde — today's pour on the ground-floor slab went in at 13:40 after the ready-mix arrived about ninety minutes behind schedule. About half the slab area set by end of day. Photos of the pour edge and the formwork detail attached in the day-log. Areas walked today: perimeter hoarding, ground-floor slab, first-floor block-work, scaffolding north elevation, site office. Block-work on the first floor is steady; the masons are about three courses ahead of where I expected. Scaffolding north elevation: yesterday's flagged tie at level two is still being assessed — engineer coming tomorrow morning to sign off. Materials: 18 m³ of ready-mix from Dangote arrived 13:40 (delivery docket attached); twelve lengths of 12 mm rebar from CSW arrived 10:15; sand and aggregate stock unchanged. Headcount: 38 on site today across steelfix (6), concrete (8), formwork (4), block-work (10), scaffolding (4), labourers (6). Hours per trade in the day-log. Weather: heavy rain 14:00 to 15:30 — lost time on formwork strip. NIMET Ikeja station 14:00 reading: 28°C, scattered showers. Three items on the hazard register today; two resolved, one carries to tomorrow. Full detail in the attached day-log. Tomorrow: strip formwork on the south side, second pour at 13:00 if the rain holds off. Ngozi Adekunle, Site Manager" **Sample insurer email draft (subject + body, ~190 words):** - Subject: "Leadway Policy 2087-451-A — Adelaja Residence — Day-log 2026-05-28 — Hazard summary" - Body: "Three items on the hazard register today; two resolved, one carries to tomorrow. • Hazard A47-H1 — North-corner formwork ties loose at noon walk (manager: 'the north corner formwork ties looked a bit malabsa at the noon walk'). Severity not named by manager; please advise classification. Resolution status: resolved-today at 14:15 by steelfix lead. • Hazard A47-H2 — Scaffolding north-elevation tie at level two, first observed 2026-05-27. Severity not named by manager. Resolution status: escalated — structural engineer attending site 2026-05-29 morning. • Hazard A47-H3 — Localised pooling at south boundary, runoff during the 14:00-15:30 rain. Severity: environmental (manager-named). Resolution status: resolved-today by site drainage. Hours worked: 38 on site across steelfix (6, 8 hrs), concrete (8, 11 hrs), formwork (4, 10.5 hrs), block-work (10, 9 hrs), scaffolding (4, 9 hrs), labourers (6, 9 hrs). Total person-hours: 360. Progress: about half the ground-floor slab set; manager did not name an overall site percentage. Weather: NIMET Ikeja station 14:00 reading: 28°C, scattered showers 14:00-15:30. Source: NIMET Lagos. Full day-log with photographs and audit trail attached. Ngozi Adekunle, Site Manager" **Sample input artefacts (for the build to demonstrate):** - Five photographs of the ground-floor slab walk, captured between 17:31:02 and 17:32:39, EXIF intact, geotagged to the Lekki site. - A 38-second voice note in English with Yoruba code-switches as transcribed above. - A photograph of the on-site rain gauge reading 18 mm. - A photograph of the Dangote Cement Lagos delivery docket for the 18 m³ ready-mix. - A pre-loaded carry-forward hazard from 2026-05-27 (scaffold tie level two), to demonstrate the multi-day register. - A second day's walk on 2026-05-29 showing the structural engineer's sign-off resolving the carry-forward hazard. **Sample voice copy:** - Onboarding: "Walk the site. We'll write the log." - Processing: "Reading the photos of the slab pour…" / "Listening to the Yoruba in the voice note…" / "Checking yesterday's open hazards…" / "Drafting the client email…" - Empty home: "No walks yet on this site. Press 'Start day-log walk' when you're ready — five minutes at the end of the day is the whole job." - Error (couldn't transcribe): "We couldn't make out part of that voice note — the wind was loud at minute one. Want to re-record that bit, or type a few words you remember?" - Save confirmation: "Day-log saved — 5 areas, 22 photos, 3 hazards. Client and insurer emails ready to review." - Hazard auto-detected: "We flagged a hazard from your voice note — 'the formwork ties looked a bit malabsa'. Tap to confirm or edit." - Carry-forward reminder: "Yesterday's scaffold-tie hazard is still open. Carry it forward on today's log?" - Low-confidence note: "Some words in the transcript were unclear. Tap any underlined word to see alternates." **Sample sub-contractor invitation email subject + body:** - Subject: "Adelaja Residence — your trade's portion of the daily log" - Body: "Hi — I'm sharing your trade's rows of the daily site log on Adelaja Residence so you and your team can see headcount, hours, and any hazards flagged on your work. You'll only see steelfix rows, not the rest of the site. Tap to view. — Ngozi Adekunle, Site Manager" [Open Site] ## 9. Media & assets - **Hero image (landing screen):** A photographed-looking shot of a PM mid-walk at a residential frame at 17:30, phone in hand, hi-vis vest, hoarding blue behind, late-afternoon shadow across the slab, dust on the screen. Generate via Nano Banana 2 with a prompt emphasising "Lagos high-density residential frame, PM walking the slab at 17:30 with phone in hand, hi-vis amber vest, hoarding blue plywood, late-afternoon golden shadow, no faces in close-up, real worn concrete, dust in the air". - **App icon / wordmark:** Set in the clean grotesque. Slightly worn concrete texture behind it. A small amber dot for the active state. No icon — just type with the dot. - **Empty-state illustration:** A simple line drawing of a clipboard with a single page on it, an envelope behind it, and a small smartphone shape. Hand-drawn aesthetic, not a flat icon. - **Demo photographs of the day:** Generated per the prompts in section 8a — Nano Banana 2 prompts that specifically request "fresh concrete slab in evening light, formwork at the corner, rebar coverage detail, the pour edge stopped mid-slab, no people in frame". Each demo photograph should look like a site manager's working phone shot, not a stock image. - **Site-overview map:** A static GeoJSON in `/data/site-overview/` for the demo Lagos site outline; if Mapbox is configured, use the "muted neutral" style. Period maps are not relevant here — site-overview is current-day. - **Stock fallbacks:** If image generation fails, fall back to the photographed sample from `/public/samples/sample-slab.jpg`. Never to a "🏗️" emoji. - **Generated imagery:** prefer Nano Banana 2 over stock photography for the hero. Never for the day's photos — those are the user's. Prompt for warmth, asymmetry, dust, and slight imperfection — avoid the glossy render look. - **Optimisation:** WebP/AVIF for thumbnails of the day's photos; keep the originals in storage at upload resolution with EXIF intact. `loading="lazy"`, explicit `width`/`height` to prevent layout shift. - **Icons:** `lucide-react` for UI. Use sparingly — never decorative-only. ### Build-time asset manifest (explicit specs) Every image, illustration, and visual reference mentioned above must resolve to ONE of the three buckets below — runtime-generated, seed-shipped, or user-supplied. Do NOT ship `` tags whose `src` is not listed here. Do NOT depend on bare "section 8a prompts" without binding them to explicit paths and model IDs. **Bucket 1 — Runtime-generated (Nano Banana Pro `gemini-3-pro-image` for hero/demo photographs; Nano Banana 2 `gemini-3.1-flash-image` for in-app illustrations and reference-conditioned variants).** Cached to Firebase Storage; served via signed URL. Every reference above to "Nano Banana 2" or "Nano Banana Pro" MUST be wired to one of these specific calls with an explicit model id: - `/public/generated/hero.webp` (2400×1500, WebP) — model `gemini-3-pro-image` — uses the literal prompt described as "Hero image (landing screen)" above. Run once at build; commit a `/public/samples/hero-fallback.webp` (1600×1000) generated from the same prompt with `gemini-3.1-flash-image` so the page renders if quota is exhausted. - `/public/generated/demo/{demo-slug}-{NN}.webp` (1600×1200, WebP) — model `gemini-3.1-flash-image` (reference-conditioned where the prior frame is passed as input) — one path per "Demo X" image referenced above. The slug derives from the seed example in section 8a; the NN index covers each frame in the demo sequence. - `/public/generated/illustrations/{name}.webp` (1024×1024, WebP) — model `gemini-3.1-flash-image` — one path per named illustration above ("Empty-state illustration", "Recipe-card hero illustrations", "Curriculum picker imagery", "Period-style frames", etc.). Each illustration's prompt is the literal description above; ship a deterministic seed in the request so re-runs are reproducible. **Bucket 2 — Seed assets shipped with the deliverable.** Every "Stock fallback" path referenced above (e.g. `/public/samples/sample-X.jpg`) is generated once via Nano Banana 2 (`gemini-3.1-flash-image`) at 1024×1024 WebP using the same prompt as its Bucket-1 counterpart, then committed to the repo so the page renders identically if Gemini quota is exhausted or the user is offline. Replace any `.jpg` extension above with `.webp` to match the optimisation rule. Also commit these empty-state seeds (1024×1024 WebP, single-stroke hand-drawn line, no colour fill): - `/public/samples/empty-state-primary.webp` — line drawing of the app's primary empty surface (the named "Empty-state illustration" above), generated from that exact prompt. - `/public/samples/empty-state-archive.webp` — line drawing of an empty saved/archive view, single-stroke outline. - `/public/samples/empty-state-error.webp` — line drawing of a hand placing a single object aside with care, used when an AI call fails. **Bucket 3 — User-supplied.** Uploads from the user's camera / file picker land at the Firebase Storage path conventional for this template (named in section 4b). The build ships with Bucket-1 + Bucket-2 only; no user-supplied images at first paint. **Hard rules** - Every `` tag MUST have a `src` that resolves to a path listed in Bucket 1, Bucket 2, or a Bucket 3 upload path. Anything else is a build error. - No bare `image.jpg` / `hero.jpg` / `placeholder.png` references anywhere in the code. - Model IDs: `gemini-3-pro-image` for hero-quality photographic generation; `gemini-3.1-flash-image` for in-app illustrations, reference-conditioned variants, empty-state seeds, and stock fallbacks. Never use a legacy model id (no `imagen-*`, no `gemini-1.5-*-image`). - File format: WebP everywhere (AVIF acceptable where the target browsers support it). No `.jpg` / `.jpeg` / `.png` in `/public/samples/`. ## 10. Interactivity & states - Every interactive element has hover, focus, active, and disabled states. - Forms validate inline and show specific error messages (not "Invalid input"). - Loading states use skeletons that match the eventual layout, not spinners. - Empty states explain the next action with a button whose label fits THIS app's domain: "Start your first walk", "Add the site's areas", "Invite the client", "Invite the insurer broker" — never a generic "Add your first item". - Smooth scroll for in-page anchors. - All AI-generated content streams in token-by-token where supported, with a clear "thinking…" indicator before content starts arriving. The indicator names the specific step: "Listening to the Yoruba…", "Reading the slab photos…", "Checking yesterday's hazards…". - If an AI call fails, show a calm, specific error ("We couldn't transcribe the second voice note — try re-recording that area, or type the gist into the side panel?") and offer retry. - Low-confidence words in the transcript are faintly underlined; tapping reveals the alternates the model considered. - The "send email" confirmation slides in with 120-ms easing; `prefers-reduced-motion` users see an instant transition. - The hazard register's carry-forward chip pulses gently once on first appearance, then is static — to draw the eye without being a continuous animation. - All voice-note playbacks expose a transcript that scrolls in sync; a "pause to read this passage" button stops at any sentence. ## 11. Tech & responsive requirements - **TTS markdown-stripping preprocessor:** before sending any user-authored markdown to `gemini-3.1-flash-tts-preview`, strip non-spoken markdown: `#`/`##`/`###` headings (keep the title text), `**bold**` (keep the inner text), `[label](url)` (keep `label`, drop URL), `` ``` `` fenced code blocks (skip entirely), `>` block-quote markers (keep the text), and `|` table pipes (read row-by-row as sentences). Insert `…` between sentences for a short pause and a blank line plus `—` between paragraphs for a long pause. The model does not understand markdown; raw markdown will be read aloud as literal characters ("asterisk asterisk"). - **File downloads on Safari / Firefox:** when offering local-disk save of any export (PDF, CSV, MP3, ZIP, JSON, image), fall back to `` with a blob URL — the File System Access API (`showSaveFilePicker()`) is Chromium-only. Detect with `'showSaveFilePicker' in window`; otherwise use the anchor-download path. - **Stack:** React + TypeScript + Tailwind CSS. Functional components + hooks. Use Shadcn UI primitives where appropriate. - **Build runtime:** AI Studio Build — full-stack with Cloud Run server-side functions. All Gemini API calls happen server-side; API key lives in Secrets Manager, never in client bundle. - **Model selection:** explicitly pin `gemini-3.5-flash` for per-area parse / translate / hazard-register-resolution / email-drafts and `gemini-3.5-flash` for weather grounding and materials-supplier grounding. Set `thinkingLevel` explicitly per call. - **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed Adelaja Residence site on first launch. - **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it; Phone sign-in below as the recommended path for field PMs. - **Storage:** Firebase Storage for original photographs (EXIF preserved) and voice notes. Pre-signed URLs only. Upload pipeline avoids canvas re-encoding so EXIF survives. - **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+. The per-area walk screen is the primary mobile target — one-handed use, big tap targets, gloves-compatible. - Use `clamp()` for fluid typography. Prefer container queries over media queries for component-level responsiveness. - Use `dvh` / `svh` instead of `vh`. Respect safe-area insets on iOS. - Zero horizontal overflow at any width. Zero layout shift on load. - Persist user data in Firestore. Use real-time listeners on the per-site day-log timeline so a senior PM seeing the same site sees the walk as it's being captured. - Optimistic UI on writes; reconcile on response. - Per-area capture flow uses the Web Camera API with multi-shot mode and EXIF preservation; falls back to native camera otherwise. Voice notes use MediaRecorder with the m4a/aac codec. - **iOS Safari gotchas (graceful degradation):** camera and mic permissions do NOT persist across page reloads on iOS — re-request on every walk; an incoming call interrupts the audio session (`MediaStreamTrack.onmute` fires) — auto-pause the voice note, save what was captured, and prompt the PM to resume or restart; backgrounded Safari tabs pause `getUserMedia` — pair `visibilitychange` with a screen Wake Lock during an active walk so a long site capture isn't dropped; rotation drops the camera track on iOS — re-bind on `orientationchange`; always offer `` as a hard fallback when WebRTC is denied; on Low Power Mode iOS may degrade resolution — record this in the EXIF note rather than failing. - Offline-first capture: photographs and voice notes are written to IndexedDB if the network is unavailable; parsing runs on reconnect; a clear "pending sync" indicator is visible at all times when there is unsynced content. ## 12. Accessibility (WCAG 2.2 AA) - Semantic HTML — `header`, `nav`, `main`, `section`, `article`, `footer`. - All interactive controls reachable by keyboard with a visible focus ring. - Color contrast ≥ 4.5:1 for body, 3:1 for large text and UI components. The hazard-red `#C03A2B` against soft chalk `#F4F4F0` is verified at 5.6:1. - All images have meaningful `alt` text. The day's photographs have `alt` describing the artefact ("close-up of north-corner formwork ties on ground-floor slab, captured 17:31:24 by Ngozi Adekunle on iPhone 14 Pro"). - Form fields have associated `