{
  "schema_version": "1.0",
  "nodes": [
    {
      "id": "listing",
      "type": "input/json_value",
      "label": "Property sheet (from the agency)",
      "parameters": {
        "required": true
      }
    },
    {
      "id": "photos",
      "type": "input/bundle_manifest",
      "label": "Photo shoot (bundle)",
      "parameters": {
        "validation": "fail",
        "verifyChecksum": "always",
        "allowExternalUrls": false,
        "manifest": "__PHOTO_BUNDLE_MANIFEST__"
      }
    },
    {
      "id": "assets",
      "type": "utility/extract",
      "label": "Verified photos",
      "parameters": {
        "path": "assets",
        "expected_type": "json",
        "on_missing": "fail"
      }
    },
    {
      "id": "photo_rows",
      "type": "enumerate/json",
      "label": "One photo at a time",
      "parameters": {
        "jsonConfig": {
          "arrayPath": "(root)",
          "properties": [
            {
              "sourcePath": "id",
              "outputPort": "asset_id",
              "outputType": "text",
              "selected": true,
              "label": "Asset ID"
            },
            {
              "sourcePath": "metadata.role",
              "outputPort": "role",
              "outputType": "text",
              "selected": true,
              "label": "Declared role"
            },
            {
              "sourcePath": "storageRef",
              "outputPort": "photo",
              "outputType": "image",
              "selected": true,
              "label": "Verified photo"
            }
          ]
        },
        "emptyBehavior": "fail"
      }
    },
    {
      "id": "triage",
      "type": "ai/image_analysis",
      "label": "Look at the photo",
      "parameters": {
        "system_prompt": "You are a careful real-estate photo editor. Judge only what is visible in the photograph. Never state measurements, prices or facts that a photo cannot show. Return one JSON object and nothing else.",
        "prompt": "Assess this photograph for a property listing and return:\n{\n  \"room_type\": one of \"exterior\", \"living_room\", \"kitchen\", \"bedroom\", \"bathroom\", \"terrace\", \"hallway\", \"other\",\n  \"listing_ready\": true when the photo is sharp, well exposed and presents the space well enough to publish; false otherwise,\n  \"caption\": an Italian caption of what is visible, at most 44 characters, no numbers, no superlatives (for example \"Cucina aperta con isola\"),\n  \"reason\": one short English sentence explaining listing_ready\n}",
        "response_format": "json_object",
        "temperature": 0.1,
        "max_tokens": 4000
      }
    },
    {
      "id": "triage_contract",
      "type": "utility/json_schema_validate",
      "label": "Assessment is complete",
      "parameters": {
        "schema_source": "inline",
        "schema_definition": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "object",
          "additionalProperties": false,
          "required": [
            "room_type",
            "listing_ready",
            "caption",
            "reason"
          ],
          "properties": {
            "room_type": {
              "type": "string",
              "enum": [
                "exterior",
                "living_room",
                "kitchen",
                "bedroom",
                "bathroom",
                "terrace",
                "hallway",
                "other"
              ]
            },
            "listing_ready": {
              "type": "boolean"
            },
            "caption": {
              "type": "string",
              "minLength": 4,
              "maxLength": 48
            },
            "reason": {
              "type": "string",
              "minLength": 4,
              "maxLength": 300
            }
          }
        },
        "mode": "fail"
      }
    },
    {
      "id": "ready",
      "type": "utility/extract",
      "label": "Publishable?",
      "parameters": {
        "path": "listing_ready",
        "expected_type": "boolean",
        "on_missing": "fail"
      }
    },
    {
      "id": "caption",
      "type": "utility/extract",
      "label": "Caption",
      "parameters": {
        "path": "caption",
        "expected_type": "text",
        "on_missing": "fail"
      }
    },
    {
      "id": "reason",
      "type": "utility/extract",
      "label": "Why",
      "parameters": {
        "path": "reason",
        "expected_type": "text",
        "on_missing": "fail"
      }
    },
    {
      "id": "is_hero",
      "type": "utility/predicate",
      "label": "Declared as cover?",
      "parameters": {
        "operation": "equals",
        "compare_text": "hero"
      }
    },
    {
      "id": "not_hero",
      "type": "utility/predicate",
      "label": "Not the cover?",
      "parameters": {
        "operation": "not_equals",
        "compare_text": "hero"
      }
    },
    {
      "id": "in_gallery",
      "type": "utility/boolean_combine",
      "label": "Publishable and not the cover",
      "parameters": {
        "mode": "all"
      }
    },
    {
      "id": "gallery_photo",
      "type": "utility/filter",
      "label": "Keep for the gallery",
      "parameters": {}
    },
    {
      "id": "hero_photo",
      "type": "utility/filter",
      "label": "Keep as cover",
      "parameters": {}
    },
    {
      "id": "gallery",
      "type": "aggregate/json",
      "label": "Gallery",
      "parameters": {
        "jsonAggregateConfig": {
          "columns": [
            {
              "name": "caption",
              "inputPort": "caption",
              "type": "text"
            },
            {
              "name": "photo",
              "inputPort": "photo",
              "type": "image",
              "whenAbsent": "skip_row"
            }
          ]
        },
        "outputName": "gallery",
        "emptyBehavior": "fail"
      }
    },
    {
      "id": "hero",
      "type": "aggregate/json",
      "label": "Cover",
      "parameters": {
        "jsonAggregateConfig": {
          "columns": [
            {
              "name": "photo",
              "inputPort": "photo",
              "type": "image",
              "whenAbsent": "skip_row"
            }
          ]
        },
        "outputName": "hero",
        "emptyBehavior": "fail"
      }
    },
    {
      "id": "review",
      "type": "aggregate/json",
      "label": "Photo review",
      "parameters": {
        "jsonAggregateConfig": {
          "columns": [
            {
              "name": "asset_id",
              "inputPort": "asset_id",
              "type": "text"
            },
            {
              "name": "role",
              "inputPort": "role",
              "type": "text"
            },
            {
              "name": "listing_ready",
              "inputPort": "listing_ready",
              "type": "boolean"
            },
            {
              "name": "caption",
              "inputPort": "caption",
              "type": "text"
            },
            {
              "name": "reason",
              "inputPort": "reason",
              "type": "text"
            }
          ]
        },
        "outputName": "photo_review",
        "emptyBehavior": "fail"
      }
    },
    {
      "id": "sheet",
      "type": "utility/extract",
      "label": "Sheet as text",
      "parameters": {
        "path": "$",
        "expected_type": "json",
        "on_missing": "fail"
      }
    },
    {
      "id": "brief",
      "type": "text/template",
      "label": "Property sheet for the writer",
      "parameters": {
        "template": "SCHEDA DELL'IMMOBILE (JSON)\n[listing]",
        "placeholders": [
          {
            "name": "listing",
            "inputPort": "listing"
          }
        ]
      }
    },
    {
      "id": "writer",
      "type": "ai/text_generation",
      "label": "Write the listing",
      "parameters": {
        "system_prompt": "You write property listings for an estate agency on Lake Como.\nYou receive the property sheet as JSON. Write in Italian, precise and warm, for a buyer; no superlatives, no exclamation marks.\n\nReturn one JSON object and nothing else:\n{\n  \"headline\": \"the listing title, at most 56 characters\",\n  \"description\": \"at most 520 characters, two short paragraphs separated by a blank line: the flat and its light, then the terrace, the building and the place\",\n  \"highlights\": [ { \"text\": \"at most 36 characters\" } ]  — exactly 4 strengths,\n  \"social_line\": \"one sentence for a social post, at most 90 characters, no hashtags, no emoji\"\n}\n\nRules:\n- Use only the facts in the sheet. Never invent views, finishes, distances, services, neighbours or history.\n- Do not write the price, the reference, the agent or the contacts: the listing prints them.\n- A number you write must be one of the sheet's numbers, written the same way; write square metres as \"m²\", never \"mq\".\n- Do not just repeat the list of facts (the listing prints them in a grid): say what they mean for living there.",
        "response_format": "json_object",
        "temperature": 0.4,
        "max_tokens": 4000
      }
    },
    {
      "id": "copy_contract",
      "type": "utility/json_schema_validate",
      "label": "Words fit the listing",
      "parameters": {
        "schema_source": "inline",
        "schema_definition": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "object",
          "additionalProperties": false,
          "required": [
            "headline",
            "description",
            "highlights",
            "social_line"
          ],
          "properties": {
            "headline": {
              "type": "string",
              "minLength": 10,
              "maxLength": 62
            },
            "description": {
              "type": "string",
              "minLength": 150,
              "maxLength": 575
            },
            "highlights": {
              "type": "array",
              "minItems": 4,
              "maxItems": 4,
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 4,
                    "maxLength": 40
                  }
                }
              }
            },
            "social_line": {
              "type": "string",
              "minLength": 20,
              "maxLength": 100
            }
          }
        },
        "mode": "fail"
      }
    },
    {
      "id": "render_sheet",
      "type": "design/template_render",
      "label": "Compose the listing",
      "parameters": {
        "template_id": "__TEMPLATE_ID__",
        "template_revision": "__TEMPLATE_REVISION__",
        "output": "both",
        "page_selection": "1-2",
        "missing_policy": "fail_required",
        "overflow_policy": "template",
        "max_repeat": 9
      }
    },
    {
      "id": "render_social",
      "type": "design/template_render",
      "label": "Compose the social post",
      "parameters": {
        "template_id": "__TEMPLATE_ID__",
        "template_revision": "__TEMPLATE_REVISION__",
        "output": "image",
        "page_selection": "3",
        "missing_policy": "fail_required",
        "overflow_policy": "template",
        "max_repeat": 9
      }
    },
    {
      "id": "listing_pdf",
      "type": "output/pdf",
      "label": "Listing — PDF",
      "parameters": {
        "output_name": "listing_pdf"
      }
    },
    {
      "id": "listing_pages",
      "type": "output/json",
      "label": "Listing — page images",
      "parameters": {
        "output_name": "listing_pages"
      }
    },
    {
      "id": "social_post",
      "type": "output/image",
      "label": "Social post (4:5)",
      "parameters": {
        "output_name": "social_post"
      }
    },
    {
      "id": "review_output",
      "type": "output/json",
      "label": "Photo review",
      "parameters": {
        "output_name": "photo_review"
      }
    },
    {
      "id": "copy_output",
      "type": "output/json",
      "label": "Approved words",
      "parameters": {
        "output_name": "listing_copy"
      }
    },
    {
      "id": "layout_report",
      "type": "output/json",
      "label": "Layout report",
      "parameters": {
        "output_name": "layout_report"
      }
    }
  ],
  "connections": [
    {
      "from_node": "photos",
      "from_output": "bundle",
      "to_node": "assets",
      "to_input": "json"
    },
    {
      "from_node": "assets",
      "from_output": "value",
      "to_node": "photo_rows",
      "to_input": "json"
    },
    {
      "from_node": "photo_rows",
      "from_output": "photo",
      "to_node": "triage",
      "to_input": "images_0"
    },
    {
      "from_node": "triage",
      "from_output": "json",
      "to_node": "triage_contract",
      "to_input": "document"
    },
    {
      "from_node": "triage_contract",
      "from_output": "normalized_json",
      "to_node": "ready",
      "to_input": "json"
    },
    {
      "from_node": "triage_contract",
      "from_output": "normalized_json",
      "to_node": "caption",
      "to_input": "json"
    },
    {
      "from_node": "triage_contract",
      "from_output": "normalized_json",
      "to_node": "reason",
      "to_input": "json"
    },
    {
      "from_node": "photo_rows",
      "from_output": "role",
      "to_node": "is_hero",
      "to_input": "a"
    },
    {
      "from_node": "photo_rows",
      "from_output": "role",
      "to_node": "not_hero",
      "to_input": "a"
    },
    {
      "from_node": "ready",
      "from_output": "value",
      "to_node": "in_gallery",
      "to_input": "conditions_0"
    },
    {
      "from_node": "not_hero",
      "from_output": "result",
      "to_node": "in_gallery",
      "to_input": "conditions_1"
    },
    {
      "from_node": "photo_rows",
      "from_output": "photo",
      "to_node": "gallery_photo",
      "to_input": "value"
    },
    {
      "from_node": "in_gallery",
      "from_output": "result",
      "to_node": "gallery_photo",
      "to_input": "condition"
    },
    {
      "from_node": "photo_rows",
      "from_output": "photo",
      "to_node": "hero_photo",
      "to_input": "value"
    },
    {
      "from_node": "is_hero",
      "from_output": "result",
      "to_node": "hero_photo",
      "to_input": "condition"
    },
    {
      "from_node": "caption",
      "from_output": "value",
      "to_node": "gallery",
      "to_input": "caption"
    },
    {
      "from_node": "gallery_photo",
      "from_output": "value",
      "to_node": "gallery",
      "to_input": "photo"
    },
    {
      "from_node": "hero_photo",
      "from_output": "value",
      "to_node": "hero",
      "to_input": "photo"
    },
    {
      "from_node": "photo_rows",
      "from_output": "asset_id",
      "to_node": "review",
      "to_input": "asset_id"
    },
    {
      "from_node": "photo_rows",
      "from_output": "role",
      "to_node": "review",
      "to_input": "role"
    },
    {
      "from_node": "ready",
      "from_output": "value",
      "to_node": "review",
      "to_input": "listing_ready"
    },
    {
      "from_node": "caption",
      "from_output": "value",
      "to_node": "review",
      "to_input": "caption"
    },
    {
      "from_node": "reason",
      "from_output": "value",
      "to_node": "review",
      "to_input": "reason"
    },
    {
      "from_node": "listing",
      "from_output": "json",
      "to_node": "sheet",
      "to_input": "json"
    },
    {
      "from_node": "sheet",
      "from_output": "value",
      "to_node": "brief",
      "to_input": "listing"
    },
    {
      "from_node": "brief",
      "from_output": "result",
      "to_node": "writer",
      "to_input": "prompt"
    },
    {
      "from_node": "writer",
      "from_output": "json",
      "to_node": "copy_contract",
      "to_input": "document"
    },
    {
      "from_node": "copy_contract",
      "from_output": "normalized_json",
      "to_node": "render_sheet",
      "to_input": "data_0"
    },
    {
      "from_node": "listing",
      "from_output": "json",
      "to_node": "render_sheet",
      "to_input": "data_1"
    },
    {
      "from_node": "hero",
      "from_output": "result",
      "to_node": "render_sheet",
      "to_input": "hero"
    },
    {
      "from_node": "gallery",
      "from_output": "result",
      "to_node": "render_sheet",
      "to_input": "gallery"
    },
    {
      "from_node": "copy_contract",
      "from_output": "normalized_json",
      "to_node": "render_social",
      "to_input": "data_0"
    },
    {
      "from_node": "listing",
      "from_output": "json",
      "to_node": "render_social",
      "to_input": "data_1"
    },
    {
      "from_node": "hero",
      "from_output": "result",
      "to_node": "render_social",
      "to_input": "hero"
    },
    {
      "from_node": "gallery",
      "from_output": "result",
      "to_node": "render_social",
      "to_input": "gallery"
    },
    {
      "from_node": "render_sheet",
      "from_output": "pdf",
      "to_node": "listing_pdf",
      "to_input": "pdf"
    },
    {
      "from_node": "render_sheet",
      "from_output": "pages_manifest",
      "to_node": "listing_pages",
      "to_input": "json"
    },
    {
      "from_node": "render_social",
      "from_output": "image",
      "to_node": "social_post",
      "to_input": "image"
    },
    {
      "from_node": "review",
      "from_output": "result",
      "to_node": "review_output",
      "to_input": "json"
    },
    {
      "from_node": "copy_contract",
      "from_output": "normalized_json",
      "to_node": "copy_output",
      "to_input": "json"
    },
    {
      "from_node": "render_sheet",
      "from_output": "layout_report",
      "to_node": "layout_report",
      "to_input": "json"
    }
  ],
  "layout": {
    "nodes": {
      "listing": {
        "x": 0,
        "y": 900
      },
      "sheet": {
        "x": 210,
        "y": 1100
      },
      "photos": {
        "x": 0,
        "y": 0
      },
      "assets": {
        "x": 420,
        "y": 0
      },
      "photo_rows": {
        "x": 840,
        "y": 0
      },
      "triage": {
        "x": 1260,
        "y": 0
      },
      "triage_contract": {
        "x": 1680,
        "y": 0
      },
      "ready": {
        "x": 2100,
        "y": -200
      },
      "caption": {
        "x": 2100,
        "y": 0
      },
      "reason": {
        "x": 2100,
        "y": 200
      },
      "is_hero": {
        "x": 1260,
        "y": 420
      },
      "not_hero": {
        "x": 1260,
        "y": 600
      },
      "in_gallery": {
        "x": 2520,
        "y": 300
      },
      "gallery_photo": {
        "x": 2940,
        "y": 300
      },
      "hero_photo": {
        "x": 2940,
        "y": 520
      },
      "gallery": {
        "x": 3360,
        "y": 200
      },
      "hero": {
        "x": 3360,
        "y": 520
      },
      "review": {
        "x": 2940,
        "y": -260
      },
      "brief": {
        "x": 420,
        "y": 900
      },
      "writer": {
        "x": 840,
        "y": 900
      },
      "copy_contract": {
        "x": 1260,
        "y": 900
      },
      "render_sheet": {
        "x": 3780,
        "y": 500
      },
      "render_social": {
        "x": 3780,
        "y": 860
      },
      "listing_pdf": {
        "x": 4220,
        "y": 420
      },
      "listing_pages": {
        "x": 4220,
        "y": 580
      },
      "social_post": {
        "x": 4220,
        "y": 860
      },
      "review_output": {
        "x": 3360,
        "y": -260
      },
      "copy_output": {
        "x": 1680,
        "y": 1100
      },
      "layout_report": {
        "x": 4220,
        "y": 1020
      }
    }
  }
}
