orbital-pv 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- orbital_pv-0.3.0/LICENSE +21 -0
- orbital_pv-0.3.0/PKG-INFO +209 -0
- orbital_pv-0.3.0/README.md +151 -0
- orbital_pv-0.3.0/apps/space-pv-api/src/space_pv_api/__init__.py +1 -0
- orbital_pv-0.3.0/apps/space-pv-api/src/space_pv_api/config.py +13 -0
- orbital_pv-0.3.0/apps/space-pv-api/src/space_pv_api/main.py +202 -0
- orbital_pv-0.3.0/apps/space-pv-api/src/space_pv_api/schemas.py +61 -0
- orbital_pv-0.3.0/apps/space-pv-cli/src/space_pv_cli/__init__.py +0 -0
- orbital_pv-0.3.0/apps/space-pv-cli/src/space_pv_cli/main.py +61 -0
- orbital_pv-0.3.0/orbital_pv.egg-info/PKG-INFO +209 -0
- orbital_pv-0.3.0/orbital_pv.egg-info/SOURCES.txt +69 -0
- orbital_pv-0.3.0/orbital_pv.egg-info/dependency_links.txt +1 -0
- orbital_pv-0.3.0/orbital_pv.egg-info/entry_points.txt +2 -0
- orbital_pv-0.3.0/orbital_pv.egg-info/requires.txt +44 -0
- orbital_pv-0.3.0/orbital_pv.egg-info/top_level.txt +11 -0
- orbital_pv-0.3.0/packages/space-pv-analytics/src/space_pv_analytics/__init__.py +17 -0
- orbital_pv-0.3.0/packages/space-pv-analytics/src/space_pv_analytics/performance.py +149 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/__init__.py +25 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/birds.py +27 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/cdaweb.py +34 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/celestrak.py +31 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/http_base.py +47 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/naif.py +23 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/satnogs.py +43 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/space_track.py +37 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/sscweb.py +27 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/swpc.py +28 -0
- orbital_pv-0.3.0/packages/space-pv-connectors/src/space_pv_connectors/tabular.py +124 -0
- orbital_pv-0.3.0/packages/space-pv-core/src/space_pv_core/__init__.py +19 -0
- orbital_pv-0.3.0/packages/space-pv-core/src/space_pv_core/constants.py +7 -0
- orbital_pv-0.3.0/packages/space-pv-core/src/space_pv_core/models.py +71 -0
- orbital_pv-0.3.0/packages/space-pv-orbit/src/space_pv_orbit/__init__.py +18 -0
- orbital_pv-0.3.0/packages/space-pv-orbit/src/space_pv_orbit/astropy_engine.py +17 -0
- orbital_pv-0.3.0/packages/space-pv-orbit/src/space_pv_orbit/attitude.py +39 -0
- orbital_pv-0.3.0/packages/space-pv-orbit/src/space_pv_orbit/geometry.py +100 -0
- orbital_pv-0.3.0/packages/space-pv-orbit/src/space_pv_orbit/skyfield_engine.py +39 -0
- orbital_pv-0.3.0/packages/space-pv-orbit/src/space_pv_orbit/spice_engine.py +35 -0
- orbital_pv-0.3.0/packages/space-pv-orchestration/src/space_pv_orchestration/__init__.py +5 -0
- orbital_pv-0.3.0/packages/space-pv-orchestration/src/space_pv_orchestration/celery_app.py +28 -0
- orbital_pv-0.3.0/packages/space-pv-orchestration/src/space_pv_orchestration/config.py +7 -0
- orbital_pv-0.3.0/packages/space-pv-orchestration/src/space_pv_orchestration/tasks.py +35 -0
- orbital_pv-0.3.0/packages/space-pv-pvmodel/src/space_pv_pvmodel/__init__.py +10 -0
- orbital_pv-0.3.0/packages/space-pv-pvmodel/src/space_pv_pvmodel/desoto.py +46 -0
- orbital_pv-0.3.0/packages/space-pv-pvmodel/src/space_pv_pvmodel/model.py +66 -0
- orbital_pv-0.3.0/packages/space-pv-space-env/src/space_pv_space_env/__init__.py +3 -0
- orbital_pv-0.3.0/packages/space-pv-space-env/src/space_pv_space_env/solar.py +18 -0
- orbital_pv-0.3.0/packages/space-pv-storage/src/space_pv_storage/__init__.py +41 -0
- orbital_pv-0.3.0/packages/space-pv-storage/src/space_pv_storage/config.py +12 -0
- orbital_pv-0.3.0/packages/space-pv-storage/src/space_pv_storage/duckdb_store.py +121 -0
- orbital_pv-0.3.0/packages/space-pv-storage/src/space_pv_storage/jobs.py +59 -0
- orbital_pv-0.3.0/packages/space-pv-storage/src/space_pv_storage/map_store.py +201 -0
- orbital_pv-0.3.0/packages/space-pv-storage/src/space_pv_storage/postgres.py +53 -0
- orbital_pv-0.3.0/pyproject.toml +158 -0
- orbital_pv-0.3.0/setup.cfg +4 -0
- orbital_pv-0.3.0/src/orbitalpv/__init__.py +77 -0
- orbital_pv-0.3.0/src/orbitalpv/analytics.py +19 -0
- orbital_pv-0.3.0/src/orbitalpv/data.py +5 -0
- orbital_pv-0.3.0/src/orbitalpv/environment.py +5 -0
- orbital_pv-0.3.0/src/orbitalpv/orbit.py +145 -0
- orbital_pv-0.3.0/src/orbitalpv/pv.py +17 -0
- orbital_pv-0.3.0/tests/test_attitude.py +12 -0
- orbital_pv-0.3.0/tests/test_connector_validation.py +17 -0
- orbital_pv-0.3.0/tests/test_models.py +8 -0
- orbital_pv-0.3.0/tests/test_orbit_geometry.py +28 -0
- orbital_pv-0.3.0/tests/test_performance.py +20 -0
- orbital_pv-0.3.0/tests/test_platform_structure.py +13 -0
- orbital_pv-0.3.0/tests/test_public_api.py +32 -0
- orbital_pv-0.3.0/tests/test_pvmodel_math.py +8 -0
- orbital_pv-0.3.0/tests/test_satnogs_connector.py +5 -0
- orbital_pv-0.3.0/tests/test_space_env.py +14 -0
- orbital_pv-0.3.0/tests/test_space_gis_structure.py +12 -0
orbital_pv-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orbital-pv
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Open-source spacecraft photovoltaic performance analytics
|
|
5
|
+
Author: OrbitalPV contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Documentation, https://julianascencio.github.io/orbital-pv/
|
|
8
|
+
Project-URL: Source, https://github.com/julianascencio/orbital-pv
|
|
9
|
+
Project-URL: Issues, https://github.com/julianascencio/orbital-pv/issues
|
|
10
|
+
Keywords: photovoltaics,space,satellite,orbit,solar,telemetry
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Requires-Python: >=3.13
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: httpx<0.29,>=0.28
|
|
20
|
+
Requires-Dist: numpy>=2.3
|
|
21
|
+
Requires-Dist: pandas>=2.2
|
|
22
|
+
Requires-Dist: pvlib<0.16,>=0.15.2
|
|
23
|
+
Provides-Extra: orbit
|
|
24
|
+
Requires-Dist: astropy<9,>=8.0; extra == "orbit"
|
|
25
|
+
Requires-Dist: skyfield<2,>=1.55; extra == "orbit"
|
|
26
|
+
Requires-Dist: sgp4<3,>=2.27; extra == "orbit"
|
|
27
|
+
Requires-Dist: spiceypy<9,>=8.2; extra == "orbit"
|
|
28
|
+
Provides-Extra: excel
|
|
29
|
+
Requires-Dist: openpyxl>=3.1; extra == "excel"
|
|
30
|
+
Provides-Extra: radiation
|
|
31
|
+
Requires-Dist: spacepy>=0.7; extra == "radiation"
|
|
32
|
+
Provides-Extra: examples
|
|
33
|
+
Requires-Dist: matplotlib>=3.10; extra == "examples"
|
|
34
|
+
Provides-Extra: app
|
|
35
|
+
Requires-Dist: celery[redis]<6,>=5.6; extra == "app"
|
|
36
|
+
Requires-Dist: duckdb<2,>=1.5; extra == "app"
|
|
37
|
+
Requires-Dist: fastapi<1,>=0.141; extra == "app"
|
|
38
|
+
Requires-Dist: psycopg[binary]<4,>=3.2; extra == "app"
|
|
39
|
+
Requires-Dist: pydantic-settings<3,>=2.7; extra == "app"
|
|
40
|
+
Requires-Dist: sqlalchemy<3,>=2.0; extra == "app"
|
|
41
|
+
Requires-Dist: uvicorn[standard]<1,>=0.34; extra == "app"
|
|
42
|
+
Provides-Extra: full
|
|
43
|
+
Requires-Dist: astropy<9,>=8.0; extra == "full"
|
|
44
|
+
Requires-Dist: celery[redis]<6,>=5.6; extra == "full"
|
|
45
|
+
Requires-Dist: duckdb<2,>=1.5; extra == "full"
|
|
46
|
+
Requires-Dist: fastapi<1,>=0.141; extra == "full"
|
|
47
|
+
Requires-Dist: matplotlib>=3.10; extra == "full"
|
|
48
|
+
Requires-Dist: openpyxl>=3.1; extra == "full"
|
|
49
|
+
Requires-Dist: psycopg[binary]<4,>=3.2; extra == "full"
|
|
50
|
+
Requires-Dist: pydantic-settings<3,>=2.7; extra == "full"
|
|
51
|
+
Requires-Dist: sgp4<3,>=2.27; extra == "full"
|
|
52
|
+
Requires-Dist: skyfield<2,>=1.55; extra == "full"
|
|
53
|
+
Requires-Dist: spacepy>=0.7; extra == "full"
|
|
54
|
+
Requires-Dist: spiceypy<9,>=8.2; extra == "full"
|
|
55
|
+
Requires-Dist: sqlalchemy<3,>=2.0; extra == "full"
|
|
56
|
+
Requires-Dist: uvicorn[standard]<1,>=0.34; extra == "full"
|
|
57
|
+
Dynamic: license-file
|
|
58
|
+
|
|
59
|
+
# OrbitalPV
|
|
60
|
+
|
|
61
|
+
Open-source full-stack lab for **spacecraft photovoltaic performance analytics**: PV physics, orbit/Sun geometry, real spacecraft data connectors, degradation/anomaly analytics, reproducible research labs, and a web application platform.
|
|
62
|
+
|
|
63
|
+
The structure follows a modular separation-of-concerns approach: domain models, physics engines, connectors and analytics are independent workspace packages with consistent quality tooling.
|
|
64
|
+
|
|
65
|
+
## Install from PyPI
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python -m pip install orbital-pv
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use the public API as:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import orbitalpv as opv
|
|
75
|
+
|
|
76
|
+
positions_m = opv.circular_orbit_positions_m(
|
|
77
|
+
altitude_m=550_000.0,
|
|
78
|
+
inclination_deg=51.6,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
g_am0 = opv.am0_irradiance(opv.ASTRONOMICAL_UNIT_M)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Optional extras:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python -m pip install "orbital-pv[examples]"
|
|
88
|
+
python -m pip install "orbital-pv[orbit]"
|
|
89
|
+
python -m pip install "orbital-pv[full]"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Foundation stack
|
|
93
|
+
|
|
94
|
+
- **pvlib** — tested PV equations and I-V/single-diode reference models behind a space-specific adapter.
|
|
95
|
+
- **Skyfield + python-sgp4** — Earth-orbit catalog propagation from CCSDS OMM/TLE.
|
|
96
|
+
- **Astropy** — time, units, frames and baseline solar-system coordinates.
|
|
97
|
+
- **SpiceyPy / NASA SPICE** — mission-grade ephemeris, attitude, clocks and frame transforms.
|
|
98
|
+
- **SpacePy / later radiation engines** — radiation/environment workstream.
|
|
99
|
+
- **HTTP connectors** — CelesTrak, Space-Track, NASA SSCWeb, NAIF, CDAWeb/HAPI, NOAA SWPC, SatNOGS DB and BIRDS EPS.
|
|
100
|
+
|
|
101
|
+
`poliastro` is intentionally not a core dependency because its upstream repository is archived.
|
|
102
|
+
|
|
103
|
+
## Repository map
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
orbital-pv/
|
|
107
|
+
├── src/orbitalpv/ # public PyPI API
|
|
108
|
+
├── apps/
|
|
109
|
+
│ ├── space-pv-cli/
|
|
110
|
+
│ ├── space-pv-api/
|
|
111
|
+
│ └── space-pv-web/
|
|
112
|
+
├── packages/
|
|
113
|
+
│ ├── space-pv-core/
|
|
114
|
+
│ ├── space-pv-orbit/
|
|
115
|
+
│ ├── space-pv-pvmodel/
|
|
116
|
+
│ ├── space-pv-space-env/
|
|
117
|
+
│ ├── space-pv-connectors/
|
|
118
|
+
│ ├── space-pv-analytics/
|
|
119
|
+
│ ├── space-pv-storage/
|
|
120
|
+
│ └── space-pv-orchestration/
|
|
121
|
+
├── examples/
|
|
122
|
+
├── labs/
|
|
123
|
+
├── data/{raw,bronze,silver,gold}/
|
|
124
|
+
├── docs/
|
|
125
|
+
└── tests/
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The `space_pv_*` packages remain available as lower-level implementation modules for contributors and advanced use, while `orbitalpv` is the stable user-facing import.
|
|
129
|
+
|
|
130
|
+
## Documentation
|
|
131
|
+
|
|
132
|
+
**https://julianascencio.github.io/orbital-pv/**
|
|
133
|
+
|
|
134
|
+
The documentation uses Sphinx, PyData Sphinx Theme, and Sphinx-Gallery. Gallery examples execute during the documentation build and use the same `orbitalpv` API installed from PyPI.
|
|
135
|
+
|
|
136
|
+
Build locally:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
make docs
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Development
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv sync --all-packages --group dev --group lab --group orbit --group pv --group space --group app
|
|
146
|
+
uv run pytest
|
|
147
|
+
uv run ruff check .
|
|
148
|
+
uv run ty check
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Science pipeline
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
real orbit/telemetry/environment data
|
|
155
|
+
|
|
|
156
|
+
v
|
|
157
|
+
canonical time + frames
|
|
158
|
+
|
|
|
159
|
+
+---------+----------+
|
|
160
|
+
| |
|
|
161
|
+
orbit + Sun PV telemetry
|
|
162
|
+
| |
|
|
163
|
+
v |
|
|
164
|
+
eclipse + AOI + AM0 |
|
|
165
|
+
| |
|
|
166
|
+
+----------+---------+
|
|
167
|
+
v
|
|
168
|
+
expected PV performance
|
|
169
|
+
|
|
|
170
|
+
v
|
|
171
|
+
residual / degradation
|
|
172
|
+
|
|
|
173
|
+
v
|
|
174
|
+
anomaly + EOL analytics
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
See `docs/foundation_stack.md`, `docs/connectors/catalog.md`, `docs/architecture.md` and `docs/state_of_art.md`.
|
|
178
|
+
|
|
179
|
+
## Application platform
|
|
180
|
+
|
|
181
|
+
The web/application layer is intentionally separated from the scientific packages:
|
|
182
|
+
|
|
183
|
+
- **React + TypeScript + Vite** — frontend application.
|
|
184
|
+
- **FastAPI** — API and job submission boundary.
|
|
185
|
+
- **PostgreSQL** — durable multi-user/transactional application state and job metadata.
|
|
186
|
+
- **DuckDB** — local analytics, parquet-backed research datasets and fast time-series exploration.
|
|
187
|
+
- **Celery + Redis** — long-running ingestion, orbit, PV and analytics jobs.
|
|
188
|
+
- **Flower** — local Celery worker/task observability.
|
|
189
|
+
|
|
190
|
+
Start the development stack with:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
docker compose up --build
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Then open React on `http://localhost:5173`, FastAPI docs on `http://localhost:8000/docs`, and Flower on `http://localhost:5555`.
|
|
197
|
+
|
|
198
|
+
## Space GIS and dynamic visualization
|
|
199
|
+
|
|
200
|
+
The platform separates scientific orbital reference-frame state from Earth-fixed GIS render products. PostGIS serves hot satellite positions, ground tracks, stations and footprints; DuckDB/Parquet retain analytical history and chart rollups; CesiumJS renders time-dynamic 3D orbital scenes. See `docs/space_gis_and_visualization.md`.
|
|
201
|
+
|
|
202
|
+
## Literature library
|
|
203
|
+
|
|
204
|
+
A curated, source-tagged literature library is maintained in [`docs/literature/`](docs/literature/README.md). It covers the 2026 state of the art in space-PV cells and arrays, mission-aware performance modelling, radiation/degradation, digital twins, telemetry/AI, and open flight datasets/APIs.
|
|
205
|
+
|
|
206
|
+
## Roadmap evidence
|
|
207
|
+
|
|
208
|
+
- `docs/literature/09_ieso_2026_workshop_roadmap.md` — 2026 IESO workshop targets translated into repository features and R&D priorities.
|
|
209
|
+
- `docs/product_feature_roadmap.md` — prioritized product epics derived from the literature and 2026 IESO workshop.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# OrbitalPV
|
|
2
|
+
|
|
3
|
+
Open-source full-stack lab for **spacecraft photovoltaic performance analytics**: PV physics, orbit/Sun geometry, real spacecraft data connectors, degradation/anomaly analytics, reproducible research labs, and a web application platform.
|
|
4
|
+
|
|
5
|
+
The structure follows a modular separation-of-concerns approach: domain models, physics engines, connectors and analytics are independent workspace packages with consistent quality tooling.
|
|
6
|
+
|
|
7
|
+
## Install from PyPI
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python -m pip install orbital-pv
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Use the public API as:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import orbitalpv as opv
|
|
17
|
+
|
|
18
|
+
positions_m = opv.circular_orbit_positions_m(
|
|
19
|
+
altitude_m=550_000.0,
|
|
20
|
+
inclination_deg=51.6,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
g_am0 = opv.am0_irradiance(opv.ASTRONOMICAL_UNIT_M)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Optional extras:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
python -m pip install "orbital-pv[examples]"
|
|
30
|
+
python -m pip install "orbital-pv[orbit]"
|
|
31
|
+
python -m pip install "orbital-pv[full]"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Foundation stack
|
|
35
|
+
|
|
36
|
+
- **pvlib** — tested PV equations and I-V/single-diode reference models behind a space-specific adapter.
|
|
37
|
+
- **Skyfield + python-sgp4** — Earth-orbit catalog propagation from CCSDS OMM/TLE.
|
|
38
|
+
- **Astropy** — time, units, frames and baseline solar-system coordinates.
|
|
39
|
+
- **SpiceyPy / NASA SPICE** — mission-grade ephemeris, attitude, clocks and frame transforms.
|
|
40
|
+
- **SpacePy / later radiation engines** — radiation/environment workstream.
|
|
41
|
+
- **HTTP connectors** — CelesTrak, Space-Track, NASA SSCWeb, NAIF, CDAWeb/HAPI, NOAA SWPC, SatNOGS DB and BIRDS EPS.
|
|
42
|
+
|
|
43
|
+
`poliastro` is intentionally not a core dependency because its upstream repository is archived.
|
|
44
|
+
|
|
45
|
+
## Repository map
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
orbital-pv/
|
|
49
|
+
├── src/orbitalpv/ # public PyPI API
|
|
50
|
+
├── apps/
|
|
51
|
+
│ ├── space-pv-cli/
|
|
52
|
+
│ ├── space-pv-api/
|
|
53
|
+
│ └── space-pv-web/
|
|
54
|
+
├── packages/
|
|
55
|
+
│ ├── space-pv-core/
|
|
56
|
+
│ ├── space-pv-orbit/
|
|
57
|
+
│ ├── space-pv-pvmodel/
|
|
58
|
+
│ ├── space-pv-space-env/
|
|
59
|
+
│ ├── space-pv-connectors/
|
|
60
|
+
│ ├── space-pv-analytics/
|
|
61
|
+
│ ├── space-pv-storage/
|
|
62
|
+
│ └── space-pv-orchestration/
|
|
63
|
+
├── examples/
|
|
64
|
+
├── labs/
|
|
65
|
+
├── data/{raw,bronze,silver,gold}/
|
|
66
|
+
├── docs/
|
|
67
|
+
└── tests/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The `space_pv_*` packages remain available as lower-level implementation modules for contributors and advanced use, while `orbitalpv` is the stable user-facing import.
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
74
|
+
**https://julianascencio.github.io/orbital-pv/**
|
|
75
|
+
|
|
76
|
+
The documentation uses Sphinx, PyData Sphinx Theme, and Sphinx-Gallery. Gallery examples execute during the documentation build and use the same `orbitalpv` API installed from PyPI.
|
|
77
|
+
|
|
78
|
+
Build locally:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
make docs
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv sync --all-packages --group dev --group lab --group orbit --group pv --group space --group app
|
|
88
|
+
uv run pytest
|
|
89
|
+
uv run ruff check .
|
|
90
|
+
uv run ty check
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Science pipeline
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
real orbit/telemetry/environment data
|
|
97
|
+
|
|
|
98
|
+
v
|
|
99
|
+
canonical time + frames
|
|
100
|
+
|
|
|
101
|
+
+---------+----------+
|
|
102
|
+
| |
|
|
103
|
+
orbit + Sun PV telemetry
|
|
104
|
+
| |
|
|
105
|
+
v |
|
|
106
|
+
eclipse + AOI + AM0 |
|
|
107
|
+
| |
|
|
108
|
+
+----------+---------+
|
|
109
|
+
v
|
|
110
|
+
expected PV performance
|
|
111
|
+
|
|
|
112
|
+
v
|
|
113
|
+
residual / degradation
|
|
114
|
+
|
|
|
115
|
+
v
|
|
116
|
+
anomaly + EOL analytics
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
See `docs/foundation_stack.md`, `docs/connectors/catalog.md`, `docs/architecture.md` and `docs/state_of_art.md`.
|
|
120
|
+
|
|
121
|
+
## Application platform
|
|
122
|
+
|
|
123
|
+
The web/application layer is intentionally separated from the scientific packages:
|
|
124
|
+
|
|
125
|
+
- **React + TypeScript + Vite** — frontend application.
|
|
126
|
+
- **FastAPI** — API and job submission boundary.
|
|
127
|
+
- **PostgreSQL** — durable multi-user/transactional application state and job metadata.
|
|
128
|
+
- **DuckDB** — local analytics, parquet-backed research datasets and fast time-series exploration.
|
|
129
|
+
- **Celery + Redis** — long-running ingestion, orbit, PV and analytics jobs.
|
|
130
|
+
- **Flower** — local Celery worker/task observability.
|
|
131
|
+
|
|
132
|
+
Start the development stack with:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
docker compose up --build
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Then open React on `http://localhost:5173`, FastAPI docs on `http://localhost:8000/docs`, and Flower on `http://localhost:5555`.
|
|
139
|
+
|
|
140
|
+
## Space GIS and dynamic visualization
|
|
141
|
+
|
|
142
|
+
The platform separates scientific orbital reference-frame state from Earth-fixed GIS render products. PostGIS serves hot satellite positions, ground tracks, stations and footprints; DuckDB/Parquet retain analytical history and chart rollups; CesiumJS renders time-dynamic 3D orbital scenes. See `docs/space_gis_and_visualization.md`.
|
|
143
|
+
|
|
144
|
+
## Literature library
|
|
145
|
+
|
|
146
|
+
A curated, source-tagged literature library is maintained in [`docs/literature/`](docs/literature/README.md). It covers the 2026 state of the art in space-PV cells and arrays, mission-aware performance modelling, radiation/degradation, digital twins, telemetry/AI, and open flight datasets/APIs.
|
|
147
|
+
|
|
148
|
+
## Roadmap evidence
|
|
149
|
+
|
|
150
|
+
- `docs/literature/09_ieso_2026_workshop_roadmap.md` — 2026 IESO workshop targets translated into repository features and R&D priorities.
|
|
151
|
+
- `docs/product_feature_roadmap.md` — prioritized product epics derived from the literature and 2026 IESO workshop.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Space PV HTTP API."""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class APISettings(BaseSettings):
|
|
5
|
+
model_config = SettingsConfigDict(env_prefix="SPACE_PV_", env_file=".env", extra="ignore")
|
|
6
|
+
|
|
7
|
+
api_host: str = "0.0.0.0"
|
|
8
|
+
api_port: int = 8000
|
|
9
|
+
cors_origins: str = "http://localhost:5173"
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def allowed_origins(self) -> list[str]:
|
|
13
|
+
return [origin.strip() for origin in self.cors_origins.split(",") if origin.strip()]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
from datetime import UTC, datetime, timedelta
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI, HTTPException
|
|
6
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
7
|
+
|
|
8
|
+
from space_pv_orchestration.celery_app import celery_app
|
|
9
|
+
from space_pv_storage import (
|
|
10
|
+
Base,
|
|
11
|
+
JobRun,
|
|
12
|
+
attach_celery_task,
|
|
13
|
+
create_engine_from_settings,
|
|
14
|
+
create_job,
|
|
15
|
+
get_track_samples,
|
|
16
|
+
list_jobs,
|
|
17
|
+
list_latest_satellite_states,
|
|
18
|
+
session_factory,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from .config import APISettings
|
|
22
|
+
from .schemas import (
|
|
23
|
+
JobAccepted,
|
|
24
|
+
JobCreate,
|
|
25
|
+
JobRead,
|
|
26
|
+
MapSatelliteState,
|
|
27
|
+
MapScene,
|
|
28
|
+
MapTrack,
|
|
29
|
+
OrbitSample,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
settings = APISettings()
|
|
33
|
+
engine = create_engine_from_settings()
|
|
34
|
+
SessionLocal = session_factory()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@asynccontextmanager
|
|
38
|
+
async def lifespan(_: FastAPI):
|
|
39
|
+
# Development bootstrap. Replace with Alembic migrations before production.
|
|
40
|
+
Base.metadata.create_all(engine)
|
|
41
|
+
yield
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
app = FastAPI(
|
|
45
|
+
title="Space PV Analytics API",
|
|
46
|
+
version="0.1.0",
|
|
47
|
+
description="API for satellite ingestion, orbit/PV computations and analytics jobs.",
|
|
48
|
+
lifespan=lifespan,
|
|
49
|
+
)
|
|
50
|
+
app.add_middleware(
|
|
51
|
+
CORSMiddleware,
|
|
52
|
+
allow_origins=settings.allowed_origins,
|
|
53
|
+
allow_credentials=True,
|
|
54
|
+
allow_methods=["*"],
|
|
55
|
+
allow_headers=["*"],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.get("/health")
|
|
60
|
+
def health() -> dict[str, str]:
|
|
61
|
+
return {"status": "ok", "service": "space-pv-api"}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@app.post("/api/v1/jobs", response_model=JobAccepted, status_code=202)
|
|
65
|
+
def submit_job(request: JobCreate) -> JobAccepted:
|
|
66
|
+
with SessionLocal() as session:
|
|
67
|
+
job = create_job(session, request.job_type, request.payload)
|
|
68
|
+
task = celery_app.send_task(
|
|
69
|
+
"space_pv.analytics.execute",
|
|
70
|
+
args=[job.id, request.job_type, request.payload],
|
|
71
|
+
queue="analytics",
|
|
72
|
+
)
|
|
73
|
+
attach_celery_task(session, job, task.id)
|
|
74
|
+
return JobAccepted(job_id=job.id, task_id=task.id, job_type=request.job_type)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _job_to_schema(job: JobRun) -> JobRead:
|
|
78
|
+
return JobRead(
|
|
79
|
+
id=job.id,
|
|
80
|
+
task_name=job.task_name,
|
|
81
|
+
celery_task_id=job.celery_task_id,
|
|
82
|
+
status=job.status.value,
|
|
83
|
+
payload=job.payload,
|
|
84
|
+
result=job.result,
|
|
85
|
+
error=job.error,
|
|
86
|
+
created_at=job.created_at,
|
|
87
|
+
started_at=job.started_at,
|
|
88
|
+
finished_at=job.finished_at,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.get("/api/v1/jobs", response_model=list[JobRead])
|
|
93
|
+
def jobs(limit: int = 100) -> list[JobRead]:
|
|
94
|
+
with SessionLocal() as session:
|
|
95
|
+
return [_job_to_schema(job) for job in list_jobs(session, min(max(limit, 1), 500))]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@app.get("/api/v1/jobs/{job_id}", response_model=JobRead)
|
|
99
|
+
def get_job(job_id: str) -> JobRead:
|
|
100
|
+
with SessionLocal() as session:
|
|
101
|
+
job = session.get(JobRun, job_id)
|
|
102
|
+
if job is None:
|
|
103
|
+
raise HTTPException(status_code=404, detail="Job not found")
|
|
104
|
+
return _job_to_schema(job)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@app.get("/api/v1/map/satellites/latest", response_model=list[MapSatelliteState])
|
|
108
|
+
def latest_satellite_map_states(limit: int = 2000) -> list[MapSatelliteState]:
|
|
109
|
+
with SessionLocal() as session:
|
|
110
|
+
rows = list_latest_satellite_states(session, min(max(limit, 1), 10000))
|
|
111
|
+
return [MapSatelliteState(**row) for row in rows]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.get("/api/v1/map/satellites/{satellite_id}/track", response_model=MapTrack)
|
|
115
|
+
def satellite_track(
|
|
116
|
+
satellite_id: str,
|
|
117
|
+
start: datetime,
|
|
118
|
+
end: datetime,
|
|
119
|
+
lod: int = 1,
|
|
120
|
+
) -> MapTrack:
|
|
121
|
+
if end <= start:
|
|
122
|
+
raise HTTPException(status_code=400, detail="end must be after start")
|
|
123
|
+
with SessionLocal() as session:
|
|
124
|
+
segments = get_track_samples(session, satellite_id, start, end, lod)
|
|
125
|
+
if not segments:
|
|
126
|
+
raise HTTPException(status_code=404, detail="No cached track for requested window")
|
|
127
|
+
|
|
128
|
+
samples: list[OrbitSample] = []
|
|
129
|
+
for segment in segments:
|
|
130
|
+
epoch = segment["start_at"]
|
|
131
|
+
for offset_s, lon, lat, altitude_m in segment["samples"]:
|
|
132
|
+
timestamp = epoch + timedelta(seconds=float(offset_s))
|
|
133
|
+
if start <= timestamp <= end:
|
|
134
|
+
samples.append(
|
|
135
|
+
OrbitSample(
|
|
136
|
+
timestamp=timestamp,
|
|
137
|
+
longitude_deg=float(lon),
|
|
138
|
+
latitude_deg=float(lat),
|
|
139
|
+
altitude_m=float(altitude_m),
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
return MapTrack(
|
|
143
|
+
satellite_id=satellite_id,
|
|
144
|
+
name=satellite_id,
|
|
145
|
+
source=str(segments[0]["source"]),
|
|
146
|
+
samples=samples,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _demo_track(
|
|
151
|
+
name: str,
|
|
152
|
+
inclination_deg: float,
|
|
153
|
+
altitude_m: float,
|
|
154
|
+
phase_deg: float,
|
|
155
|
+
epoch: datetime,
|
|
156
|
+
) -> MapTrack:
|
|
157
|
+
"""Visual-only deterministic samples used to validate the Cesium pipeline.
|
|
158
|
+
|
|
159
|
+
These are deliberately not presented as propagated ephemerides. Real tracks come
|
|
160
|
+
from space-pv-orbit and are cached in gis.orbit_track_segments.
|
|
161
|
+
"""
|
|
162
|
+
period_s = 95.0 * 60.0
|
|
163
|
+
samples: list[OrbitSample] = []
|
|
164
|
+
for offset_s in range(0, 2 * 60 * 60 + 1, 30):
|
|
165
|
+
theta = 2.0 * math.pi * offset_s / period_s + math.radians(phase_deg)
|
|
166
|
+
latitude = inclination_deg * math.sin(theta)
|
|
167
|
+
longitude = ((math.degrees(theta) * 0.78 + phase_deg + 180.0) % 360.0) - 180.0
|
|
168
|
+
samples.append(
|
|
169
|
+
OrbitSample(
|
|
170
|
+
timestamp=epoch + timedelta(seconds=offset_s),
|
|
171
|
+
longitude_deg=longitude,
|
|
172
|
+
latitude_deg=latitude,
|
|
173
|
+
altitude_m=altitude_m,
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
return MapTrack(
|
|
177
|
+
satellite_id=name.lower().replace(" ", "-"),
|
|
178
|
+
name=name,
|
|
179
|
+
source="visual_demo",
|
|
180
|
+
samples=samples,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@app.get("/api/v1/map/demo", response_model=MapScene)
|
|
185
|
+
def demo_map_scene() -> MapScene:
|
|
186
|
+
epoch = datetime.now(UTC).replace(microsecond=0)
|
|
187
|
+
return MapScene(
|
|
188
|
+
generated_at=epoch,
|
|
189
|
+
tracks=[
|
|
190
|
+
_demo_track("Demo LEO 51.6", 51.6, 420_000.0, 0.0, epoch),
|
|
191
|
+
_demo_track("Demo SSO 97.4", 82.0, 550_000.0, 115.0, epoch),
|
|
192
|
+
_demo_track("Demo LEO 35", 35.0, 650_000.0, 235.0, epoch),
|
|
193
|
+
],
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def run() -> None:
|
|
198
|
+
import uvicorn
|
|
199
|
+
|
|
200
|
+
uvicorn.run(
|
|
201
|
+
"space_pv_api.main:app", host=settings.api_host, port=settings.api_port, reload=False
|
|
202
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class JobCreate(BaseModel):
|
|
8
|
+
job_type: str = Field(min_length=1, max_length=200)
|
|
9
|
+
payload: dict[str, Any] = Field(default_factory=dict)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class JobAccepted(BaseModel):
|
|
13
|
+
job_id: str
|
|
14
|
+
task_id: str
|
|
15
|
+
job_type: str
|
|
16
|
+
status: str = "queued"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class JobRead(BaseModel):
|
|
20
|
+
id: str
|
|
21
|
+
task_name: str
|
|
22
|
+
celery_task_id: str | None
|
|
23
|
+
status: str
|
|
24
|
+
payload: dict[str, Any]
|
|
25
|
+
result: dict[str, Any] | None
|
|
26
|
+
error: str | None
|
|
27
|
+
created_at: datetime
|
|
28
|
+
started_at: datetime | None
|
|
29
|
+
finished_at: datetime | None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MapSatelliteState(BaseModel):
|
|
33
|
+
satellite_id: str
|
|
34
|
+
name: str
|
|
35
|
+
norad_id: int | None = None
|
|
36
|
+
observed_at: datetime
|
|
37
|
+
longitude_deg: float
|
|
38
|
+
latitude_deg: float
|
|
39
|
+
altitude_m: float
|
|
40
|
+
illumination_fraction: float | None = None
|
|
41
|
+
pv_power_w: float | None = None
|
|
42
|
+
source: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class OrbitSample(BaseModel):
|
|
46
|
+
timestamp: datetime
|
|
47
|
+
longitude_deg: float
|
|
48
|
+
latitude_deg: float
|
|
49
|
+
altitude_m: float
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MapTrack(BaseModel):
|
|
53
|
+
satellite_id: str
|
|
54
|
+
name: str
|
|
55
|
+
source: str
|
|
56
|
+
samples: list[OrbitSample]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MapScene(BaseModel):
|
|
60
|
+
generated_at: datetime
|
|
61
|
+
tracks: list[MapTrack]
|
|
File without changes
|