Skip to content

OpenConceptLab/ocl_issues#2908 | One recommendation rule: the OCL AI Assistant decides when it has analysed the current candidates, the score orders the rest - #110

Open
paynejd wants to merge 9 commits into
mainfrom
OpenConceptLab/ocl_issues#2908-ai-first-rule
Open

paynejd wants to merge 9 commits into
mainfrom
OpenConceptLab/ocl_issues#2908-ai-first-rule

Conversation

@paynejd

@paynejd paynejd commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Implements OpenConceptLab/ocl_issues#2908 and closes OpenConceptLab/ocl_issues#2333. The reranker refactor and the broader reasoning layer stay in OpenConceptLab/ocl_issues#2593.

The rule. If the OCL AI Assistant has analysed the row's current candidates, it decides what is proposed, and the score orders everything else. If it hasn't, the score decides, as before. The rule is one function, getRowRecommendation in viewBuilders.js. The panel's order, the grid's columns and filter, and AutoMatch's proposal all read its result, so the logic lives in one place.

Commits

  1. The rule (getRowRecommendation), with a test for each row of the ticket's table. The replay over saved projects is on the ticket (aggregates only).
  2. Panel and grid.
    • Match Quality, sorted by score, starts with "OCL AI Assistant's pick" and "Alternates", then the score buckets.
    • A summary line gives the verdict, the pick and the top score, and says whether they differ. "Read analysis" opens the full explanation.
    • Group → AI Analysis is removed.
    • The grid gets an "OCL AI Assistant" column: the agreement state, running or failed (click to retry), sortable and filterable, with the other side in its tooltip.
    • An "OCL AI Assistant disagrees (n)" filter sits next to the Match Quality buckets.
    • Target Code shows the AI icon only on the AI's pick, and the orange icon is gone. Match Quality stays score-only.
  3. AutoMatch.
    • The proposal follows the rule, using any current analysis.
    • When the run's AI step returns for a row, the row is decided again, unless the user changed it after the run set it.
    • An analysis run on one row changes no mapping.
    • The row log records chosen_by (ai_assistant | score) and the verdict. A row left unmapped by REJECT, by INSUFFICIENT, or by a pick that isn't a candidate is logged as auto_match_no_proposal.

Choices where the ticket is silent

  • Latest analysis: the latest analysis decides. An unknown or missing verdict counts as no analysis.
  • Out of date: the score rule applies, but the pick and alternates are still shown.
  • REJECT and INSUFFICIENT: they ignore any primary candidate. Their alternates still lead the order.
  • A pick that isn't a target-repo candidate, including CONDITIONAL with no primary: the score rule applies. The state is "Pick isn't a candidate", it counts in the disagreement filter, and it is logged.
  • "Agrees": the pick ties the top score, or no candidate has a score yet.
  • "Read analysis": the full explanation now opens on "Read analysis" or a new run. The summary line stands in for it otherwise.
  • The AI column: it shows for users who can run the OCL AI Assistant, or on a project that has analyses.

Also fixed on the way

  • A withdrawn proposal stayed Proposed. When AutoMatch left a previously Proposed row Unmapped, the row stayed in the Proposed list with no target. Under the rule this is the routine REJECT-over-Recommended case, so the shared apply function now removes it.
  • One concept, two URLs. A code containing & reaches the grid encoded once (%26) from one place and twice (%2526) from another. The Target Code icon compares URLs decoded.

