Skip to content

MOD-FIELDSET — 58 registers stop being one undifferentiated column of inputs - #112

Merged
ibuilder merged 1 commit into
mainfrom
feat/mod-fieldsets
Jul 30, 2026
Merged

MOD-FIELDSET — 58 registers stop being one undifferentiated column of inputs#112
ibuilder merged 1 commit into
mainfrom
feat/mod-fieldsets

Conversation

@ibuilder

Copy link
Copy Markdown
Owner

Base 23f02cb7, git merge-tree CLEAN as of that SHA. Item 6 of the field sweep's "not done" list. Touches only module.json files plus test_modules.py — no engine, no renderer, no API.

The gap

62 of 133 modules had no fieldsets, so their record form rendered every field in one flat list: comparable 21 fields, listing 20, zoning 19, lease 15. That's the clearest paper-form tell at the layer a user actually touches — a form with no sections is a form nobody can scan.

Nine groups, assigned by what the field is: Identity · Classification · Parties · Dates · Money · Quantities · Status · Links · Notes. Fieldsets must be contiguous (the renderer emits one header per run), so this is a stable sort by group — relative order inside a group is the author's original order, not a reshuffle.

58 grouped. 4 left alone on purposelabor_rate, material_rate, equipment_rate, location are 3–4 field lookup tables where a section header is decoration.

Three things the automated pass got wrong

percent is not money The first rule filed zoning's lot coverage under a Money header. retainage_pct and fee_pct are money; lot_coverage_pct, efficiency_pct, diversion_pct are proportions of area, time and mass. Caught by reading the output; now decided by name
The sort split the sweep's text+reference pairs responsible classifies as Parties, responsible_contact as Links. test_module_fields (from #108) failed on the first run, exactly as written. The reference now inherits its twin's group and sorts immediately after it
A continue bound to the inner loop single-group modules had fieldsets stripped, then sorted by a key that no longer existed

A pre-existing defect this surfaced

pm_schedule, staffing and work_order have had non-contiguous fieldsets since before the sweep — each rendering a duplicate header — because test_modules checked contiguity only for an enumerated list of thirteen tier-1 modules. The check was real; its scope was the bug. Same hand-maintained-list shape as _PROTECTED_PREFIXES and the /modules key allowlist: the ones without a completeness check are the ones that failed.

