Skip to content

feat(editor): extract every label/value pair the scan can find - #15

Merged
iamgp merged 3 commits into
mainfrom
feat/scan-extract-pairs-v2
Sep 17, 2026
Merged

iamgp merged 3 commits into
mainfrom
feat/scan-extract-pairs-v2

Conversation

@iamgp

@iamgp iamgp commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Why the change

The scan skipped most of a worksheet form — rows that list their answers as label/value pairs side by side, pairs whose label is merged across the columns it spans, single-row blocks, one-row tables of formulas, and tables whose data fills only the first column — so a lab worksheet yielded 8 fields where 36 are extractable, and accepting a table retired every suggestion below it.

Special things to note

  • Recall over precision on purpose: the queue is ranked, so weaker extractions sit at the bottom instead of being dropped. The queue cap rises from 24 to 80 suggestions.
  • Version number | 6 still comes back as a discriminator rather than a field, so the two never overlap.
  • Table data rows are still not read as pairs, and a 2-row label/value block with no title is still a cover block (fields) rather than a table; the title is what makes it a table.

Change outline

The pair scan walks each row in both directions. Four guards decided what a pair is, and each one was dropping real form content.

 findKeyValueBlockSuggestions
-  valueCol = col + direction                    # a merged label lost its value
+  valueCol = past the label's own merge         # "label | (merged) | Yes"
-  if (a value follows) continue                 # a second pair hid the first
+  if (a value follows && it is not a pair) continue
+  if the value is the pair on the left          # "AB | AB | 08/02/2024"
-  if (size < 2) continue                        # single-row blocks dropped
+  if (size < 2 && not form-like) continue
+  isFormLabel(label)                            # sentences are labels next to their value
+  hasOwnValue(cell)                             # a merge repeats one value, not columns
 findCoverBlockSuggestions
-  valueCols.length < 2                          # one answer under a label row
+  valueCols.length < 1
 scanWorkbookForSuggestions
-  .slice(0, 24)                                 # the tail was truncated
+  .slice(0, MAX_SUGGESTIONS)

The table paths gained two measurements, and the pruning rule was wrong for open-ended fields:

 findTableSuggestions / findTitledTableSuggestions
   typedSampleCells  += values that start with "="     # a one-row table of formulas
+  measureFirstColumnBlock(startCol)                   # data may fill only column A
+  effectiveDepth = max(depth, firstColumnBlock.depth)
+  boundsEnd      = max(strictBlock.endRow, firstColumnBlock.endRow)

 dropSuggestionsCoveredBy
-  field contains the suggestion                 # "A23:F" runs to the bottom of the sheet,
+  field and suggestion start inside each other  # so containment retired every card below

The shapes that were being missed, from the reported worksheets:

row 3   Operator (Full name) | Alison Fernandes | Date | 07/02/2024
        -> 4 fields (was: date only, and operator dropped as "table data")

row 29  PBS aliquoted for Blank and NPC as per PRDSOP | (merged A:B) | Yes
        -> 1 field (was: nothing, the label's merge hid the value)

row 18  Incubator | 21LAB421 | 21LAB421        (merged B:D)
        -> 1 field (was: nothing, the merge read as extra columns)

row 66  Date last performed | 01/02/2023 | Date next due | 01/03/2024
        -> 2 fields (was: nothing, a single row never qualified)

Table 3: NTCs      Well# | CtΨ | CNΨ | CtRPPH1 | CNRPPH1  over one data row   -> table
Table 6 (A block)  MQCN NTC,Ψ | MQCN NTC,RPPH1  over formula cells             -> table
Table 1            Extract. Assay ID | IPC-EX Sample ID | ...  first column only -> table
 editor/src/
 └── lib/
     ├── suggestions.ts          # pair, merge, single-row, formula and first-column rules
     └── suggestions.test.ts     # 8 new cases

The scan skipped most of a worksheet form: rows that list their answers as
label/value pairs side by side, pairs whose label is merged across the columns it
spans, and single-row blocks were all dropped, and the queue stopped at 24
suggestions. A lab worksheet yielded 8 fields where 36 are extractable.

- A row that alternates labels and values is a form row: every pair in it is a
  field, instead of the first pair hiding the rest behind a "table row" guard.
- A label merged across columns pairs with the value after the merge, so
  "PBS aliquoted for Blank and NPC as per PRDSOP | (merged) | Yes" is one field.
- A merged value counts once: the cells a merge repeats are not extra columns, so
  "Incubator | 21LAB421" (merged across B:D) is no longer read as a table row.
- Sentence-length labels are allowed next to their value, and a lone pair in a
  form row no longer needs a second row to qualify.
- The cover-block reading accepts a single value column, so a merged label row
  with one answer underneath is extracted.
- The queue caps rise from 24 to 80 suggestions, 40 per kind, so the ranked tail
  is available instead of truncated.
Two shapes were still missed on the calculation sheets, and accepting a table
retired unrelated suggestions.

- A one-row table whose values are formulas ("MQCN NTC,Ψ | MQCN NTC,RPPH1" over
  "=IFERROR(..) | =IFERROR(..)") now counts as structured, so the titled table is
  suggested instead of nothing.
- A table whose data fills only its first column ("Extract. Assay ID | IPC-EX
  Sample ID | TqM-Int-Dup-XXX" with just the IDs below) is measured on that column
  when the rest of the row is empty.
- Accepting a table no longer retires every card below it. An open-ended field
  ("A23:F") runs to the bottom of the sheet, and containment pruning treated that
  as covering everything underneath; a field and a suggestion now have to start
  inside each other.
Accepting a card removes it by suggestion id, which only covers the drawer path
that started from that card. A field carrying the suggestion's name answers it
wherever it points, so the list is pruned by name as well as by region.
@iamgp
iamgp merged commit ebdaeeb into main Sep 17, 2026
6 checks passed
@iamgp
iamgp deleted the feat/scan-extract-pairs-v2 branch September 17, 2026 16:11
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