Tests

  • Unit: recommendationRule.test.js (40 tests) and rowQuality.test.js (the new per-row cache). npm run test:all gives 603 unit and 6 integration tests, all passing, after merging main (e5e5ce6). CI's lint passes.
  • A real project, rendered offline: the branch and main were built and run against a local mock API serving a read-only copy of a 20-row production project with current analyses. This checked:
    • the panel's sections and summary line;
    • the AI column, its tooltip and the disagreement filter;
    • the Target Code icon;
    • "Read analysis".
  • AutoMatch, live in a mock (no production traffic): 7 rows, two algorithms and rerank, with the AI stubbed per row. Results:
    • RECOMMEND of another candidate → the pick is proposed.
    • REJECT and INSUFFICIENT over Recommended → withdrawn, Unmapped.
    • RECOMMEND of the top score → kept, with chosen_by: ai_assistant.
    • CONDITIONAL in Available → proposed.
    • A failed call → the score's proposal stays.
    • A row the user un-mapped while its AI answer was held → left alone.
    • A single-row analysis afterwards → reorders the panel, mapping unchanged.
    • A second run over current analyses → the AI is called only for the row whose analysis was missing, and the rule decides the rest from the saved analyses.
    • An AI-only run on an approved row the AI rejects → Unmapped, no longer Approved.
    • A pick from another source that shares a target candidate's code → "Pick isn't a candidate", and the score's proposal stays (logged).

Review

  • Codex, 3 passes (each posted below as a review):
    • Pass 1: 2 P1 and 2 P2, all fixed in de6c38d. An AI-only run kept the approval of a row it re-proposed. A later proposal in the same run could overwrite a user's edit. A foreign pick could match a target candidate by code. A pick that isn't a candidate wasn't logged when the proposal stayed the same.
    • Pass 2: all of pass 1 resolved. 1 P1 and 2 P2, fixed in 7dbbb12. The disagreement filter could look selected while filtering nothing. Two targets without URLs looked the same to the user-change check. Saved analyses were hidden where the AI Assistant isn't configured.
    • Pass 3: all resolved. 1 P1 and 1 P2, fixed in 17f2698. Badges stayed on an older analysis after its explanation was closed. A row without candidates lost its out-of-date analysis.
  • Simplification review: 4 possible cuts. 1 applied (6e713b6). 3 declined, two of them because tests the acceptance criteria require.
  • Codex check of 7dbbb12..6e713b6: clean.
  • Codex check of 6e713b6..e5e5ce6 (the merge of main, which brought in the AutoMatch dialog rework): clean.

Docs

The rule is documented in the Mapper pipeline docs, an acceptance criterion. They're private team docs.

Still open

  • An AI-only AutoMatch run on a project whose analyses are all current is blocked as "no rows to analyse". So re-proposing an already-analysed project by the rule needs a run that retrieves candidates. Not changed here.
  • The single-algorithm case of F1.2 (an edit between the batch's proposal and the rerank sweep's) is checked by reading the code, not end to end.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm

paynejd and others added 3 commits October 20, 2026 16:16
… AI Assistant decides when it has analysed the current candidates, the score orders the rest

getRowRecommendation returns a row's candidate order (the AI's pick, its
alternates by score, then today's score buckets), what to propose and who
chose it, and how the AI and the score agree. RECOMMEND and CONDITIONAL
propose the AI's pick in any score bucket; REJECT and INSUFFICIENT propose
nothing, even over a Recommended score. Without an analysis of the current
candidate pool (the fingerprint check Auto Match already uses), the top score
is proposed if it's Recommended, as before. A pick that isn't a target-repo
candidate falls back to the score rule.

Not wired in yet: the panel, the grid and Auto Match follow in the next
commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TGqXPeHFfYAdqAr7rmXwCv
…dates panel and the grid

Match Quality, sorted by score, now follows getRowRecommendation: the OCL AI
Assistant's pick and its alternates first when it has analysed the row's
current candidates, then the score buckets. A summary line gives the AI's
verdict, its pick and the top score, and says whether they differ; "Read
analysis" opens the full explanation. Group > AI Analysis is gone.

The grid gains an "OCL AI Assistant" column (agreement state, running or
failed, sortable and filterable, with the other side in its tooltip) and an
"OCL AI Assistant disagrees (n)" filter. Target Code shows the AI icon only
on the AI's pick, comparing URLs decoded, since a code with "&" arrives
encoded once or twice. Match Quality stays score-only. Badges read "OCL AI
Assistant's pick" and "OCL AI Assistant alternate". New strings in en, es,
zh and pt-BR.

A per-row cache replaces the best-candidate-score cache and also keys on the
row's analyses; the candidate-pool fingerprint is built only for rows with
analyses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm
…tion rule