Contiguity is a property of the renderer, so the gate now asserts it over every module that uses fieldsets — costs nothing, needs no upkeep. It deliberately does not require every module to have fieldsets (a three-field lookup legitimately doesn't), but having them and interleaving them is never legitimate. The ungrouped count is a floor, not an equality.

The first mutation was a no-op, which is the part worth recording

Moving zoning's only Notes field to the front and re-running: the gate passed. That looks like a mutation surviving. It isn't — relocating a single-field run leaves every run unique, so the mutation could not express the defect at all. A genuine interleave (a Quantities field into the middle of Identity) fails it with the runs printed:

zoning: fieldsets must be contiguous, got ['Identity', 'Quantities', 'Identity',
'Classification', 'Quantities', 'Notes'] — the renderer emits one header per run,
so an interleaved fieldset draws its header twice

A mutation that cannot express the defect is not evidence, and a passing test against one proves nothing.

Verification

  • Backend 448/448, zero failures — a clean run with nothing else contending.
  • Web 910/914. All four accounted for and none from this branch: three pass solo (5s-timeout load flakes), and one is an artifact of my own setup — I linked node_modules into the worktree as a junction, so Vite denies C:/Server/modelmaker/node_modules/pdfjs-dist/... as outside the project root. CI has real node_modules and won't see it.
  • Built in an isolated git worktree; the shared checkout was never touched.

No version files or CHANGELOG — the release lane batches those.

🤖 Generated with Claude Code

@strix-security

strix-security Bot commented Jul 30, 2026

Copy link
Copy Markdown

Strix Security Review

No security issues found.

Updated for fe9aa4d.


Reviewed by Strix
Re-run review · Configure security review settings

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

MOD-FIELDSET: Add fieldsets to module forms and enforce contiguity in tests

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add semantic fieldsets to previously flat module record forms for faster scanning.
• Reorder fields via stable grouping to keep each fieldset contiguous in the renderer.
• Expand test gate to enforce fieldset contiguity across all modules that use fieldsets.
Diagram

graph TD
  A["module.json field defs"] --> B["API module loader"] --> C["Record form renderer"] --> D["User form UI"]
  A --> E["test_modules.py"] --> F["CI gate"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Renderer groups by fieldset (ignore ordering)
  • ➕ Eliminates the contiguity constraint in configs
  • ➕ Reduces the need for stable-sort reordering in module.json
  • ➖ Requires engine/renderer changes (out of this PR’s stated scope)
  • ➖ Potential UI edge cases (ordering within a group becomes ambiguous)
2. Add explicit `fieldsets` schema section with ordering
  • ➕ Centralizes fieldset ordering and labeling per module
  • ➕ Allows renderer to validate/normalize without relying on field order
  • ➖ Schema expansion and migration effort across modules
  • ➖ Still requires renderer/loader changes to interpret the new structure
3. Auto-assign fieldsets from field types/names at load time
  • ➕ Fewer per-module config changes long-term
  • ➕ Can enforce consistent rules in one place
  • ➖ Hard to capture nuanced semantics (e.g., percent fields) reliably
  • ➖ Adds hidden logic that makes module behavior less explicit/config-driven

Recommendation: Keep the PR’s approach (explicit fieldset annotations + stable reordering) because it improves UX without touching engine/renderer code and makes grouping decisions visible in config. The strengthened global contiguity test is the right safeguard given the renderer behavior; renderer-based regrouping was considered but would expand scope and risk.

Files changed (62) +1935 / -1213

Tests (1) +27 / -0
test_modules.pyEnforce fieldset completeness/contiguity across all modules using fieldsets +27/-0

Enforce fieldset completeness/contiguity across all modules using fieldsets

• Adds a repo-wide gate that (1) disallows mixed fieldset/non-fieldset fields within a module using fieldsets and (2) enforces contiguous fieldset runs. Tracks modules with no fieldsets and asserts an upper bound to prevent regressions back to fully flat forms.

services/api/test_modules.py

Other (61) +1908 / -1213
module.jsonGroup action item fields into Identity/Classification/Parties/Dates/Links +38/-29

Group action item fields into Identity/Classification/Parties/Dates/Links

• Adds fieldset annotations and reorders fields into contiguous semantic sections. Keeps responsible + responsible_contact adjacent under Parties to preserve pairing.

services/api/modules/action_item/module.json

module.jsonAdd fieldsets and move basis text into Notes section +20/-14

Add fieldsets and move basis text into Notes section

• Annotates fields with Identity/Classification/Parties/Money/Links/Notes fieldsets and reorders accordingly. Ensures the long-form basis textarea appears under Notes.

services/api/modules/assumption/module.json

module.jsonIntroduce fieldsets for solicitation identity, dates, status, and links +19/-14

Introduce fieldsets for solicitation identity, dates, status, and links

• Adds fieldset tags and reorders fields to create separate Identity, Dates, Status, and Links sections. Keeps the bid package reference grouped under Links.

services/api/modules/bid_solicitation/module.json

module.jsonGroup budget line fields into Identity and Money sections +18/-11

Group budget line fields into Identity and Money sections

• Adds fieldset metadata and moves all currency fields into a contiguous Money run. Keeps cost code/description together under Identity.

services/api/modules/budget/module.json

module.jsonAdd fieldsets and reorder checklist identity/status/notes +16/-10

Add fieldsets and reorder checklist identity/status/notes

• Reorders fields so identity fields (category/location/name) are contiguous and annotated. Moves result into Status and notes into Notes fieldset.

services/api/modules/checklist/module.json

module.jsonRefactor clash run fields into Identity and Quantities; format JSON arrays +60/-10

Refactor clash run fields into Identity and Quantities; format JSON arrays

• Expands inline field definitions into multi-line objects with fieldsets and groups counts under Quantities. Also reformats list_columns into a multi-line array.

services/api/modules/clash_run/module.json

module.jsonSection company fields into Identity/Classification/Parties/Status/Notes +40/-30

Section company fields into Identity/Classification/Parties/Status/Notes

• Adds fieldsets and reorders contact info and trade into Identity, type into Classification, primary contact into Parties, DBE status into Status, and address/notes into Notes.

services/api/modules/company/module.json

module.jsonAdd comprehensive fieldsets for comparable analysis (Identity→Notes) +101/-80

Add comprehensive fieldsets for comparable analysis (Identity→Notes)

• Introduces fieldsets across identity, classification, dates, money, quantities, and notes. Reorders fields into contiguous runs while preserving intra-group relative order.

services/api/modules/comparable/module.json

module.jsonGroup certificate fields into Identity/Classification/Parties/Dates/Quantities/Notes +31/-23

Group certificate fields into Identity/Classification/Parties/Dates/Quantities/Notes

• Adds fieldset tags and reorders fields to separate parties and multiple date fields. Places scope under Notes and quantity-like metrics under Quantities.

services/api/modules/completion_certificate/module.json

module.jsonAdd fieldsets and prettify workflow/list_columns formatting +83/-12

Add fieldsets and prettify workflow/list_columns formatting

• Groups concept render metadata into Identity/Classification/Notes and reorders fields accordingly. Rewrites workflow states/transitions and list_columns into multi-line arrays/objects without semantic changes.

services/api/modules/concept_render/module.json

module.jsonReorder contact fields and add Identity/Links/Notes fieldsets +27/-20

Reorder contact fields and add Identity/Links/Notes fieldsets

• Moves discipline/name/email/phone/title into Identity, company reference into Links, and notes into Notes. Ensures the primary identity fields are contiguous for rendering.

services/api/modules/contact/module.json

module.jsonAdd fieldsets for coordination issue identity, classification, quantities, links, notes +39/-29

Add fieldsets for coordination issue identity, classification, quantities, links, notes

• Reorders fields so clash metadata/location/subject are contiguous under Identity. Assigns priority to Classification, numeric metrics to Quantities, meeting link to Links, and description to Notes.

services/api/modules/coordination_issue/module.json

module.jsonAdd Identity and Classification fieldsets to cost code fields +6/-3

Add Identity and Classification fieldsets to cost code fields

• Annotates key fields (code/description) as Identity and division as Classification. Ensures the fieldset runs remain contiguous.

services/api/modules/cost_code/module.json

module.jsonAdd fieldsets and reorder decision fields into semantic sections +41/-31

Add fieldsets and reorder decision fields into semantic sections

• Moves alignment/category into Classification, decided_by into Parties, due_date into Dates, and narrative textareas into Notes. Adds fieldset annotations and enforces contiguity through ordering.

services/api/modules/decision/module.json

module.jsonAdd fieldsets for deficiency identity, classification, dates, links, notes +23/-15

Add fieldsets for deficiency identity, classification, dates, links, notes

• Groups location/trade under Identity, severity under Classification, due date under Dates, inspection reference under Links, and corrective action under Notes. Reorders to keep runs contiguous.

services/api/modules/deficiency/module.json

module.jsonOrganize delivery fields into Identity/Parties/Dates/Status/Links +26/-19

Organize delivery fields into Identity/Parties/Dates/Status/Links

• Adds fieldset tags and reorders to keep description/PO/supplier in Identity, received_by in Parties, date in Dates, status in Status, and commitment reference in Links.

services/api/modules/delivery/module.json

module.jsonAdd fieldsets for design review identity/classification/status/links/notes +19/-14

Add fieldsets for design review identity/classification/status/links/notes

• Annotates and reorders fields so the core subject/discipline/status are separated and contiguous by section. Places drawing reference under Links and comments under Notes.

services/api/modules/design_review/module.json

module.jsonAdd fieldsets and reorder direct cost fields across sections +34/-25

Add fieldsets and reorder direct cost fields across sections

• Groups description under Identity, type/cost_type under Classification, vendor fields under Parties, date under Dates, amount under Money, and references under Links. Reorders to keep fieldset runs contiguous.

services/api/modules/direct_cost/module.json

module.jsonAdd fieldsets and normalize icon encoding for document module +31/-25

Add fieldsets and normalize icon encoding for document module

• Reorders fields into Identity/Classification/Status/Notes with fieldset annotations. Also replaces escaped Unicode icon with a literal character for readability.

services/api/modules/document/module.json

module.jsonAdd fieldsets and reorder drawing fields into Identity/Classification/Dates/Status/Links +44/-33

Add fieldsets and reorder drawing fields into Identity/Classification/Dates/Status/Links

• Groups identifying sheet metadata under Identity, discipline/lifecycle under Classification, issued date under Dates, status under Status, and references under Links. Reorders fields to ensure contiguous fieldset runs.

services/api/modules/drawing/module.json

module.jsonAdd fieldsets to drawing issuance and format workflow/list columns +75/-15

Add fieldsets to drawing issuance and format workflow/list columns

• Annotates issuance fields into Identity/Classification/Dates/Quantities/Notes and reorders accordingly. Also expands workflow and list_columns formatting without changing behavior.

services/api/modules/drawing_issuance/module.json

module.jsonAdd fieldsets for equipment log identity/dates/quantities/links +10/-5

Add fieldsets for equipment log identity/dates/quantities/links

• Adds fieldset tags and keeps equipment as Identity, date as Dates, hours as Quantities, and references under Links. Maintains contiguous runs for rendering.

services/api/modules/equipment_log/module.json

module.jsonAdd fieldsets and move amount into Money section +12/-8

Add fieldsets and move amount into Money section

• Annotates estimate name as Identity, type as Classification, date as Dates, and amount as Money. Reorders fields to keep fieldsets contiguous.

services/api/modules/estimate/module.json

module.jsonAdd fieldsets for estimate set identity/classification/dates/money/quantities/notes +37/-29

Add fieldsets for estimate set identity/classification/dates/money/quantities/notes

• Introduces fieldset annotations and reorders fields so classification selects are contiguous, totals are under Money, size metrics under Quantities, and notes under Notes. Keeps original relative order within groups.

services/api/modules/estimate_set/module.json

module.jsonAdd fieldsets for e-ticket identity/dates/quantities/links +16/-10

Add fieldsets for e-ticket identity/dates/quantities/links

• Tags subject as Identity, work_date as Dates, numeric totals as Quantities, and change_event reference as Links. Reorders to keep sections contiguous.

services/api/modules/eticket/module.json

module.jsonAdd fieldsets for investor identity/classification/dates/money/notes +32/-20

Add fieldsets for investor identity/classification/dates/money/notes

• Adds fieldset annotations and reorders fields into contiguous identity and classification blocks, then dates and financial fields. Places freeform notes under Notes.

services/api/modules/investor/module.json

module.jsonAdd fieldsets and reorder issue fields into Identity/Classification/Parties/Notes +31/-24

Add fieldsets and reorder issue fields into Identity/Classification/Parties/Notes

• Groups category/location/subject under Identity, priority under Classification, assignee under Parties, and description under Notes. Reorders fields to maintain contiguous runs.

services/api/modules/issue/module.json

module.jsonAdd fieldsets for JHA identity/classification/notes +16/-12

Add fieldsets for JHA identity/classification/notes

• Annotates the task as Identity, PPE as Classification, and hazards/controls as Notes. Reorders to keep fieldsets contiguous while retaining content.

services/api/modules/jha/module.json

module.jsonAdd fieldsets for journal batch identity/classification/quantities/status/notes +99/-14

Add fieldsets for journal batch identity/classification/quantities/status/notes

• Expands inline JSON into multi-line objects and assigns fieldsets to separate metadata, balance state, totals/counts, approval, and memo. Also reformats workflow state/transition arrays and list_columns.

services/api/modules/journal_batch/module.json

module.jsonAdd fieldsets and reorder lease fields into Identity/Classification/Dates/Money/Notes +53/-38

Add fieldsets and reorder lease fields into Identity/Classification/Dates/Money/Notes

• Moves tenant/suite/renewal identity fields together, lease type into Classification, start/end into Dates, and all rent/allowance metrics into Money. Keeps notes under Notes and preserves stable order within groups.

services/api/modules/lease/module.json

module.jsonAdd fieldsets for lien waiver identity/classification/dates/money/links +21/-14

Add fieldsets for lien waiver identity/classification/dates/money/links

• Introduces fieldset tags, grouping signature/vendor info under Identity, type under Classification, through date under Dates, amount under Money, and subcontract reference under Links. Reorders to ensure contiguity.

services/api/modules/lien_waiver/module.json

module.jsonAdd fieldsets and reorder listing fields into Identity/Classification/Dates/Money/Quantities/Status/Notes +75/-55

Add fieldsets and reorder listing fields into Identity/Classification/Dates/Money/Quantities/Status/Notes

• Adds multiple identity fields (address/city/zip/virtual tour) and groups pricing/NOI under Money and physical attributes under Quantities. Reorders state into Status and narrative fields into Notes, keeping fieldsets contiguous.

services/api/modules/listing/module.json

module.jsonAdd fieldsets for manpower log identity/dates/quantities/links +25/-17

Add fieldsets for manpower log identity/dates/quantities/links

• Groups company/trade under Identity, date under Dates, numeric counts under Quantities, and daily report reference under Links. Reorders fields to keep sections contiguous.

services/api/modules/manpower_log/module.json

module.jsonAdd fieldsets for market assumption identity/classification/money/quantities/notes +21/-13

Add fieldsets for market assumption identity/classification/money/quantities/notes

• Annotates scenario as Identity, region/sector as Classification, escalation override under Money, numeric schedule/location indices under Quantities, and notes under Notes. Reorders for contiguous runs.

services/api/modules/market_assumption/module.json

module.jsonAdd fieldsets for material request identity/dates/quantities/links/notes +18/-11

Add fieldsets for material request identity/dates/quantities/links/notes

• Moves material/unit to Identity, needed_by to Dates, qty to Quantities, schedule activity reference to Links, and text areas to Notes. Reorders to maintain contiguous fieldsets.

services/api/modules/material_request/module.json

module.jsonAdd fieldsets and reorder NCR fields into Identity/Classification/Dates/Status/Links/Notes +34/-26

Add fieldsets and reorder NCR fields into Identity/Classification/Dates/Status/Links/Notes

• Moves severity into Classification, due date into Dates, disposition into Status, inspection reference into Links, and descriptive textareas into Notes. Reorders to keep all fieldsets contiguous.

services/api/modules/ncr/module.json

module.jsonAdd fieldsets for observation identity/classification/notes +26/-18

Add fieldsets for observation identity/classification/notes

• Tags description/location/trade as Identity and moves all categorical selects into Classification. Assigns corrective action textarea to Notes and reorders to keep sections contiguous.

services/api/modules/observation/module.json

module.jsonAdd fieldsets for orientation identity/parties/dates +20/-14

Add fieldsets for orientation identity/parties/dates

• Groups signature/trainer/worker under Identity, company fields under Parties, and date under Dates. Reorders fields so each fieldset is contiguous.

services/api/modules/orientation/module.json

module.jsonAdd fieldsets for owner invoice identity/money/status/links +19/-14

Add fieldsets for owner invoice identity/money/status/links

• Annotates number/period under Identity, amount under Money, status under Status, and prime contract reference under Links. Reorders to enforce contiguous runs.

services/api/modules/owner_invoice/module.json

module.jsonAdd fieldsets for photo identity/classification/dates +22/-15

Add fieldsets for photo identity/classification/dates

• Groups caption/location/trade under Identity, tags under Classification, and date/date_taken under Dates. Reorders to keep fieldset runs contiguous.

services/api/modules/photo/module.json

module.jsonFix fieldset contiguity by repositioning est_hours within Schedule run +7/-7

Fix fieldset contiguity by repositioning est_hours within Schedule run

• Moves est_hours to be adjacent to other Schedule-fieldset entries, preventing interleaving with other fieldsets. No semantic field changes beyond ordering.

services/api/modules/pm_schedule/module.json

module.jsonAdd fieldsets for pretask plan identity/quantities/notes +18/-13

Add fieldsets for pretask plan identity/quantities/notes

• Groups signature/task under Identity, crew size under Quantities, and controls/hazards under Notes. Reorders to keep fieldset runs contiguous.

services/api/modules/pretask_plan/module.json

module.jsonAdd fieldsets for price observation identity/classification/parties/dates/money/quantities +31/-23

Add fieldsets for price observation identity/classification/parties/dates/money/quantities

• Annotates and reorders fields so source select is under Classification, vendor fields under Parties, observed date under Dates, unit price under Money, and quantity under Quantities. Maintains contiguous fieldsets.

services/api/modules/price_observation/module.json

module.jsonAdd fieldsets for production quantity identity/quantities/links +17/-12

Add fieldsets for production quantity identity/quantities/links

• Moves description/unit into Identity, quantity into Quantities, and references into Links. Reorders to keep fieldset runs contiguous.

services/api/modules/production_quantity/module.json

module.jsonAdd fieldsets for proposal identity/money/quantities/links/notes +23/-16

Add fieldsets for proposal identity/money/quantities/links/notes

• Annotates subject under Identity, amount under Money, schedule days under Quantities, references under Links, and narrative fields under Notes. Reorders to ensure contiguous runs.

services/api/modules/proposal/module.json

module.jsonAdd fieldsets and reorder responsibility fields into Identity/Classification/Notes +30/-24

Add fieldsets and reorder responsibility fields into Identity/Classification/Notes

• Groups activity/milestone/reference under Identity, category/phase under Classification, and notes under Notes. Reorders to make each fieldset contiguous.

services/api/modules/responsibility/module.json

module.jsonAdd fieldsets for risk identity/classification/parties/dates/money/quantities/notes +40/-28

Add fieldsets for risk identity/classification/parties/dates/money/quantities/notes

• Moves category/impact/probability/strategy into Classification, owner into Parties, review date into Dates, cost exposure into Money, schedule days into Quantities, and narrative fields into Notes. Reorders to maintain contiguity.

services/api/modules/risk/module.json

module.jsonAdd fieldsets for safety violation identity/classification/parties/dates/links/notes +27/-20

Add fieldsets for safety violation identity/classification/parties/dates/links/notes

• Places severity under Classification, company fields under Parties, due date under Dates, observation reference under Links, and corrective action under Notes. Reorders fields to keep runs contiguous.

services/api/modules/safety_violation/module.json

module.jsonAdd fieldsets for selection identity/classification/dates/money/status +26/-17

Add fieldsets for selection identity/classification/dates/money/status

• Groups identity fields including selected option, assigns category to Classification, due date to Dates, allowance/actual cost to Money, and client approval to Status. Reorders to ensure contiguity.

services/api/modules/selection/module.json

module.jsonAdd fieldsets for site logistics identity/classification/parties/dates/notes +25/-19

Add fieldsets for site logistics identity/classification/parties/dates/notes

• Moves category select to Classification, booking company to Parties, date to Dates, and description to Notes, while keeping location identity grouped. Reorders for contiguous fieldsets.

services/api/modules/site_logistics/module.json

module.jsonAdd fieldsets for schedule of values identity/money/quantities/links +34/-24

Add fieldsets for schedule of values identity/money/quantities/links

• Groups item identifiers under Identity, retainage and scheduled value under Money, progress amounts under Quantities, and references under Links. Reorders to keep each fieldset contiguous.

services/api/modules/sov/module.json

module.jsonAdd fieldsets and keep responsible + responsible_contact paired under Parties +48/-38

Add fieldsets and keep responsible + responsible_contact paired under Parties

• Adds fieldsets and reorders spec section fields into Identity/Classification/Parties/Links/Notes. Ensures the responsible contractor text and linked reference remain adjacent and in the same fieldset run.

services/api/modules/spec_section/module.json

module.jsonFix fieldset contiguity by moving cost_code into Cost run +7/-7

Fix fieldset contiguity by moving cost_code into Cost run

• Repositions cost_code to be contiguous with other Cost-fieldset fields, preventing duplicate headers. No semantic changes beyond ordering.

services/api/modules/staffing/module.json

module.jsonAdd fieldsets for test record identity/dates/status/links +30/-22

Add fieldsets for test record identity/dates/status/links

• Groups lab/name/spec section/test type under Identity, date under Dates, result under Status, and inspection under Links. Reorders to maintain contiguous fieldset runs.

services/api/modules/test_record/module.json

module.jsonAdd fieldsets for timesheet identity/dates/quantities/links +14/-9

Add fieldsets for timesheet identity/dates/quantities/links

• Adds trade to Identity, assigns date to Dates, hours to Quantities, and cost code reference to Links. Reorders fields to keep fieldsets contiguous.

services/api/modules/timesheet/module.json

module.jsonAdd fieldsets for toolbox talk identity/dates/quantities +12/-8

Add fieldsets for toolbox talk identity/dates/quantities

• Moves presenter/topic under Identity, date under Dates, and attendees under Quantities. Reorders to keep fieldset runs contiguous.

services/api/modules/toolbox_talk/module.json

module.jsonAdd fieldsets for transmittal identity/classification/parties/notes +28/-22

Add fieldsets for transmittal identity/classification/parties/notes

• Places method/purpose into Classification, to_company into Parties, and contents/items into Notes. Adds fieldset tags and reorders for contiguous section runs.

services/api/modules/transmittal/module.json

module.jsonAdd fieldsets and normalize icon encoding for value engineering module +13/-9

Add fieldsets and normalize icon encoding for value engineering module

• Annotates subject under Identity, savings under Money, status under Status, and description under Notes. Also replaces escaped Unicode icon with a literal character for readability.

services/api/modules/value_engineering/module.json

module.jsonAdd fieldsets for weekly plan identity/classification/dates/status +23/-18

Add fieldsets for weekly plan identity/classification/dates/status

• Groups task/trade under Identity, variance reason under Classification, week under Dates, and status under Status. Reorders to keep fieldset runs contiguous.

services/api/modules/weekly_plan/module.json

module.jsonFix fieldset contiguity by moving pm_schedule reference into Work run +7/-7

Fix fieldset contiguity by moving pm_schedule reference into Work run

• Reorders pm_schedule to be contiguous with other Work-fieldset fields, avoiding interleaved headers. No semantic field changes beyond ordering.

services/api/modules/work_order/module.json

module.jsonAdd fieldsets and reorder zoning fields into Identity/Classification/Quantities/Notes +70/-51

Add fieldsets and reorder zoning fields into Identity/Classification/Quantities/Notes

• Moves site/jurisdiction into Identity, use type into Classification, and all dimensional/percent constraints into Quantities. Keeps notes under Notes and ensures fieldset runs are contiguous.

services/api/modules/zoning/module.json

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 55 rules

Grey Divider


Remediation recommended

1. Ungrouped cap masks regressions 🐞 Bug ⚙ Maintainability
Description
The new global fieldset gate only enforces len(ungrouped) <= 8, which means several non-lookup
modules could lose all fieldsets and still pass CI as long as the total stays under the cap. This
undermines the stated intent (“58 grouped … must not quietly drift back”) because it doesn’t
constrain *which* modules are allowed to be ungrouped or why.
Code

services/api/test_modules.py[R113-126]

+    ungrouped = []
+    for k, mod in mods.items():
+        seq = [f.get("fieldset") for f in mod["fields"] if f["type"] != "rollup"]
+        if not any(seq):
+            ungrouped.append(k)
+            continue
+        assert all(seq), f"{k}: some fields have a fieldset and some do not: {seq}"
+        runs = [fs for i, fs in enumerate(seq) if i == 0 or fs != seq[i - 1]]
+        assert len(runs) == len(set(runs)), (
+            f"{k}: fieldsets must be contiguous, got {runs} — the renderer emits one header per run, "
+            "so an interleaved fieldset draws its header twice")
+    # A floor, not an equality: new small modules may legitimately join this set, but the 58 grouped by
+    # the sweep must not quietly drift back to a single undifferentiated column of inputs.
+    assert len(ungrouped) <= 8, f"{len(ungrouped)} modules have no fieldsets at all: {sorted(ungrouped)}"
Relevance

●● Moderate

PR explicitly prefers a permissive “floor” cap; tightening to per-module allowlist is more
subjective without precedent.

PR-#4

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new test uses only a global count cap for modules with no fieldsets, so it can’t prevent
specific modules from regressing as long as the total remains under 8. The currently-ungrouped
modules shown are small lookup tables, suggesting the gate should encode that property rather than a
permissive global cap.

services/api/test_modules.py[101-126]
services/api/modules/labor_rate/module.json[1-30]
services/api/modules/location/module.json[1-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`test_modules.py` introduces a new global check that allows up to 8 modules to have no `fieldset` at all. This is a weak regression guard because any module can become ungrouped (lose all fieldsets) as long as the total ungrouped count remains under 8.

## Issue Context
The surrounding comment explains the intent: allow small lookup-table modules to omit fieldsets, but prevent real “forms” from drifting back to an undifferentiated column. A simple count cap doesn’t encode that intent.

## Fix Focus Areas
- services/api/test_modules.py[113-126]

## Suggested fix
Replace (or augment) the `len(ungrouped) <= 8` assertion with a rule that encodes *why* a module may be ungrouped, without needing a hand-maintained list. Examples:

1) **Size-based heuristic** (low maintenance):
- If a module has no fieldsets, assert it has at most N non-rollup fields (e.g., `<= 4`) and/or has no `reference` fields.

2) **Explicit allowed set** (strongest):
- Keep an allowlist for ungrouped modules (`{"labor_rate", "material_rate", "equipment_rate", "location"}`) and assert `set(ungrouped) <= allowed`.

Either approach prevents large real modules from silently losing fieldsets while still permitting legitimate lookup-table modules.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +113 to +126
ungrouped = []
for k, mod in mods.items():
seq = [f.get("fieldset") for f in mod["fields"] if f["type"] != "rollup"]
if not any(seq):
ungrouped.append(k)
continue
assert all(seq), f"{k}: some fields have a fieldset and some do not: {seq}"
runs = [fs for i, fs in enumerate(seq) if i == 0 or fs != seq[i - 1]]
assert len(runs) == len(set(runs)), (
f"{k}: fieldsets must be contiguous, got {runs} — the renderer emits one header per run, "
"so an interleaved fieldset draws its header twice")
# A floor, not an equality: new small modules may legitimately join this set, but the 58 grouped by
# the sweep must not quietly drift back to a single undifferentiated column of inputs.
assert len(ungrouped) <= 8, f"{len(ungrouped)} modules have no fieldsets at all: {sorted(ungrouped)}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Ungrouped cap masks regressions 🐞 Bug ⚙ Maintainability

The new global fieldset gate only enforces len(ungrouped) <= 8, which means several non-lookup
modules could lose all fieldsets and still pass CI as long as the total stays under the cap. This
undermines the stated intent (“58 grouped … must not quietly drift back”) because it doesn’t
constrain *which* modules are allowed to be ungrouped or why.
Agent Prompt
## Issue description
`test_modules.py` introduces a new global check that allows up to 8 modules to have no `fieldset` at all. This is a weak regression guard because any module can become ungrouped (lose all fieldsets) as long as the total ungrouped count remains under 8.

## Issue Context
The surrounding comment explains the intent: allow small lookup-table modules to omit fieldsets, but prevent real “forms” from drifting back to an undifferentiated column. A simple count cap doesn’t encode that intent.

## Fix Focus Areas
- services/api/test_modules.py[113-126]

## Suggested fix
Replace (or augment) the `len(ungrouped) <= 8` assertion with a rule that encodes *why* a module may be ungrouped, without needing a hand-maintained list. Examples:

1) **Size-based heuristic** (low maintenance):
- If a module has no fieldsets, assert it has at most N non-rollup fields (e.g., `<= 4`) and/or has no `reference` fields.

