feat: Major update - Physics docs, Zernike OPD, insights, NX journals, tools
Documentation: - Add docs/06_PHYSICS/ with Zernike fundamentals and OPD method docs - Add docs/guides/CMA-ES_EXPLAINED.md optimization guide - Update CLAUDE.md and ATOMIZER_CONTEXT.md with current architecture - Update OP_01_CREATE_STUDY protocol Planning: - Add DYNAMIC_RESPONSE plans for random vibration/PSD support - Add OPTIMIZATION_ENGINE_MIGRATION_PLAN for code reorganization Insights System: - Update design_space, modal_analysis, stress_field, thermal_field insights - Improve error handling and data validation NX Journals: - Add analyze_wfe_zernike.py for Zernike WFE analysis - Add capture_study_images.py for automated screenshots - Add extract_expressions.py and introspect_part.py utilities - Add user_generated_journals/journal_top_view_image_taking.py Tests & Tools: - Add comprehensive Zernike OPD test suite - Add audit_v10 tests for WFE validation - Add tools for Pareto graphs and mirror data extraction - Add migrate_studies_to_topics.py utility Knowledge Base: - Initialize LAC (Learning Atomizer Core) with failure/success patterns Dashboard: - Update Setup.tsx and launch_dashboard.py - Add restart-dev.bat helper script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
155
tools/migrate_studies_to_topics.py
Normal file
155
tools/migrate_studies_to_topics.py
Normal file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Migration script to reorganize studies into topic-based subfolders.
|
||||
|
||||
Run with --dry-run first to preview changes:
|
||||
python migrate_studies_to_topics.py --dry-run
|
||||
|
||||
Then run without flag to execute:
|
||||
python migrate_studies_to_topics.py
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
STUDIES_DIR = Path(__file__).parent / "studies"
|
||||
|
||||
# Topic classification based on study name prefixes
|
||||
TOPIC_MAPPING = {
|
||||
'bracket_': 'Simple_Bracket',
|
||||
'drone_gimbal_': 'Drone_Gimbal',
|
||||
'm1_mirror_': 'M1_Mirror',
|
||||
'uav_arm_': 'UAV_Arm',
|
||||
'simple_beam_': 'Simple_Beam',
|
||||
}
|
||||
|
||||
# Files/folders to skip (not studies)
|
||||
SKIP_ITEMS = {
|
||||
'm1_mirror_all_trials_export.csv', # Data export file
|
||||
'.gitkeep',
|
||||
'__pycache__',
|
||||
}
|
||||
|
||||
|
||||
def classify_study(study_name: str) -> str:
|
||||
"""Determine which topic folder a study belongs to."""
|
||||
for prefix, topic in TOPIC_MAPPING.items():
|
||||
if study_name.startswith(prefix):
|
||||
return topic
|
||||
return '_Other'
|
||||
|
||||
|
||||
def get_studies_to_migrate():
|
||||
"""Get list of studies that need migration (not already in topic folders)."""
|
||||
studies = []
|
||||
|
||||
for item in STUDIES_DIR.iterdir():
|
||||
# Skip non-directories and special items
|
||||
if not item.is_dir():
|
||||
continue
|
||||
if item.name in SKIP_ITEMS:
|
||||
continue
|
||||
if item.name.startswith('.'):
|
||||
continue
|
||||
|
||||
# Check if this is already a topic folder (contains study subdirs)
|
||||
# A topic folder would have subdirs with 1_setup folders
|
||||
is_topic_folder = any(
|
||||
(sub / "1_setup").exists()
|
||||
for sub in item.iterdir()
|
||||
if sub.is_dir()
|
||||
)
|
||||
|
||||
if is_topic_folder:
|
||||
print(f"[SKIP] {item.name} - already a topic folder")
|
||||
continue
|
||||
|
||||
# Check if this is a study (has 1_setup or optimization_config.json)
|
||||
is_study = (
|
||||
(item / "1_setup").exists() or
|
||||
(item / "optimization_config.json").exists()
|
||||
)
|
||||
|
||||
if is_study:
|
||||
topic = classify_study(item.name)
|
||||
studies.append({
|
||||
'name': item.name,
|
||||
'source': item,
|
||||
'topic': topic,
|
||||
'target': STUDIES_DIR / topic / item.name
|
||||
})
|
||||
else:
|
||||
print(f"[SKIP] {item.name} - not a study (no 1_setup folder)")
|
||||
|
||||
return studies
|
||||
|
||||
|
||||
def migrate_studies(dry_run: bool = True):
|
||||
"""Migrate studies to topic folders."""
|
||||
studies = get_studies_to_migrate()
|
||||
|
||||
if not studies:
|
||||
print("\nNo studies to migrate. All studies are already organized.")
|
||||
return
|
||||
|
||||
# Group by topic for display
|
||||
by_topic = {}
|
||||
for s in studies:
|
||||
if s['topic'] not in by_topic:
|
||||
by_topic[s['topic']] = []
|
||||
by_topic[s['topic']].append(s)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("MIGRATION PLAN")
|
||||
print("="*60)
|
||||
|
||||
for topic in sorted(by_topic.keys()):
|
||||
print(f"\n{topic}/")
|
||||
for s in by_topic[topic]:
|
||||
print(f" +-- {s['name']}/")
|
||||
|
||||
print(f"\nTotal: {len(studies)} studies to migrate")
|
||||
|
||||
if dry_run:
|
||||
print("\n[DRY RUN] No changes made. Run without --dry-run to execute.")
|
||||
return
|
||||
|
||||
# Execute migration
|
||||
print("\n" + "="*60)
|
||||
print("EXECUTING MIGRATION")
|
||||
print("="*60)
|
||||
|
||||
# Create topic folders
|
||||
created_topics = set()
|
||||
for s in studies:
|
||||
topic_dir = STUDIES_DIR / s['topic']
|
||||
if s['topic'] not in created_topics:
|
||||
topic_dir.mkdir(exist_ok=True)
|
||||
created_topics.add(s['topic'])
|
||||
print(f"[CREATE] {s['topic']}/")
|
||||
|
||||
# Move studies
|
||||
for s in studies:
|
||||
try:
|
||||
shutil.move(str(s['source']), str(s['target']))
|
||||
print(f"[MOVE] {s['name']} -> {s['topic']}/{s['name']}")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to move {s['name']}: {e}")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("MIGRATION COMPLETE")
|
||||
print("="*60)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Migrate studies to topic folders")
|
||||
parser.add_argument('--dry-run', action='store_true',
|
||||
help='Preview changes without executing')
|
||||
args = parser.parse_args()
|
||||
|
||||
migrate_studies(dry_run=args.dry_run)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user