feat(canvas): Custom extractor components, migrator, and MCP spec tools
Canvas Components: - CustomExtractorNode.tsx: Node for custom Python extractors - CustomExtractorPanel.tsx: Configuration panel for custom extractors - ConnectionStatusIndicator.tsx: WebSocket status display - atomizer-spec.ts: TypeScript types for AtomizerSpec v2.0 Config: - migrator.py: Legacy config to AtomizerSpec v2.0 migration - Updated __init__.py exports for config and extractors MCP Tools: - spec.ts: MCP tools for spec manipulation - index.ts: Tool registration updates
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* ConnectionStatusIndicator - Visual indicator for WebSocket connection status.
|
||||
*/
|
||||
|
||||
import { ConnectionStatus } from '../../hooks/useSpecWebSocket';
|
||||
|
||||
interface ConnectionStatusIndicatorProps {
|
||||
status: ConnectionStatus;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visual indicator for WebSocket connection status.
|
||||
* Can be used in the canvas UI to show sync state.
|
||||
*/
|
||||
export function ConnectionStatusIndicator({
|
||||
status,
|
||||
className = '',
|
||||
}: ConnectionStatusIndicatorProps) {
|
||||
const statusConfig = {
|
||||
disconnected: {
|
||||
color: 'bg-gray-500',
|
||||
label: 'Disconnected',
|
||||
},
|
||||
connecting: {
|
||||
color: 'bg-yellow-500 animate-pulse',
|
||||
label: 'Connecting...',
|
||||
},
|
||||
connected: {
|
||||
color: 'bg-green-500',
|
||||
label: 'Connected',
|
||||
},
|
||||
reconnecting: {
|
||||
color: 'bg-yellow-500 animate-pulse',
|
||||
label: 'Reconnecting...',
|
||||
},
|
||||
};
|
||||
|
||||
const config = statusConfig[status];
|
||||
|
||||
return (
|
||||
<div className={`flex items-center gap-2 ${className}`}>
|
||||
<div className={`w-2 h-2 rounded-full ${config.color}`} />
|
||||
<span className="text-xs text-dark-400">{config.label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConnectionStatusIndicator;
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* CustomExtractorNode - Canvas node for custom Python extractors
|
||||
*
|
||||
* Displays custom extractors defined with inline Python code.
|
||||
* Visually distinct from builtin extractors with a code icon.
|
||||
*
|
||||
* P3.11: Custom extractor UI component
|
||||
*/
|
||||
|
||||
import { memo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Code2 } from 'lucide-react';
|
||||
import { BaseNode } from './BaseNode';
|
||||
|
||||
export interface CustomExtractorNodeData {
|
||||
type: 'customExtractor';
|
||||
label: string;
|
||||
configured: boolean;
|
||||
extractorId?: string;
|
||||
extractorName?: string;
|
||||
functionName?: string;
|
||||
functionSource?: string;
|
||||
outputs?: Array<{ name: string; units?: string }>;
|
||||
dependencies?: string[];
|
||||
}
|
||||
|
||||
function CustomExtractorNodeComponent(props: NodeProps<CustomExtractorNodeData>) {
|
||||
const { data } = props;
|
||||
|
||||
// Show validation status
|
||||
const hasCode = !!data.functionSource?.trim();
|
||||
const hasOutputs = (data.outputs?.length ?? 0) > 0;
|
||||
const isConfigured = hasCode && hasOutputs;
|
||||
|
||||
return (
|
||||
<BaseNode
|
||||
{...props}
|
||||
icon={<Code2 size={16} />}
|
||||
iconColor={isConfigured ? 'text-violet-400' : 'text-dark-500'}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className={isConfigured ? 'text-white' : 'text-dark-400'}>
|
||||
{data.extractorName || data.functionName || 'Custom Extractor'}
|
||||
</span>
|
||||
{!isConfigured && (
|
||||
<span className="text-xs text-amber-400">Needs configuration</span>
|
||||
)}
|
||||
{isConfigured && data.outputs && (
|
||||
<span className="text-xs text-dark-400">
|
||||
{data.outputs.length} output{data.outputs.length !== 1 ? 's' : ''}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</BaseNode>
|
||||
);
|
||||
}
|
||||
|
||||
export const CustomExtractorNode = memo(CustomExtractorNodeComponent);
|
||||
@@ -0,0 +1,360 @@
|
||||
/**
|
||||
* CustomExtractorPanel - Panel for editing custom Python extractors
|
||||
*
|
||||
* Provides a code editor for writing custom extraction functions,
|
||||
* output definitions, and validation.
|
||||
*
|
||||
* P3.12: Custom extractor UI component
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { X, Play, AlertCircle, CheckCircle, Plus, Trash2, HelpCircle } from 'lucide-react';
|
||||
|
||||
interface CustomExtractorOutput {
|
||||
name: string;
|
||||
units?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface CustomExtractorPanelProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialName?: string;
|
||||
initialFunctionName?: string;
|
||||
initialSource?: string;
|
||||
initialOutputs?: CustomExtractorOutput[];
|
||||
initialDependencies?: string[];
|
||||
onSave: (data: {
|
||||
name: string;
|
||||
functionName: string;
|
||||
source: string;
|
||||
outputs: CustomExtractorOutput[];
|
||||
dependencies: string[];
|
||||
}) => void;
|
||||
}
|
||||
|
||||
// Common styling classes
|
||||
const inputClass =
|
||||
'w-full px-3 py-2 bg-dark-800 border border-dark-600 text-white placeholder-dark-400 rounded-lg focus:border-primary-500 focus:outline-none transition-colors';
|
||||
const labelClass = 'block text-sm font-medium text-dark-300 mb-1';
|
||||
|
||||
// Default extractor template
|
||||
const DEFAULT_SOURCE = `def extract(op2_path, bdf_path=None, params=None, working_dir=None):
|
||||
"""
|
||||
Custom extractor function.
|
||||
|
||||
Args:
|
||||
op2_path: Path to the OP2 results file
|
||||
bdf_path: Optional path to the BDF model file
|
||||
params: Dictionary of current design parameters
|
||||
working_dir: Path to the current trial directory
|
||||
|
||||
Returns:
|
||||
Dictionary of output_name -> value
|
||||
OR a single float value
|
||||
OR a list/tuple of values (mapped to outputs in order)
|
||||
"""
|
||||
import numpy as np
|
||||
from pyNastran.op2.op2 import OP2
|
||||
|
||||
# Load OP2 results
|
||||
op2 = OP2(op2_path, debug=False)
|
||||
|
||||
# Example: compute custom metric
|
||||
# ... your extraction logic here ...
|
||||
|
||||
result = 0.0
|
||||
|
||||
return {"custom_output": result}
|
||||
`;
|
||||
|
||||
export function CustomExtractorPanel({
|
||||
isOpen,
|
||||
onClose,
|
||||
initialName = '',
|
||||
initialFunctionName = 'extract',
|
||||
initialSource = DEFAULT_SOURCE,
|
||||
initialOutputs = [{ name: 'custom_output', units: '' }],
|
||||
initialDependencies = [],
|
||||
onSave,
|
||||
}: CustomExtractorPanelProps) {
|
||||
const [name, setName] = useState(initialName);
|
||||
const [functionName, setFunctionName] = useState(initialFunctionName);
|
||||
const [source, setSource] = useState(initialSource);
|
||||
const [outputs, setOutputs] = useState<CustomExtractorOutput[]>(initialOutputs);
|
||||
const [dependencies] = useState<string[]>(initialDependencies);
|
||||
const [validation, setValidation] = useState<{
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
} | null>(null);
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
const [showHelp, setShowHelp] = useState(false);
|
||||
|
||||
// Add a new output
|
||||
const addOutput = useCallback(() => {
|
||||
setOutputs((prev) => [...prev, { name: '', units: '' }]);
|
||||
}, []);
|
||||
|
||||
// Remove an output
|
||||
const removeOutput = useCallback((index: number) => {
|
||||
setOutputs((prev) => prev.filter((_, i) => i !== index));
|
||||
}, []);
|
||||
|
||||
// Update an output
|
||||
const updateOutput = useCallback(
|
||||
(index: number, field: keyof CustomExtractorOutput, value: string) => {
|
||||
setOutputs((prev) =>
|
||||
prev.map((out, i) => (i === index ? { ...out, [field]: value } : out))
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Validate the code
|
||||
const validateCode = useCallback(async () => {
|
||||
setIsValidating(true);
|
||||
setValidation(null);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/spec/validate-extractor', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
function_name: functionName,
|
||||
source: source,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
setValidation({
|
||||
valid: result.valid,
|
||||
errors: result.errors || [],
|
||||
});
|
||||
} catch (error) {
|
||||
setValidation({
|
||||
valid: false,
|
||||
errors: ['Failed to validate: ' + (error instanceof Error ? error.message : 'Unknown error')],
|
||||
});
|
||||
} finally {
|
||||
setIsValidating(false);
|
||||
}
|
||||
}, [functionName, source]);
|
||||
|
||||
// Handle save
|
||||
const handleSave = useCallback(() => {
|
||||
// Filter out empty outputs
|
||||
const validOutputs = outputs.filter((o) => o.name.trim());
|
||||
|
||||
if (!name.trim()) {
|
||||
setValidation({ valid: false, errors: ['Name is required'] });
|
||||
return;
|
||||
}
|
||||
|
||||
if (validOutputs.length === 0) {
|
||||
setValidation({ valid: false, errors: ['At least one output is required'] });
|
||||
return;
|
||||
}
|
||||
|
||||
onSave({
|
||||
name: name.trim(),
|
||||
functionName: functionName.trim() || 'extract',
|
||||
source,
|
||||
outputs: validOutputs,
|
||||
dependencies: dependencies.filter((d) => d.trim()),
|
||||
});
|
||||
onClose();
|
||||
}, [name, functionName, source, outputs, dependencies, onSave, onClose]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div className="bg-dark-850 rounded-xl shadow-2xl w-[900px] max-h-[90vh] flex flex-col border border-dark-700">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-dark-700">
|
||||
<h2 className="text-lg font-semibold text-white">Custom Extractor</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setShowHelp(!showHelp)}
|
||||
className="p-2 text-dark-400 hover:text-white hover:bg-dark-700 rounded-lg transition-colors"
|
||||
title="Show help"
|
||||
>
|
||||
<HelpCircle size={20} />
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 text-dark-400 hover:text-white hover:bg-dark-700 rounded-lg transition-colors"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-auto p-6">
|
||||
{/* Help Section */}
|
||||
{showHelp && (
|
||||
<div className="mb-4 p-4 bg-primary-900/20 border border-primary-700 rounded-lg">
|
||||
<h3 className="text-sm font-semibold text-primary-400 mb-2">How Custom Extractors Work</h3>
|
||||
<ul className="text-sm text-dark-300 space-y-1">
|
||||
<li>• Your function receives the path to OP2 results and optional BDF/params</li>
|
||||
<li>• Use pyNastran, numpy, scipy for data extraction and analysis</li>
|
||||
<li>• Return a dictionary mapping output names to numeric values</li>
|
||||
<li>• Outputs can be used as objectives or constraints in optimization</li>
|
||||
<li>• Code runs in a sandboxed environment (no file I/O beyond OP2/BDF)</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
{/* Left Column - Basic Info & Outputs */}
|
||||
<div className="space-y-4">
|
||||
{/* Name */}
|
||||
<div>
|
||||
<label className={labelClass}>Extractor Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="My Custom Extractor"
|
||||
className={inputClass}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Function Name */}
|
||||
<div>
|
||||
<label className={labelClass}>Function Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={functionName}
|
||||
onChange={(e) => setFunctionName(e.target.value)}
|
||||
placeholder="extract"
|
||||
className={`${inputClass} font-mono`}
|
||||
/>
|
||||
<p className="text-xs text-dark-500 mt-1">
|
||||
Name of the Python function in your code
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Outputs */}
|
||||
<div>
|
||||
<label className={labelClass}>Outputs</label>
|
||||
<div className="space-y-2">
|
||||
{outputs.map((output, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={output.name}
|
||||
onChange={(e) => updateOutput(index, 'name', e.target.value)}
|
||||
placeholder="output_name"
|
||||
className={`${inputClass} font-mono flex-1`}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={output.units || ''}
|
||||
onChange={(e) => updateOutput(index, 'units', e.target.value)}
|
||||
placeholder="units"
|
||||
className={`${inputClass} w-24`}
|
||||
/>
|
||||
<button
|
||||
onClick={() => removeOutput(index)}
|
||||
className="p-2 text-red-400 hover:text-red-300 hover:bg-red-900/20 rounded-lg transition-colors"
|
||||
disabled={outputs.length === 1}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
onClick={addOutput}
|
||||
className="flex items-center gap-1 text-sm text-primary-400 hover:text-primary-300 transition-colors"
|
||||
>
|
||||
<Plus size={14} />
|
||||
Add Output
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Validation Status */}
|
||||
{validation && (
|
||||
<div
|
||||
className={`p-3 rounded-lg border ${
|
||||
validation.valid
|
||||
? 'bg-green-900/20 border-green-700'
|
||||
: 'bg-red-900/20 border-red-700'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{validation.valid ? (
|
||||
<CheckCircle size={16} className="text-green-400" />
|
||||
) : (
|
||||
<AlertCircle size={16} className="text-red-400" />
|
||||
)}
|
||||
<span
|
||||
className={`text-sm font-medium ${
|
||||
validation.valid ? 'text-green-400' : 'text-red-400'
|
||||
}`}
|
||||
>
|
||||
{validation.valid ? 'Code is valid' : 'Validation failed'}
|
||||
</span>
|
||||
</div>
|
||||
{validation.errors.length > 0 && (
|
||||
<ul className="mt-2 text-sm text-red-300 space-y-1">
|
||||
{validation.errors.map((err, i) => (
|
||||
<li key={i}>• {err}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Column - Code Editor */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className={labelClass}>Python Code</label>
|
||||
<button
|
||||
onClick={validateCode}
|
||||
disabled={isValidating}
|
||||
className="flex items-center gap-1 px-3 py-1 bg-primary-600 hover:bg-primary-500
|
||||
text-white text-sm rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
<Play size={14} />
|
||||
{isValidating ? 'Validating...' : 'Validate'}
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
value={source}
|
||||
onChange={(e) => {
|
||||
setSource(e.target.value);
|
||||
setValidation(null);
|
||||
}}
|
||||
className={`${inputClass} h-[400px] font-mono text-sm resize-none`}
|
||||
spellCheck={false}
|
||||
/>
|
||||
<p className="text-xs text-dark-500">
|
||||
Available modules: numpy, scipy, pyNastran, math, statistics
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-dark-700">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-dark-300 hover:text-white hover:bg-dark-700 rounded-lg transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="px-4 py-2 bg-primary-600 hover:bg-primary-500 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Save Extractor
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
572
atomizer-dashboard/frontend/src/types/atomizer-spec.ts
Normal file
572
atomizer-dashboard/frontend/src/types/atomizer-spec.ts
Normal file
@@ -0,0 +1,572 @@
|
||||
/**
|
||||
* AtomizerSpec v2.0 TypeScript Types
|
||||
*
|
||||
* These types match the JSON Schema at optimization_engine/schemas/atomizer_spec_v2.json
|
||||
* This is the single source of truth for optimization configuration.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Position Types
|
||||
// ============================================================================
|
||||
|
||||
export interface CanvasPosition {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Meta Types
|
||||
// ============================================================================
|
||||
|
||||
export type SpecCreatedBy = 'canvas' | 'claude' | 'api' | 'migration' | 'manual';
|
||||
|
||||
export interface SpecMeta {
|
||||
/** Schema version (e.g., "2.0") */
|
||||
version: string;
|
||||
/** When the spec was created (ISO 8601) */
|
||||
created?: string;
|
||||
/** When the spec was last modified (ISO 8601) */
|
||||
modified?: string;
|
||||
/** Who/what created the spec */
|
||||
created_by?: SpecCreatedBy;
|
||||
/** Who/what last modified the spec */
|
||||
modified_by?: string;
|
||||
/** Unique study identifier (snake_case) */
|
||||
study_name: string;
|
||||
/** Human-readable description */
|
||||
description?: string;
|
||||
/** Tags for categorization */
|
||||
tags?: string[];
|
||||
/** Real-world engineering context */
|
||||
engineering_context?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Model Types
|
||||
// ============================================================================
|
||||
|
||||
export interface NxPartConfig {
|
||||
/** Path to .prt file */
|
||||
path?: string;
|
||||
/** File hash for change detection */
|
||||
hash?: string;
|
||||
/** Idealized part filename (_i.prt) */
|
||||
idealized_part?: string;
|
||||
}
|
||||
|
||||
export interface FemConfig {
|
||||
/** Path to .fem file */
|
||||
path?: string;
|
||||
/** Number of elements */
|
||||
element_count?: number;
|
||||
/** Number of nodes */
|
||||
node_count?: number;
|
||||
}
|
||||
|
||||
export type SolverType = 'nastran' | 'NX_Nastran' | 'abaqus';
|
||||
export type SubcaseType = 'static' | 'modal' | 'thermal' | 'buckling';
|
||||
|
||||
export interface Subcase {
|
||||
id: number;
|
||||
name?: string;
|
||||
type?: SubcaseType;
|
||||
}
|
||||
|
||||
export interface SimConfig {
|
||||
/** Path to .sim file */
|
||||
path: string;
|
||||
/** Solver type */
|
||||
solver: SolverType;
|
||||
/** Solution type (e.g., SOL101) */
|
||||
solution_type?: string;
|
||||
/** Defined subcases */
|
||||
subcases?: Subcase[];
|
||||
}
|
||||
|
||||
export interface NxSettings {
|
||||
nx_install_path?: string;
|
||||
simulation_timeout_s?: number;
|
||||
auto_start_nx?: boolean;
|
||||
}
|
||||
|
||||
export interface ModelConfig {
|
||||
nx_part?: NxPartConfig;
|
||||
fem?: FemConfig;
|
||||
sim: SimConfig;
|
||||
nx_settings?: NxSettings;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Design Variable Types
|
||||
// ============================================================================
|
||||
|
||||
export type DesignVariableType = 'continuous' | 'integer' | 'categorical';
|
||||
|
||||
export interface DesignVariableBounds {
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface DesignVariable {
|
||||
/** Unique identifier (pattern: dv_XXX) */
|
||||
id: string;
|
||||
/** Human-readable name */
|
||||
name: string;
|
||||
/** NX expression name (must match model) */
|
||||
expression_name: string;
|
||||
/** Variable type */
|
||||
type: DesignVariableType;
|
||||
/** Value bounds */
|
||||
bounds: DesignVariableBounds;
|
||||
/** Current/initial value */
|
||||
baseline?: number;
|
||||
/** Physical units (mm, deg, etc.) */
|
||||
units?: string;
|
||||
/** Step size for integer/discrete */
|
||||
step?: number;
|
||||
/** Whether to include in optimization */
|
||||
enabled?: boolean;
|
||||
/** Description */
|
||||
description?: string;
|
||||
/** Canvas position */
|
||||
canvas_position?: CanvasPosition;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Extractor Types
|
||||
// ============================================================================
|
||||
|
||||
export type ExtractorType =
|
||||
| 'displacement'
|
||||
| 'frequency'
|
||||
| 'stress'
|
||||
| 'mass'
|
||||
| 'mass_expression'
|
||||
| 'zernike_opd'
|
||||
| 'zernike_csv'
|
||||
| 'temperature'
|
||||
| 'custom_function';
|
||||
|
||||
export interface ExtractorConfig {
|
||||
/** Inner radius for Zernike (mm) */
|
||||
inner_radius_mm?: number;
|
||||
/** Outer radius for Zernike (mm) */
|
||||
outer_radius_mm?: number;
|
||||
/** Number of Zernike modes */
|
||||
n_modes?: number;
|
||||
/** Low-order modes to filter */
|
||||
filter_low_orders?: number;
|
||||
/** Displacement unit */
|
||||
displacement_unit?: string;
|
||||
/** Reference subcase ID */
|
||||
reference_subcase?: number;
|
||||
/** NX expression name (for mass_expression) */
|
||||
expression_name?: string;
|
||||
/** Mode number (for frequency) */
|
||||
mode_number?: number;
|
||||
/** Element type (for stress) */
|
||||
element_type?: string;
|
||||
/** Result type */
|
||||
result_type?: string;
|
||||
/** Metric type */
|
||||
metric?: string;
|
||||
/** Additional config properties */
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface CustomFunction {
|
||||
/** Function name */
|
||||
name?: string;
|
||||
/** Python module path */
|
||||
module?: string;
|
||||
/** Function signature */
|
||||
signature?: string;
|
||||
/** Python source code */
|
||||
source_code?: string;
|
||||
}
|
||||
|
||||
export interface ExtractorOutput {
|
||||
/** Output name (used by objectives/constraints) */
|
||||
name: string;
|
||||
/** Specific metric (max, total, rms, etc.) */
|
||||
metric?: string;
|
||||
/** Subcase ID for this output */
|
||||
subcase?: number;
|
||||
/** Units */
|
||||
units?: string;
|
||||
}
|
||||
|
||||
export interface Extractor {
|
||||
/** Unique identifier (pattern: ext_XXX) */
|
||||
id: string;
|
||||
/** Human-readable name */
|
||||
name: string;
|
||||
/** Extractor type */
|
||||
type: ExtractorType;
|
||||
/** Whether this is a built-in extractor */
|
||||
builtin?: boolean;
|
||||
/** Type-specific configuration */
|
||||
config?: ExtractorConfig;
|
||||
/** Custom function definition (for custom_function type) */
|
||||
function?: CustomFunction;
|
||||
/** Output values this extractor produces */
|
||||
outputs: ExtractorOutput[];
|
||||
/** Canvas position */
|
||||
canvas_position?: CanvasPosition;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Objective Types
|
||||
// ============================================================================
|
||||
|
||||
export type OptimizationDirection = 'minimize' | 'maximize';
|
||||
|
||||
export interface ObjectiveSource {
|
||||
/** Reference to extractor */
|
||||
extractor_id: string;
|
||||
/** Which output from the extractor */
|
||||
output_name: string;
|
||||
}
|
||||
|
||||
export interface Objective {
|
||||
/** Unique identifier (pattern: obj_XXX) */
|
||||
id: string;
|
||||
/** Human-readable name */
|
||||
name: string;
|
||||
/** Optimization direction */
|
||||
direction: OptimizationDirection;
|
||||
/** Weight for weighted sum (multi-objective) */
|
||||
weight?: number;
|
||||
/** Where the value comes from */
|
||||
source: ObjectiveSource;
|
||||
/** Target value (for goal programming) */
|
||||
target?: number;
|
||||
/** Units */
|
||||
units?: string;
|
||||
/** Description */
|
||||
description?: string;
|
||||
/** Canvas position */
|
||||
canvas_position?: CanvasPosition;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Constraint Types
|
||||
// ============================================================================
|
||||
|
||||
export type ConstraintType = 'hard' | 'soft';
|
||||
export type ConstraintOperator = '<=' | '>=' | '<' | '>' | '==';
|
||||
export type PenaltyMethod = 'linear' | 'quadratic' | 'exponential';
|
||||
|
||||
export interface ConstraintSource {
|
||||
extractor_id: string;
|
||||
output_name: string;
|
||||
}
|
||||
|
||||
export interface PenaltyConfig {
|
||||
/** Penalty method */
|
||||
method?: PenaltyMethod;
|
||||
/** Penalty weight */
|
||||
weight?: number;
|
||||
/** Soft margin before penalty kicks in */
|
||||
margin?: number;
|
||||
}
|
||||
|
||||
export interface Constraint {
|
||||
/** Unique identifier (pattern: con_XXX) */
|
||||
id: string;
|
||||
/** Human-readable name */
|
||||
name: string;
|
||||
/** Constraint type */
|
||||
type: ConstraintType;
|
||||
/** Comparison operator */
|
||||
operator: ConstraintOperator;
|
||||
/** Constraint threshold value */
|
||||
threshold: number;
|
||||
/** Where the value comes from */
|
||||
source: ConstraintSource;
|
||||
/** Penalty method configuration */
|
||||
penalty_config?: PenaltyConfig;
|
||||
/** Description */
|
||||
description?: string;
|
||||
/** Canvas position */
|
||||
canvas_position?: CanvasPosition;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Optimization Types
|
||||
// ============================================================================
|
||||
|
||||
export type AlgorithmType = 'TPE' | 'CMA-ES' | 'NSGA-II' | 'RandomSearch' | 'SAT_v3' | 'GP-BO';
|
||||
export type SurrogateType = 'MLP' | 'GNN' | 'ensemble';
|
||||
|
||||
export interface AlgorithmConfig {
|
||||
/** Population size (evolutionary algorithms) */
|
||||
population_size?: number;
|
||||
/** Number of generations */
|
||||
n_generations?: number;
|
||||
/** Mutation probability */
|
||||
mutation_prob?: number | null;
|
||||
/** Crossover probability */
|
||||
crossover_prob?: number;
|
||||
/** Random seed */
|
||||
seed?: number;
|
||||
/** Number of startup trials (TPE) */
|
||||
n_startup_trials?: number;
|
||||
/** Initial sigma (CMA-ES) */
|
||||
sigma0?: number;
|
||||
/** Additional config properties */
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface Algorithm {
|
||||
type: AlgorithmType;
|
||||
config?: AlgorithmConfig;
|
||||
}
|
||||
|
||||
export interface OptimizationBudget {
|
||||
/** Maximum number of trials */
|
||||
max_trials?: number;
|
||||
/** Maximum time in hours */
|
||||
max_time_hours?: number;
|
||||
/** Stop if no improvement for N trials */
|
||||
convergence_patience?: number;
|
||||
}
|
||||
|
||||
export interface SurrogateConfig {
|
||||
/** Number of models in ensemble */
|
||||
n_models?: number;
|
||||
/** Network architecture layers */
|
||||
architecture?: number[];
|
||||
/** Retrain every N trials */
|
||||
train_every_n_trials?: number;
|
||||
/** Minimum training samples */
|
||||
min_training_samples?: number;
|
||||
/** Acquisition function candidates */
|
||||
acquisition_candidates?: number;
|
||||
/** FEA validations per round */
|
||||
fea_validations_per_round?: number;
|
||||
}
|
||||
|
||||
export interface Surrogate {
|
||||
enabled?: boolean;
|
||||
type?: SurrogateType;
|
||||
config?: SurrogateConfig;
|
||||
}
|
||||
|
||||
export interface OptimizationConfig {
|
||||
algorithm: Algorithm;
|
||||
budget: OptimizationBudget;
|
||||
surrogate?: Surrogate;
|
||||
canvas_position?: CanvasPosition;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Workflow Types
|
||||
// ============================================================================
|
||||
|
||||
export interface WorkflowStage {
|
||||
id: string;
|
||||
name: string;
|
||||
algorithm?: string;
|
||||
trials?: number;
|
||||
purpose?: string;
|
||||
}
|
||||
|
||||
export interface WorkflowTransition {
|
||||
from: string;
|
||||
to: string;
|
||||
condition?: string;
|
||||
}
|
||||
|
||||
export interface Workflow {
|
||||
stages?: WorkflowStage[];
|
||||
transitions?: WorkflowTransition[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Reporting Types
|
||||
// ============================================================================
|
||||
|
||||
export interface InsightConfig {
|
||||
include_html?: boolean;
|
||||
show_pareto_evolution?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface Insight {
|
||||
type?: string;
|
||||
for_trials?: string;
|
||||
config?: InsightConfig;
|
||||
}
|
||||
|
||||
export interface ReportingConfig {
|
||||
auto_report?: boolean;
|
||||
report_triggers?: string[];
|
||||
insights?: Insight[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Canvas Types
|
||||
// ============================================================================
|
||||
|
||||
export interface CanvasViewport {
|
||||
x: number;
|
||||
y: number;
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
export interface CanvasEdge {
|
||||
source: string;
|
||||
target: string;
|
||||
sourceHandle?: string;
|
||||
targetHandle?: string;
|
||||
}
|
||||
|
||||
export interface CanvasGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
node_ids: string[];
|
||||
}
|
||||
|
||||
export interface CanvasConfig {
|
||||
layout_version?: string;
|
||||
viewport?: CanvasViewport;
|
||||
edges?: CanvasEdge[];
|
||||
groups?: CanvasGroup[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main AtomizerSpec Type
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* AtomizerSpec v2.0 - The unified configuration schema for Atomizer optimization studies.
|
||||
*
|
||||
* This is the single source of truth used by:
|
||||
* - Canvas UI (rendering and editing)
|
||||
* - Backend API (validation and storage)
|
||||
* - Claude Assistant (reading and modifying)
|
||||
* - Optimization Engine (execution)
|
||||
*/
|
||||
export interface AtomizerSpec {
|
||||
/** Metadata about the spec */
|
||||
meta: SpecMeta;
|
||||
/** NX model files and configuration */
|
||||
model: ModelConfig;
|
||||
/** Design variables (NX expressions) to optimize */
|
||||
design_variables: DesignVariable[];
|
||||
/** Physics extractors that compute outputs from FEA results */
|
||||
extractors: Extractor[];
|
||||
/** Optimization objectives (minimize/maximize) */
|
||||
objectives: Objective[];
|
||||
/** Hard and soft constraints */
|
||||
constraints?: Constraint[];
|
||||
/** Optimization algorithm configuration */
|
||||
optimization: OptimizationConfig;
|
||||
/** Multi-stage optimization workflow */
|
||||
workflow?: Workflow;
|
||||
/** Reporting configuration */
|
||||
reporting?: ReportingConfig;
|
||||
/** Canvas UI state (persisted for reconstruction) */
|
||||
canvas?: CanvasConfig;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Types for API Responses
|
||||
// ============================================================================
|
||||
|
||||
export interface SpecValidationError {
|
||||
type: 'schema' | 'semantic' | 'reference';
|
||||
path: string[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SpecValidationWarning {
|
||||
type: string;
|
||||
path: string[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SpecValidationReport {
|
||||
valid: boolean;
|
||||
errors: SpecValidationError[];
|
||||
warnings: SpecValidationWarning[];
|
||||
summary: {
|
||||
design_variables: number;
|
||||
extractors: number;
|
||||
objectives: number;
|
||||
constraints: number;
|
||||
custom_functions: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SpecModification {
|
||||
operation: 'set' | 'add' | 'remove';
|
||||
path: string;
|
||||
value?: unknown;
|
||||
}
|
||||
|
||||
export interface SpecUpdateResult {
|
||||
success: boolean;
|
||||
hash: string;
|
||||
modified: string;
|
||||
modified_by: string;
|
||||
}
|
||||
|
||||
export interface SpecPatchRequest {
|
||||
path: string;
|
||||
value: unknown;
|
||||
modified_by?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Node Types for Canvas
|
||||
// ============================================================================
|
||||
|
||||
export type SpecNodeType =
|
||||
| 'designVar'
|
||||
| 'extractor'
|
||||
| 'objective'
|
||||
| 'constraint'
|
||||
| 'model'
|
||||
| 'solver'
|
||||
| 'algorithm';
|
||||
|
||||
export interface SpecNodeBase {
|
||||
id: string;
|
||||
type: SpecNodeType;
|
||||
position: CanvasPosition;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WebSocket Types
|
||||
// ============================================================================
|
||||
|
||||
export type SpecSyncMessageType =
|
||||
| 'spec_updated'
|
||||
| 'validation_error'
|
||||
| 'node_added'
|
||||
| 'node_removed'
|
||||
| 'connection_ack';
|
||||
|
||||
export interface SpecSyncMessage {
|
||||
type: SpecSyncMessageType;
|
||||
timestamp: string;
|
||||
hash?: string;
|
||||
modified_by?: string;
|
||||
changes?: Array<{
|
||||
path: string;
|
||||
old: unknown;
|
||||
new: unknown;
|
||||
}>;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface SpecClientMessage {
|
||||
type: 'subscribe' | 'patch_node' | 'add_node' | 'remove_node' | 'update_position';
|
||||
study_id: string;
|
||||
node_id?: string;
|
||||
data?: Record<string, unknown>;
|
||||
position?: CanvasPosition;
|
||||
}
|
||||
Reference in New Issue
Block a user