Skip to content

F07 — Property listing from a photo bundle

An estate agency uploads the photo shoot of an apartment in one go (a bundle of eight photos) together with the sheet of certain facts; Madoo has the AI look at every photo, keeps in the gallery only the publishable ones, has the texts written from the sheet, and produces from the same template a two-page PDF sheet and a 4:5 social post. Price, areas, rooms and every other fact are printed as the agency wrote them.

The listing and the social post produced by the workflow: an A4 page with a full-width cover photo of the building, the price, the AI headline, a grid of nine facts, the description and four highlights; a second A4 page with a gallery of six photos with captions and the agent’s box; and a 4 post with the cover photo, a band with the contract, an AI line, three facts and the price

A reference run: 24 seconds, 0.17 credits. PDF · the social post · photo-by-photo review · the AI copy · layout report.

Lario Case (a fictional agency on Lake Como) puts a three-room apartment with a terrace in Cernobbio on the market. The photographer delivers eight shots: seven good ones and one taken in a hurry — a dark, blurred, tilted hallway — as happens in every real shoot.

Input Who provides it Example
Photo shoot the photographer, through the agent: each photo with a declared role (hero for the cover, then living_room, kitchen, …) Photo shoot (bundle), the shoot saved as default value (spec)
Property sheet the agency’s management system: contract, area, address, price, surface, rooms, bedrooms, bathrooms, floor, terrace, energy class and index, year, fees, heating, parking, availability, agent, link one JSON object

The bundle is not a convenience container: Madoo checks that every file belongs to the workspace and matches its checksum before any paid call, and every photo carries its identity, role and rights through to the result. The photos are the example’s starting data, generated once as a consistent shoot (same parquet, same windows, same lake).

  • A person chooses the cover, the AI filters the gallery. The agent declares in the bundle which photo is the cover (role: hero); the AI does not change it. For all the others the AI says whether they are publishable: in the reference run seven out of eight yes, the hallway no (“dark, motion-blurred, and partly obstructed by a coat”). Only the publishable photos that are not the cover go into the gallery: six photos.
  • Keeping only some rows of a list. For each photo utility/filter lets the image through only if its condition is true; otherwise the value is absent. The photo column of aggregate/json has whenAbsent: skip_row: a discarded photo leaves the list instead of becoming an empty row. The same mechanism, with the condition “is the cover”, produces the one-photo list the template uses as cover. The same run also produces the full photo-by-photo review, discarded photos included, for whoever has to review the shoot. See Iteration.
  • Facts from the sheet, words from the AI. The sheet reaches the render on data_1 and wins over any value with the same key in the AI copy on data_0. The instructions let the AI use only the facts of the sheet, not write price, reference or contacts, write numbers as the sheet does and square metres as “m²”. A JSON Schema checks headline, description, four highlights and the line for the post. See Templates in workflows.
  • One template, two products. Pages 1–2 are the A4 sheet, page 3 is the 4:5 post (540 × 675 pt, an image of 1125 × 1406 px). Two render nodes read the same template with two page selections (1-2 → PDF, 3 → image). The post opens its own numbering sequence, so the sheet’s footer says “1 / 2” and “2 / 2”. See Numbering sequences and covers.

Master page Listing pages (a footer with the agency and {{page}} / {{sequencePages}}) on the two A4 pages; the post has no master. Playfair Display for price and titles, Montserrat for labels and values, Inter for text; lake blue #1d3557, sand #f1ece3, gold #c8a45c.

Page Content How it is built
Listing (A4) full-width cover, contract and place, price, reference, AI headline, nine facts in a grid, AI description, four highlights the list hero (one photo); below, a flowing Layout; the facts are cells with label and value; the list highlights in two columns
Photos and contacts (A4) Le foto (up to nine, three per row, with AI caption), other details, the agent’s box with the button Vedi l’annuncio online →, disclaimer the list gallery as a grid; the button exists only when there is a link
Social post (540 × 675 pt) the cover, a band with contract, AI line, three facts, price and agency the list hero full-page; numberingStart: 1
Data Field How it prints
Price price, number it-IT currency without decimals: 485.000 €
Surface, terrace area_sqm, terrace_sqm, numbers suffix: 98 m², 22 m²
Condominium fees condo_fees, number suffix: 180 €/mese
Year year_built, number no separator: 2008
Rooms in the post rooms, number the same format as in the sheet; the word “locali” is a fixed text beside it
Photos gallery[].photo, hero[].photo fill, rounded corners in the gallery (Images)
Link listing_url (optional) the button’s link; without it the agent’s box has no button
AI copy headline, description, highlights[].text, social_line text from the validated AI output
Photo captions gallery[].caption from the photo analysis (Look at the photo), checked by Assessment is complete