2) **Explicit allowed set** (strongest):
- Keep an allowlist for ungrouped modules (`{"labor_rate", "material_rate", "equipment_rate", "location"}`) and assert `set(ungrouped) <= allowed`.

Either approach prevents large real modules from silently losing fieldsets while still permitting legitimate lookup-table modules.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

…iated column of inputs

62 of 133 modules had NO fieldsets, so their record form rendered every field in one flat list:
`comparable` 21 fields, `listing` 20, `zoning` 19, `lease` 15. That is the clearest paper-form
tell at the layer a user actually touches — a form with no sections is a form nobody can scan.

Nine groups, assigned by what the field IS rather than by where it happened to sit:
Identity · Classification · Parties · Dates · Money · Quantities · Status · Links · Notes.

Fieldsets must be CONTIGUOUS (the renderer emits one header per run, so an interleaved fieldset
draws its header twice), so this is a STABLE SORT by group — relative order inside a group is the
author's original order, not a reshuffle.

THREE THINGS THE AUTOMATED PASS GOT WRONG, ALL CAUGHT BY READING THE OUTPUT OR BY A GATE:

1. `percent` is not money. The first pass mapped every percent to Money and filed **zoning's lot
   coverage under a Money header**. `retainage_pct` and `fee_pct` are money; `lot_coverage_pct`,
   `efficiency_pct` and `diversion_pct` are proportions of area, time and mass. Now decided by
   name, and zoning reads Identity · Classification · Quantities · Notes.