AutoMatch's proposal for a row now comes from getRowRecommendation, with any
current analysis the row has: the OCL AI Assistant's pick when it has analysed
the row's current candidates, nothing on REJECT or INSUFFICIENT, and the top
score if Recommended otherwise, as before. setAutoMatched and the
single-algorithm batch path share one function; pickTopRowView is gone.

When the run's AI step returns for a row, the row is decided again, unless the
user changed it after the run set it. An analysis the user runs on one row
reorders its candidates and updates the grid but changes no mapping. A
withdrawn proposal no longer stays Proposed.

The row's log records who chose (chosen_by: ai_assistant | score) and the
verdict; a REJECT, INSUFFICIENT or a pick that isn't a candidate that leaves
the row unmapped is logged as auto_match_no_proposal.

closes OpenConceptLab/ocl_issues#2908, closes OpenConceptLab/ocl_issues#2333

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm
@paynejd paynejd added type/feature New or improved functionality component/mapper Mapping tools labels Oct 20, 2026
…an approval it replaces, later proposals keep the user's edits, a foreign pick isn't a target concept

- A row the run decides is no longer Approved. A full run clears approvals
  when it starts; an AI-only run didn't, so a re-proposed row kept showing
  Approved. (F1.1)
- A row the run already proposed and the user changed since is left alone by
  the run's later proposals too, such as the single-algorithm rerank sweep.
  The run's view of the row is updated as it applies a proposal, so the check
  doesn't wait for a render. (F1.2)
- The AI's pick is matched by code only within its own source, so another
  source's concept with the same code isn't taken for a target candidate.
  (F1.3)
- A pick that isn't a candidate is logged when the AI step returns, also when
  the score's proposal stays the same. (F1.4)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm

@paynejd paynejd left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Codex review, pass 1 (codex-cli 0.163.0, commit 11c3394)

Verdict: findings, 4 finding(s).

  • F1.1 (P1) src/components/map-projects/MapProject.jsx:2386: An AI-only run can replace an approved mapping while retaining its approval. → Fixed in de6c38d: a row the run decides is no longer Approved, as a full run already clears approvals when it starts. Checked live with an AI-only run on an approved row.
  • F1.2 (P1) src/components/map-projects/MapProject.jsx:2406: Repeated proposals bypass the protection for rows the user changed during the run. → Fixed in de6c38d: the run's later proposals, such as the single-algorithm rerank sweep, leave alone a row the user changed since the run proposed it. The run's view of the row is updated as it applies a proposal.
  • F1.3 (P2) src/components/map-projects/viewBuilders.js:629: Code-only fallback can turn a non-target AI pick into a different target-repository concept. → Fixed in de6c38d: the code fallback matches only within the AI candidate's own source, with tests for both cases. Checked live with a foreign concept that shares a target code.
  • F1.4 (P2) src/components/map-projects/MapProject.jsx:2418: The unchanged-proposal shortcut suppresses the required invalid-pick diagnostic. → Fixed in de6c38d: a pick that isn't a candidate goes through the apply step and is logged even when the proposal is unchanged. Checked live.
