chore: save local changes
This commit is contained in:
197
frontend/src/utils/crossoverToDAG.ts
Normal file
197
frontend/src/utils/crossoverToDAG.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import type { CrossoverPair, AttributeDAG, CategoryDefinition, DAGNode } from '../types';
|
||||
|
||||
/**
|
||||
* Result of crossover transformation - two separate DAGs
|
||||
* Each path receives attributes from the other path
|
||||
*/
|
||||
export interface CrossoverDAGResult {
|
||||
// Path A receives attributes from Path B
|
||||
pathA: AttributeDAG;
|
||||
// Path B receives attributes from Path A
|
||||
pathB: AttributeDAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert selected crossover pairs into two separate AttributeDAGs.
|
||||
*
|
||||
* The crossover logic:
|
||||
* - When pair (A's "防水布" × B's "鋁合金") is selected:
|
||||
* - Path A gets "鋁合金" (from B) to transform with A's context
|
||||
* - Path B gets "防水布" (from A) to transform with B's context
|
||||
*
|
||||
* This allows experts to think about:
|
||||
* - "What if umbrella had aluminum alloy?" (A gets B's attribute)
|
||||
* - "What if bicycle had waterproof fabric?" (B gets A's attribute)
|
||||
*/
|
||||
export function crossoverPairsToDAGs(
|
||||
pairs: CrossoverPair[],
|
||||
dagA: AttributeDAG,
|
||||
dagB: AttributeDAG
|
||||
): CrossoverDAGResult {
|
||||
// Collect attributes that each path receives from the other
|
||||
const attributesForA: Map<string, Set<string>> = new Map(); // category -> attributes from B
|
||||
const attributesForB: Map<string, Set<string>> = new Map(); // category -> attributes from A
|
||||
|
||||
for (const pair of pairs) {
|
||||
// Path A receives the target node (from B)
|
||||
// Path B receives the source node (from A)
|
||||
if (pair.sourcePathId === 'A' && pair.targetPathId === 'B') {
|
||||
// A's attribute crossed with B's attribute
|
||||
// A gets B's attribute, B gets A's attribute
|
||||
const categoryForA = pair.targetNode.category;
|
||||
const categoryForB = pair.sourceNode.category;
|
||||
|
||||
if (!attributesForA.has(categoryForA)) attributesForA.set(categoryForA, new Set());
|
||||
if (!attributesForB.has(categoryForB)) attributesForB.set(categoryForB, new Set());
|
||||
|
||||
attributesForA.get(categoryForA)!.add(pair.targetNode.name);
|
||||
attributesForB.get(categoryForB)!.add(pair.sourceNode.name);
|
||||
} else if (pair.sourcePathId === 'B' && pair.targetPathId === 'A') {
|
||||
// B's attribute crossed with A's attribute
|
||||
// A gets B's attribute, B gets A's attribute (reverse direction)
|
||||
const categoryForA = pair.sourceNode.category;
|
||||
const categoryForB = pair.targetNode.category;
|
||||
|
||||
if (!attributesForA.has(categoryForA)) attributesForA.set(categoryForA, new Set());
|
||||
if (!attributesForB.has(categoryForB)) attributesForB.set(categoryForB, new Set());
|
||||
|
||||
attributesForA.get(categoryForA)!.add(pair.sourceNode.name);
|
||||
attributesForB.get(categoryForB)!.add(pair.targetNode.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Build DAG for Path A (receives attributes from B)
|
||||
const pathAResult = buildCrossoverDAG(
|
||||
dagA.query,
|
||||
attributesForA,
|
||||
`Crossover: ${dagB.query} → ${dagA.query}`
|
||||
);
|
||||
|
||||
// Build DAG for Path B (receives attributes from A)
|
||||
const pathBResult = buildCrossoverDAG(
|
||||
dagB.query,
|
||||
attributesForB,
|
||||
`Crossover: ${dagA.query} → ${dagB.query}`
|
||||
);
|
||||
|
||||
return {
|
||||
pathA: pathAResult,
|
||||
pathB: pathBResult,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an AttributeDAG from crossover attributes
|
||||
*/
|
||||
function buildCrossoverDAG(
|
||||
originalQuery: string,
|
||||
attributesByCategory: Map<string, Set<string>>,
|
||||
_crossoverDescription: string
|
||||
): AttributeDAG {
|
||||
const categories: CategoryDefinition[] = [];
|
||||
const nodes: DAGNode[] = [];
|
||||
let nodeIndex = 0;
|
||||
let categoryIndex = 0;
|
||||
|
||||
for (const [categoryName, attributes] of attributesByCategory.entries()) {
|
||||
categories.push({
|
||||
name: categoryName,
|
||||
description: `Crossover attributes for ${categoryName}`,
|
||||
is_fixed: false,
|
||||
order: categoryIndex,
|
||||
});
|
||||
categoryIndex++;
|
||||
|
||||
for (const attrName of attributes) {
|
||||
nodes.push({
|
||||
id: `crossover-${nodeIndex}`,
|
||||
name: attrName,
|
||||
category: categoryName,
|
||||
order: nodeIndex,
|
||||
});
|
||||
nodeIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
query: originalQuery,
|
||||
categories,
|
||||
nodes,
|
||||
edges: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy function - converts to single DAG (deprecated)
|
||||
* Kept for backwards compatibility
|
||||
*/
|
||||
export function crossoverPairsToDAG(
|
||||
pairs: CrossoverPair[],
|
||||
queryA: string,
|
||||
queryB: string
|
||||
): AttributeDAG {
|
||||
// Group pairs by crossType
|
||||
const pairsByType = pairs.reduce((acc, pair) => {
|
||||
if (!acc[pair.crossType]) acc[pair.crossType] = [];
|
||||
acc[pair.crossType].push(pair);
|
||||
return acc;
|
||||
}, {} as Record<string, CrossoverPair[]>);
|
||||
|
||||
// Create categories from crossTypes
|
||||
const categories: CategoryDefinition[] = Object.keys(pairsByType).map((crossType, index) => ({
|
||||
name: formatCrossTypeName(crossType),
|
||||
description: getCrossTypeDescription(crossType),
|
||||
is_fixed: false,
|
||||
order: index,
|
||||
}));
|
||||
|
||||
// Create nodes from pairs
|
||||
const nodes: DAGNode[] = [];
|
||||
let nodeIndex = 0;
|
||||
|
||||
for (const [crossType, typePairs] of Object.entries(pairsByType)) {
|
||||
const categoryName = formatCrossTypeName(crossType);
|
||||
|
||||
for (const pair of typePairs) {
|
||||
nodes.push({
|
||||
id: `crossover-${nodeIndex}`,
|
||||
name: `${pair.sourceNode.name} × ${pair.targetNode.name}`,
|
||||
category: categoryName,
|
||||
order: nodeIndex,
|
||||
});
|
||||
nodeIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
query: `${queryA} × ${queryB}`,
|
||||
categories,
|
||||
nodes,
|
||||
edges: [],
|
||||
};
|
||||
}
|
||||
|
||||
function formatCrossTypeName(crossType: string): string {
|
||||
if (crossType.startsWith('same-')) {
|
||||
const category = crossType.replace('same-', '');
|
||||
return `Same: ${category}`;
|
||||
}
|
||||
if (crossType.startsWith('cross-')) {
|
||||
const parts = crossType.replace('cross-', '').split('-');
|
||||
if (parts.length >= 2) {
|
||||
return `Cross: ${parts[0]} × ${parts.slice(1).join('-')}`;
|
||||
}
|
||||
}
|
||||
return crossType;
|
||||
}
|
||||
|
||||
function getCrossTypeDescription(crossType: string): string {
|
||||
if (crossType.startsWith('same-')) {
|
||||
const category = crossType.replace('same-', '');
|
||||
return `Crossover pairs from the same category: ${category}`;
|
||||
}
|
||||
if (crossType.startsWith('cross-')) {
|
||||
return `Crossover pairs from different categories`;
|
||||
}
|
||||
return `Crossover pairs`;
|
||||
}
|
||||
Reference in New Issue
Block a user