convert_floor_to_plan
ChatGPTStep 2 of 2 in the image → hzplan pipeline. Runs full inference + conversion on ONE floor (picked by floor_id from a prior split_floor_plan response) and either mints a new hzplan (plan_id omitted) or appends the floor as a new storey to an existing plan (plan_id given). Burns 1 unit of plan_from_image_monthly_* quota per call. WHEN TO CALL After split_floor_plan returned successfully, the user has seen the previews, and they've told you which floor(s) to import. Call this ONCE per chosen floor. For a 3-floor plan the user wants fully imported, that's 1 split_floor_plan call + 3 convert_floor_to_plan calls. DO NOT retry a successful call. DO NOT call this before split_floor_plan. WHAT TO PASS image: the SAME image used in split_floor_plan (ChatGPT auto-fills again from the user's upload). floor_id: the floor_id from the split_floor_plan response. storey_name: human label for this storey, e.g. 'ground', 'first', 'kitchen floor'. Default to the suggested_name from split_floor_plan if the user doesn't say otherwise. plan_id: OMIT on the first conversion (mints a brand-new plan). PASS the previous call's plan_id on all subsequent conversions so all floors land on ONE plan as separate storeys. plan_title: only relevant when plan_id is omitted — title for the new plan. WHAT YOU GET BACK ON SUCCESS { ok: true, plan_id, storey_id, quota, summary } - plan_id: pass this to the NEXT convert_floor_to_plan call if there are more floors to import. - storey_id: the storey id assigned inside the plan ('s1', 's2', ...). - quota: current usage info. - summary: diagnostic counts (walls, openings, mm_per_px) — usually fine to ignore. NEXT STEPS - After all chosen floors are converted, tell the user the plan is ready. Render it with render_storey_widget(plan_id, storey_id) to show the result. - The user can then refine the plan with upsert_entity / delete_entity / set_object_feature_side etc. — those operate on walls / openings / objects inside the storey. ERROR CODES feature_not_enabled: account lacks plan_from_image entitlement. Tell the user; don't retry. quota_exceeded: monthly limit reached. Tell the user. floor_id_not_found: floor_id doesn't match the image you passed. Did the image change? Call split_floor_plan again with the current image. hzplan_validation_failed: the converted plan didn't pass our solver. Report to the user. service_*: transient pipeline error. Safe to retry ONCE. image_url_fetch_failed: user's image URL expired. Ask them to re-upload, then start over with split_floor_plan.
convert_floor_to_plan
ChatGPTStep 2 of 2 in the image → hzplan pipeline. Runs full inference + conversion on ONE floor (picked by floor_id from a prior split_floor_plan response) and either mints a new hzplan (plan_id omitted) or appends the floor as a new storey to an existing plan (plan_id given). Burns 1 unit of plan_from_image_monthly_* quota per call. WHEN TO CALL After split_floor_plan returned successfully, the user has seen the previews, and they've told you which floor(s) to import. Call this ONCE per chosen floor. For a 3-floor plan the user wants fully imported, that's 1 split_floor_plan call + 3 convert_floor_to_plan calls. DO NOT retry a successful call. DO NOT call this before split_floor_plan. WHAT TO PASS image: the SAME image used in split_floor_plan (ChatGPT auto-fills again from the user's upload). floor_id: the floor_id from the split_floor_plan response. storey_name: human label for this storey, e.g. 'ground', 'first', 'kitchen floor'. Default to the suggested_name from split_floor_plan if the user doesn't say otherwise. plan_id: OMIT on the first conversion (mints a brand-new plan). PASS the previous call's plan_id on all subsequent conversions so all floors land on ONE plan as separate storeys. plan_title: only relevant when plan_id is omitted — title for the new plan. WHAT YOU GET BACK ON SUCCESS { ok: true, plan_id, storey_id, quota, summary } - plan_id: pass this to the NEXT convert_floor_to_plan call if there are more floors to import. - storey_id: the storey id assigned inside the plan ('s1', 's2', ...). - quota: current usage info. - summary: diagnostic counts (walls, openings, mm_per_px) — usually fine to ignore. NEXT STEPS - After all chosen floors are converted, tell the user the plan is ready. Render it with render_storey_widget(plan_id, storey_id) to show the result. - The user can then refine the plan with upsert_entity / delete_entity / set_object_feature_side etc. — those operate on walls / openings / objects inside the storey. ERROR CODES feature_not_enabled: account lacks plan_from_image entitlement. Tell the user; don't retry. quota_exceeded: monthly limit reached. Tell the user. floor_id_not_found: floor_id doesn't match the image you passed. Did the image change? Call split_floor_plan again with the current image. hzplan_validation_failed: the converted plan didn't pass our solver. Report to the user. service_*: transient pipeline error. Safe to retry ONCE. image_url_fetch_failed: user's image URL expired. Ask them to re-upload, then start over with split_floor_plan.
create_plan
ChatGPTUse this when the user wants to start a brand-new house plan from scratch. Creates an empty private plan owned by the current user and returns its plan_id; follow up with create_storey, then add walls/openings/objects via upsert_entity. To build a plan from a floor-plan image instead, use split_floor_plan + convert_floor_to_plan.
create_plan
ChatGPTUse this when the user wants to start a brand-new house plan from scratch. Creates an empty private plan owned by the current user and returns its plan_id; follow up with create_storey, then add walls/openings/objects via upsert_entity. To build a plan from a floor-plan image instead, use split_floor_plan + convert_floor_to_plan.
create_storey
ChatGPTCreate a new empty storey in an existing plan and return its current storey state. After create_storey, immediately call upsert_entity with type=storey and size_mm(width_mm, depth_mm) so the geometry engine has a fixed storey rectangle. Then place outer walls relative to storey anchors like s1.left, s1.right, s1.top, and s1.bottom.
create_storey
ChatGPTCreate a new empty storey in an existing plan and return its current storey state. After create_storey, immediately call upsert_entity with type=storey and size_mm(width_mm, depth_mm) so the geometry engine has a fixed storey rectangle. Then place outer walls relative to storey anchors like s1.left, s1.right, s1.top, and s1.bottom.
delete_entity
ChatGPTDelete one wall, opening, or object from the current plan head by its entity id — this is the single tool for removing in-storey entities. To remove an entire floor and everything on it, use delete_storey instead. Deletion fails if another entity still depends on this one (for example an opening still hosting on a wall, or an offset(...) anchored to it); the response then lists the blocking entity ids so you can re-host or re-anchor them, then retry.
delete_entity
ChatGPTDelete one wall, opening, or object from the current plan head by its entity id — this is the single tool for removing in-storey entities. To remove an entire floor and everything on it, use delete_storey instead. Deletion fails if another entity still depends on this one (for example an opening still hosting on a wall, or an offset(...) anchored to it); the response then lists the blocking entity ids so you can re-host or re-anchor them, then retry.
delete_illustration
ChatGPTArchive one private illustration from the current user's library.
delete_illustration
ChatGPTArchive one private illustration from the current user's library.
delete_plan
ChatGPTUse this when the user wants to permanently delete an entire plan. Removes the plan and all of its storeys, along with any uploaded floor-plan images and public snapshots tied to it. This is destructive and cannot be undone. Prefer this over deleting storeys one by one when the whole plan should go; confirm with the user first because there is no recovery.
delete_plan
ChatGPTUse this when the user wants to permanently delete an entire plan. Removes the plan and all of its storeys, along with any uploaded floor-plan images and public snapshots tied to it. This is destructive and cannot be undone. Prefer this over deleting storeys one by one when the whole plan should go; confirm with the user first because there is no recovery.
delete_storey
ChatGPTUse this when the user wants to remove an entire floor. Deletes one storey and everything on it — its rooms, spaces, objects, walls, and openings — from the current plan head. This is destructive and is not reversible through the API. It fails only if an entity on another storey still references something on this one; the response then lists the blocking entity ids so they can be re-anchored or deleted first, then retry.
delete_storey
ChatGPTUse this when the user wants to remove an entire floor. Deletes one storey and everything on it — its rooms, spaces, objects, walls, and openings — from the current plan head. This is destructive and is not reversible through the API. It fails only if an entity on another storey still references something on this one; the response then lists the blocking entity ids so they can be re-anchored or deleted first, then retry.
get_authoring_guide
ChatGPTCall this first when authoring or editing plans if you need the supported predicates, recommended sequencing, or the MCP usage rule that resources are widget shells only while tools handle all reads and writes.
get_authoring_guide
ChatGPTCall this first when authoring or editing plans if you need the supported predicates, recommended sequencing, or the MCP usage rule that resources are widget shells only while tools handle all reads and writes.
get_illustration
ChatGPTGet one illustration, including its stored SVG markup. Use this tool for illustration data; MCP resources are widget HTML only.
get_illustration
ChatGPTGet one illustration, including its stored SVG markup. Use this tool for illustration data; MCP resources are widget HTML only.
get_object
ChatGPTGet one object with its current constraints and solved placement metadata. Use set_object_illustration for illustration-only changes; use this before upsert_entity when you need to preserve existing assertions during broader object edits.
get_object
ChatGPTGet one object with its current constraints and solved placement metadata. Use set_object_illustration for illustration-only changes; use this before upsert_entity when you need to preserve existing assertions during broader object edits.
get_plan
ChatGPTGet the stored top-level summary for one plan. This returns summary data, not the full plan document.
get_plan
ChatGPTGet the stored top-level summary for one plan. This returns summary data, not the full plan document.
get_plan_topology
ChatGPTGet the plan's spatial room topology. Returns one node per derived free-space region (the rooms shown in the UI) with its storey, an interior center point, area, and bound name/kind (null until named); and edges for the openings/doorways connecting rooms. To name a room, pass that node's center as the (x_mm, y_mm) to name_space_at_point.
get_plan_topology
ChatGPTGet the plan's spatial room topology. Returns one node per derived free-space region (the rooms shown in the UI) with its storey, an interior center point, area, and bound name/kind (null until named); and edges for the openings/doorways connecting rooms. To name a room, pass that node's center as the (x_mm, y_mm) to name_space_at_point.
get_storey_plan
ChatGPTGet bounded structured detail for one storey. Use storey_id from get_plan when possible.
get_storey_plan
ChatGPTGet bounded structured detail for one storey. Use storey_id from get_plan when possible.
list_illustrations
ChatGPTList accessible system illustrations and the current user's private illustrations. Use this tool for illustration discovery; do not use MCP resources for library data.
list_illustrations
ChatGPTList accessible system illustrations and the current user's private illustrations. Use this tool for illustration discovery; do not use MCP resources for library data.
list_plans
ChatGPTUse this when you need to find a plan_id or show the user what plans they have. Lists the current user's private plans with their ids and titles. Read-only.
list_plans
ChatGPTUse this when you need to find a plan_id or show the user what plans they have. Lists the current user's private plans with their ids and titles. Read-only.
list_room_objects
ChatGPTList current objects in one room.
list_room_objects
ChatGPTList current objects in one room.
name_space_at_point
ChatGPTCreate or update a semantic space name by dropping an interior pin on a solved storey. Use this for named floor areas such as bathroom, bedroom, hall, kitchen, or garage after walls and openings already define the enclosure. This does not author new geometry. Instead it stores a semantic pin and binds the name onto the derived free-space region that contains that point. The point must fall inside exactly one derived free-space region. Provide space_id to rename or move an existing named space; omit it to create a new one.
name_space_at_point
ChatGPTCreate or update a semantic space name by dropping an interior pin on a solved storey. Use this for named floor areas such as bathroom, bedroom, hall, kitchen, or garage after walls and openings already define the enclosure. This does not author new geometry. Instead it stores a semantic pin and binds the name onto the derived free-space region that contains that point. The point must fall inside exactly one derived free-space region. Provide space_id to rename or move an existing named space; omit it to create a new one.
render_named_room_widget
ChatGPTRender one named room from a storey in the ChatGPT widget.
render_named_room_widget
ChatGPTRender one named room from a storey in the ChatGPT widget.
render_plan_topology_widget
ChatGPTRender the whole-plan topology graph in the ChatGPT widget.
render_plan_topology_widget
ChatGPTRender the whole-plan topology graph in the ChatGPT widget.
render_storey_widget
ChatGPTRender one storey in the ChatGPT widget.
render_storey_widget
ChatGPTRender one storey in the ChatGPT widget.
render_svg_preview
ChatGPTRender one illustration SVG preview in the ChatGPT widget. Preview only; this does not save anything. Use upsert_illustration to persist SVG markup.
render_svg_preview
ChatGPTRender one illustration SVG preview in the ChatGPT widget. Preview only; this does not save anything. Use upsert_illustration to persist SVG markup.
request_image_upload
ChatGPTGet a one-time upload link for a floor-plan image when you can't attach it directly or the file is too large to pass inline. Works for ANY client (Claude, ChatGPT, etc.). Quota-free. FLOW 1. Call this (no args) → { upload_id, upload_url }. 2. Show the user the upload_url and ask them to open it and drop their floor-plan image (one tap). 3. Then call split_floor_plan with image_ref: { upload_id }. If they haven't finished you'll get code 'upload_pending' — wait a moment and retry. 4. Reuse the SAME upload_id for convert_floor_to_plan — one upload covers split + convert. ChatGPT: prefer attaching the file (pass image). But if that keeps failing or the file is too large, FALL BACK to this upload link — the user just opens it and drops the image, and you only ever pass back image_ref:{ upload_id } (no base64). LAST-RESORT FALLBACK: if the user can't or won't use the link, tell them they can create the plan directly in the web app: https://hzplan.uk/plans/new/from-image — never leave them with no way forward.
request_image_upload
ChatGPTGet a one-time upload link for a floor-plan image when you can't attach it directly or the file is too large to pass inline. Works for ANY client (Claude, ChatGPT, etc.). Quota-free. FLOW 1. Call this (no args) → { upload_id, upload_url }. 2. Show the user the upload_url and ask them to open it and drop their floor-plan image (one tap). 3. Then call split_floor_plan with image_ref: { upload_id }. If they haven't finished you'll get code 'upload_pending' — wait a moment and retry. 4. Reuse the SAME upload_id for convert_floor_to_plan — one upload covers split + convert. ChatGPT: prefer attaching the file (pass image). But if that keeps failing or the file is too large, FALL BACK to this upload link — the user just opens it and drops the image, and you only ever pass back image_ref:{ upload_id } (no base64). LAST-RESORT FALLBACK: if the user can't or won't use the link, tell them they can create the plan directly in the web app: https://hzplan.uk/plans/new/from-image — never leave them with no way forward.
rescale_storey
ChatGPTMultiplies every length / size / offset on storey_id by factor. Wall thicknesses, image bboxes, angles, and other absolute / unitless values are NOT touched. Runs the geometry engine after the rescale and refuses to commit if the storey becomes infeasible. WHEN TO CALL After convert_floor_to_plan if the AI inspects the image and decides the plan's scale is wrong (door-width heuristic is typically ±20% off). Also usable on hand-authored plans where the user wants to scale a storey up/down. Call ONCE per scale change — if the call succeeds, the plan is updated; if it fails (solver_infeasible / solver_ambiguous), the plan is unchanged. WHAT TO PASS plan_id: the plan owning the storey. storey_id: the storey to rescale (e.g. 's1', 's2'). factor: number in [0.1, 10]. Compute as target_real_world_length / current_plan_length. Example: AI sees a wall on the image and thinks it's 4000 mm in real life; the plan currently reports that wall as 3500 mm; factor = 4000 / 3500 ≈ 1.143. WHAT YOU GET BACK ON SUCCESS { ok: true, plan_id, storey_id, content_version, scaled_count: { length_mm, size_mm, offset }, storey_size_mm: [w, h] } - scaled_count reports how many of each predicate were touched (sanity check the factor was applied to walls + openings). - storey_size_mm is the new outer dimensions in mm. ERROR CODES plan_not_found / storey_not_found: bad ids. invalid_factor: factor out of [0.1, 10]. solver_infeasible: rescaled storey is overconstrained (e.g. a fixed-size object no longer fits). Plan was NOT modified. Try a different factor or fix the object first. solver_ambiguous: rescaled storey is underconstrained. Add missing offsets and retry, or revert. solver_error: the geometry engine itself failed; surface to user. Do NOT retry on success. Compose multiple rescales by calling once per storey.
rescale_storey
ChatGPTMultiplies every length / size / offset on storey_id by factor. Wall thicknesses, image bboxes, angles, and other absolute / unitless values are NOT touched. Runs the geometry engine after the rescale and refuses to commit if the storey becomes infeasible. WHEN TO CALL After convert_floor_to_plan if the AI inspects the image and decides the plan's scale is wrong (door-width heuristic is typically ±20% off). Also usable on hand-authored plans where the user wants to scale a storey up/down. Call ONCE per scale change — if the call succeeds, the plan is updated; if it fails (solver_infeasible / solver_ambiguous), the plan is unchanged. WHAT TO PASS plan_id: the plan owning the storey. storey_id: the storey to rescale (e.g. 's1', 's2'). factor: number in [0.1, 10]. Compute as target_real_world_length / current_plan_length. Example: AI sees a wall on the image and thinks it's 4000 mm in real life; the plan currently reports that wall as 3500 mm; factor = 4000 / 3500 ≈ 1.143. WHAT YOU GET BACK ON SUCCESS { ok: true, plan_id, storey_id, content_version, scaled_count: { length_mm, size_mm, offset }, storey_size_mm: [w, h] } - scaled_count reports how many of each predicate were touched (sanity check the factor was applied to walls + openings). - storey_size_mm is the new outer dimensions in mm. ERROR CODES plan_not_found / storey_not_found: bad ids. invalid_factor: factor out of [0.1, 10]. solver_infeasible: rescaled storey is overconstrained (e.g. a fixed-size object no longer fits). Plan was NOT modified. Try a different factor or fix the object first. solver_ambiguous: rescaled storey is underconstrained. Add missing offsets and retry, or revert. solver_error: the geometry engine itself failed; surface to user. Do NOT retry on success. Compose multiple rescales by calling once per storey.
send_feedback
ChatGPTSend the user's feedback to the HZplan developers. Use when a tool failed, behaved unexpectedly, was confusing, or the user has a complaint, suggestion, or praise. Quota-free. CONSENT — REQUIRED: first draft the message, show the user the EXACT text you will send, and get their explicit agreement. Never send feedback the user hasn't seen and approved, and never include anything they didn't agree to share.
send_feedback
ChatGPTSend the user's feedback to the HZplan developers. Use when a tool failed, behaved unexpectedly, was confusing, or the user has a complaint, suggestion, or praise. Quota-free. CONSENT — REQUIRED: first draft the message, show the user the EXACT text you will send, and get their explicit agreement. Never send feedback the user hasn't seen and approved, and never include anything they didn't agree to share.
set_object_feature_side
ChatGPTUpdate only the directional feature_side on an existing object (top/bottom/left/right, or null to clear) while preserving every other assertion — kind, size_mm, offsets, illustration_id, model_3d, etc. ALWAYS prefer this over upsert_entity when changing only the facing direction; upsert_entity replaces the whole assertion list and will silently drop attachments like model_3d.
set_object_feature_side
ChatGPTUpdate only the directional feature_side on an existing object (top/bottom/left/right, or null to clear) while preserving every other assertion — kind, size_mm, offsets, illustration_id, model_3d, etc. ALWAYS prefer this over upsert_entity when changing only the facing direction; upsert_entity replaces the whole assertion list and will silently drop attachments like model_3d.
set_object_height
ChatGPTUpdate only the height_mm on an existing object (positive integer of millimetres, or null to clear and fall back to the kind default) while preserving every other assertion — kind, size_mm, offsets, illustration_id, model_3d, feature_side, etc. ALWAYS prefer this over upsert_entity when changing only the rendered height; upsert_entity replaces the whole assertion list and will silently drop attachments like model_3d. Use this when a generated 3D model looks too tall or too short in the 3D walk — the 3D generation parameter only changes the GLB's baked proportions; the renderer scales to whatever this assertion (or the kind default) says.
set_object_height
ChatGPTUpdate only the height_mm on an existing object (positive integer of millimetres, or null to clear and fall back to the kind default) while preserving every other assertion — kind, size_mm, offsets, illustration_id, model_3d, feature_side, etc. ALWAYS prefer this over upsert_entity when changing only the rendered height; upsert_entity replaces the whole assertion list and will silently drop attachments like model_3d. Use this when a generated 3D model looks too tall or too short in the 3D walk — the 3D generation parameter only changes the GLB's baked proportions; the renderer scales to whatever this assertion (or the kind default) says.
set_object_illustration
ChatGPTAttach, replace, or clear an illustration on an existing object while preserving the object's other assertions automatically. Prefer this over upsert_entity when you only want to change illustration_id.
set_object_illustration
ChatGPTAttach, replace, or clear an illustration on an existing object while preserving the object's other assertions automatically. Prefer this over upsert_entity when you only want to change illustration_id.
split_floor_plan
ChatGPTStep 1 of 2 in the image → hzplan pipeline. Detects how many distinct floor plans are present on a single uploaded image (residential plans frequently show ground + first + sometimes basement on one page) and returns one bounding box + preview crop per detected floor. Quota-free. WHEN TO CALL Call this ONCE per image the user wants to convert into an hzplan. If it returns ok:true, USE the floors[] result — show previews to the user, ask which to import, then call convert_floor_to_plan for each chosen floor. DO NOT retry a successful call. DO NOT issue duplicate calls back-to-back. WHAT TO PASS image: the user's uploaded image (ChatGPT fills download_url + file_id automatically; the model never has to base64-encode anything). Other clients can use raw_url (data: URI or http URL) or raw_base64 instead — non-ChatGPT only. FALLBACK if the image is hard to pass (ChatGPT base64 failing, file too large): call request_image_upload, have the user drop the image on the link, then pass image_ref:{ upload_id }. As a last resort, point the user to the web app: https://hzplan.uk/plans/new/from-image WHAT YOU GET BACK ON SUCCESS { ok: true, floors: [ { floor_id, suggested_name, bbox_px, source } ] } PLUS one preview image per floor attached as image content (in the same order as floors[]). - floor_id: opaque string. PASS THIS to convert_floor_to_plan to convert that specific floor. - suggested_name: 'ground' / 'first' / 'second' / 'third' / 'fourth' or 'floor N' for >5 floors. Use as the default storey_name unless the user overrides. - bbox_px: [x1, y1, x2, y2] location of the floor in the source image's pixel space. - the attached preview images let the user confirm which floor is which; floors[][i] corresponds to attached image i. NEXT STEPS AFTER A SUCCESSFUL CALL 1. Show the user the attached floor preview images (one per entry in floors[], same order). 2. Ask which floors to import and confirm storey names. 3. For each chosen floor, call convert_floor_to_plan with: same image, the floor_id, the storey_name, and plan_id (omit on the first conversion to mint a new plan; pass on subsequent ones to keep adding storeys to the same plan). ERROR CODES service_misconfigured: pipeline isn't deployed. Don't retry; tell the user. splitter_no_floors: image probably isn't a residential floor plan. Ask the user for a clearer image. image_url_fetch_failed: the user's image URL expired (signed URLs are short-lived). Ask the user to re-upload. unsupported_image_format: image isn't PNG/JPEG/WEBP. service_http_error / service_timeout: transient pipeline error — safe to retry ONCE.
split_floor_plan
ChatGPTStep 1 of 2 in the image → hzplan pipeline. Detects how many distinct floor plans are present on a single uploaded image (residential plans frequently show ground + first + sometimes basement on one page) and returns one bounding box + preview crop per detected floor. Quota-free. WHEN TO CALL Call this ONCE per image the user wants to convert into an hzplan. If it returns ok:true, USE the floors[] result — show previews to the user, ask which to import, then call convert_floor_to_plan for each chosen floor. DO NOT retry a successful call. DO NOT issue duplicate calls back-to-back. WHAT TO PASS image: the user's uploaded image (ChatGPT fills download_url + file_id automatically; the model never has to base64-encode anything). Other clients can use raw_url (data: URI or http URL) or raw_base64 instead — non-ChatGPT only. FALLBACK if the image is hard to pass (ChatGPT base64 failing, file too large): call request_image_upload, have the user drop the image on the link, then pass image_ref:{ upload_id }. As a last resort, point the user to the web app: https://hzplan.uk/plans/new/from-image WHAT YOU GET BACK ON SUCCESS { ok: true, floors: [ { floor_id, suggested_name, bbox_px, source } ] } PLUS one preview image per floor attached as image content (in the same order as floors[]). - floor_id: opaque string. PASS THIS to convert_floor_to_plan to convert that specific floor. - suggested_name: 'ground' / 'first' / 'second' / 'third' / 'fourth' or 'floor N' for >5 floors. Use as the default storey_name unless the user overrides. - bbox_px: [x1, y1, x2, y2] location of the floor in the source image's pixel space. - the attached preview images let the user confirm which floor is which; floors[][i] corresponds to attached image i. NEXT STEPS AFTER A SUCCESSFUL CALL 1. Show the user the attached floor preview images (one per entry in floors[], same order). 2. Ask which floors to import and confirm storey names. 3. For each chosen floor, call convert_floor_to_plan with: same image, the floor_id, the storey_name, and plan_id (omit on the first conversion to mint a new plan; pass on subsequent ones to keep adding storeys to the same plan). ERROR CODES service_misconfigured: pipeline isn't deployed. Don't retry; tell the user. splitter_no_floors: image probably isn't a residential floor plan. Ask the user for a clearer image. image_url_fetch_failed: the user's image URL expired (signed URLs are short-lived). Ask the user to re-upload. unsupported_image_format: image isn't PNG/JPEG/WEBP. service_http_error / service_timeout: transient pipeline error — safe to retry ONCE.
upsert_entities
ChatGPTApply many entity creations/updates to one plan in a single call. Intended for blast-then-repair authoring when importing from a floor plan image: emit the whole plan (storey size + all walls + openings + objects) as one batch, read the per-item and global solver report, then resubmit a follow-up batch that only contains the corrections. Items are applied in order against the same in-memory document; if any item fails to apply OR the solver reports infeasible/ambiguous/overlapping storeys, nothing is persisted and the full per-item + per-storey report is returned so you can fix and retry. Prefer create_storey first (so s1 exists) and pass entity_id on items you want to update instead of create.
upsert_entities
ChatGPTApply many entity creations/updates to one plan in a single call. Intended for blast-then-repair authoring when importing from a floor plan image: emit the whole plan (storey size + all walls + openings + objects) as one batch, read the per-item and global solver report, then resubmit a follow-up batch that only contains the corrections. Items are applied in order against the same in-memory document; if any item fails to apply OR the solver reports infeasible/ambiguous/overlapping storeys, nothing is persisted and the full per-item + per-storey report is returned so you can fix and retry. Prefer create_storey first (so s1 exists) and pass entity_id on items you want to update instead of create.
upsert_entity
ChatGPTCreate or update one authored entity in the current plan head. Supported phase-1 entity types are storey, wall, opening, and object. Assertions are entity-local: do not repeat entity_id in args. Use tools for all data reads and writes; MCP resources are widget HTML only and are never required for CRUD. When changing only an existing object's illustration link, prefer set_object_illustration instead of rebuilding the full assertion list manually. Storey workflow: after create_storey, upsert the storey with size_mm(width_mm, depth_mm) before adding structural entities. Only walls and openings create or divide rooms. Use name_space_at_point(...) to name enclosed floor areas such as bathroom, bedroom, hall, kitchen, or garage after walls and openings define them. Do not create those spaces as objects. Objects are furniture, fixtures, and equipment such as cupboards, sofas, beds, tables, toilets, sinks, and tubs; they may align to walls or surrounding spaces but they do not replace partition walls. Some furniture is directional: feature_side(top|bottom|left|right) is the direction the object FACES — a sofa, armchair, or toilet whose feature_side is left faces (opens toward) the left. To face a window on the left wall, set feature_side=left. Exceptions: a bed's feature_side is its headboard side, and a sink/bath's is its plumbing (wall/tap) side. When updating an existing entity, fetch it first and resend every assertion you want to keep. Storey predicates: ceiling_height_mm(number_mm), eye_height_mm(number_mm), size_mm(width_mm, depth_mm). Wall predicates: on_storey(storey_id), kind(value), axis(horizontal|vertical), angle_deg(number), thickness_mm(number_mm), length_mm(number_mm), height_mm(number_mm), offset(anchor, reference_anchor, delta_mm). Offsets constrain solved rectangle edges, not wall centerlines, so think in left, right, top, and bottom edges. When you want a gap between two entities, offset the closest pair of edges that represents that gap. Outer walls should usually be positioned relative to storey anchors, for example offset(left, s1.left, 0) or offset(bottom, s1.bottom, 0). For interior wall corners, place the first wall from storey anchors, then place the closing wall by sharing a corner with the existing wall instead of pinning both walls absolutely again. Prefer left, right, top, and bottom for wall placement; use start or end only when you explicitly need along-wall anchors. A wall is either axis-aligned (use axis(horizontal|vertical)) or angled (use angle_deg(number) where 0 is east and 90 is north). Never combine axis and angle_deg on one wall. length_mm is always measured along the wall's own direction, and offsets still anchor the wall's axis-aligned bounding box. Opening predicates: on_storey(storey_id), kind(value), length_mm(number_mm), sill_height_mm(number_mm), head_height_mm(number_mm), host(target_id), hinge(start|end), opens_to(left|right), offset(anchor, reference_anchor, delta_mm). Use arch as a friendly alias for archway. Examples: a west outer wall uses kind(outer)+axis(vertical)+length_mm(...)+offset(left, s1.left, 0)+offset(top, s1.top, 0); a south outer wall uses kind(outer)+axis(horizontal)+length_mm(...)+offset(left, s1.left, 0)+offset(bottom, s1.bottom, 0); a horizontal partition that closes against an existing vertical wall can use kind(inner)+axis(horizontal)+length_mm(...)+offset(right, w_existing.left, 0)+offset(top, w_existing.top, 0); a 45° diagonal wall of 1 m bounding box uses kind(outer)+angle_deg(45)+length_mm(1414)+thickness_mm(200)+offset(left, w_west.left, 0)+offset(top, w_north.bottom, 0); a single hinged door uses kind(door)+hinge+opens_to; a garden door uses kind(garden_door)+opens_to; a built-in wardrobe / cupboard door uses kind(cupboard_door)+opens_to (no hinge); a garage door uses kind(garage_door) (panel style) or kind(roller_door) (corrugated roll-up) and must not include hinge or opens_to; an archway uses kind(archway) and must not include hinge or opens_to. Object predicates: on_storey(storey_id), kind(value), category(value), movable(boolean), height_mm(numb…
upsert_entity
ChatGPTCreate or update one authored entity in the current plan head. Supported phase-1 entity types are storey, wall, opening, and object. Assertions are entity-local: do not repeat entity_id in args. Use tools for all data reads and writes; MCP resources are widget HTML only and are never required for CRUD. When changing only an existing object's illustration link, prefer set_object_illustration instead of rebuilding the full assertion list manually. Storey workflow: after create_storey, upsert the storey with size_mm(width_mm, depth_mm) before adding structural entities. Only walls and openings create or divide rooms. Use name_space_at_point(...) to name enclosed floor areas such as bathroom, bedroom, hall, kitchen, or garage after walls and openings define them. Do not create those spaces as objects. Objects are furniture, fixtures, and equipment such as cupboards, sofas, beds, tables, toilets, sinks, and tubs; they may align to walls or surrounding spaces but they do not replace partition walls. Some furniture is directional: feature_side(top|bottom|left|right) is the direction the object FACES — a sofa, armchair, or toilet whose feature_side is left faces (opens toward) the left. To face a window on the left wall, set feature_side=left. Exceptions: a bed's feature_side is its headboard side, and a sink/bath's is its plumbing (wall/tap) side. When updating an existing entity, fetch it first and resend every assertion you want to keep. Storey predicates: ceiling_height_mm(number_mm), eye_height_mm(number_mm), size_mm(width_mm, depth_mm). Wall predicates: on_storey(storey_id), kind(value), axis(horizontal|vertical), angle_deg(number), thickness_mm(number_mm), length_mm(number_mm), height_mm(number_mm), offset(anchor, reference_anchor, delta_mm). Offsets constrain solved rectangle edges, not wall centerlines, so think in left, right, top, and bottom edges. When you want a gap between two entities, offset the closest pair of edges that represents that gap. Outer walls should usually be positioned relative to storey anchors, for example offset(left, s1.left, 0) or offset(bottom, s1.bottom, 0). For interior wall corners, place the first wall from storey anchors, then place the closing wall by sharing a corner with the existing wall instead of pinning both walls absolutely again. Prefer left, right, top, and bottom for wall placement; use start or end only when you explicitly need along-wall anchors. A wall is either axis-aligned (use axis(horizontal|vertical)) or angled (use angle_deg(number) where 0 is east and 90 is north). Never combine axis and angle_deg on one wall. length_mm is always measured along the wall's own direction, and offsets still anchor the wall's axis-aligned bounding box. Opening predicates: on_storey(storey_id), kind(value), length_mm(number_mm), sill_height_mm(number_mm), head_height_mm(number_mm), host(target_id), hinge(start|end), opens_to(left|right), offset(anchor, reference_anchor, delta_mm). Use arch as a friendly alias for archway. Examples: a west outer wall uses kind(outer)+axis(vertical)+length_mm(...)+offset(left, s1.left, 0)+offset(top, s1.top, 0); a south outer wall uses kind(outer)+axis(horizontal)+length_mm(...)+offset(left, s1.left, 0)+offset(bottom, s1.bottom, 0); a horizontal partition that closes against an existing vertical wall can use kind(inner)+axis(horizontal)+length_mm(...)+offset(right, w_existing.left, 0)+offset(top, w_existing.top, 0); a 45° diagonal wall of 1 m bounding box uses kind(outer)+angle_deg(45)+length_mm(1414)+thickness_mm(200)+offset(left, w_west.left, 0)+offset(top, w_north.bottom, 0); a single hinged door uses kind(door)+hinge+opens_to; a garden door uses kind(garden_door)+opens_to; a built-in wardrobe / cupboard door uses kind(cupboard_door)+opens_to (no hinge); a garage door uses kind(garage_door) (panel style) or kind(roller_door) (corrugated roll-up) and must not include hinge or opens_to; an archway uses kind(archway) and must not include hinge or opens_to. Object predicates: on_storey(storey_id), kind(value), category(value), movable(boolean), height_mm(numb…
upsert_illustration
ChatGPTCreate or update a private SVG illustration in the current user's library. Omit illustration_id to create a new illustration (name and svg_markup are then required); pass illustration_id to update an existing one (only the fields you include change, the rest are preserved). render_svg_preview only previews — this tool persists.
upsert_illustration
ChatGPTCreate or update a private SVG illustration in the current user's library. Omit illustration_id to create a new illustration (name and svg_markup are then required); pass illustration_id to update an existing one (only the fields you include change, the rest are preserved). render_svg_preview only previews — this tool persists.