Scoring Strategies¶
Scoring strategies assign a numeric score in [0, 1] to each candidate window. The score determines priority order during time allocation.
Design pattern¶
The Strategy + Factory pattern is used:
factory = ScoringStrategyFactory()
strategy = factory.get_strategy(profile.category)
score = strategy.calculate_score(profile, time, astronomy_engine)
StandardStrategy¶
Category: STANDARD
For regular targets with no temporal urgency. Computes a weighted average of five components:
| Component | Weight | Formula |
|---|---|---|
| Altitude | 1.0 | alt_deg / 90° |
| Airmass | 0.5 | 1 - (airmass - 1) / 4 |
| Lunar illumination | 1.0 | 1 - moon_phase |
| Lunar separation | 1.0 | moon_sep_deg / 180° |
| Target priority | 0.5 | clip(priority, 0, 1) |
All components are normalised to [0, 1]. The weighted mean produces the final score.
Example: A target at zenith (alt=90°, airmass=1.0) with the Moon far away and priority 1.0 will score ≈ 1.0.
PeriodicStrategy¶
Category: PERIODIC (exoplanet transits, eclipsing binary minima)
Adds a Gaussian urgency component centred on the transit midpoint:
urgency = exp(−(t − t_mid)² / (2 × σ²))
where σ = 0.75 × half_window_duration
- Outside the transit window: score = 0.0 (discarded).
- At transit centre: urgency ≈ 1.0.
- At transit edges: urgency ≈ 0.5.
| Component | Weight |
|---|---|
| Urgency (Gaussian) | 3.0 |
| Altitude | 1.0 |
| Airmass | 0.5 |
| Lunar illumination | 0.75 |
| Lunar separation | 0.75 |
| Priority | 0.5 |
The high urgency weight (3.0) ensures transits are prioritised over standard targets.
TransientStrategy¶
Category: TRANSIENT (supernovae, GRBs, novae)
Urgency decays exponentially with a half-life of 6 hours:
urgency = 2^(−hours_since_alert / 6.0)
| Time since alert | Urgency |
|---|---|
| 0 h (discovery) | 1.0 |
| 6 h | 0.5 |
| 12 h | 0.25 |
| 24 h | 0.063 |
| 48 h | 0.004 |
| Component | Weight |
|---|---|
| Urgency (exponential) | 3.0 |
| Altitude | 1.0 |
| Airmass | 0.5 |
| Lunar illumination | 0.5 |
| Lunar separation | 0.5 |
| Priority | 0.5 |
After the profile's fade_date, the score is 0.0.
NonSiderealStrategy¶
Category: NON_SIDEREAL (asteroids, comets)
Adapts coordinate computation for objects with proper motion using ephemerides. The target position is updated at each time evaluation. Scoring components are equivalent to StandardStrategy.
Implementing a custom strategy¶
from scheduler.scoring_strategies.base import ScoringStrategy
class MyCustomStrategy(ScoringStrategy):
def calculate_score(self, profile, time, astronomy_engine) -> float:
# Your logic here
return score # value in [0.0, 1.0]
Register the strategy in ScoringStrategyFactory by adding it to the category mapping dictionary.