Setting Up and Maintaining a Maps Component in the Thinkwise Software Factory
Reference for the maps component's full lifecycle: source column → map → map_base_layer/
map_overlay → map_data_mapping → (optionally) drawable-shape task wiring → (optionally) an
external-API pipeline that populates the map's coordinates. Every entity/field name below was
confirmed live against a real model (sf/manage_maps domain, 32 entity sets) and against a working
reference application (MAPS_AND_ROUTES) that implements address geocoding and route calculation —
not guessed from documentation.
Apply this whenever an MCP connector with Software Factory access (sf_mcp, indicium) is used to
create, inspect, or troubleshoot a maps component — follow the connector's standard discovery→act
flow; never guess entity/task/property names.
map/map_base_layer/map_overlay/map_data_mapping typically live in a manage_maps-style
domain; the map's source tab/col/dom/ref live in a manage_datamodel-style domain;
control_proc/control_proc_template live in a manage_control_procedures-style domain;
web_connection/web_connection_endpoint live in a manage_webconnections-style domain;
process_flow/process_action live in a manage_process_flows-style domain — try these directly
first, and only escalate to search_capabilities/get_available_domains on an
entity_set_not_found/domain_not_found-style rejection rather than re-discovering a domain that
already resolved earlier this session.
For general data-modeling rules (naming, domain reuse, reference direction) see
thinkwise_datamodeling_guidelines. For control-procedure mechanics (code groups, static vs. SQL
assignment, branch_rdbms_type, and — critically — the two-step "generate code group" then
"generate object code" sequence) see thinkwise_software_factory_create_control_procedures; every
control procedure referenced below is created and generated exactly that way. This skill only covers
what's specific to maps.
What a maps component is
A maps component renders one table's rows as geographic content — pins, lines, circles, rectangles or
polygons — on a tile-based map (Leaflet), modeled once per table (tab_id) and reused by every screen
that shows it. It is built from four cooperating entities, all keyed by (model_id, branch_id, tab_id, …):
| Entity | Cardinality | Purpose |
|---|---|---|
map |
one per table | Center/zoom, and which columns feed coordinates, styling, popup, label, and drawn-shape capture |
map_base_layer |
many per table | The tile provider(s) underneath everything |
map_overlay |
many per table | Optional extra tile layer(s) on top, independently togglable |
map_data_mapping |
many per table, keyed by (dom_id, map_data_mapping_id) |
How each row's geometry is styled, one row per domain element |
A map both displays data (rows → styled shapes, driven by map_data_mapping) and can capture
it (a user draws a shape → a task writes it back, driven by the *_location_task_parmtr_id fields on
map — see "Drawable shapes" below). These are two independent, optional wiring paths on the same
map record; a table can use either, both, or neither (e.g. a purely server-calculated map, like the
route example below, wires neither).
Every one of these four entities also has a tab_variant_* counterpart (tab_variant_map,
tab_variant_map_base_layer, tab_variant_map_overlay, tab_variant_map_data_mapping) that
overrides the table-level defaults for one specific tab variant — see "Tab-variant overrides" below.
The CoordSets JSON contract
The map does not read raw latitude/longitude columns directly. map.latitude_longitude_col_id must
point at a column producing a small JSON envelope, referred to as CoordSets, structured as a list
of coordinate rings — one shape for a point, a line, or a multi-ring polygon:
{ "CoordSets": [ [ { "Lon": "5.979388", "Lat": "52.208479" } ] ] }
- A marker or circle has exactly one ring with exactly one point.
- A line has one ring with multiple points, in path order.
- A polygon/rectangle has one or more rings (outer boundary, optional inner holes).
- A circle additionally carries a sibling
"Radius"key (meters) alongside"CoordSets"— see "Drawable shapes" below for the full shape.
This column is almost always a calculated field (col.calculated_field_type = expression,
col.calculated_field_query holding the SQL) so it always reflects the row's live coordinates without
a stored duplicate. Verified example, straight from a production table (SQL Server dialect):
-- col: address.map_entity_coordinates, domain: varchar_max
concat('{ "CoordSets": [ [ { "Lon": "', t1.longitude, '", "Lat": "', t1.latitude, '" } ] ] }')
Check branch_rdbms_type before writing this kind of SQL. See the
thinkwise_software_factory_create_control_procedures skill for the full dialect-check walkthrough;
the essential rule is: never assume SQL Server syntax works on PostgreSQL, or vice versa — verify
first. Specifically for CoordSets: concat(...) works as shown on SQL Server; on PostgreSQL the
equivalent is typically json_build_object('CoordSets', json_build_array(json_build_array( json_build_object('Lon', t1.longitude, 'Lat', t1.latitude))))::text or an equivalent
jsonb_build_object/format() construction.
A calculated field is not the only option: for a table whose geometry is written by an external
process (route calculation, geocoding, a drawn-shape task — see later sections), map_entity_coordinates
is instead an ordinary stored column that a control procedure updates directly with a hand-built
CoordSets string.
Transposing Lon/Lat is a silent, hard-to-notice bug — sanity-check it against a real place.
Both are ordinary numeric values sitting next to each other in the same concat/json_build_object
call, so swapping which source column feeds which JSON key produces a string that is still perfectly
valid CoordSets JSON and generates without any error — it just plots every row in the wrong spot (a
verified case: real-world coordinates with Lon/Lat swapped rendered a Netherlands address in the
Indian Ocean off the Horn of Africa). After writing this expression, check the map against at least
one row whose real-world location you actually know, not just that the column generated and the map
renders some pin.
map — field reference
Keyed by (model_id, branch_id, tab_id) — one row per table that has a maps component.
| Column | Purpose |
|---|---|
initial_latitude / initial_longitude / initial_zoom_level |
Where the map centers when first opened, if not auto-fitting to data |
latitude_longitude_col_id |
The CoordSets-producing column (above) |
data_mapping_col_id |
The domain-based column selecting which map_data_mapping style applies to a row — see next section for the exact matching contract |
popup_col_id |
Column shown in the shape's popup on click — often an HTML calculated field |
use_custom_label_col_id (flag) / label_col_id |
Optional always-visible label per shape, instead of only-on-click popup content |
marker_location_task_parmtr_id, line_location_task_parmtr_id, polygon_location_task_parmtr_id, rectangle_location_task_parmtr_id, circle_location_task_parmtr_id |
Geometric Type Creation wiring — which task parameter receives the coordinates when a user draws that shape type on the map. Leave all five unset for a purely display/server-calculated map (verified: the reference model's two maps used none of them). See "Drawable shapes" below |
Lookup restriction on the five *_location_task_parmtr_id fields: their navigation properties
target task_parmtr.only_alphanumeric — only task parameters passing that filter are selectable.
Confirm a candidate parameter satisfies it (via get_entity_definition/get_domain_definition
before assigning; don't assume a parameter is eligible just because it exists) rather than assuming
any task parameter can be wired in.
map_base_layer / map_overlay — field reference
Both are XYZ tile layers with the same shape; overlays add opacity and menu visibility since they're
meant to be independently toggled on top of a base layer. Keyed by (model_id, branch_id, tab_id, map_base_layer_id) / (…, map_overlay_id).
| Column | Purpose |
|---|---|
map_base_layer_uri / map_overlay_uri |
XYZ tile URL template, e.g. https://…/{x}/{y}/{z} or a provider-specific query string form |
order_no |
Stacking order among multiple layers |
min_zoom_level / max_zoom_level |
Zoom range the tileset itself serves — not a client-side UI limit. Setting this higher than the provider actually supports returns blank/upscaled tiles, it does not unlock more detail |
tile_size |
Pixel size of each tile, if the provider deviates from the 256px default |
attribution_html |
Attribution shown in the map's corner — mandatory for most public tile providers' terms of use |
show_map_base_layer / show_map_overlay |
Default visibility |
opacity (overlay only, Edm.Byte 0–255) |
Transparency so the base layer stays visible underneath |
show_in_menu (overlay only) |
Whether users can toggle this overlay themselves |
If the user hasn't specified which tile provider(s) to use, ask — per
thinkwise_software_factory_mcp_base's "Ask, don't default" convention, don't pick one unilaterally
just because a URL template is easy to construct.
Verified example — three base layers on one table, sharing one URL template with only the lyrs query
parameter changed (Google classic tile endpoint: m = road, p = terrain, y = hybrid/satellite),
all zoom 0–18:
road: https://mt0.google.com/vt/lyrs=m&hl=en&x={x}&y={y}&z={z}
terrain: https://mt0.google.com/vt/lyrs=p&hl=en&x={x}&y={y}&z={z}
satellite: https://mt0.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}
Overlays are optional — reach for one only when a layer needs to be independently toggled and opacity-blended over whichever base layer is active (traffic, weather, custom raster/heat data).
Bearer/OAuth2-token-authenticated tile sources are not supported directly. The component is built
for a long-lived token embeddable in the URL (a query-string API key, as above). A short-lived bearer
token needing an OAuth2 refresh flow can't be handled by the browser/server tile request — front it
with your own proxy web service that injects and renews the header, and point map_base_layer_uri /
map_overlay_uri at the proxy instead of the token-protected source directly.
map_data_mapping — field reference and the domain-element matching contract
Keyed by (model_id, branch_id, tab_id, dom_id, map_data_mapping_id) — one row per element of the
domain referenced by map.data_mapping_col_id. The matching contract, verified against a real
model: the column map.data_mapping_col_id points at must use domain dom_id; each
map_data_mapping_id must equal one of that domain's element IDs (elemnt.elemnt_id). A domain with
4 elements (verified: an IMAGE_COMBO-controlled varchar domain, no_of_elemnt = 4) needs exactly
4 map_data_mapping rows to give every element a style — an element with no matching row has no
defined rendering.
| Column | Purpose |
|---|---|
geometric_type (Edm.Byte, enum) |
marker = 0 · line = 1 · polygon = 2 · rectangle = 3 · circle = 4 |
border_color / fill_color (Edm.Int32) |
Signed 32-bit ARGB — see decoding formula below |
border_width |
Outline width |
border_opacity / fill_opacity (Edm.Byte, 0–100) |
Separate from the color's own alpha channel — both are applied |
allow_drag_drop (flag) |
Lets a user reposition this shape by dragging it on the map, writing the new position back through drag/drop logic — not the same mechanism as the generic drag_drop/drag_drop_parmtr/drag_drop_matrix entity family covered in thinkwise_software_factory_subject_components; this is a map-specific flag with no task/parameter wiring of its own |
show_map_data_mapping (flag) |
Default on/off state for this style's legend/layer entry — must be turned on for the style to actually render on the map at all; don't assume it defaults to visible, verify it explicitly on every row |
A marker row ignores all its color/width/opacity fields — style comes from the element's icon instead
Verified directly against the platform's own Layout and Default control procedures for
map_data_mapping (read from the Software Factory's own meta-model, not inferred): the moment
geometric_type is set to marker (0), a Default-type control procedure clears border_color and
fill_color to null and resets border_width/border_opacity/fill_opacity to fixed baseline
values, and a Layout-type control procedure hides all five fields (border_color/border_width/
border_opacity/fill_color/fill_opacity) on the form for the rest of that row's life. Don't spend
effort choosing marker colors — they're discarded. A marker's actual visual appearance is driven by
the domain element's own icon/icon_id field (on elemnt, the same row map_data_mapping_id
must match), not by anything on map_data_mapping itself. Only line/polygon/rectangle/circle
rows actually use the color/width/opacity fields below.
The built-in icon catalog is not reliably queryable through a metadata-driven connector — a
model's icon/icon.icon_look_up entity can return zero rows even when icons are visibly in use
elsewhere in that same model. This is now explained by thinkwise_software_factory_icons: nearly every
icon-bearing entity, elemnt included, actually carries two independent mechanisms — an icon_id
foreign key into the shared repository, and a local icon/icon_data file uploaded straight onto the
row. A model whose markers were all styled via the local upload path (never entered into the shared
icon repository) will show icons rendering fine in the app while the icon entity itself sits empty.
Don't try to enumerate or guess a valid icon_id from a possibly-empty repository query. If the user
hasn't specified which icon a marker should use, ask (or hand them the picker) — per
thinkwise_software_factory_mcp_base's "Ask, don't default" convention, and per
thinkwise_software_factory_icons's own rule to ask rather than guess when no confident match exists —
rather than choosing one unilaterally.
Decoding/encoding border_color and fill_color (non-marker geometric types only)
These are stored as a signed 32-bit integer holding an ARGB value, alpha in the high byte:
unsigned = value if value >= 0 else value + 2**32
alpha = (unsigned >> 24) & 0xFF
red = (unsigned >> 16) & 0xFF
green = (unsigned >> 8) & 0xFF
blue = unsigned & 0xFF
To encode a desired ARGB back into the field, do the reverse and re-sign if the unsigned value exceeds
2**31 - 1:
unsigned = (alpha << 24) | (red << 16) | (green << 8) | blue
value = unsigned if unsigned < 2**31 else unsigned - 2**32
Verified worked example from a production map_data_mapping row styling a route line: field value
-16776961 → unsigned 4278190335 → 0xFF0000FF → alpha FF (opaque), red 00, green 00, blue
FF — solid opaque blue. Get this formula wrong and a control procedure or staged write silently
produces the wrong color (e.g. transparent, or a completely different hue) with no error at any step.
A single table's map_data_mapping rows can mix geometric types freely under one domain — verified:
one table used a 4-element domain where three elements were markers (origin_marker,
destination_marker, waypoint_marker) and the fourth was a line (the calculated route path),
letting one map render both point and path geometry from the same coordinate column.
Legend/layer-ordering caveat: Community reports note inconsistencies between how map_data_mapping
elements are ordered/sequenced in the Software Factory vs. how they render in a running app, and that
show_map_data_mapping hasn't always behaved as expected across Windows GUI vs. Universal GUI —
verify actual rendered behavior on the target GUI and platform version rather than trusting the
modeled order_no/flag alone.
Setting up a basic map — step by step
- Confirm scope with the user before creating anything — which table needs the map, what
geometry types it needs (marker/line/polygon/rectangle/circle, or a mix), the initial center/zoom,
which tile provider(s) to use, and whether end-user drawing (Geometric Type Creation) is needed.
This is
thinkwise_software_factory_mcp_base's "Shared conventions" — Confirm-before-mutate and Ask, don't default — applied to a map specifically: don't create the coordinate column, themaprow, or anything else in the steps below until this is settled. - Confirm the connector's actual domain keys for maps / data model / control procedures
(
search_capabilitiesorget_available_domains) — don't assumemanage_mapsetc. literally. - Get (or build) a CoordSets-producing column on the target table — a calculated field per "The
CoordSets JSON contract" above, checking
branch_rdbms_typefirst if hand-writing the SQL. - Create the
maprow for the table: setlatitude_longitude_col_idto that column,data_mapping_col_idto a domain-based column,popup_col_idto whatever should show on click, andinitial_latitude/initial_longitude/initial_zoom_levelfrom what the user confirmed in step 0 — if that wasn't pinned down yet, ask for the initial center/zoom now, or infer it from a known reference row already confirmed with the user, rather than picking an arbitrary default. - Add at least one
map_base_layerwith a working tile URL template, a zoom range that matches what the provider actually serves, and (for most public providers)attribution_html. If the tile provider(s) weren't already confirmed in step 0, ask which to use rather than picking one unilaterally. - Add one
map_data_mappingrow per element of the domain used in step 3'sdata_mapping_col_id— pickgeometric_typeper element; forline/polygon/rectangle/circleelements, also set border/fill styling, decoding/encoding colors per the formula below; formarkerelements, set aniconon the element instead (see the marker exception above) and leave color fields untouched. API limitation, verified live:map_data_mappingcould not be created or edited through a metadata-driven staging API in a verified session. Its key spans two independent "parents" at once (tab_id, reached viamap;dom_id/elemnt_id, reached via the domain's elements), and every path tried failed in a different way: staging undermaportabas parent reported no detail navigation to this entity; staging underdomas parent reported no such parent at all for this entity; a plain add with no parent left every key field permanently read-only, so even supplying the full compound key as ordinary fields (the usual fallback for this shape of weak entity — seethinkwise_datamodeling_guidelines) never became possible; and its own_overviewsibling entity (which does resolve a detail navigation frommap, and confirms themap_data_mapping_id-must-equal-an-elemnt_idcontract via alookup_map_data_mapping_id → elemntnavigation) was rejected outright on commit. Don't spend a session's budget chasing this — treat it as a manual step in the Software Factory's own UI by default, and only revisit if a specific connector's domain metadata is confirmed to expose it differently. - Verify the eligible domain elements'
elemnt_ids exactly match themap_data_mapping_ids created — a mismatch silently leaves some rows unstyled rather than erroring. - Only if the table needs end-user drawing: wire the relevant
*_location_task_parmtr_idfield(s) onmap— see "Drawable shapes" next. - Only if coordinates come from an external service rather than user input or plain columns: build the web-connection/process-flow/control-procedure pipeline — see "External-API pattern" below.
Drawable shapes (Geometric Type Creation)
Drawing is the mirror image of data mapping: instead of a column feeding the map, the map feeds a
task. Since the map component's shape-drawing tools shipped, users can draw markers, circles,
rectangles, lines and polygons directly on the canvas; each draw action executes the task wired into
the matching map.*_location_task_parmtr_id field, passing the drawn geometry as that parameter's
value.
A circle's captured JSON carries one extra key beyond a plain CoordSets ring — "Radius", in meters:
{
"CoordSets": [ [ { "Lat": 52.20839876100734, "Lon": 5.97939399968484 } ] ],
"Radius": 63.19832731146133
}
Setup steps:
Create a task whose parameters are the target row's primary key column(s), plus one parameter (satisfying the
only_alphanumericeligibility above) that will receive the drawn geometry as CoordSets JSON — for circles, the receiving control procedure also needs the radius, either as a second parameter or parsed out of the same JSON payload depending on how the connector surfaces it. (This task, like any new task, starts with a bracket-placeholder translation — seethinkwise_software_factory_translation_objectsto give it a real label before it reaches an end user's toolbar.)Assign the task as a table task on the same table the map is defined on, mapping the primary-key parameters to their columns (
use_primary_keyon the table-task-creation task, or explicitcol_id_n/task_parmtr_id_npairs).Write the control procedure that saves the incoming JSON onto the row — a plain
update, verified pattern:-- task parameters: company_id, company_address_id (PK), geofence_coordset (task input, the JSON above) update company_address set geofence_coordset = @geofence_coordset, geofence_data_mapping_type = 3 where company_id = @company_id and company_address_id = @company_address_idWire it into the map: set the relevant
map.*_location_task_parmtr_idfield (the "Geometric Type Creation" setting) to the task parameter created in step 1.
Known platform gap: no built-in surface-area or length calculation for a drawn shape (a circle
shows its radius on hover; an irregular polygon does not get an area). If a use case needs it (e.g. a
farm field's area in square meters), compute it from the CoordSets coordinates yourself — this is an
open, voted Community idea, not yet shipped; check its status before building a custom workaround.
Circle sizing is in screen pixels for the rendered marker itself, distinct from the geographic
Radius value captured on draw — the drawing tool's on-canvas circle uses Leaflet's CircleMarker,
whose own radius option is pixel-based and does not scale with zoom the way the captured Radius
(meters) does. Don't conflate the two when styling vs. when processing captured data.
Tab-variant overrides
Every one of the four map entities has a tab_variant_* counterpart that overrides the table-level
default for one specific tab variant, without redefining the map from scratch:
| Entity | Overrides |
|---|---|
tab_variant_map |
Center/zoom and column wiring, per variant |
tab_variant_map_base_layer |
Base layer set, per variant |
tab_variant_map_overlay |
Overlay set, per variant |
tab_variant_map_data_mapping |
Styling, per variant |
Use this when the same table's map needs to look or behave differently depending on which screen opens
it (a compact dashboard summary map vs. a full detail-screen map), without maintaining two physical
map definitions. Each has a matching _overview read entity and a task_reset_tab_variant_map*_overview
bound task (task_reset_tab_variant_map_overview, task_reset_tab_variant_map_base_layer_overview,
task_reset_tab_variant_map_data_mapping_overview, task_reset_tab_variant_map_overlay_overview) to
revert a variant's override back to the table-level default in one call, rather than deleting override
rows individually.
External-API pattern: geocoding and route calculation
For populating a map's coordinates from an external service instead of user input — a table task
(dummy trigger) → process flow → web_connection call → task → control procedure that parses the
response and writes CoordSets, including the verified process_action sequence, web_connection/
web_connection_endpoint field reference, and the full openjson/PostgreSQL JSON-parsing SQL examples —
read references/external_api_pattern.md.
Bound-task quick reference
Exact task names verified on the map entities — use these rather than guessing when a connector needs to mutate/inspect map configuration programmatically:
| Entity | Bound tasks |
|---|---|
map |
task_delete_map, task_show_history, task_unlink_generated_object |
map_base_layer |
task_copy_map_base_layer, task_delete_map_base_layer, task_rename_map_base_layer, task_show_history, task_unlink_generated_object |
map_overlay |
task_copy_map_overlay, task_delete_map_overlay, task_rename_map_overlay, task_show_history, task_unlink_generated_object |
map_data_mapping |
task_show_history, task_unlink_generated_object (no copy/rename — delete and re-add to change its key) |
tab_variant_map / _base_layer / _overlay / _data_mapping |
task_reset_tab_variant_map_overview, task_reset_tab_variant_map_base_layer_overview, task_reset_tab_variant_map_overlay_overview, task_reset_tab_variant_map_data_mapping_overview — revert an override back to the table-level default |
task_copy_map_base_layer / task_copy_map_overlay take from_tab_id/from_map_base_layer_id/
to_tab_id/to_map_base_layer_id (or the overlay equivalents) — useful for cloning a working tile
setup onto a new table rather than re-typing URIs and zoom ranges.
Known pitfalls (Community-verified)
- Pagination hides markers, not a hard limit. The map only renders rows on the currently loaded page — if locations are silently missing, raise the table's/task's page size before assuming a platform cap.
- First load can center on (0, 0) ("Null Island"). A map showing a route has been reported to
open there on the very first render, then auto-fit correctly on every later open — a confirmed bug,
not a modeling error. Setting an explicit
initial_latitude/initial_longitudemay mask it for small extents. - Max zoom is the tileset's limit, not the GUI's — see
map_base_layer/map_overlayfield reference above. - No bearer/OAuth2 tile layers directly — see the same section; use a proxy.
- Data-mapping legend order/visibility toggle has had cross-GUI inconsistencies — verify on the actual target GUI and version rather than trusting the modeled order alone.
- Circle drawing size is pixel-based on-canvas, distinct from the geometric
Radiuscaptured in the drawn shape's JSON — don't conflate visual size with the meters value. - No built-in shape area/length calculation — compute from
CoordSetsyourself if needed; watch the relevant Community idea for platform support. - A
markerrow's color/width/opacity fields are hidden and nulled by the platform itself — style a marker via its domain element'siconinstead, not viamap_data_mapping's color fields. See the marker exception under themap_data_mappingfield reference above. Lon/Lattransposed in a CoordSets expression produces no error, just a silently wrong pin location — verify against a known real-world coordinate, not just that the map renders something.
Pre-flight checklist
- Confirm the connector's actual domain keys for maps / data model / control procedures / web
connections / process flows before assuming
manage_maps-style names hold. map.latitude_longitude_col_idmust point at a column producing valid CoordSets JSON — verify the dialect (branch_rdbms_type) before hand-writing any SQL that builds or parses it.map.data_mapping_col_id's domain element IDs must exactly match themap_data_mapping_ids created for thatdom_id— a mismatch silently leaves rows unstyled, it does not error.- For
markerrows, don't setborder_color/fill_color/etc. at all — the platform hides and nulls them; set the domain element'siconinstead. Decode/encodeborder_color/fill_coloras signed-32-bit ARGB using the ⟨alpha, red, green, blue⟩ formula above only for the other four geometric types. - Expect
map_data_mappingitself to be unstageable through a metadata-driven write API — confirm the connector's own behavior before assuming otherwise, and default to treating it as a manual step. map_base_layer/map_overlaymax_zoom_levelmust match what the tile provider actually serves, not an aspirational value.- Bearer/OAuth2-secured tile layers need a proxy; bearer/OAuth2-secured API calls for
geocoding/routing are natively supported via
web_connection.authentication_type. - The five
map.*_location_task_parmtr_idfields only accept task parameters passing theonly_alphanumericeligibility filter — confirm before assigning. - The dummy-trigger-task pattern (
task_type_id = 'DUMMY', all parameters hidden) is expected and correct for a process-flow-driven map data source — don't add UI-visible fields or dynamic-model code to it; the logic belongs in the process flow and its control procedures. - Verify the actual process-variable name carrying a web connection's raw response body
(
@response_http_contentin the verified reference model) against the connector's own process-flow variable list — don't port it blindly. openjson/cross apply/for json path(SQL Server) vs.jsonb_*/json_agg/json_build_object(PostgreSQL) — checkbranch_rdbms_typebefore writing any JSON parsing/building control procedure, same as every other control procedure.- Delete-and-reinsert detail/map rows on recalculation rather than diff-and-update, and don't forget a cascade-delete handler on the parent.
- For anything not map-specific (naming, domain reuse, reference direction, control-procedure
generation mechanics) defer to
thinkwise_datamodeling_guidelinesandthinkwise_software_factory_create_control_proceduresrather than duplicating those rules here.