2. The sort SPLIT the sweep's text+reference pairs — `responsible` classifies as Parties and
   `responsible_contact` as Links, so a naive sort separates them every time.
   `test_module_fields` caught it on the first run, exactly as written. The reference now INHERITS
   its twin's group and sorts immediately after it.
3. A `continue` bound to the inner loop instead of the outer, so single-group modules had their
   fieldsets stripped and then sorted by a key that no longer existed.

FOUR MODULES LEFT UNGROUPED ON PURPOSE: `labor_rate`, `material_rate`, `equipment_rate` and
`location` are 3-4 field lookup tables. A section header on three fields is decoration.

AND A PRE-EXISTING DEFECT THE WORK SURFACED. `pm_schedule`, `staffing` and `work_order` have had
non-contiguous fieldsets since before the sweep — each rendering a duplicate header — because
`test_modules` checked contiguity only for an ENUMERATED list of thirteen tier-1 modules. The
check was real; its scope was the bug. Same hand-maintained-list shape as `_PROTECTED_PREFIXES`
and the `/modules` key allowlist: the ones without a completeness check are the ones that failed.

Contiguity is a property of the RENDERER, so the gate now asserts it over EVERY module that uses
fieldsets, which costs nothing and needs no upkeep. It deliberately does not require every module
to HAVE fieldsets — a three-field lookup legitimately has none — but a module that has them and
interleaves them is never legitimate. The count of ungrouped modules is a floor, not an equality,
so a new small module can join the set while the 58 cannot quietly drift back.

