feat: Add Studio UI, intake system, and extractor improvements
Dashboard: - Add Studio page with drag-drop model upload and Claude chat - Add intake system for study creation workflow - Improve session manager and context builder - Add intake API routes and frontend components Optimization Engine: - Add CLI module for command-line operations - Add intake module for study preprocessing - Add validation module with gate checks - Improve Zernike extractor documentation - Update spec models with better validation - Enhance solve_simulation robustness Documentation: - Add ATOMIZER_STUDIO.md planning doc - Add ATOMIZER_UX_SYSTEM.md for UX patterns - Update extractor library docs - Add study-readme-generator skill Tools: - Add test scripts for extraction validation - Add Zernike recentering test Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -245,17 +245,45 @@ def _get_study_error_info(study_dir: Path, results_dir: Path) -> dict:
|
||||
|
||||
def _load_study_info(study_dir: Path, topic: Optional[str] = None) -> Optional[dict]:
|
||||
"""Load study info from a study directory. Returns None if not a valid study."""
|
||||
# Look for optimization config (check multiple locations)
|
||||
config_file = study_dir / "optimization_config.json"
|
||||
if not config_file.exists():
|
||||
config_file = study_dir / "1_setup" / "optimization_config.json"
|
||||
if not config_file.exists():
|
||||
# Look for config file - prefer atomizer_spec.json (v2.0), fall back to legacy optimization_config.json
|
||||
config_file = None
|
||||
is_atomizer_spec = False
|
||||
|
||||
# Check for AtomizerSpec v2.0 first
|
||||
for spec_path in [
|
||||
study_dir / "atomizer_spec.json",
|
||||
study_dir / "1_setup" / "atomizer_spec.json",
|
||||
]:
|
||||
if spec_path.exists():
|
||||
config_file = spec_path
|
||||
is_atomizer_spec = True
|
||||
break
|
||||
|
||||
# Fall back to legacy optimization_config.json
|
||||
if config_file is None:
|
||||
for legacy_path in [
|
||||
study_dir / "optimization_config.json",
|
||||
study_dir / "1_setup" / "optimization_config.json",
|
||||
]:
|
||||
if legacy_path.exists():
|
||||
config_file = legacy_path
|
||||
break
|
||||
|
||||
if config_file is None:
|
||||
return None
|
||||
|
||||
# Load config
|
||||
with open(config_file) as f:
|
||||
config = json.load(f)
|
||||
|
||||
# Normalize AtomizerSpec v2.0 to legacy format for compatibility
|
||||
if is_atomizer_spec and "meta" in config:
|
||||
# Extract study_name and description from meta
|
||||
meta = config.get("meta", {})
|
||||
config["study_name"] = meta.get("study_name", study_dir.name)
|
||||
config["description"] = meta.get("description", "")
|
||||
config["version"] = meta.get("version", "2.0")
|
||||
|
||||
# Check if results directory exists (support both 2_results and 3_results)
|
||||
results_dir = study_dir / "2_results"
|
||||
if not results_dir.exists():
|
||||
@@ -311,12 +339,21 @@ def _load_study_info(study_dir: Path, topic: Optional[str] = None) -> Optional[d
|
||||
best_trial = min(history, key=lambda x: x["objective"])
|
||||
best_value = best_trial["objective"]
|
||||
|
||||
# Get total trials from config (supports both formats)
|
||||
total_trials = (
|
||||
config.get("optimization_settings", {}).get("n_trials")
|
||||
or config.get("optimization", {}).get("n_trials")
|
||||
or config.get("trials", {}).get("n_trials", 50)
|
||||
)
|
||||
# Get total trials from config (supports AtomizerSpec v2.0 and legacy formats)
|
||||
total_trials = None
|
||||
|
||||
# AtomizerSpec v2.0: optimization.budget.max_trials
|
||||
if is_atomizer_spec:
|
||||
total_trials = config.get("optimization", {}).get("budget", {}).get("max_trials")
|
||||
|
||||
# Legacy formats
|
||||
if total_trials is None:
|
||||
total_trials = (
|
||||
config.get("optimization_settings", {}).get("n_trials")
|
||||
or config.get("optimization", {}).get("n_trials")
|
||||
or config.get("optimization", {}).get("max_trials")
|
||||
or config.get("trials", {}).get("n_trials", 100)
|
||||
)
|
||||
|
||||
# Get accurate status using process detection
|
||||
status = get_accurate_study_status(study_dir.name, trial_count, total_trials, has_db)
|
||||
@@ -380,7 +417,12 @@ async def list_studies():
|
||||
continue
|
||||
|
||||
# Check if this is a study (flat structure) or a topic folder (nested structure)
|
||||
is_study = (item / "1_setup").exists() or (item / "optimization_config.json").exists()
|
||||
# Support both AtomizerSpec v2.0 (atomizer_spec.json) and legacy (optimization_config.json)
|
||||
is_study = (
|
||||
(item / "1_setup").exists()
|
||||
or (item / "atomizer_spec.json").exists()
|
||||
or (item / "optimization_config.json").exists()
|
||||
)
|
||||
|
||||
if is_study:
|
||||
# Flat structure: study directly in studies/
|
||||
@@ -396,10 +438,12 @@ async def list_studies():
|
||||
if sub_item.name.startswith("."):
|
||||
continue
|
||||
|
||||
# Check if this subdirectory is a study
|
||||
sub_is_study = (sub_item / "1_setup").exists() or (
|
||||
sub_item / "optimization_config.json"
|
||||
).exists()
|
||||
# Check if this subdirectory is a study (AtomizerSpec v2.0 or legacy)
|
||||
sub_is_study = (
|
||||
(sub_item / "1_setup").exists()
|
||||
or (sub_item / "atomizer_spec.json").exists()
|
||||
or (sub_item / "optimization_config.json").exists()
|
||||
)
|
||||
if sub_is_study:
|
||||
study_info = _load_study_info(sub_item, topic=item.name)
|
||||
if study_info:
|
||||
|
||||
Reference in New Issue
Block a user