feat: added rule to prevent one belief being connected to both inferredBelief inputs

ref: N25B-433
This commit is contained in:
JGerla
2026-01-13 11:32:23 +01:00
parent f8f0f12128
commit 3d6e065dd5
7 changed files with 51 additions and 27 deletions

View File

@@ -43,3 +43,4 @@ export const noSelfConnections : HandleRule =
} }

View File

@@ -10,6 +10,7 @@ import {allowOnlyConnectionsFromType} from "../HandleRules.ts";
import useFlowStore from '../VisProgStores.tsx'; import useFlowStore from '../VisProgStores.tsx';
import { TextField } from '../../../../components/TextField.tsx'; import { TextField } from '../../../../components/TextField.tsx';
import { MultilineTextField } from '../../../../components/MultilineTextField.tsx'; import { MultilineTextField } from '../../../../components/MultilineTextField.tsx';
import {noMatchingLeftRightBelief} from "./BeliefGlobals.ts";
/** /**
* The default data structure for a BasicBelief node * The default data structure for a BasicBelief node
@@ -185,7 +186,8 @@ export default function BasicBeliefNode(props: NodeProps<BasicBeliefNode>) {
</div> </div>
)} )}
<MultiConnectionHandle type="source" position={Position.Right} id="source" rules={[ <MultiConnectionHandle type="source" position={Position.Right} id="source" rules={[
allowOnlyConnectionsFromType(["norm", "trigger"]), allowOnlyConnectionsFromType(["norm", "trigger", "inferred_belief"]),
noMatchingLeftRightBelief
]}/> ]}/>
</div> </div>
</> </>

View File

@@ -0,0 +1,25 @@
import { type Node } from '@xyflow/react';
import {type HandleRule, ruleResult} from "../HandleRuleLogic.ts";
import useFlowStore from "../VisProgStores.tsx";
import {BasicBeliefReduce} from "./BasicBeliefNode.tsx";
import {type InferredBeliefNodeData, InferredBeliefReduce} from "./InferredBeliefNode.tsx";
export function BeliefGlobals(beliefNode: Node, nodes: Node[]) {
switch (beliefNode.type) {
case 'basic_belief':
return BasicBeliefReduce(beliefNode, nodes);
case 'inferred_belief':
return InferredBeliefReduce(beliefNode, nodes);
}
}
export const noMatchingLeftRightBelief : HandleRule = (connection, _)=> {
const { nodes } = useFlowStore.getState();
const thisNode = nodes.find(node => node.id === connection.target && node.type === 'inferred_belief');
if (!thisNode) return ruleResult.satisfied;
const iBelief = (thisNode.data as InferredBeliefNodeData).inferredBelief;
return (iBelief.left === connection.source || iBelief.right === connection.source)
? ruleResult.notSatisfied("Connecting one belief to both input handles of an inferred belief node is not allowed")
: ruleResult.satisfied;
}

View File

@@ -1,12 +0,0 @@
import { type Node } from '@xyflow/react';
import {BasicBeliefReduce} from "./BasicBeliefNode.tsx";
import {InferredBeliefReduce} from "./InferredBeliefNode.tsx";
export function BeliefReduce(beliefNode: Node, nodes: Node[]) {
switch (beliefNode.type) {
case 'basic_belief':
return BasicBeliefReduce(beliefNode, nodes);
case 'inferred_belief':
return InferredBeliefReduce(beliefNode, nodes);
}
}

View File

@@ -7,7 +7,7 @@ import {
import {useState} from "react"; import {useState} from "react";
import { Toolbar } from '../components/NodeComponents.tsx'; import { Toolbar } from '../components/NodeComponents.tsx';
import {type HandleRule, type RuleResult, ruleResult} from "../HandleRuleLogic.ts"; import {type HandleRule, type RuleResult, ruleResult} from "../HandleRuleLogic.ts";
import {BeliefReduce} from "./BeliefReduce.ts"; import {BeliefGlobals, noMatchingLeftRightBelief} from "./BeliefGlobals.ts";
import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx"; import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx";
import {allowOnlyConnectionsFromType} from "../HandleRules.ts"; import {allowOnlyConnectionsFromType} from "../HandleRules.ts";
import useFlowStore from "../VisProgStores.tsx"; import useFlowStore from "../VisProgStores.tsx";
@@ -30,7 +30,7 @@ export type InferredBeliefNodeData = {
* and a left and right BeliefNode (can be both an inferred and a basic belief) * and a left and right BeliefNode (can be both an inferred and a basic belief)
* in the form of their corresponding id's * in the form of their corresponding id's
*/ */
type InferredBelief = { export type InferredBelief = {
left: string | undefined, left: string | undefined,
operator: boolean, operator: boolean,
right: string | undefined, right: string | undefined,
@@ -106,12 +106,16 @@ const noBeliefCycles : HandleRule = (connection, _): RuleResult => {
* @returns {RuleResult} * @returns {RuleResult}
*/ */
function checkForCycle(targetNodeId: string, currentNodeId: string) : RuleResult { function checkForCycle(targetNodeId: string, currentNodeId: string) : RuleResult {
const outgoingBeliefs = getOutgoers({id: currentNodeId}, nodes, edges).filter(node => node.type === 'inferred_belief'); const outgoingBeliefs = getOutgoers({id: currentNodeId}, nodes, edges)
.filter(node => node.type === 'inferred_belief');
if (outgoingBeliefs.length === 0) return ruleResult.satisfied; if (outgoingBeliefs.length === 0) return ruleResult.satisfied;
if (outgoingBeliefs.some(node => node.id === targetNodeId)) return ruleResult if (outgoingBeliefs.some(node => node.id === targetNodeId)) return ruleResult
.notSatisfied(defaultErrorMessage); .notSatisfied(defaultErrorMessage);
const next = outgoingBeliefs.map(node => checkForCycle(targetNodeId, node.id)).find(result => !result.isSatisfied); const next = outgoingBeliefs.map(node => checkForCycle(targetNodeId, node.id))
.find(result => !result.isSatisfied);
return next return next
? next ? next
: ruleResult.satisfied; : ruleResult.satisfied;
@@ -123,6 +127,7 @@ const noBeliefCycles : HandleRule = (connection, _): RuleResult => {
} }
/** /**
* Defines how an InferredBelief node should be rendered * Defines how an InferredBelief node should be rendered
* @param {NodeProps<InferredBeliefNode>} props - Node properties provided by React Flow, including `id` and `data`. * @param {NodeProps<InferredBeliefNode>} props - Node properties provided by React Flow, including `id` and `data`.
@@ -169,17 +174,20 @@ export default function InferredBeliefNode(props: NodeProps<InferredBeliefNode>)
{/* outgoing connections */} {/* outgoing connections */}
<MultiConnectionHandle type="source" position={Position.Right} id="source" rules={[ <MultiConnectionHandle type="source" position={Position.Right} id="source" rules={[
allowOnlyConnectionsFromType(["norm", "trigger"]), allowOnlyConnectionsFromType(["norm", "trigger"]),
noBeliefCycles noBeliefCycles,
noMatchingLeftRightBelief
]}/> ]}/>
{/* incoming connections */} {/* incoming connections */}
<SingleConnectionHandle type="target" position={Position.Left} style={{top: '30%'}} id="beliefLeft" rules={[ <SingleConnectionHandle type="target" position={Position.Left} style={{top: '30%'}} id="beliefLeft" rules={[
allowOnlyConnectionsFromType(["basic_belief", "inferred_belief"]), allowOnlyConnectionsFromType(["basic_belief", "inferred_belief"]),
noBeliefCycles noBeliefCycles,
noMatchingLeftRightBelief
]}/> ]}/>
<SingleConnectionHandle type="target" position={Position.Left} style={{top: '70%'}} id="beliefRight" rules={[ <SingleConnectionHandle type="target" position={Position.Left} style={{top: '70%'}} id="beliefRight" rules={[
allowOnlyConnectionsFromType(["basic_belief", "inferred_belief"]), allowOnlyConnectionsFromType(["basic_belief", "inferred_belief"]),
noBeliefCycles noBeliefCycles,
noMatchingLeftRightBelief
]}/> ]}/>
</div> </div>
</> </>
@@ -202,9 +210,9 @@ export function InferredBeliefReduce(node: Node, nodes: Node[]) {
const result: Record<string, unknown> = { const result: Record<string, unknown> = {
id: node.id, id: node.id,
left: BeliefReduce(leftBelief, nodes), left: BeliefGlobals(leftBelief, nodes),
operator: data.inferredBelief.operator ? "AND" : "OR", operator: data.inferredBelief.operator ? "AND" : "OR",
right: BeliefReduce(rightBelief, nodes), right: BeliefGlobals(rightBelief, nodes),
}; };
return result return result

View File

@@ -9,7 +9,7 @@ import { TextField } from '../../../../components/TextField';
import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx"; import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx";
import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts"; import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts";
import useFlowStore from '../VisProgStores'; import useFlowStore from '../VisProgStores';
import {BeliefReduce} from "./BeliefReduce.ts"; import {BeliefGlobals} from "./BeliefGlobals.ts";
/** /**
* The default data dot a phase node * The default data dot a phase node
@@ -108,7 +108,7 @@ export function NormReduce(node: Node, nodes: Node[]) {
const conditionNode = nodes.find((node) => node.id === data.condition); const conditionNode = nodes.find((node) => node.id === data.condition);
// In case something went wrong, and our condition doesn't actually exist; // In case something went wrong, and our condition doesn't actually exist;
if (conditionNode == undefined) return result; if (conditionNode == undefined) return result;
result["condition"] = BeliefReduce(conditionNode, nodes) result["condition"] = BeliefGlobals(conditionNode, nodes)
} }
return result return result
} }

View File

@@ -12,7 +12,7 @@ import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../H
import useFlowStore from '../VisProgStores'; import useFlowStore from '../VisProgStores';
import { PlanReduce, type Plan } from '../components/Plan'; import { PlanReduce, type Plan } from '../components/Plan';
import PlanEditorDialog from '../components/PlanEditor'; import PlanEditorDialog from '../components/PlanEditor';
import {BeliefReduce} from "./BeliefReduce.ts"; import {BeliefGlobals} from "./BeliefGlobals.ts";
/** /**
* The default data structure for a Trigger node * The default data structure for a Trigger node
@@ -86,13 +86,13 @@ export default function TriggerNode(props: NodeProps<TriggerNode>) {
/** /**
* Reduces each Trigger, including its children down into its core data. * Reduces each Trigger, including its children down into its core data.
* @param node - The Trigger node to reduce. * @param node - The Trigger node to reduce.
* @param _nodes - The list of all nodes in the current flow graph. * @param nodes - The list of all nodes in the current flow graph.
* @returns A simplified object containing the node label and its list of triggers. * @returns A simplified object containing the node label and its list of triggers.
*/ */
export function TriggerReduce(node: Node, nodes: Node[]) { export function TriggerReduce(node: Node, nodes: Node[]) {
const data = node.data as TriggerNodeData; const data = node.data as TriggerNodeData;
const conditionNode = data.condition ? nodes.find((n)=>n.id===data.condition) : undefined const conditionNode = data.condition ? nodes.find((n)=>n.id===data.condition) : undefined
const conditionData = conditionNode ? BeliefReduce(conditionNode, nodes) : "" const conditionData = conditionNode ? BeliefGlobals(conditionNode, nodes) : ""
return { return {
id: node.id, id: node.id,
condition: conditionData, // Make sure we have a condition before reducing, or default to "" condition: conditionData, // Make sure we have a condition before reducing, or default to ""