feat: Add release workflow, contributing guidelines, and enhance READ… #5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Mission JSON Validate | ||
| on: | ||
| push: | ||
| paths: | ||
| - 'src/**' | ||
| - 'schemas/**' | ||
| - '.github/workflows/mission-validate.yml' | ||
| pull_request: | ||
| jobs: | ||
| mission-validate: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.12' | ||
| - name: Install deps | ||
| run: | | ||
| python -m pip install -U pip | ||
| python -m pip install -r requirements.txt jsonschema | ||
| - name: Run tiny mission and export JSON + perf CSV | ||
| run: | | ||
| python -m impulse.mission_cli --waypoints examples/waypoints/simple.json --export mission.json --perf-csv perf.csv --hybrid simulate-first | ||
| - name: Validate mission.json against schema | ||
| run: | | ||
| python bin/validate_mission_json.py mission.json | ||
| - name: Upload artifacts | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: mission-export | ||
| path: | | ||
| mission.json | ||
| perf.csv | ||
| perf-aggregate: | ||
| needs: mission-validate | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.12' | ||
| - name: Install deps | ||
| run: | | ||
| python -m pip install -U pip | ||
| python -m pip install -r requirements.txt | ||
| - name: Download perf.csv artifact | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: mission-export | ||
| path: ./artifacts | ||
| - name: Plot perf summary | ||
| run: | | ||
| python bin/plot_perf_csv.py --csv ./artifacts/perf.csv --out perf_summary.png | ||
| - name: Aggregate perf summary JSON | ||
| run: | | ||
| # Simulate multiple runs if only one CSV is present | ||
| cp ./artifacts/perf.csv ./artifacts/perf_run2.csv | ||
| python bin/aggregate_perf_csv.py ./artifacts/perf.csv ./artifacts/perf_run2.csv --out perf_aggregate.json | ||
| - name: 40 Eridani A UQ + analysis (100 samples) | ||
| run: | | ||
| python -m src.uq_validation.impulse_uq_runner --samples 100 --seed 123 --out uq_summary.json --jsonl-out uq_records.jsonl --dist-profile data/dist_profile_40eridani.csv | ||
| python bin/aggregate_perf_csv.py ./artifacts/perf.csv --out perf_aggregate.json | ||
| # Generate plots from notebook logic with plain Python (no execution needed beyond plotting utilities already used) | ||
| python - << 'PY' | ||
| import json | ||
| from pathlib import Path | ||
| import matplotlib | ||
| matplotlib.use('Agg') | ||
| import matplotlib.pyplot as plt | ||
| import numpy as np | ||
| records = [] | ||
| if Path('uq_records.jsonl').exists(): | ||
| for line in Path('uq_records.jsonl').read_text().splitlines(): | ||
| if line.strip(): | ||
| records.append(json.loads(line)) | ||
| energies = [r.get('planned_energy', 0) for r in records] | ||
| if energies: | ||
| plt.figure(figsize=(6,4)) | ||
| plt.hist(energies, bins=15, color='#4C78A8', edgecolor='white') | ||
| plt.title('Planned Energy Distribution') | ||
| plt.xlabel('Energy (J)') | ||
| plt.ylabel('Count') | ||
| plt.tight_layout() | ||
| plt.savefig('40eridani_energy.png', dpi=150) | ||
| feas = [1 if r.get('feasible') else 0 for r in records] | ||
| if feas: | ||
| n = len(feas) | ||
| win = max(1, n//10) | ||
| roll = np.convolve(feas, np.ones(win)/win, mode='same') | ||
| plt.figure(figsize=(6,4)) | ||
| plt.plot(roll, color='#F58518') | ||
| plt.ylim(0,1) | ||
| plt.title('Feasible Fraction (rolling)') | ||
| plt.xlabel('Sample Index') | ||
| plt.ylabel('Feasible Fraction') | ||
| plt.tight_layout() | ||
| plt.savefig('40eridani_feasibility.png', dpi=150) | ||
| PY | ||
| - name: Generate extended 40 Eridani plots | ||
| run: | | ||
| python - << 'PY' | ||
| import json | ||
| from pathlib import Path | ||
| import matplotlib | ||
| matplotlib.use('Agg') | ||
| import matplotlib.pyplot as plt | ||
| import numpy as np | ||
| records = [] | ||
| if Path('uq_records.jsonl').exists(): | ||
| for line in Path('uq_records.jsonl').read_text().splitlines(): | ||
| if line.strip(): | ||
| records.append(json.loads(line)) | ||
| energies = [r.get('planned_energy', 0) for r in records] | ||
| if energies: | ||
| plt.figure(figsize=(6,4)) | ||
| plt.hist(energies, bins=30, color='#4C78A8', edgecolor='white') | ||
| plt.title('Planned Energy Distribution (Extended)') | ||
| plt.xlabel('Energy (J)') | ||
| plt.ylabel('Count') | ||
| plt.tight_layout() | ||
| plt.savefig('40eridani_energy_extended.png', dpi=150) | ||
| feas = [1 if r.get('feasible') else 0 for r in records] | ||
| if feas: | ||
| n = len(feas) | ||
| win = max(1, n//5) | ||
| roll = np.convolve(feas, np.ones(win)/win, mode='same') | ||
| plt.figure(figsize=(6,4)) | ||
| plt.plot(roll, color='#F58518') | ||
| plt.ylim(0,1) | ||
| plt.title('Feasible Fraction (rolling, Extended)') | ||
| plt.xlabel('Sample Index') | ||
| plt.ylabel('Feasible Fraction') | ||
| plt.tight_layout() | ||
| plt.savefig('40eridani_feasibility_extended.png', dpi=150) | ||
| PY | ||
| - name: Upload perf summary | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: perf-summary | ||
| path: perf_summary.png | ||
| - name: Upload 40 Eridani artifacts | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: 40eridani-artifacts | ||
| path: | | ||
| uq_summary.json | ||
| uq_records.jsonl | ||
| perf_aggregate.json | ||
| 40eridani_energy.png | ||
| 40eridani_feasibility.png | ||
| 40eridani_energy_extended.png | ||
| 40eridani_feasibility_extended.png | ||
| - name: Comment summary on PR | ||
| if: github.event_name == 'pull_request' | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| echo "Posting PR summary..." | ||
| ENERGY=$(jq -r '.segment_energy_mean' perf_aggregate.json) | ||
| TIME=$(jq -r '.segment_time_mean' perf_aggregate.json) | ||
| FEAS=$(jq -r '.feasible_fraction // empty' uq_summary.json 2>/dev/null || echo "") | ||
| BODY="Perf Aggregate Summary:%0A- Mean Segment Energy: ${ENERGY} J%0A- Mean Segment Time: ${TIME} s%0A- Feasible Fraction (UQ): ${FEAS}" | ||
| gh pr comment ${{ github.event.pull_request.number }} --body "$BODY" | ||