A field that appears in several places must have the same format everywhere (the engine checks it): that is why in the post “3 locali” and “terrazzo 22 m²” are a number in the sheet’s format plus a fixed word.

Design choices worth copying:

  • The cover is a one-row list. Filtering produces a list, so the cover arrives as hero with one photo and the template repeats it once — on the sheet and on the post.
  • The post opens its own numbering sequence (numberingStart: 1) instead of counting in the sheet’s pages.
  • Three sample sets — Cernobbio (6 foto pubblicabili), Tre foto, senza link, Nove foto (il massimo) — show the full gallery and the agent’s box without a button.
Photo shoot (bundle) ──► Verified photos ──► One photo at a time (one iteration per photo)
photo ──► Look at the photo (AI) ──► Assessment is complete (JSON Schema) ──► Publishable? · Caption · Why
role ──► Declared as cover? · Not the cover?
Publishable? + Not the cover? ──► Publishable and not the cover
photo ──► Keep for the gallery (if publishable and not the cover) ──► Gallery (skip_row) ◄── Caption
photo ──► Keep as cover (if declared as cover) ──► Cover (skip_row)
asset_id, role, judgement ──► Photo review
Property sheet ──► Sheet as text ──► Property sheet for the writer ──► Write the listing (AI) ──► Words fit the listing
Words fit the listing ──data_0──┐
Property sheet ──data_1─────────┼──► Compose the listing (pages 1-2) ──► PDF · Page images · Layout report
Gallery, Cover ─────────────────┴──► Compose the social post (page 3) ──► Social post (4:5)
Node Type Why
Photo shoot (bundle) input/bundle_manifest, validation: fail, verifyChecksum: always The shoot as one verified unit of work; an altered file stops the workflow before any spending
Verified photos utility/extract (assets) The list of verified assets of the bundle
One photo at a time enumerate/json Ports asset_id, role (from the metadata) and photo (the verified image); eight photos, eight iterations
Look at the photo ai/image_analysis, json_object, temperature 0.1 Room, publishable yes/no, an Italian caption of what is visible, the reason
Assessment is complete utility/json_schema_validate, mode fail Allowed values and lengths before the judgement decides anything
Publishable? / Caption / Why utility/extract The three answers as separate values
Declared as cover? / Not the cover? utility/predicate on the role (equals / not_equals hero) The choice of the cover stays with the agent
Publishable and not the cover utility/boolean_combine, mode all The gallery’s condition
Keep for the gallery / Keep as cover utility/filter The image passes or becomes absent
Gallery / Cover aggregate/json, column photo with whenAbsent: skip_row Only the rows with a photo; the images of excluded rows are not even copied
Photo review aggregate/json All eight photos, with judgement and reason
Sheet as text / Property sheet for the writer utility/extract, text/template The sheet, as JSON text, in the writer’s message
Write the listing / Words fit the listing ai/text_generation + utility/json_schema_validate Headline, description, highlights, the line for the post — from the sheet only
Compose the listing / Compose the social post design/template_render, pages 1-2 (output both) and 3 (output image) Same template, same data, two products
Outputs output/pdf, output/image, output/json ×4 The PDF, the page images, the post, the photo review, the approved words, the layout report

Reading a run: Photo review lists all eight photos with listing_ready and the reason; the gallery has six photos, the cover one. The description stays sober because the AI cannot add anything the sheet does not say: for a richer text you need a richer sheet, not a freer AI.

Cost: 0.17 credits per run — eight photo judgements 0.08, the listing texts 0.09; bundle verification, filters and composition are free. The eight photos are starting data: 40 credits, once.

The example installs into your workspace with an API key, through the public API only — see Installing an example:

Terminal window
node madoo-install-example.mjs f07 --api <your Madoo API URL> --key <key.json>

It uploads the eight photos, composes the photo bundle from photo-bundle-spec.json (Madoo derives type, size and checksum), validates it and saves it as the default value of Photo shoot, then publishes the template and the workflow. To run it, open the workflow and paste the property sheet from run-inputs.json, or add --run (about 0.2 credits).

  • Another property: another bundle (the photos with their roles) and another sheet; up to nine photos in the gallery.
  • Another cover: change the hero role in the bundle, without touching the workflow.
  • A stricter or looser judgement: the instructions of Look at the photo say what makes a photo publishable.
  • Improve or stage photos: photos judged to need work or virtual staging can go through an ai/image_transform declared as a simulation, as in F06.

manifest · template · workflow · photo bundle spec · run inputs