1-
21import * as go from "gojs" ;
32
43// --- Pattern and connection rules ---
@@ -133,17 +132,27 @@ export function setupDiagramValidation(diagram: go.Diagram) {
133132 const fromName = getNodeName ( link . fromNode ) ;
134133 const toName = getNodeName ( link . toNode ) ;
135134
136- // Prevent invalid connection
135+ // Prevent invalid connection by rule table
137136 if ( ! validNext [ fromName ] || ! validNext [ fromName ] . includes ( toName ) ) {
138- // Remove the link immediately
139137 diagram . model . startTransaction ( "remove invalid link" ) ;
140138 ( diagram . model as go . GraphLinksModel ) . removeLinkData ( link . data ) ;
141139 diagram . model . commitTransaction ( "remove invalid link" ) ;
142- // Optionally, show a warning (you can use a toast or alert)
143- alert ( "❌ Invalid connection! Edge will be removed." ) ;
140+ alert ( "Invalid connection. Edge removed." ) ;
144141 return ;
145142 }
146143
144+ // Enforce process output cardinality: exactly one of symbol/model/data
145+ if ( PROCESS_NODES . has ( fromName ) && OUTPUT_TARGETS . has ( toName ) ) {
146+ const outCount = countProcessOutputs ( link . fromNode ) ;
147+ if ( outCount > 1 ) {
148+ diagram . model . startTransaction ( "enforce process output cardinality" ) ;
149+ ( diagram . model as go . GraphLinksModel ) . removeLinkData ( link . data ) ;
150+ diagram . model . commitTransaction ( "enforce process output cardinality" ) ;
151+ alert ( "A process can have exactly one output (symbol, model, or data)." ) ;
152+ return ;
153+ }
154+ }
155+
147156 // 🎯 FIXED: Only merge nodes if they have SAME NAME AND SAME LABEL
148157 const fromLabel = link . fromNode . data . label || "" ;
149158 const toLabel = link . toNode . data . label || "" ;
@@ -171,6 +180,22 @@ export function validateGoJSDiagram(diagram: go.Diagram): string {
171180 else if ( part instanceof go . Link && part . fromNode && part . toNode ) links . push ( part ) ;
172181 } ) ;
173182
183+ // Cardinality issues within the selection
184+ const tooManyOutputs : string [ ] = [ ] ;
185+ const missingOutputs : string [ ] = [ ] ;
186+
187+ nodes . forEach ( node => {
188+ const name = getNodeName ( node ) ;
189+ if ( ! PROCESS_NODES . has ( name ) ) return ;
190+ const outSel = links . filter ( l =>
191+ l . fromNode === node &&
192+ l . toNode &&
193+ OUTPUT_TARGETS . has ( getNodeName ( l . toNode ) )
194+ ) . length ;
195+ if ( outSel > 1 ) tooManyOutputs . push ( name ) ;
196+ if ( outSel === 0 ) missingOutputs . push ( name ) ;
197+ } ) ;
198+
174199 // Group nodes by name (logical nodes)
175200 const nodesByName : { [ logicalName : string ] : go . Node [ ] } = { } ;
176201 nodes . forEach ( node => {
@@ -255,21 +280,29 @@ export function validateGoJSDiagram(diagram: go.Diagram): string {
255280 matchedPatterns . length > 0 &&
256281 unmatchedLogicalNodes . length === 0 &&
257282 isolatedLogicalNodes . length === 0 &&
258- disconnectedNodes . length === 0
283+ disconnectedNodes . length === 0 &&
284+ tooManyOutputs . length === 0 &&
285+ missingOutputs . length === 0
259286 ) {
260287 let summary = "✅ Valid pattern(s) detected:\n\n" ;
261- for ( const [ pattern , logicalNodeSet ] of Object . entries ( matchedNodesByPattern ) ) {
288+ for ( const [ pattern ] of Object . entries ( matchedNodesByPattern ) ) {
262289 summary += `• ${ pattern } \n` ;
263290 }
264291 return summary ;
265292 } else {
266293 let summary = "❌ Invalid pattern: Issues detected.\n\n" ;
267294 if ( matchedPatterns . length > 0 ) {
268295 summary += "✅ Partial matches found:\n" ;
269- for ( const [ pattern , logicalNodeSet ] of Object . entries ( matchedNodesByPattern ) ) {
296+ for ( const [ pattern ] of Object . entries ( matchedNodesByPattern ) ) {
270297 summary += ` • ${ pattern } \n` ;
271298 }
272299 }
300+ if ( tooManyOutputs . length > 0 ) {
301+ summary += `\n⚠️ Processes with more than one output (symbol/model/data): ${ Array . from ( new Set ( tooManyOutputs ) ) . join ( ", " ) } ` ;
302+ }
303+ if ( missingOutputs . length > 0 ) {
304+ summary += `\n⚠️ Processes with no output (symbol/model/data): ${ Array . from ( new Set ( missingOutputs ) ) . join ( ", " ) } ` ;
305+ }
273306 if ( unmatchedLogicalNodes . length > 0 ) {
274307 summary += `\n⚠️ Unmatched logical nodes: ${ unmatchedLogicalNodes . join ( ", " ) } ` ;
275308 }
@@ -340,8 +373,6 @@ export function validateEntireDiagram(diagram: go.Diagram): string {
340373 getNodeName ( link . toNode ! )
341374 ] ) ;
342375
343- console . log ( '🔗 Connections found:' , edgeNameList ) ;
344-
345376 // Check for invalid connections first
346377 const invalidConnections : string [ ] = [ ] ;
347378 edgeNameList . forEach ( ( [ from , to ] ) => {
@@ -420,6 +451,22 @@ export function validateEntireDiagram(diagram: go.Diagram): string {
420451 return node . findLinksConnected ( ) . count === 0 ;
421452 } ) ;
422453
454+ // Cardinality checks for whole diagram
455+ const tooManyOutputs : string [ ] = [ ] ;
456+ const missingOutputs : string [ ] = [ ] ;
457+
458+ nodes . forEach ( node => {
459+ const name = getNodeName ( node ) ;
460+ if ( ! PROCESS_NODES . has ( name ) ) return ;
461+ let out = 0 ;
462+ node . findLinksOutOf ( ) . each ( l => {
463+ const t = l . toNode ? getNodeName ( l . toNode ) : '' ;
464+ if ( OUTPUT_TARGETS . has ( t ) ) out ++ ;
465+ } ) ;
466+ if ( out > 1 ) tooManyOutputs . push ( `${ name } (key ${ node . data . key } )` ) ;
467+ if ( out === 0 ) missingOutputs . push ( `${ name } (key ${ node . data . key } )` ) ;
468+ } ) ;
469+
423470 console . log ( '📊 Validation Results:' , {
424471 matchedPatterns : matchedPatterns . length ,
425472 unmatchedLogicalNodes : unmatchedLogicalNodes . length ,
@@ -496,6 +543,20 @@ export function validateEntireDiagram(diagram: go.Diagram): string {
496543 summary += "\n" ;
497544 }
498545
546+ // Cardinality issues
547+ const hasCardinalityIssues = tooManyOutputs . length > 0 || missingOutputs . length > 0 ;
548+
549+ if ( hasCardinalityIssues ) {
550+ summary += `⚠️ CARDINALITY ISSUES:\n` ;
551+ if ( tooManyOutputs . length > 0 ) {
552+ summary += ` • Processes with more than one output: ${ tooManyOutputs . join ( ", " ) } \n` ;
553+ }
554+ if ( missingOutputs . length > 0 ) {
555+ summary += ` • Processes with no output: ${ missingOutputs . join ( ", " ) } \n` ;
556+ }
557+ summary += "\n" ;
558+ }
559+
499560 // Recommendations
500561 summary += `💡 RECOMMENDATIONS:\n` ;
501562 if ( status === "VALID" ) {
@@ -515,4 +576,17 @@ export function validateEntireDiagram(diagram: go.Diagram): string {
515576
516577 console . log ( `🏁 Final status: ${ status } ` ) ;
517578 return summary ;
579+ }
580+
581+ const PROCESS_NODES = new Set ( [ 'training' , 'engineering' , 'transform' , 'deduce' ] ) ;
582+ const OUTPUT_TARGETS = new Set ( [ 'symbol' , 'model' , 'data' ] ) ;
583+
584+ function countProcessOutputs ( node : go . Node ) : number {
585+ if ( ! node ) return 0 ;
586+ let n = 0 ;
587+ node . findLinksOutOf ( ) . each ( l => {
588+ const tgt = l . toNode ? getNodeName ( l . toNode ) : '' ;
589+ if ( OUTPUT_TARGETS . has ( tgt ) ) n ++ ;
590+ } ) ;
591+ return n ;
518592}
0 commit comments