findings.json
{
  "reviewer": "codex-cli 0.163.0",
  "perspective": "defects",
  "mode": "full",
  "commit": "11c339493c05a32d9183c14be5fc820e1d64ab3c",
  "verdict": "findings",
  "prior": [],
  "findings": [
    {
      "id": "F1.1",
      "severity": "P1",
      "tag": null,
      "file": "src/components/map-projects/MapProject.jsx",
      "line": 2386,
      "claim": "An AI-only run can replace an approved mapping while retaining its approval.",
      "scenario": "Run AutoMatch without Retrieve Candidates on selected approved rows, or with All Including Approved. AI-only runs skip clearing reviewed statuses. When the AI picks another target, proposeRowMapping changes the target and adds readyForReview but leaves reviewed intact. The grid and exports therefore treat the new, unreviewed mapping as Approved. REJECT similarly leaves an unmapped row marked Approved.",
      "action": "Clear the row's approval when AutoMatch replaces or withdraws its mapping, and keep the recorded run snapshot consistent. Test both outcomes on an approved row in an AI-only run.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F1.2",
      "severity": "P1",
      "tag": null,
      "file": "src/components/map-projects/MapProject.jsx",
      "line": 2406,
      "claim": "Repeated proposals bypass the protection for rows the user changed during the run.",
      "scenario": "In a single-algorithm run, an early batch proposes a target. The user changes that mapping while later batches run. The subsequent bulk rerank sweep calls setAutoMatched again, unconditionally overwriting the user's edit and replacing runRowMappingsRef with that new proposal. When the AI returns, the equality guard passes and changes the mapping again. The required preservation of the user's edit fails.",
      "action": "Apply the mapping-change guard before subsequent proposals from the same run, including the rerank sweep, and do not replace the saved run snapshot when a user edit is detected.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F1.3",
      "severity": "P2",
      "tag": null,
      "file": "src/components/map-projects/viewBuilders.js",
      "line": 629,
      "claim": "Code-only fallback can turn a non-target AI pick into a different target-repository concept.",
      "scenario": "The AI names a concept from another repository whose code also exists among the target candidates. Its concept_key does not match a target view, but the fallback ignores the repository and matches the shared code. AutoMatch then proposes that target concept as the AI's pick, potentially below Recommended, instead of applying the score fallback and recording pick_not_candidate.",
      "action": "Validate repository identity as well as code during fallback resolution. A known foreign concept_key must not resolve to a target concept solely because their codes match. Add a cross-repository code-collision test.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F1.4",
      "severity": "P2",
      "tag": null,
      "file": "src/components/map-projects/MapProject.jsx",
      "line": 2418,
      "claim": "The unchanged-proposal shortcut suppresses the required invalid-pick diagnostic.",
      "scenario": "AutoMatch first proposes the top score. Its AI step then returns a non-candidate pick, including CONDITIONAL with a null primary. The rule correctly falls back to the same score proposal, but URL and chosenBy are unchanged, so this return prevents getAutoMatchLogExtras from recording ai_pick_not_candidate. The raw analysis is logged, but the invalid-pick fallback is not identified.",
      "action": "Record the invalid-pick fallback independently of whether the mapping changes, while retaining the shortcut for redundant mapping updates. Test this through the re-decision path.",
      "lines_delta": null,
      "confidence": "high"
    }
  ]
}

…r hides at zero, URL-less targets are told apart, saved analyses show without the AI service

- The "OCL AI Assistant disagrees" filter shows only while some row
  disagrees, like the Match Quality buckets, so it can't look selected while
  filtering nothing. (F2.1)
- A mapped target without a URL (a bridge target before its lookup) is told
  apart by its source and code, so the run's check for a row the user
  changed sees a switch between two such targets. (F2.2)