Mutation-checked, and the FIRST mutation was a no-op worth recording: moving zoning's only `Notes`
field to the front merely relocates that run and leaves every run unique, so the gate passed and
proved nothing. A real interleave — moving a Quantities field into the middle of Identity — fails
it with the runs printed. A mutation that cannot express the defect is not evidence.

No version files or CHANGELOG — the release lane batches those.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ibuilder
ibuilder force-pushed the feat/mod-fieldsets branch from fe9aa4d to 9bb3564 Compare July 30, 2026 22:40
@ibuilder

Copy link
Copy Markdown
Owner Author

Rebased onto bf41a415 (post-#111). git merge-base is current main, so this branch's CI is the merged-tree result by construction rather than by argument. merge-tree CLEAN.

The estimate conflict, resolved by hand as you asked

#111 added estimate.line_items (a table); this PR reorders and groups the same register. Main had date → line_items, this branch had date → amount.

I reconstructed from main's version rather than text-merging, because the constraint that matters is adjacency and a textual union satisfies the diff, the schema validator, and nothing else — the same shape as the refCell fake link: plausible output nobody questions.

line_items goes in Money, immediately after amount. It is the G703 itemisation of that figure, so putting it in its own section would separate a total from the lines that produce it — which is the split this whole sweep exists to close.

order: name · basis · date · amount · line_items
runs : Identity · Classification · Dates · Money      contiguous: true

Every field main had is present — I asserted the set rather than assuming it, and printed any leftover the resolution didn't name (there were none).

Verification after the rebase

  • test_modules, test_module_config, test_module_fields, test_module_tables, test_module_schema — all PASS
  • Manifest 456, unchanged (this branch adds no test), _manifest_guard() reports none unregistered
  • The widened contiguity gate this PR introduces, run across all 133 registers: no violations

On your merged-tree point

Agreed, and I've adopted it: rebasing onto main immediately before asking for a merge makes merge-base == main, so branch CI stops being a proxy. Cheaper than a combined integration worktree and it removes the path-disjointness argument entirely.

#113 next — manifest union, main's side plus mine, verified with _manifest_guard(). I confirmed 456 by executing git show origin/main:services/api/run_tests.py and cross-checking the unique test_* literals in that same object; both agree, so it's a number from main and not from a worktree. Mine will make 457.

@ibuilder
ibuilder merged commit 78f712a into main Jul 30, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant