Follow-up to #359
Issue #359 fixed the most common Concept-demotion trigger: the per-chunk glossary_definitions path unconditionally overwriting an existing entity's label to "Definition". That fix lands in PR (in flight at time of filing).
A code-review subagent on the #359 PR flagged two residual triggers of the SAME bug class that are out of scope for #359 but still suppress purple Concept badges in less common situations. Filing this issue so the work isn't lost.
Residual trigger 1 — pre-loop definitions parameter
backend/src/requirements_graphrag_api/core/context.py:200-221 runs BEFORE the per-chunk loop:
```python
if definitions:
for defn in definitions:
if defn.get("score", 0) >= 0.5:
...
all_entities[defn["term"]] = {
"definition": defn.get("definition"),
"label": "Definition", # <-- hardcoded
}
```
If a chat-flow caller ever passes the definitions parameter (currently the orchestrator at core/agentic/orchestrator.py:163 does NOT pass it — only the structured-search path may), entries land first as label "Definition". When the per-chunk entity-path subsequently encounters the same name as a :Concept graph node, the if name not in all_entities check at line 235 is False, the elif at line 237 only updates definition (never label), and the Concept label is silently suppressed.
Residual trigger 2 — cross-chunk first-wins ordering
Within the per-chunk loop, if chunk N's glossary_definitions populates {label: \"Definition\"} for a term first, chunk N+1's :Concept entity with the same name cannot upgrade the label. Same root cause as Trigger 1: the entity-path's elif at line 237-238 never updates label.
Why this is a separate issue from #359
#359 fixed the unconditional STOMP on existing entries (line 253). These two residuals are about FIRST-WRITER-WINS preventing a Concept upgrade. Different mechanism, same outcome.
Suggested fix sketch (not committing to an approach)
Make the entity-path's elif (line 237-238) ALSO upgrade the label when the new label is more specific than the existing one. "Definition" should be considered the LEAST specific label (it's a fallback for glossary-only entries); any actual Neo4j node label (Concept, Standard, Bestpractice, Challenge, Methodology, Artifact, Tool, Role, Processstage, Industry, Organization, Outcome) should win over Definition.
Pseudocode:
```python
elif definition and not all_entities[name].get("definition"):
all_entities[name]["definition"] = definition
elif all_entities[name].get("label") == "Definition" and label != "Definition":
all_entities[name]["label"] = label # upgrade Definition -> Concept/Standard/etc.
```
This needs a regression test asserting the upgrade direction works correctly without re-introducing the demotion direction.
HITL gates
Same as #359: backend-only, no preview verification path, post-merge production check by user.
Workflow
Defer until #359 ships and is production-verified. Then re-evaluate based on whether either trigger is firing in practice (data probe production Neo4j; check if definitions arg is being passed anywhere in the chat flow; check if cross-chunk ordering produces demotions for any Concept the user actually retrieves).
If neither trigger is firing in practice, this issue can be closed as not-reproducible-in-production with a note that the code paths still have the latent bug and would need addressing if a future caller adds the trigger.
Follow-up to #359
Issue #359 fixed the most common Concept-demotion trigger: the per-chunk glossary_definitions path unconditionally overwriting an existing entity's label to "Definition". That fix lands in PR (in flight at time of filing).
A code-review subagent on the #359 PR flagged two residual triggers of the SAME bug class that are out of scope for #359 but still suppress purple Concept badges in less common situations. Filing this issue so the work isn't lost.
Residual trigger 1 — pre-loop
definitionsparameterbackend/src/requirements_graphrag_api/core/context.py:200-221runs BEFORE the per-chunk loop:```python
if definitions:
for defn in definitions:
if defn.get("score", 0) >= 0.5:
...
all_entities[defn["term"]] = {
"definition": defn.get("definition"),
"label": "Definition", # <-- hardcoded
}
```
If a chat-flow caller ever passes the
definitionsparameter (currently the orchestrator atcore/agentic/orchestrator.py:163does NOT pass it — only the structured-search path may), entries land first as label "Definition". When the per-chunk entity-path subsequently encounters the same name as a:Conceptgraph node, theif name not in all_entitiescheck at line 235 is False, the elif at line 237 only updates definition (never label), and the Concept label is silently suppressed.Residual trigger 2 — cross-chunk first-wins ordering
Within the per-chunk loop, if chunk N's
glossary_definitionspopulates{label: \"Definition\"}for a term first, chunk N+1's:Conceptentity with the samenamecannot upgrade the label. Same root cause as Trigger 1: the entity-path's elif at line 237-238 never updates label.Why this is a separate issue from #359
#359 fixed the unconditional STOMP on existing entries (line 253). These two residuals are about FIRST-WRITER-WINS preventing a Concept upgrade. Different mechanism, same outcome.
Suggested fix sketch (not committing to an approach)
Make the entity-path's elif (line 237-238) ALSO upgrade the label when the new label is more specific than the existing one. "Definition" should be considered the LEAST specific label (it's a fallback for glossary-only entries); any actual Neo4j node label (Concept, Standard, Bestpractice, Challenge, Methodology, Artifact, Tool, Role, Processstage, Industry, Organization, Outcome) should win over Definition.
Pseudocode:
```python
elif definition and not all_entities[name].get("definition"):
all_entities[name]["definition"] = definition
elif all_entities[name].get("label") == "Definition" and label != "Definition":
all_entities[name]["label"] = label # upgrade Definition -> Concept/Standard/etc.
```
This needs a regression test asserting the upgrade direction works correctly without re-introducing the demotion direction.
HITL gates
Same as #359: backend-only, no preview verification path, post-merge production check by user.
Workflow
Defer until #359 ships and is production-verified. Then re-evaluate based on whether either trigger is firing in practice (data probe production Neo4j; check if
definitionsarg is being passed anywhere in the chat flow; check if cross-chunk ordering produces demotions for any Concept the user actually retrieves).If neither trigger is firing in practice, this issue can be closed as not-reproducible-in-production with a note that the code paths still have the latent bug and would need addressing if a future caller adds the trigger.