- The OCL AI Assistant column, its filter and the Target Code icon show on a
  project with analyses also where the AI Assistant isn't configured; the
  panel and AutoMatch already use those analyses. (F2.3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm

@paynejd paynejd left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Codex review, pass 2 (codex-cli 0.163.0, commit de6c38d)

Verdict: findings, 3 finding(s); earlier findings: F1.1 resolved, F1.2 resolved, F1.3 resolved, F1.4 resolved.

  • F2.1 (P1) src/components/map-projects/MapProject.jsx:3249: The disagreement filter stops filtering while remaining visibly selected. → Fixed in 7dbbb12: the chip shows only while some row disagrees, like the Match Quality buckets, and the filter applies only then.
  • F2.2 (P2) src/components/map-projects/autoMatchRows.js:185: The mapping guard cannot distinguish different targets that lack URLs, allowing AutoMatch to overwrite a user's change. → Fixed in 7dbbb12: a mapped target without a URL is identified by its source and code (getMappingTarget), with a test for switching between two such targets.
  • F2.3 (P2) src/components/map-projects/MapProject.jsx:3267: Saved analyses do not expose the AI column when the AI service is unconfigured. → Fixed in 7dbbb12: the column, its filter and the Target Code icon show on a project with analyses, configured or not. Running and retrying stay with users who can run the AI.
findings.json
{
  "reviewer": "codex-cli 0.163.0",
  "perspective": "defects",
  "mode": "verify",
  "commit": "de6c38d784ee8b7830bf2e81f14690d00082bde2",
  "verdict": "findings",
  "prior": [
    {
      "id": "F1.1",
      "status": "resolved"
    },
    {
      "id": "F1.2",
      "status": "resolved"
    },
    {
      "id": "F1.3",
      "status": "resolved"
    },
    {
      "id": "F1.4",
      "status": "resolved"
    }
  ],
  "findings": [
    {
      "id": "F2.1",
      "severity": "P1",
      "tag": null,
      "file": "src/components/map-projects/MapProject.jsx",
      "line": 3249,
      "claim": "The disagreement filter stops filtering while remaining visibly selected.",
      "scenario": "Select the disagreement filter, then rerun analysis so the last disagreement becomes Agrees or Out of date. aiDisagreesCount becomes zero, bypassing the filter and displaying every otherwise-visible row, while the chip remains filled and selected with count zero. Reproduced using the actual getRows function.",
      "action": "Apply the predicate whenever the filter is selected, or explicitly deactivate the filter when its count reaches zero so its displayed state matches its behavior.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F2.2",
      "severity": "P2",
      "tag": null,
      "file": "src/components/map-projects/autoMatchRows.js",
      "line": 185,
      "claim": "The mapping guard cannot distinguish different targets that lack URLs, allowing AutoMatch to overwrite a user's change.",
      "scenario": "A run proposes bridge target A whose lookup has not supplied an ocl_url. The user maps target B, also without a URL, keeping the same map type and approval state. Both snapshots have url:null, so isSameRowMapping returns true. When the AI returns REJECT, proposeRowMappingAfterAI removes the user's mapping to B. This was reproduced with the actual re-decision function.",
      "action": "Include a stable target identity, such as source/repository plus concept code when the URL is absent, in mapping snapshots and comparisons. Add coverage for changing between two URL-less targets.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F2.3",
      "severity": "P2",
      "tag": null,
      "file": "src/components/map-projects/MapProject.jsx",
      "line": 3267,
      "claim": "Saved analyses do not expose the AI column when the AI service is unconfigured.",
      "scenario": "Open a project containing saved analyses after AI_ASSISTANT_API_URL has been removed or on an installation without that setting. The outer URL check hides the AI column and forces the disagreement count to zero, although the recommendation rule still uses those analyses. This violates the stated choice to show the column on projects with analyses.",
      "action": "Show analysis-derived indicators independently of service configuration; gate only the ability to run or retry analysis. Apply the same distinction to the Target Code pick icon.",
      "lines_delta": null,
      "confidence": "high"
    }
  ]
}

…sis returns the badges to the latest, a row without candidates keeps its out-of-date analysis

- Closing the full explanation after paging back to an older analysis
  returns the candidate badges to the latest analysis, which the order, the
  summary line and the grid follow. (F3.1)
- A row whose candidates a refresh cleared, with saved analyses, still gets
  a recommendation: Out of date, with its summary line, rather than nothing.
  (F3.2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm

@paynejd paynejd left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Codex review, pass 3 (codex-cli 0.163.0, commit 7dbbb12)

Verdict: findings, 2 finding(s); earlier findings: F1.1 resolved, F1.2 resolved, F1.3 resolved, F1.4 resolved, F2.1 resolved, F2.2 resolved, F2.3 resolved.

  • F3.1 (P1) src/components/map-projects/Candidates.jsx:640: Closing analysis history leaves candidate badges identifying an older pick. → Fixed in 17f2698: closing the explanation returns the analysis page, and so the candidate badges, to the latest analysis.
  • F3.2 (P2) src/components/map-projects/rowQuality.js:111: The recommendation cache hides saved analyses when candidate state is absent. → Fixed in 17f2698: the per-row cache computes a row that has analyses but no candidate state, so the row shows Out of date with its summary line. Covered by a new cache test.
findings.json
{
  "reviewer": "codex-cli 0.163.0",
  "perspective": "defects",
  "mode": "verify",
  "commit": "7dbbb12b4de5fd70c0e2775be310e6bd6d4935bf",
  "verdict": "findings",
  "prior": [
    {
      "id": "F1.1",
      "status": "resolved"
    },
    {
      "id": "F1.2",
      "status": "resolved"
    },
    {
      "id": "F1.3",
      "status": "resolved"
    },
    {
      "id": "F1.4",
      "status": "resolved"
    },
    {
      "id": "F2.1",
      "status": "resolved"
    },
    {
      "id": "F2.2",
      "status": "resolved"
    },
    {
      "id": "F2.3",
      "status": "resolved"
    }
  ],
  "findings": [
    {
      "id": "F3.1",
      "severity": "P1",
      "tag": null,
      "file": "src/components/map-projects/Candidates.jsx",
      "line": 640,
      "claim": "Closing analysis history leaves candidate badges identifying an older pick.",
      "scenario": "An older analysis picks A and the latest picks B. Open Read analysis, navigate to the older entry, then close it. analysisPage remains on the older entry, so A retains the pick badge while the summary, pick section and grid identify B. This persists after the historical explanation is hidden.",
      "action": "Reset analysisPage to the latest entry when closing the explanation, so the visible badges agree with the current recommendation.",
      "lines_delta": null,
      "confidence": "high"
    },
    {
      "id": "F3.2",
      "severity": "P2",
      "tag": null,
      "file": "src/components/map-projects/rowQuality.js",
      "line": 111,
      "claim": "The recommendation cache hides saved analyses when candidate state is absent.",
      "scenario": "Refresh an analysed row in a single-algorithm project, and let the matching service remain throttled. Refresh deletes the row state, and the throttled response does not recreate it. This early return suppresses the saved analysis: the AI column becomes empty instead of Out of date, and the summary and Read analysis link disappear.",
      "action": "Compute the recommendation for rows with saved analyses even when rowState is absent, using an empty candidate list and an unavailable fingerprint. Add coverage for this cache case.",
      "lines_delta": null,
      "confidence": "high"
    }
  ]
}

…e to viewBuilders.js

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm

@paynejd paynejd left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Simplification review (claude subagent, commit 17f2698)

4 possible cut(s):

Codex check of commits 7dbbb12..6e713b6 (codex-cli 0.163.0): the pass 3 fixes and the S1 cut.

Verdict: clean.

simplify.json and check.json
{
  "reviewer": "claude subagent",
  "perspective": "simplify",
  "mode": "full",
  "commit": "17f26981d73a6b15e17f57986368aed7b29cb4b0",
  "verdict": "findings",
  "prior": [],
  "findings": [
    {
      "id": "S1",
      "severity": null,
      "tag": "shrink",
      "file": "src/components/map-projects/viewBuilders.js",
      "line": 734,
      "claim": "AI_DISAGREEMENTS is exported but only isAIDisagreement uses it, in the same file.",
      "action": "Make it a non-exported const, or inline the array into isAIDisagreement.",
      "lines_delta": -1,
      "confidence": "high",
      "scenario": null
    },
    {
      "id": "S2",
      "severity": null,
      "tag": "shrink",
      "file": "src/components/map-projects/autoMatchRows.js",
      "line": 162,
      "claim": "getAutoMatchLogExtras returns the same shape from three branches; the early pick_not_candidate branch only differs by a flag.",
      "action": "One return: { chosen_by, ...(chosenBy === 'ai_assistant' || agreement === 'pick_not_candidate' ? { verdict } : {}), ...(chosenBy === 'ai_assistant' && confidence ? { confidence } : {}), ...(agreement === 'pick_not_candidate' ? { ai_pick_not_candidate: true } : {}) }, or drop the log key if no consumer reads it.",
      "lines_delta": -3,
      "confidence": "low",
      "scenario": null
    },
    {
      "id": "S3",
      "severity": null,
      "tag": "dup-test",
      "file": "src/components/map-projects/__tests__/recommendationRule.test.js",
      "line": 232,
      "claim": "The INSUFFICIENT test repeats the REJECT test at line 214 with a different verdict string; the code path is the same table lookup.",
      "action": "Fold into one parameterised test over ['REJECT','INSUFFICIENT'] checking agreement no_match/not_enough.",
      "lines_delta": -6,
      "confidence": "medium",
      "scenario": null
    },
    {
      "id": "S4",
      "severity": null,
      "tag": "dup-test",
      "file": "src/components/map-projects/__tests__/recommendationRule.test.js",
      "line": 185,
      "claim": "The Low Ranked pick test asserts the same proposal, chosenBy, agreement and pickRank as the different-pick test at line 168, with only the score data changed.",
      "action": "Merge into the line 168 test by adding the Low Ranked data as a second case.",
      "lines_delta": -6,
      "confidence": "low",
      "scenario": null
    }
  ]
}
{
  "reviewer": "codex-cli 0.163.0",
  "perspective": "defects",
  "mode": "cuts",
  "commit": "6e713b64f44ee400c0d0b004ddb0ba6a19ba4d27",
  "verdict": "clean",
  "prior": [
    {
      "id": "F3.1",
      "status": "resolved"
    },
    {
      "id": "F3.2",
      "status": "resolved"
    }
  ],
  "findings": []
}

@paynejd
paynejd requested a review from snyaggarwal October 21, 2026 10:33
…nceptLab/ocl_online#465)

Conflicts: the autoMatchRows import list and the assistant-naming test's key
list; both keep main's entries and add this PR's. The run snapshot now follows
main's runRows, which are worked out before the run starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm

@paynejd paynejd left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Codex check of commits 6e713b6..e5e5ce6 (codex-cli 0.163.0, commit e5e5ce6): the merge of main, which brings in the AutoMatch dialog rework (OpenConceptLab/ocl_online#465). Only the two import and key lists conflicted. The check looked for places where main's run-row selection and status clearing combine wrongly with this PR's run snapshot and proposals.

Verdict: clean. Lint and CI pass, as do 603 unit and 6 integration tests, and the production build succeeds.

merge-check.json
{
  "reviewer": "codex-cli 0.163.0",
  "perspective": "defects",
  "mode": "cuts",
  "commit": "e5e5ce6c53c0850d316a0fdd142a18113e0580b2",
  "verdict": "clean",
  "prior": [],
  "findings": []
}

…nt, token segregation)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@snyaggarwal snyaggarwal left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tried this locally on a project whose first row has a precomputed analysis. The grid behaves as the rule says. Row 1 is mapped to CIEL:168270 (the top score). The OCL AI Assistant picked CIEL:1065, but the candidates have changed since, so the column reads "Out of date" and Target Code has no AI icon. That's correct under the rule, but two things make that row easy to miss (screenshots in the next comment):

  1. The "Out of date" tooltip doesn't name the pick. Other states put "the other side" in the tooltip; this one only says the score decides. In the grid you can't tell the AI preferred something else without opening the row.
  2. "Out of date" rows aren't in the "OCL AI Assistant disagrees" filter. These are the rows where an older analysis disagreed and nobody has looked again, so they're arguably the ones a reviewer most wants to find. If keeping them out is deliberate, a separate "Out of date (n)" filter would help.

Neither blocks the PR.

Screenshots:
image
image


// The tooltip names the other side: the top score, and where the pick ranks.
const getStatusTooltip = (t, status, recommendation, candidatesScore) => {
if(status === 'out_of_date') return t('map_project.ai_out_of_date_help')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The out-of-date tooltip could include the old pick and its rank, as different_pick does, e.g. "Picked CIEL:1065 (rank 3); the candidates changed since, so the score decides". recommendation.pick and pickRank are still set for out-of-date analyses, so the data is already here.

}

// The agreement states the grid's "OCL AI Assistant disagrees" filter shows.
const AI_DISAGREEMENTS = ['different_pick', 'pick_not_candidate', 'no_match', 'not_enough']

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI_DISAGREEMENTS leaves out out_of_date, even when the old pick differs from the mapped target. Worth either counting those rows here (when pick isn't the top) or giving them their own filter.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/mapper Mapping tools type/feature New or improved functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OCL should consider AI Assistant response in auto-match mapping selection

2 participants