astrox-python 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,152 @@
1
+ """Simple-coverage FOM routes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import Any
7
+
8
+ from astrox import components
9
+
10
+ from ._core import CoverageGrid
11
+ from ._fom import post_coverage_input, post_time_value_by_grid_point_input
12
+
13
+ __all__ = [
14
+ "by_grid_point",
15
+ "by_grid_point_at_time",
16
+ "grid_stats",
17
+ "grid_stats_over_time",
18
+ ]
19
+
20
+
21
+ def by_grid_point(
22
+ *,
23
+ start: str,
24
+ stop: str,
25
+ grid: CoverageGrid,
26
+ assets: Sequence[components.Entity],
27
+ minimum_assets: int | None = None,
28
+ exactly_assets: int | None = None,
29
+ grid_point_sensor: components.EntitySensor | None = None,
30
+ grid_point_constraints: Sequence[components.Constraint] | None = None,
31
+ include_asset_access_results: bool | None = None,
32
+ include_coverage_points: bool | None = None,
33
+ step_s: float | None = None,
34
+ description: str | None = None,
35
+ ) -> dict[str, Any]:
36
+ """Compute simple-coverage FOM values for each grid point."""
37
+ return post_coverage_input(
38
+ "/Coverage/FOM/ValueByGridPoint/SimpleCoverage",
39
+ start=start,
40
+ stop=stop,
41
+ grid=grid,
42
+ assets=assets,
43
+ minimum_assets=minimum_assets,
44
+ exactly_assets=exactly_assets,
45
+ grid_point_sensor=grid_point_sensor,
46
+ grid_point_constraints=grid_point_constraints,
47
+ include_asset_access_results=include_asset_access_results,
48
+ include_coverage_points=include_coverage_points,
49
+ step_s=step_s,
50
+ description=description,
51
+ )
52
+
53
+
54
+ def by_grid_point_at_time(
55
+ *,
56
+ time: str,
57
+ start: str,
58
+ stop: str,
59
+ grid: CoverageGrid,
60
+ assets: Sequence[components.Entity],
61
+ minimum_assets: int | None = None,
62
+ exactly_assets: int | None = None,
63
+ grid_point_sensor: components.EntitySensor | None = None,
64
+ grid_point_constraints: Sequence[components.Constraint] | None = None,
65
+ include_asset_access_results: bool | None = None,
66
+ include_coverage_points: bool | None = None,
67
+ step_s: float | None = None,
68
+ description: str | None = None,
69
+ ) -> dict[str, Any]:
70
+ """Compute simple-coverage FOM values for each grid point at one time."""
71
+ return post_time_value_by_grid_point_input(
72
+ "/Coverage/FOM/ValueByGridPointAtTime/SimpleCoverage",
73
+ time=time,
74
+ start=start,
75
+ stop=stop,
76
+ grid=grid,
77
+ assets=assets,
78
+ minimum_assets=minimum_assets,
79
+ exactly_assets=exactly_assets,
80
+ grid_point_sensor=grid_point_sensor,
81
+ grid_point_constraints=grid_point_constraints,
82
+ include_asset_access_results=include_asset_access_results,
83
+ include_coverage_points=include_coverage_points,
84
+ step_s=step_s,
85
+ description=description,
86
+ )
87
+
88
+
89
+ def grid_stats(
90
+ *,
91
+ start: str,
92
+ stop: str,
93
+ grid: CoverageGrid,
94
+ assets: Sequence[components.Entity],
95
+ minimum_assets: int | None = None,
96
+ exactly_assets: int | None = None,
97
+ grid_point_sensor: components.EntitySensor | None = None,
98
+ grid_point_constraints: Sequence[components.Constraint] | None = None,
99
+ include_asset_access_results: bool | None = None,
100
+ include_coverage_points: bool | None = None,
101
+ step_s: float | None = None,
102
+ description: str | None = None,
103
+ ) -> dict[str, Any]:
104
+ """Compute minimum, maximum, and average simple-coverage FOM values."""
105
+ return post_coverage_input(
106
+ "/Coverage/FOM/GridStats/SimpleCoverage",
107
+ start=start,
108
+ stop=stop,
109
+ grid=grid,
110
+ assets=assets,
111
+ minimum_assets=minimum_assets,
112
+ exactly_assets=exactly_assets,
113
+ grid_point_sensor=grid_point_sensor,
114
+ grid_point_constraints=grid_point_constraints,
115
+ include_asset_access_results=include_asset_access_results,
116
+ include_coverage_points=include_coverage_points,
117
+ step_s=step_s,
118
+ description=description,
119
+ )
120
+
121
+
122
+ def grid_stats_over_time(
123
+ *,
124
+ start: str,
125
+ stop: str,
126
+ grid: CoverageGrid,
127
+ assets: Sequence[components.Entity],
128
+ minimum_assets: int | None = None,
129
+ exactly_assets: int | None = None,
130
+ grid_point_sensor: components.EntitySensor | None = None,
131
+ grid_point_constraints: Sequence[components.Constraint] | None = None,
132
+ include_asset_access_results: bool | None = None,
133
+ include_coverage_points: bool | None = None,
134
+ step_s: float | None = None,
135
+ description: str | None = None,
136
+ ) -> dict[str, Any]:
137
+ """Compute simple-coverage grid statistics over time."""
138
+ return post_coverage_input(
139
+ "/Coverage/FOM/GridStatsOverTime/SimpleCoverage",
140
+ start=start,
141
+ stop=stop,
142
+ grid=grid,
143
+ assets=assets,
144
+ minimum_assets=minimum_assets,
145
+ exactly_assets=exactly_assets,
146
+ grid_point_sensor=grid_point_sensor,
147
+ grid_point_constraints=grid_point_constraints,
148
+ include_asset_access_results=include_asset_access_results,
149
+ include_coverage_points=include_coverage_points,
150
+ step_s=step_s,
151
+ description=description,
152
+ )
astrox/exceptions.py ADDED
@@ -0,0 +1,92 @@
1
+ """Custom exceptions for the astrox package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class AstroxError(Exception):
9
+ """Base exception for all astrox errors."""
10
+
11
+ pass
12
+
13
+
14
+ class AstroxAPIError(AstroxError):
15
+ """API returned IsSuccess=false or other API-level error."""
16
+
17
+ def __init__(self, message: str, endpoint: str, response: Any):
18
+ """Initialize API error.
19
+
20
+ Args:
21
+ message: Error message from API
22
+ endpoint: API endpoint that was called
23
+ response: Response object from requests
24
+ """
25
+ self.message = message
26
+ self.endpoint = endpoint
27
+ self.response = response
28
+ super().__init__(message)
29
+
30
+
31
+ class AstroxHTTPError(AstroxError):
32
+ """HTTP status code indicates error."""
33
+
34
+ def __init__(self, status_code: int, message: str, endpoint: str, response: Any):
35
+ """Initialize HTTP error.
36
+
37
+ Args:
38
+ status_code: HTTP status code
39
+ message: Error message
40
+ endpoint: API endpoint that was called
41
+ response: Response object from requests
42
+ """
43
+ self.status_code = status_code
44
+ self.message = message
45
+ self.endpoint = endpoint
46
+ self.response = response
47
+ super().__init__(f"HTTP {status_code}: {message}")
48
+
49
+
50
+ class AstroxTimeoutError(AstroxError):
51
+ """Request timed out."""
52
+
53
+ def __init__(self, endpoint: str, timeout: float):
54
+ """Initialize timeout error.
55
+
56
+ Args:
57
+ endpoint: API endpoint that was called
58
+ timeout: Timeout value in seconds
59
+ """
60
+ self.endpoint = endpoint
61
+ self.timeout = timeout
62
+ super().__init__(f"Request to {endpoint} timed out after {timeout}s")
63
+
64
+
65
+ class AstroxConnectionError(AstroxError):
66
+ """Failed to connect to API."""
67
+
68
+ def __init__(self, message: str, original_error: Exception | None):
69
+ """Initialize connection error.
70
+
71
+ Args:
72
+ message: Error message
73
+ original_error: Original exception that caused the error
74
+ """
75
+ self.message = message
76
+ self.original_error = original_error
77
+ super().__init__(message)
78
+
79
+
80
+ class AstroxValidationError(AstroxError):
81
+ """Response validation failed."""
82
+
83
+ def __init__(self, message: str, errors: list[dict[str, Any]]):
84
+ """Initialize validation error.
85
+
86
+ Args:
87
+ message: Error message
88
+ errors: List of validation errors from the response model
89
+ """
90
+ self.message = message
91
+ self.errors = errors
92
+ super().__init__(message)
astrox/lighting.py ADDED
@@ -0,0 +1,121 @@
1
+ """Lighting analysis functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from numbers import Real
7
+ from typing import Any
8
+
9
+ from astrox import components
10
+ from astrox._http import raw
11
+
12
+ __all__ = [
13
+ "lighting_times",
14
+ "solar_aer",
15
+ "solar_intensity",
16
+ ]
17
+
18
+
19
+ def _include_if_supplied(payload: dict[str, Any], wire_key: str, value: Any) -> None:
20
+ if value is not None:
21
+ payload[wire_key] = value
22
+
23
+
24
+ def _number_sequence_to_list(value: Sequence[float], *, parameter: str) -> list[float]:
25
+ if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
26
+ raise TypeError(f"{parameter} must be a sequence of numbers")
27
+ items = list(value)
28
+ if not all(isinstance(item, Real) and not isinstance(item, bool) for item in items):
29
+ raise TypeError(f"{parameter} must be a sequence of numbers")
30
+ return items
31
+
32
+
33
+ def _string_sequence_to_list(value: Sequence[str], *, parameter: str) -> list[str]:
34
+ if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
35
+ raise TypeError(f"{parameter} must be a sequence of strings")
36
+ items = list(value)
37
+ if not all(isinstance(item, str) for item in items):
38
+ raise TypeError(f"{parameter} must be a sequence of strings")
39
+ return items
40
+
41
+
42
+ def lighting_times(
43
+ *,
44
+ start: str,
45
+ stop: str,
46
+ position: components.EntityPosition,
47
+ description: str | None = None,
48
+ az_el_mask_data: Sequence[float] | None = None,
49
+ occultation_bodies: Sequence[str] | None = None,
50
+ ) -> dict[str, Any]:
51
+ """Compute sunlight, penumbra, and umbra intervals for a position source."""
52
+ payload: dict[str, Any] = {
53
+ "Start": start,
54
+ "Stop": stop,
55
+ "Position": components._position_to_wire(position),
56
+ }
57
+ _include_if_supplied(payload, "Description", description)
58
+ if az_el_mask_data is not None:
59
+ payload["AzElMaskData"] = _number_sequence_to_list(
60
+ az_el_mask_data,
61
+ parameter="az_el_mask_data",
62
+ )
63
+ if occultation_bodies is not None:
64
+ payload["OccultationBodies"] = _string_sequence_to_list(
65
+ occultation_bodies,
66
+ parameter="occultation_bodies",
67
+ )
68
+
69
+ return raw.post("/Lighting/LightingTimes", json=payload)
70
+
71
+
72
+ def solar_intensity(
73
+ *,
74
+ start: str,
75
+ stop: str,
76
+ position: components.EntityPosition,
77
+ description: str | None = None,
78
+ az_el_mask_data: Sequence[float] | None = None,
79
+ step_s: float | None = None,
80
+ occultation_bodies: Sequence[str] | None = None,
81
+ ) -> dict[str, Any]:
82
+ """Compute solar intensity samples for a position source."""
83
+ payload: dict[str, Any] = {
84
+ "Start": start,
85
+ "Stop": stop,
86
+ "Position": components._position_to_wire(position),
87
+ }
88
+ _include_if_supplied(payload, "Description", description)
89
+ _include_if_supplied(payload, "TimeStepSec", step_s)
90
+ if az_el_mask_data is not None:
91
+ payload["AzElMaskData"] = _number_sequence_to_list(
92
+ az_el_mask_data,
93
+ parameter="az_el_mask_data",
94
+ )
95
+ if occultation_bodies is not None:
96
+ payload["OccultationBodies"] = _string_sequence_to_list(
97
+ occultation_bodies,
98
+ parameter="occultation_bodies",
99
+ )
100
+
101
+ return raw.post("/Lighting/SolarIntensity", json=payload)
102
+
103
+
104
+ def solar_aer(
105
+ *,
106
+ start: str,
107
+ stop: str,
108
+ position: components.EntityPosition,
109
+ text: str | None = None,
110
+ step_s: int | None = None,
111
+ ) -> dict[str, Any]:
112
+ """Compute solar azimuth, elevation, and range samples for a position source."""
113
+ payload: dict[str, Any] = {
114
+ "Start": start,
115
+ "Stop": stop,
116
+ "Position": components._position_to_wire(position),
117
+ }
118
+ _include_if_supplied(payload, "Text", text)
119
+ _include_if_supplied(payload, "TimeStepSec", step_s)
120
+
121
+ return raw.post("/Lighting/SolarAER", json=payload)