File size: 3,374 Bytes
9e6ee24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from app.utils import safe_float, safe_int

FEATURE_COLUMNS = [
    "delta_mean_motion","delta_inclination","delta_eccentricity","delta_raan","delta_bstar",
    "launch_year_gap","same_object_type","same_shell","shell_density_proxy","close_approach_proxy",
    "persistence_proxy","recurrence_count","trend_delta_score","score_volatility_proxy",
    "graph_degree_sum","graph_common_neighbors","graph_jaccard","graph_local_density",
]

def normalize_object(raw):
    name = raw.get("OBJECT_NAME") or raw.get("object_name") or "UNKNOWN"
    norad = raw.get("NORAD_CAT_ID") or raw.get("norad_cat_id")
    intl = raw.get("OBJECT_ID") or raw.get("object_id") or ""
    launch_year = int(intl[:4]) if len(intl) >= 4 and intl[:4].isdigit() else None
    return {
        "object_id": str(norad or name),
        "norad_cat_id": safe_int(norad, 0) or None,
        "object_name": name,
        "object_type": raw.get("OBJECT_TYPE") or raw.get("object_type"),
        "mean_motion": safe_float(raw.get("MEAN_MOTION") or raw.get("mean_motion")),
        "inclination": safe_float(raw.get("INCLINATION") or raw.get("inclination")),
        "eccentricity": safe_float(raw.get("ECCENTRICITY") or raw.get("eccentricity")),
        "raan": safe_float(raw.get("RA_OF_ASC_NODE") or raw.get("raan")),
        "bstar": safe_float(raw.get("BSTAR") or raw.get("bstar")),
        "launch_year": launch_year,
    }

def orbital_shell_key(obj):
    mm = safe_float(obj.get("mean_motion"))
    inc = safe_float(obj.get("inclination"))
    ecc = safe_float(obj.get("eccentricity"))
    return f"mm:{int(mm)}|inc:{int(inc//5)*5}|ecc:{int(ecc*1000)//10}"

def base_pair_features(a, b):
    mm1, mm2 = safe_float(a.get("mean_motion")), safe_float(b.get("mean_motion"))
    inc1, inc2 = safe_float(a.get("inclination")), safe_float(b.get("inclination"))
    ecc1, ecc2 = safe_float(a.get("eccentricity")), safe_float(b.get("eccentricity"))
    raan1, raan2 = safe_float(a.get("raan")), safe_float(b.get("raan"))
    b1, b2 = safe_float(a.get("bstar")), safe_float(b.get("bstar"))
    ly1, ly2 = safe_int(a.get("launch_year")), safe_int(b.get("launch_year"))
    same_type = 1 if (a.get("object_type") or "") == (b.get("object_type") or "") else 0
    same_shell = 1 if orbital_shell_key(a) == orbital_shell_key(b) else 0
    delta_mm = abs(mm1 - mm2)
    delta_inc = abs(inc1 - inc2)
    delta_ecc = abs(ecc1 - ecc2)
    delta_raan = abs(raan1 - raan2)
    delta_bstar = abs(b1 - b2)
    launch_gap = abs(ly1 - ly2) if ly1 and ly2 else 25
    shell_density_proxy = max(0.0, 10.0 - delta_mm) + max(0.0, 8.0 - delta_inc / 2.0)
    close_approach_proxy = 1.0 / (1.0 + delta_mm + delta_inc / 10.0 + delta_ecc * 50.0 + delta_raan / 60.0)
    persistence_proxy = 1.0 if same_shell else 0.25
    return {
        "delta_mean_motion": delta_mm,
        "delta_inclination": delta_inc,
        "delta_eccentricity": delta_ecc,
        "delta_raan": delta_raan,
        "delta_bstar": delta_bstar,
        "launch_year_gap": float(launch_gap),
        "same_object_type": float(same_type),
        "same_shell": float(same_shell),
        "shell_density_proxy": float(shell_density_proxy),
        "close_approach_proxy": float(close_approach_proxy),
        "persistence_proxy": float(persistence_proxy),
    }

def combine_features(a, b, trend, graph):
    return {**base_pair_features(a, b), **trend, **graph}