knapsack scheduling algorithm

This commit is contained in:
2026-07-12 14:19:58 -04:00
parent 031da48ddd
commit 9e100d60d0
56 changed files with 1936 additions and 1286 deletions
+23 -18
View File
@@ -7,7 +7,7 @@ A test scheduler for the NJTH which compiles a best schedule according to the ru
- DUT test result directory path, REF test result directory path
- Estimated runtime for each type of test for calculations
- Rotation definition
- Test config definition
- Test exclusions
- Top priority tests
- Allow user to manually override and enter tests that needs to be run tonight
@@ -15,23 +15,30 @@ A test scheduler for the NJTH which compiles a best schedule according to the ru
## Output
### Backend
- Compile a schedule that would require the lowest amount of time in total according to the following rules and priority:
- Compile a schedule that would require the lowest amount of time and least amount of wasted time in total according to the following rules and priority:
- **Rules**
- Can be run in the same night
- Same rotation and same test point P2P/COE tests
- COERXBE002: T1F -> STA63, T2A -> STA56, T3E -> STA4, R3
- COERXBE003: T1F -> STA63, T2A -> STA56, T3E -> STA4, R3
- Different rotations but all stations are at same testpoints
- R2 P2PRXAC003 5G T1C -> STA4
- R1 P2PRXAC012 2G T1C -> STA4
- Same rotation and same test point P3P tests
- Needs to be run back to back
- Test Windows
- Weekday night time test window: Monday - Thursday
- Starts 5PM of that day, ends 9AM the next day
- Capcity: 17 hrs (16hrs + 1hr margin)
- Ex. Monday 6/29 5PM - Tuesday 6/30 9AM
- Weekday day time test window: Monday-Thursday
- Starts 9AM - 5PM of that day
- Capacity: 8 hrs
- Weekend test window: Friday - Sunday
- Starts Friday 5PM, ends Monday 9AM
- Capcity: 65 hrs (64hrs + 1hr margin)
- Holiday test window
- All tests in the same test window needs to have the same test config
- Tests are performed on one device in each test window, unless overrides by user
- Some tests needs to be run back to back
- P2P COE pairings
- Run P2P first then its COE pairings
- P2P up link (RX) and down link (TX)
- Ex. P2PRXAX001 and P2PTXAX001
- COE P2P bundle
- DUT and REF tests
- P3P throttled(TH) and unthrottled(UT)
- Same tests, different devices(DUT, REF) needs to be run in back to back test windows
- Ex. Monday
- **Priority**
- Default priority: COE P2P pair -> P2P only -> P3P
- 1: highest priority (must run tonight)
@@ -40,8 +47,6 @@ A test scheduler for the NJTH which compiles a best schedule according to the ru
- 4: P3P
- 5: lowest priority
- Priority 1 and 5 needs to be entered manually
- Same test points and same rotation will be run first
- Same test points but different will run if there is still remaining testing time for the day
- **Calculation**
- Use the estimate run time for each type of test to fit as many tests as possible in a shift of testing.
- Weekdays are 16hrs, weekends are 24 hours with 1hr margin
@@ -137,7 +142,7 @@ build_test_bundles():
- Create bundles of tests that needs to be run back to back
- Types of test bundles and their order
1. Failed tests rerun if exist
2. The base is P2P and has COE pairs: P2P on DUT -> COE pairings on DUT -> P2P on REF -> COE pairings on REF
2. The P2P and COE pairs: P2P -> COE pairings
3. P2P tests without COE pairs: P2PRX on DUT -> P2PTX on DUT -> P2PRX on REF -> P2PTX on REF
4. P3P tests (ALL ROT's)
```
+210 -43
View File
@@ -1,7 +1,7 @@
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Any
from datetime import date, datetime
from datetime import date, datetime, timedelta
import os
from fastapi import FastAPI, HTTPException
@@ -9,9 +9,15 @@ from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
import db
import graph
from parser import CsvValidationError, parse_target_csv
from scheduler import SchedulerTest, compile_schedule, remove_from_active, reset_scheduler_state
# Ensure new_scheduler resolves the same DUT/REF labels as db records.
os.environ.setdefault("DUT", db.DEVICE_DUT)
os.environ.setdefault("REF", db.DEVICE_REF)
from scheduler import Scheduler, Test as SchedulerTest
from test_config import build_bundle_test_configs, build_config_rows
from test_window import get_shift_capacity_for_date, get_shift_sequence, is_off_day
from watcher import configure_result_watcher, stop_result_watcher
@@ -21,6 +27,7 @@ DUT = os.getenv("DUT", "CGW453").strip()
REF = os.getenv("REF", "CGW452").strip()
class LoadTestsRequest(BaseModel):
csv_path: str = Field(..., description="Absolute or backend-relative path to target CSV")
@@ -80,10 +87,179 @@ class SaveHolidaysRequest(BaseModel):
dates: list[str] = Field(default_factory=list)
SHIFT_LABELS = {
1: "12AM9AM",
2: "9AM5PM",
3: "5PM12AM",
}
SHIFT_BOUNDARIES = {
1: ((0, 0), (9, 0)),
2: ((9, 0), (17, 0)),
3: ((17, 0), (24, 0)),
}
def _serialize_schedule_row(row: db.ScheduleRow) -> dict[str, Any]:
return {
"test_id": row.test_id,
"device": row.device,
"scheduled_date": row.scheduled_date,
"shift_index": row.shift_index,
"sequence_in_shift": row.sequence_in_shift,
"test_type": row.test_type,
"rotation": row.rotation,
"config": row.config,
"status": row.status,
"priority": row.priority,
"estimated_minutes": row.estimated_minutes,
}
def _window_segment_key(day: date, shift_index: int) -> str:
return f"{day.isoformat()}::shift{shift_index}"
def _combine_date_time(day: date, hour: int, minute: int) -> str:
if hour == 24:
return datetime.combine(day + timedelta(days=1), datetime.min.time()).isoformat(timespec="minutes")
return datetime.combine(day, datetime.min.time()).replace(hour=hour, minute=minute).isoformat(timespec="minutes")
def _window_intersects_week(segments: list[tuple[date, int]], week_start: date, week_end: date) -> bool:
for day, _shift_index in segments:
if week_start <= day < week_end:
return True
return False
def _build_schedule_windows(week_start: date, all_rows: list[db.ScheduleRow], holiday_dates: set[str]) -> list[dict[str, Any]]:
if not all_rows:
return []
all_dates = [datetime.strptime(row.scheduled_date, "%Y-%m-%d").date() for row in all_rows]
first_scheduled_date = min(all_dates)
last_scheduled_date = max(all_dates)
week_end = week_start + timedelta(days=7)
rows_by_segment: dict[tuple[str, int], list[dict[str, Any]]] = {}
weekday_shift2_dates: set[str] = set()
for row in all_rows:
serialized = _serialize_schedule_row(row)
rows_by_segment.setdefault((row.scheduled_date, row.shift_index), []).append(serialized)
row_date = datetime.strptime(row.scheduled_date, "%Y-%m-%d").date()
if row.shift_index == 2 and not is_off_day(row_date, holiday_dates):
weekday_shift2_dates.add(row.scheduled_date)
simulation_start = min(first_scheduled_date, week_start) - timedelta(days=7)
while is_off_day(simulation_start, holiday_dates):
simulation_start -= timedelta(days=1)
simulation_end = max(last_scheduled_date, week_end) + timedelta(days=7)
current_date = simulation_start
windows: list[dict[str, Any]] = []
seen_window_ids: set[str] = set()
while current_date <= simulation_end:
current_iso = current_date.isoformat()
if current_iso in weekday_shift2_dates:
segments = get_shift_sequence(current_date, holiday_dates, daytime_shift2_only=True)
window_id = f"{current_iso}-shift2"
if window_id not in seen_window_ids and _window_intersects_week(segments, week_start, week_end):
seen_window_ids.add(window_id)
shift_tests = [
test
for day, shift_index in segments
for test in rows_by_segment.get((day.isoformat(), shift_index), [])
]
available_runtime_minutes = sum(
get_shift_capacity_for_date(day, holiday_dates, daytime_shift2_only=True).get(shift_index, 0)
for day, shift_index in segments
)
start_hour, start_minute = SHIFT_BOUNDARIES[segments[0][1]][0]
end_hour, end_minute = SHIFT_BOUNDARIES[segments[-1][1]][1]
windows.append({
"window_id": window_id,
"window_type": "daytime",
"start_date": segments[0][0].isoformat(),
"start_shift_index": segments[0][1],
"end_date": segments[-1][0].isoformat(),
"end_shift_index": segments[-1][1],
"start_at": _combine_date_time(segments[0][0], start_hour, start_minute),
"end_at": _combine_date_time(segments[-1][0], end_hour, end_minute),
"available_runtime_minutes": available_runtime_minutes,
"estimated_runtime_minutes": sum(test["estimated_minutes"] for test in shift_tests),
"bundle_test_configs": build_bundle_test_configs(shift_tests),
"segments": [
{
"date": day.isoformat(),
"shift_index": shift_index,
"label": SHIFT_LABELS[shift_index],
"segment_key": _window_segment_key(day, shift_index),
}
for day, shift_index in segments
],
"configRows": build_config_rows(shift_tests),
"tests": shift_tests,
})
segments = get_shift_sequence(current_date, holiday_dates, daytime_shift2_only=False)
last_sequence_date = segments[-1][0]
last_shift_index = segments[-1][1]
window_kind = "offday" if is_off_day(current_date, holiday_dates) else "overnight"
window_id = f"{current_iso}-shift{segments[0][1]}"
if window_id not in seen_window_ids and _window_intersects_week(segments, week_start, week_end):
seen_window_ids.add(window_id)
window_tests = [
test
for day, shift_index in segments
for test in rows_by_segment.get((day.isoformat(), shift_index), [])
]
available_runtime_minutes = sum(
get_shift_capacity_for_date(day, holiday_dates, daytime_shift2_only=False).get(shift_index, 0)
for day, shift_index in segments
)
start_hour, start_minute = SHIFT_BOUNDARIES[segments[0][1]][0]
end_hour, end_minute = SHIFT_BOUNDARIES[segments[-1][1]][1]
windows.append({
"window_id": window_id,
"window_type": window_kind,
"start_date": segments[0][0].isoformat(),
"start_shift_index": segments[0][1],
"end_date": segments[-1][0].isoformat(),
"end_shift_index": segments[-1][1],
"start_at": _combine_date_time(segments[0][0], start_hour, start_minute),
"end_at": _combine_date_time(segments[-1][0], end_hour, end_minute),
"available_runtime_minutes": available_runtime_minutes,
"estimated_runtime_minutes": sum(test["estimated_minutes"] for test in window_tests),
"bundle_test_configs": build_bundle_test_configs(window_tests),
"segments": [
{
"date": day.isoformat(),
"shift_index": shift_index,
"label": SHIFT_LABELS[shift_index],
"segment_key": _window_segment_key(day, shift_index),
}
for day, shift_index in segments
],
"configRows": build_config_rows(window_tests),
"tests": window_tests,
})
if last_shift_index in {1, 2}:
current_date = last_sequence_date
else:
current_date = last_sequence_date + timedelta(days=1)
windows.sort(key=lambda window: (window["start_at"], window["window_id"]))
return windows
@asynccontextmanager
async def lifespan(application: FastAPI):
db.init_db(DB_PATH)
graph.reset_graph_state()
settings = db.read_settings(DB_PATH)
configure_result_watcher(settings)
try:
@@ -141,21 +317,17 @@ def load_tests(request: LoadTestsRequest) -> dict[str, Any]:
raise HTTPException(status_code=400, detail=str(exc)) from exc
count = db.upsert_tests(parsed.tests, DB_PATH)
reset_scheduler_state()
graph.reset_graph_state()
all_dut_tests = db.list_tests_for_device(DUT, DB_PATH)
graph.build_and_persist_graph(all_dut_tests, DB_PATH)
print(f"Loaded {count} tests from {csv_path}, with {len(parsed.warnings)} warnings.")
return {
"loaded_tests": count,
"warnings": parsed.warnings,
"dut_graph_nodes": len(graph.get_graph()),
}
@app.post("/api/schedule/active/remove")
def remove_active_tests(request: RemoveActiveTestsRequest) -> dict[str, Any]:
remove_from_active({item.strip() for item in request.test_ids if item.strip()})
# new_scheduler does not keep global mutable active state in app lifecycle.
# Keep endpoint for compatibility with frontend calls.
return {"status": "ok", "removed": len(request.test_ids)}
@@ -167,7 +339,6 @@ def compile_schedule_endpoint(request: CompileScheduleRequest) -> dict[str, Any]
except ValueError as exc:
raise HTTPException(status_code=400, detail="start_date must be YYYY-MM-DD") from exc
reset_scheduler_state()
stored_tests = db.list_schedulable_tests(DB_PATH, rule=request.rule)
if not stored_tests:
version = db.create_schedule_version([], DB_PATH)
@@ -189,29 +360,36 @@ def compile_schedule_endpoint(request: CompileScheduleRequest) -> dict[str, Any]
config=t.config,
throttled=t.throttled,
estimated_minutes=t.estimated_minutes,
priority=t.priority,
raw_payload=t.raw_payload or {},
)
for t in stored_tests
]
# DB-backed graph retrieval ensures compile works after restart without manual save/load.
all_dut_tests = db.list_tests_for_device(DUT, DB_PATH)
graph.get_graph(DB_PATH, all_dut_tests)
holiday_dates = db.list_holidays(DB_PATH)
top_priority_pairs: set[tuple[str, str]] = set()
for test_id in {item.strip() for item in request.top_priority_tests if item.strip()}:
if any(t.test_id == test_id and t.device == DUT for t in stored_tests):
top_priority_pairs.add((test_id, DUT))
if any(t.test_id == test_id and t.device == REF for t in stored_tests):
top_priority_pairs.add((test_id, REF))
scheduler = Scheduler(
tests=scheduler_tests,
top_priority_tests=top_priority_pairs,
start_date=request.start_date,
holiday_dates=holiday_dates,
daytime_testing_today=request.daytime_testing_today,
)
try:
entries, completion_date = compile_schedule(
tests=scheduler_tests,
start_date=request.start_date,
holiday_dates=holiday_dates,
top_priority_tests={item.strip() for item in request.top_priority_tests if item.strip()},
lowest_priority_tests={item.strip() for item in request.lowest_priority_tests if item.strip()},
daytime_testing_today=request.daytime_testing_today,
)
except RuntimeError as exc:
schedule_error = scheduler.compile_schedule()
entries = scheduler.get_schedule()
except (RuntimeError, ValueError) as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
completion_date = max((e.scheduled_date for e in entries), default=None)
if schedule_error:
raise HTTPException(status_code=409, detail=schedule_error)
version = db.create_schedule_version(
[(e.test_id, e.device, e.scheduled_date, e.shift_index, e.sequence_in_shift) for e in entries],
DB_PATH,
@@ -252,25 +430,14 @@ def get_schedule_week(start: str | None = None) -> dict[str, Any]:
except ValueError as exc:
raise HTTPException(status_code=400, detail="start must be YYYY-MM-DD") from exc
week_start_date = datetime.strptime(week_start, "%Y-%m-%d").date()
rows = db.get_schedule_week(week_start, DB_PATH)
all_rows = db.get_schedule_rows_for_latest_version(DB_PATH)
holiday_dates = db.list_holidays(DB_PATH)
return {
"start_date": week_start,
"items": [
{
"test_id": row.test_id,
"device": row.device,
"scheduled_date": row.scheduled_date,
"shift_index": row.shift_index,
"sequence_in_shift": row.sequence_in_shift,
"test_type": row.test_type,
"rotation": row.rotation,
"config": row.config,
"status": row.status,
"priority": row.priority,
"estimated_minutes": row.estimated_minutes,
}
for row in rows
],
"items": [_serialize_schedule_row(row) for row in rows],
"windows": _build_schedule_windows(week_start_date, all_rows, holiday_dates),
}
if __name__ == "__main__":
@@ -1,251 +1,251 @@
Priority,Index,Interferer,COE Pair ,Rotation,TC ID,Victim Band,6GHz Power Mode,5G Test Point,5G Channel,5G Bandwidth,5G RSSI,5G Direction,5G STA,6G Test Point,6G Channel,6G Bandwidth,6G RSSI,6G Direction,6G STA,2G Test Point,2G Channel,2G Bandwidth,2G RSSI,2G Direction,2G STA
P2,3,Yes,Yes,R2,COERXAC001,5G,LPI,T1F,100,80,-76,UL,STA4,T2A,5,160,OFF,DL,STA63,T3E,6,20,-70,DL,STA56
P2,6,Yes,Yes,R2,COERXAC002,5G,LPI,T1F,161,80,-76,UL,STA4,T2A,133,160,OFF,DL,STA63,T3E,6,20,-70,DL,STA56
P2,15,Yes,Yes,R1,COERXAC003,2G,LPI,T1F,36,80,-76,DL,STA56,T2A,5,160,OFF,DL,STA63,T3E,1,20,-70,UL,STA4
P2,17,Yes,Yes,R1,COERXAC004,2G,LPI,T1F,36,80,-76,DL,STA56,T2A,133,160,OFF,DL,STA63,T3E,6,20,-70,UL,STA4
P2,19,Yes,Yes,R1,COERXAC005,2G,LPI,T1F,36,80,-76,DL,STA56,T2A,197,160,OFF,DL,STA63,T3E,11,20,-70,UL,STA4
P2,3,Yes,Yes,R1,COERXAX001,5G,LPI,T1F,100,80,-76,UL,STA56,T2A,5,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,4,Yes,Yes,R1,COERXAX002,5G,LPI,T1F,100,80,-76,UL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,6,Yes,Yes,R1,COERXAX003,5G,LPI,T1F,161,80,-76,UL,STA56,T2A,5,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,7,Yes,Yes,R1,COERXAX004,5G,LPI,T1F,161,80,-76,UL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,8,Yes,Yes,R1,COERXAX005,5G,SP,T1F,161,80,-76,UL,STA56,T2A,5,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,9,Yes,Yes,R1,COERXAX006,5G,LPI,T1F,161,80,-76,UL,STA56,T2A,133,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,11,Yes,Yes,R1,COERXAX007,5G,SP,T1F,100,160,-76,UL,STA56,T2A,5,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,12,Yes,Yes,R1,COERXAX008,5G,LPI,T1F,100,160,-76,UL,STA56,T2A,133,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,14,Yes,Yes,R2,COERXAX009,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,5,160,-79,UL,STA56,T3E,6,20,-70,DL,STA4
P2,16,Yes,Yes,R2,COERXAX010,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,133,160,-79,UL,STA56,T3E,6,20,-70,DL,STA4
P2,18,Yes,Yes,R2,COERXAX011,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,197,160,-79,UL,STA56,T3E,6,20,-70,DL,STA4
P2,20,Yes,Yes,R2,COERXAX012,6G,SP,T1F,161,80,-76,DL,STA63,T2A,5,320,-79,UL,STA56,T3E,6,20,-70,DL,STA4
P2,22,Yes,Yes,R2,COERXAX013,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,133,320,-79,UL,STA56,T3E,6,20,-70,DL,STA4
P2,24,Yes,Yes,R3,COERXAX014,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,5,160,-79,DL,STA63,T3E,1,20,-70,UL,STA56
P2,26,Yes,Yes,R3,COERXAX015,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,UL,STA56
P2,28,Yes,Yes,R3,COERXAX016,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,197,160,-79,DL,STA63,T3E,11,20,-70,UL,STA56
Priority,Index,Interferer,COE Pair ,Rotation,TC ID,Victim Band,6GHz Power Mode,5G_Test Point,5G_Channel,5G_Bandwidth,5G_RSSI,5G_Direction,5G_STA,6G_Test Point,6G_Channel,6G_Bandwidth,6G_RSSI,6G_Direction,6G_STA,2G_Test Point,2G_Channel,2G_Bandwidth,2G_RSSI,2G_Direction,2G_STA
P1,1,No,No,R3,P2PRXBE001,5G,LPI,T1F,36,80,-76,UL,STA63,,,,,,,,,,,,
P1,1,No,No,R3,P2PRXBE002,5G,LPI,T1B,36,80,-67,UL,STA63,,,,,,,,,,,,
P1,1,No,No,R3,P2PRXBE003,5G,LPI,T1C,36,80,-45,UL,STA63,,,,,,,,,,,,
P1,2,No,Yes,R3,P2PRXBE004,5G,LPI,T1F,100,80,-76,UL,STA63,,,,,,,,,,,,
P1,2,No,No,R3,P2PRXBE005,5G,LPI,T1B,100,80,-67,UL,STA63,,,,,,,,,,,,
P1,2,No,No,R3,P2PRXBE006,5G,LPI,T1C,100,80,-45,UL,STA63,,,,,,,,,,,,
P2,3,Yes,Yes,R3,COERXBE001,5G,LPI,T1F,100,80,-76,UL,STA63,T2A,5,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,4,Yes,Yes,R3,COERXBE002,5G,LPI,T1F,100,80,-76,UL,STA63,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P1,5,No,Yes,R3,P2PRXBE007,5G,LPI,T1F,161,80,-76,UL,STA63,,,,,,,,,,,,
P1,5,No,No,R3,P2PRXBE008,5G,LPI,T1B,161,80,-67,UL,STA63,,,,,,,,,,,,
P1,5,No,No,R3,P2PRXBE009,5G,LPI,T1C,161,80,-45,UL,STA63,,,,,,,,,,,,
P2,6,Yes,Yes,R3,COERXBE003,5G,LPI,T1F,161,80,-76,UL,STA63,T2A,5,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,7,Yes,Yes,R3,COERXBE004,5G,LPI,T1F,161,80,-76,UL,STA63,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,8,Yes,Yes,R3,COERXBE005,5G,SP,T1F,161,80,-76,UL,STA63,T2A,5,320,-79,DL,STA64,T3E,6,20,-70,DL,STA4
P2,9,Yes,Yes,R3,COERXBE006,5G,LPI,T1F,161,80,-76,UL,STA63,T2A,133,320,-79,DL,STA64,T3E,6,20,-70,DL,STA4
P1,10,No,Yes,R3,P2PRXBE010,5G,LPI,T1F,100,160,-76,UL,STA63,,,,,,,,,,,,
P1,10,No,No,R3,P2PRXBE011,5G,LPI,T1B,100,160,-67,UL,STA63,,,,,,,,,,,,
P1,10,No,No,R3,P2PRXBE012,5G,LPI,T1C,100,160,-45,UL,STA63,,,,,,,,,,,,
P2,11,Yes,Yes,R3,COERXBE007,5G,SP,T1F,100,160,-76,UL,STA63,T2A,5,320,-79,DL,STA64,T3E,6,20,-70,DL,STA4
P2,12,Yes,Yes,R3,COERXBE008,5G,LPI,T1F,100,160,-76,UL,STA63,T2A,133,320,-79,DL,STA64,T3E,6,20,-70,DL,STA4
P2,14,Yes,Yes,R1,COERXBE009,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,5,160,-79,UL,STA63,T3E,6,20,-70,DL,STA4
P2,16,Yes,Yes,R1,COERXBE010,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,133,160,-79,UL,STA63,T3E,6,20,-70,DL,STA4
P2,18,Yes,Yes,R1,COERXBE011,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,197,160,-79,UL,STA63,T3E,6,20,-70,DL,STA4
P2,20,Yes,Yes,R1,COERXBE012,6G,SP,T1F,161,80,-76,DL,STA56,T2A,5,320,-79,UL,STA63,T3E,6,20,-70,DL,STA4
P2,22,Yes,Yes,R1,COERXBE013,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,133,320,-79,UL,STA63,T3E,6,20,-70,DL,STA4
P2,24,Yes,Yes,R2,COERXBE014,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,5,160,-79,DL,STA56,T3E,1,20,-70,UL,STA63
P2,26,Yes,Yes,R2,COERXBE015,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,UL,STA63
P2,28,Yes,Yes,R2,COERXBE016,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,197,160,-79,DL,STA56,T3E,11,20,-70,UL,STA63
P2,3,Yes,Yes,R2,COETXAC001,5G,LPI,T1F,100,80,-76,DL,STA4,T1K2A,5,160,OFF,DL,STA63,T3E,6,20,-70,DL,STA56
P2,6,Yes,Yes,R2,COETXAC002,5G,LPI,T1F,161,80,-76,DL,STA4,T1K2A,133,160,OFF,DL,STA63,T3E,6,20,-70,DL,STA56
P2,15,Yes,Yes,R1,COETXAC003,2G,LPI,T1F,36,80,-76,DL,STA56,T1K2A,5,160,OFF,DL,STA63,T3E,1,20,-70,DL,STA4
P2,17,Yes,Yes,R1,COETXAC004,2G,LPI,T1F,36,80,-76,DL,STA56,T1K2A,133,160,OFF,DL,STA63,T3E,6,20,-70,DL,STA4
P2,19,Yes,Yes,R1,COETXAC005,2G,LPI,T1F,36,80,-76,DL,STA56,T1K2A,5,320,OFF,DL,STA63,T3E,11,20,-70,DL,STA4
P2,3,Yes,Yes,R1,COETXAX001,5G,LPI,T1F,100,80,-76,DL,STA56,T2A,5,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,4,Yes,Yes,R1,COETXAX002,5G,LPI,T1F,100,80,-76,DL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,6,Yes,Yes,R1,COETXAX003,5G,LPI,T1F,161,80,-76,DL,STA56,T2A,5,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,7,Yes,Yes,R1,COETXAX004,5G,LPI,T1F,161,80,-76,DL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,9,Yes,Yes,R1,COETXAX005,5G,SP,T1F,100,160,-76,DL,STA56,T2A,5,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,10,Yes,Yes,R1,COETXAX006,5G,LPI,T1F,100,160,-76,DL,STA56,T2A,133,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,12,Yes,Yes,R2,COETXAX007,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,5,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,14,Yes,Yes,R2,COETXAX008,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,16,Yes,Yes,R2,COETXAX009,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,197,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,18,Yes,Yes,R2,COETXAX010,6G,SP,T1F,161,80,-76,DL,STA63,T2A,5,320,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,20,Yes,Yes,R2,COETXAX011,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,133,320,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,22,Yes,Yes,R3,COETXAX012,2G,LPI,T1F,36,80,-76,DL,STA56,T2A,5,160,-79,DL,STA63,T3E,1,20,-70,DL,STA56
P2,24,Yes,Yes,R3,COETXAX013,2G,LPI,T1F,36,80,-76,DL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA56
P2,26,Yes,Yes,R3,COETXAX014,2G,LPI,T1F,36,80,-76,DL,STA56,T2A,197,160,-79,DL,STA63,T3E,11,20,-70,DL,STA56
P2,3,Yes,Yes,R3,COETXBE001,5G,LPI,T1F,100,80,-76,DL,STA63,T2A,5,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,4,Yes,Yes,R3,COETXBE002,5G,LPI,T1F,100,80,-76,DL,STA63,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,6,Yes,Yes,R3,COETXBE003,5G,LPI,T1F,161,80,-76,DL,STA63,T2A,5,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,7,Yes,Yes,R3,COETXBE004,5G,LPI,T1F,161,80,-76,DL,STA63,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,9,Yes,Yes,R3,COETXBE005,5G,SP,T1F,100,160,-76,DL,STA63,T2A,5,320,-79,DL,STA64,T3E,6,20,-70,DL,STA4
P2,10,Yes,Yes,R3,COETXBE006,5G,LPI,T1F,100,160,-76,DL,STA63,T2A,133,320,-79,DL,STA64,T3E,6,20,-70,DL,STA4
P2,12,Yes,Yes,R1,COETXBE007,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,5,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,14,Yes,Yes,R1,COETXBE008,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,16,Yes,Yes,R1,COETXBE009,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,197,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,18,Yes,Yes,R1,COETXBE010,6G,SP,T1F,161,80,-76,DL,STA56,T2A,5,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,20,Yes,Yes,R1,COETXBE011,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,133,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,22,Yes,Yes,R2,COETXBE012,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,5,160,-79,DL,STA56,T3E,1,20,-70,DL,STA63
P2,24,Yes,Yes,R2,COETXBE013,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,DL,STA63
P2,26,Yes,Yes,R2,COETXBE014,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,197,160,-79,DL,STA56,T3E,11,20,-70,DL,STA63
P1,1,No,No,R2,P2PRXAC001,5G,LPI,T1F,36,80,-76,UL,STA4,,,,,,,,,,,,
P1,1,No,No,R2,P2PRXAC002,5G,LPI,T2B,36,80,-67,UL,STA4,,,,,,,,,,,,
P1,1,No,No,R2,P2PRXAC003,5G,LPI,T1C,36,80,-45,UL,STA4,,,,,,,,,,,,
P1,2,No,Yes,R2,P2PRXAC004,5G,LPI,T1F,100,80,-76,UL,STA4,,,,,,,,,,,,
P1,2,No,No,R2,P2PRXAC005,5G,LPI,T2B,100,80,-67,UL,STA4,,,,,,,,,,,,
P1,2,No,No,R2,P2PRXAC006,5G,LPI,T1C,100,80,-45,UL,STA4,,,,,,,,,,,,
P1,5,No,Yes,R2,P2PRXAC007,5G,LPI,T1F,161,80,-76,UL,STA4,,,,,,,,,,,,
P1,5,No,No,R2,P2PRXAC008,5G,LPI,T2B,161,80,-67,UL,STA4,,,,,,,,,,,,
P1,5,No,No,R2,P2PRXAC009,5G,LPI,T1C,161,80,-45,UL,STA4,,,,,,,,,,,,
P1,14,No,Yes,R1,P2PRXAC010,2G,LPI,,,,,,,,,,,,,T3E,1,20,-70,UL,STA4
P1,14,No,No,R1,P2PRXAC011,2G,LPI,,,,,,,,,,,,,T1D,1,20,-60,UL,STA4
P1,14,No,No,R1,P2PRXAC012,2G,LPI,,,,,,,,,,,,,T1C,1,20,-45,UL,STA4
P1,16,No,Yes,R1,P2PRXAC013,2G,LPI,,,,,,,,,,,,,T3E,6,20,-70,UL,STA4
P1,16,No,No,R1,P2PRXAC014,2G,LPI,,,,,,,,,,,,,T1D,6,20,-60,UL,STA4
P1,16,No,No,R1,P2PRXAC015,2G,LPI,,,,,,,,,,,,,T1C,6,20,-45,UL,STA4
P1,18,No,Yes,R1,P2PRXAC016,2G,LPI,,,,,,,,,,,,,T3E,11,20,-70,UL,STA4
P1,18,No,No,R1,P2PRXAC017,2G,LPI,,,,,,,,,,,,,T1D,11,20,-60,UL,STA4
P1,18,No,No,R1,P2PRXAC018,2G,LPI,,,,,,,,,,,,,T1C,11,20,-45,UL,STA4
P1,1,No,No,R1,P2PRXAX001,5G,LPI,T1F,36,80,-76,UL,STA56,,,,,,,,,,,,
P1,1,No,No,R1,P2PRXAX002,5G,LPI,T2B,36,80,-67,UL,STA56,,,,,,,,,,,,
P1,1,No,No,R1,P2PRXAX003,5G,LPI,T1C,36,80,-45,UL,STA56,,,,,,,,,,,,
P1,2,No,Yes,R1,P2PRXAX004,5G,LPI,T1F,100,80,-76,UL,STA56,,,,,,,,,,,,
P1,2,No,No,R1,P2PRXAX005,5G,LPI,T2B,100,80,-67,UL,STA56,,,,,,,,,,,,
P1,2,No,No,R1,P2PRXAX006,5G,LPI,T1C,100,80,-45,UL,STA56,,,,,,,,,,,,
P1,5,No,Yes,R1,P2PRXAX007,5G,LPI,T1F,161,80,-76,UL,STA56,,,,,,,,,,,,
P1,5,No,No,R1,P2PRXAX008,5G,LPI,T2B,161,80,-67,UL,STA56,,,,,,,,,,,,
P1,5,No,No,R1,P2PRXAX009,5G,LPI,T1C,161,80,-45,UL,STA56,,,,,,,,,,,,
P1,10,No,Yes,R1,P2PRXAX010,5G,LPI,T1F,100,160,-76,UL,STA56,,,,,,,,,,,,
P1,10,No,No,R1,P2PRXAX011,5G,LPI,T2B,100,160,-67,UL,STA56,,,,,,,,,,,,
P1,10,No,No,R1,P2PRXAX012,5G,LPI,T1C,100,160,-45,UL,STA56,,,,,,,,,,,,
P1,13,No,Yes,R2,P2PRXAX013,6G,LPI,,,,,,,T2A,5,160,-79,UL,STA56,,,,,,
P1,13,No,No,R2,P2PRXAX014,6G,LPI,,,,,,,T2B,5,160,-70,UL,STA56,,,,,,
P1,13,No,No,R2,P2PRXAX015,6G,LPI,,,,,,,T1C,5,160,-45,UL,STA56,,,,,,
P1,15,No,Yes,R2,P2PRXAX016,6G,LPI,,,,,,,T2A,133,160,-79,UL,STA56,,,,,,
P1,15,No,No,R2,P2PRXAX017,6G,LPI,,,,,,,T2B,133,160,-70,UL,STA56,,,,,,
P1,15,No,No,R2,P2PRXAX018,6G,LPI,,,,,,,T1C,133,160,-45,UL,STA63,,,,,,
P1,17,No,Yes,R2,P2PRXAX019,6G,LPI,,,,,,,T2A,197,160,-79,UL,STA56,,,,,,
P1,17,No,No,R2,P2PRXAX020,6G,LPI,,,,,,,T2B,197,160,-70,UL,STA56,,,,,,
P1,17,No,No,R2,P2PRXAX021,6G,LPI,,,,,,,T1C,197,160,-45,UL,STA56,,,,,,
P1,19,No,Yes,R2,P2PRXAX022,6G,SP,,,,,,,T2A,5,320,-79,UL,STA56,,,,,,
P1,19,No,No,R2,P2PRXAX023,6G,SP,,,,,,,T2B,5,320,-70,UL,STA56,,,,,,
P1,19,No,No,R2,P2PRXAX024,6G,SP,,,,,,,T1C,5,320,-45,UL,STA56,,,,,,
P1,21,No,Yes,R2,P2PRXAX025,6G,LPI,,,,,,,T2A,133,320,-79,UL,STA56,,,,,,
P1,21,No,No,R2,P2PRXAX026,6G,LPI,,,,,,,T2B,133,320,-70,UL,STA56,,,,,,
P1,21,No,No,R2,P2PRXAX027,6G,LPI,,,,,,,T1C,133,320,-45,UL,STA56,,,,,,
P1,23,No,Yes,R3,P2PRXAX028,2G,LPI,,,,,,,,,,,,,T3E,1,20,-70,UL,STA56
P1,23,No,No,R3,P2PRXAX029,2G,LPI,,,,,,,,,,,,,T1D,1,20,-60,UL,STA56
P1,23,No,No,R3,P2PRXAX030,2G,LPI,,,,,,,,,,,,,T1C,1,20,-45,UL,STA56
P1,25,No,Yes,R3,P2PRXAX031,2G,LPI,,,,,,,,,,,,,T3E,6,20,-70,UL,STA56
P1,25,No,No,R3,P2PRXAX032,2G,LPI,,,,,,,,,,,,,T1D,6,20,-60,UL,STA56
P1,25,No,No,R3,P2PRXAX033,2G,LPI,,,,,,,,,,,,,T1C,6,20,-45,UL,STA56
P1,27,No,Yes,R3,P2PRXAX034,2G,LPI,,,,,,,,,,,,,T3E,11,20,-70,UL,STA56
P1,27,No,No,R3,P2PRXAX035,2G,LPI,,,,,,,,,,,,,T1D,11,20,-60,UL,STA56
P1,27,No,No,R3,P2PRXAX036,2G,LPI,,,,,,,,,,,,,T1C,11,20,-45,UL,STA56
P1,1,No,No,R3,P2PRXBE001,5G,LPI,T1F,36,80,-76,UL,STA63,,,,,,,,,,,,
P1,1,No,No,R3,P2PRXBE002,5G,LPI,T2B,36,80,-67,UL,STA63,,,,,,,,,,,,
P1,1,No,No,R3,P2PRXBE003,5G,LPI,T1C,36,80,-45,UL,STA63,,,,,,,,,,,,
P1,2,No,Yes,R3,P2PRXBE004,5G,LPI,T1F,100,80,-76,UL,STA63,,,,,,,,,,,,
P1,2,No,No,R3,P2PRXBE005,5G,LPI,T2B,100,80,-67,UL,STA63,,,,,,,,,,,,
P1,2,No,No,R3,P2PRXBE006,5G,LPI,T1C,100,80,-45,UL,STA63,,,,,,,,,,,,
P1,5,No,Yes,R3,P2PRXBE007,5G,LPI,T1F,161,80,-76,UL,STA63,,,,,,,,,,,,
P1,5,No,No,R3,P2PRXBE008,5G,LPI,T2B,161,80,-67,UL,STA63,,,,,,,,,,,,
P1,5,No,No,R3,P2PRXBE009,5G,LPI,T1C,161,80,-45,UL,STA63,,,,,,,,,,,,
P1,10,No,Yes,R3,P2PRXBE010,5G,LPI,T1F,100,160,-76,UL,STA63,,,,,,,,,,,,
P1,10,No,No,R3,P2PRXBE011,5G,LPI,T2B,100,160,-67,UL,STA63,,,,,,,,,,,,
P1,10,No,No,R3,P2PRXBE012,5G,LPI,T1C,100,160,-45,UL,STA63,,,,,,,,,,,,
P1,13,No,Yes,R1,P2PRXBE013,6G,LPI,,,,,,,T2A,5,160,-79,UL,STA63,,,,,,
P1,13,No,No,R1,P2PRXBE014,6G,LPI,,,,,,,T2B,5,160,-70,UL,STA63,,,,,,
P1,13,No,No,R1,P2PRXBE014,6G,LPI,,,,,,,T1B,5,160,-70,UL,STA63,,,,,,
P1,13,No,No,R1,P2PRXBE015,6G,LPI,,,,,,,T1C,5,160,-45,UL,STA63,,,,,,
P2,14,Yes,Yes,R1,COERXBE009,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,5,160,-79,UL,STA63,T3E,6,20,-70,DL,STA4
P1,15,No,Yes,R1,P2PRXBE016,6G,LPI,,,,,,,T2A,133,160,-79,UL,STA63,,,,,,
P1,15,No,No,R1,P2PRXBE017,6G,LPI,,,,,,,T2B,133,160,-70,UL,STA63,,,,,,
P1,15,No,No,R1,P2PRXBE017,6G,LPI,,,,,,,T1B,133,160,-70,UL,STA63,,,,,,
P1,15,No,No,R1,P2PRXBE018,6G,LPI,,,,,,,T1C,133,160,-45,UL,STA63,,,,,,
P2,16,Yes,Yes,R1,COERXBE010,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,133,160,-79,UL,STA63,T3E,6,20,-70,DL,STA4
P1,17,No,Yes,R1,P2PRXBE019,6G,LPI,,,,,,,T2A,197,160,-79,UL,STA63,,,,,,
P1,17,No,No,R1,P2PRXBE020,6G,LPI,,,,,,,T2B,197,160,-70,UL,STA63,,,,,,
P1,17,No,No,R1,P2PRXBE020,6G,LPI,,,,,,,T1B,197,160,-70,UL,STA63,,,,,,
P1,17,No,No,R1,P2PRXBE021,6G,LPI,,,,,,,T1C,197,160,-45,UL,STA63,,,,,,
P2,18,Yes,Yes,R1,COERXBE011,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,197,160,-79,UL,STA63,T3E,6,20,-70,DL,STA4
P1,19,No,Yes,R1,P2PRXBE022,6G,SP,,,,,,,T2A,5,320,-79,UL,STA63,,,,,,
P1,19,No,No,R1,P2PRXBE023,6G,SP,,,,,,,T2B,5,320,-70,UL,STA63,,,,,,
P1,19,No,No,R1,P2PRXBE023,6G,SP,,,,,,,T1B,5,320,-70,UL,STA63,,,,,,
P1,19,No,No,R1,P2PRXBE024,6G,SP,,,,,,,T1C,5,320,-45,UL,STA63,,,,,,
P2,20,Yes,Yes,R1,COERXBE012,6G,SP,T1F,161,80,-76,DL,STA56,T2A,5,320,-79,UL,STA63,T3E,6,20,-70,DL,STA4
P1,21,No,Yes,R1,P2PRXBE025,6G,LPI,,,,,,,T2A,133,320,-79,UL,STA63,,,,,,
P1,21,No,No,R1,P2PRXBE026,6G,LPI,,,,,,,T2B,133,320,-70,UL,STA63,,,,,,
P1,21,No,No,R1,P2PRXBE026,6G,LPI,,,,,,,T1B,133,320,-70,UL,STA63,,,,,,
P1,21,No,No,R1,P2PRXBE027,6G,LPI,,,,,,,T1C,133,320,-45,UL,STA63,,,,,,
P2,22,Yes,Yes,R1,COERXBE013,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,133,320,-79,UL,STA63,T3E,6,20,-70,DL,STA4
P1,23,No,Yes,R2,P2PRXBE028,2G,LPI,,,,,,,,,,,,,T3E,1,20,-70,UL,STA63
P1,23,No,No,R2,P2PRXBE029,2G,LPI,,,,,,,,,,,,,T1D,1,20,-60,UL,STA63
P1,23,No,No,R2,P2PRXBE030,2G,LPI,,,,,,,,,,,,,T1C,1,20,-45,UL,STA63
P2,24,Yes,Yes,R2,COERXBE014,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,5,160,-79,DL,STA56,T3E,1,20,-70,UL,STA63
P1,25,No,Yes,R2,P2PRXBE031,2G,LPI,,,,,,,,,,,,,T3E,6,20,-70,UL,STA63
P1,25,No,No,R2,P2PRXBE032,2G,LPI,,,,,,,,,,,,,T1D,6,20,-60,UL,STA63
P1,25,No,No,R2,P2PRXBE033,2G,LPI,,,,,,,,,,,,,T1C,6,20,-45,UL,STA63
P2,26,Yes,Yes,R2,COERXBE015,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,UL,STA63
P1,27,No,Yes,R2,P2PRXBE034,2G,LPI,,,,,,,,,,,,,T3E,11,20,-70,UL,STA63
P1,27,No,No,R2,P2PRXBE035,2G,LPI,,,,,,,,,,,,,T1D,11,20,-60,UL,STA63
P1,27,No,No,R2,P2PRXBE036,2G,LPI,,,,,,,,,,,,,T1C,11,20,-45,UL,STA63
P1,1,No,No,R2,P2PTXAC001,5G,LPI,T1F,36,80,-76,DL,STA4,,,,,,,,,,,,
P1,1,No,No,R2,P2PTXAC002,5G,LPI,T2B,36,80,-67,DL,STA4,,,,,,,,,,,,
P1,1,No,No,R2,P2PTXAC003,5G,LPI,T1C,36,80,-45,DL,STA4,,,,,,,,,,,,
P1,2,No,Yes,R2,P2PTXAC004,5G,LPI,T1F,100,80,-76,DL,STA4,,,,,,,,,,,,
P1,2,No,No,R2,P2PTXAC005,5G,LPI,T2B,100,80,-67,DL,STA4,,,,,,,,,,,,
P1,2,No,No,R2,P2PTXAC006,5G,LPI,T1C,100,80,-45,DL,STA4,,,,,,,,,,,,
P1,5,No,Yes,R2,P2PTXAC007,5G,LPI,T1F,161,80,-76,DL,STA4,,,,,,,,,,,,
P1,5,No,No,R2,P2PTXAC008,5G,LPI,T2B,161,80,-67,DL,STA4,,,,,,,,,,,,
P1,5,No,No,R2,P2PTXAC009,5G,LPI,T1C,161,80,-45,DL,STA4,,,,,,,,,,,,
P1,14,No,Yes,R1,P2PTXAC010,2G,LPI,,,,,,,,,,,,,T3E,1,20,-70,DL,STA4
P1,14,No,No,R1,P2PTXAC011,2G,LPI,,,,,,,,,,,,,T1D,1,20,-60,DL,STA4
P1,14,No,No,R1,P2PTXAC012,2G,LPI,,,,,,,,,,,,,T1C,1,20,-45,DL,STA4
P1,16,No,Yes,R1,P2PTXAC013,2G,LPI,,,,,,,,,,,,,T3E,6,20,-70,DL,STA4
P1,16,No,No,R1,P2PTXAC014,2G,LPI,,,,,,,,,,,,,T1D,6,20,-60,DL,STA4
P1,16,No,No,R1,P2PTXAC015,2G,LPI,,,,,,,,,,,,,T1C,6,20,-45,DL,STA4
P1,18,No,Yes,R1,P2PTXAC016,2G,LPI,,,,,,,,,,,,,T3E,11,20,-70,DL,STA4
P1,18,No,No,R1,P2PTXAC017,2G,LPI,,,,,,,,,,,,,T1D,11,20,-60,DL,STA4
P1,18,No,No,R1,P2PTXAC018,2G,LPI,,,,,,,,,,,,,T1C,11,20,-45,DL,STA4
P1,1,No,No,R1,P2PTXAX001,5G,LPI,T1F,36,80,-76,DL,STA56,,,,,,,,,,,,
P1,1,No,No,R1,P2PTXAX002,5G,LPI,T2B,36,80,-67,DL,STA56,,,,,,,,,,,,
P1,1,No,No,R1,P2PTXAX003,5G,LPI,T1C,36,80,-45,DL,STA56,,,,,,,,,,,,
P1,2,No,Yes,R1,P2PTXAX004,5G,LPI,T1F,100,80,-76,DL,STA56,,,,,,,,,,,,
P1,2,No,No,R1,P2PTXAX005,5G,LPI,T2B,100,80,-67,DL,STA56,,,,,,,,,,,,
P1,2,No,No,R1,P2PTXAX006,5G,LPI,T1C,100,80,-45,DL,STA56,,,,,,,,,,,,
P1,5,No,Yes,R1,P2PTXAX007,5G,LPI,T1F,161,80,-76,DL,STA56,,,,,,,,,,,,
P1,5,No,No,R1,P2PTXAX008,5G,LPI,T2B,161,80,-67,DL,STA56,,,,,,,,,,,,
P1,5,No,No,R1,P2PTXAX009,5G,LPI,T1C,161,80,-45,DL,STA56,,,,,,,,,,,,
P1,8,No,Yes,R1,P2PTXAX010,5G,LPI,T1F,100,160,-76,DL,STA56,,,,,,,,,,,,
P1,8,No,No,R1,P2PTXAX011,5G,LPI,T2B,100,160,-67,DL,STA56,,,,,,,,,,,,
P1,8,No,No,R1,P2PTXAX012,5G,LPI,T1C,100,160,-45,DL,STA56,,,,,,,,,,,,
P1,11,No,Yes,R2,P2PTXAX013,6G,LPI,,,,,,,T2A,5,160,-79,DL,STA56,,,,,,
P1,11,No,No,R2,P2PTXAX014,6G,LPI,,,,,,,T2B,5,160,-70,DL,STA56,,,,,,
P1,11,No,No,R2,P2PTXAX015,6G,LPI,,,,,,,T1C,5,160,-45,DL,STA56,,,,,,
P1,13,No,Yes,R2,P2PTXAX016,6G,LPI,,,,,,,T2A,133,160,-79,DL,STA56,,,,,,
P1,13,No,No,R2,P2PTXAX017,6G,LPI,,,,,,,T2B,133,160,-70,DL,STA56,,,,,,
P1,13,No,No,R2,P2PTXAX018,6G,LPI,,,,,,,T1C,133,160,-45,DL,STA56,,,,,,
P1,15,No,Yes,R2,P2PTXAX019,6G,LPI,,,,,,,T2A,197,160,-79,DL,STA56,,,,,,
P1,15,No,No,R2,P2PTXAX020,6G,LPI,,,,,,,T2B,197,160,-70,DL,STA56,,,,,,
P1,15,No,No,R2,P2PTXAX021,6G,LPI,,,,,,,T1C,197,160,-45,DL,STA56,,,,,,
P1,17,No,Yes,R2,P2PTXAX022,6G,SP,,,,,,,T2A,5,320,-79,DL,STA56,,,,,,
P1,17,No,No,R2,P2PTXAX023,6G,SP,,,,,,,T2B,5,320,-70,DL,STA56,,,,,,
P1,17,No,No,R2,P2PTXAX024,6G,SP,,,,,,,T1C,5,320,-45,DL,STA56,,,,,,
P1,19,No,Yes,R2,P2PTXAX025,6G,LPI,,,,,,,T2A,133,320,-79,DL,STA56,,,,,,
P1,19,No,No,R2,P2PTXAX026,6G,LPI,,,,,,,T2B,133,320,-70,DL,STA56,,,,,,
P1,19,No,No,R2,P2PTXAX027,6G,LPI,,,,,,,T1C,133,320,-45,DL,STA56,,,,,,
P1,21,No,Yes,R3,P2PTXAX028,2G,LPI,,,,,,,,,,,,,T3E,1,20,-70,UL,STA56
P1,21,No,No,R3,P2PTXAX029,2G,LPI,,,,,,,,,,,,,T1D,1,20,-60,UL,STA56
P1,21,No,No,R3,P2PTXAX030,2G,LPI,,,,,,,,,,,,,T1C,1,20,-45,UL,STA56
P1,23,No,Yes,R3,P2PTXAX031,2G,LPI,,,,,,,,,,,,,T3E,6,20,-70,UL,STA56
P1,23,No,No,R3,P2PTXAX032,2G,LPI,,,,,,,,,,,,,T1D,6,20,-60,UL,STA56
P1,23,No,No,R3,P2PTXAX033,2G,LPI,,,,,,,,,,,,,T1C,6,20,-45,UL,STA56
P1,25,No,Yes,R3,P2PTXAX034,2G,LPI,,,,,,,,,,,,,T3E,11,20,-70,UL,STA56
P1,25,No,No,R3,P2PTXAX035,2G,LPI,,,,,,,,,,,,,T1D,11,20,-60,UL,STA56
P1,25,No,No,R3,P2PTXAX036,2G,LPI,,,,,,,,,,,,,T1C,11,20,-45,UL,STA56
P2,28,Yes,Yes,R2,COERXBE016,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,197,160,-79,DL,STA56,T3E,11,20,-70,UL,STA63
P1,1,No,No,R1,P2PRXAX001,5G,LPI,T1F,36,80,-76,UL,STA56,,,,,,,,,,,,
P1,1,No,No,R1,P2PRXAX002,5G,LPI,T1B,36,80,-67,UL,STA56,,,,,,,,,,,,
P1,1,No,No,R1,P2PRXAX003,5G,LPI,T1C,36,80,-45,UL,STA56,,,,,,,,,,,,
P1,2,No,Yes,R1,P2PRXAX004,5G,LPI,T1F,100,80,-76,UL,STA56,,,,,,,,,,,,
P1,2,No,No,R1,P2PRXAX005,5G,LPI,T1B,100,80,-67,UL,STA56,,,,,,,,,,,,
P1,2,No,No,R1,P2PRXAX006,5G,LPI,T1C,100,80,-45,UL,STA56,,,,,,,,,,,,
P2,3,Yes,Yes,R1,COERXAX001,5G,LPI,T1F,100,80,-76,UL,STA56,T2A,5,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,4,Yes,Yes,R1,COERXAX002,5G,LPI,T1F,100,80,-76,UL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,5,No,Yes,R1,P2PRXAX007,5G,LPI,T1F,161,80,-76,UL,STA56,,,,,,,,,,,,
P1,5,No,No,R1,P2PRXAX008,5G,LPI,T1B,161,80,-67,UL,STA56,,,,,,,,,,,,
P1,5,No,No,R1,P2PRXAX009,5G,LPI,T1C,161,80,-45,UL,STA56,,,,,,,,,,,,
P2,6,Yes,Yes,R1,COERXAX003,5G,LPI,T1F,161,80,-76,UL,STA56,T2A,5,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,7,Yes,Yes,R1,COERXAX004,5G,LPI,T1F,161,80,-76,UL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,8,Yes,Yes,R1,COERXAX005,5G,SP,T1F,161,80,-76,UL,STA56,T2A,5,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,9,Yes,Yes,R1,COERXAX006,5G,LPI,T1F,161,80,-76,UL,STA56,T2A,133,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,10,No,Yes,R1,P2PRXAX010,5G,LPI,T1F,100,160,-76,UL,STA56,,,,,,,,,,,,
P1,10,No,No,R1,P2PRXAX011,5G,LPI,T1B,100,160,-67,UL,STA56,,,,,,,,,,,,
P1,10,No,No,R1,P2PRXAX012,5G,LPI,T1C,100,160,-45,UL,STA56,,,,,,,,,,,,
P2,11,Yes,Yes,R1,COERXAX007,5G,SP,T1F,100,160,-76,UL,STA56,T2A,5,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,12,Yes,Yes,R1,COERXAX008,5G,LPI,T1F,100,160,-76,UL,STA56,T2A,133,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,13,No,Yes,R2,P2PRXAX013,6G,LPI,,,,,,,T2A,5,160,-79,UL,STA56,,,,,,
P1,13,No,No,R2,P2PRXAX014,6G,LPI,,,,,,,T1B,5,160,-70,UL,STA56,,,,,,
P1,13,No,No,R2,P2PRXAX015,6G,LPI,,,,,,,T1C,5,160,-45,UL,STA56,,,,,,
P2,14,Yes,Yes,R2,COERXAX009,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,5,160,-79,UL,STA56,T3E,6,20,-70,DL,STA4
P1,15,No,Yes,R2,P2PRXAX016,6G,LPI,,,,,,,T2A,133,160,-79,UL,STA56,,,,,,
P1,15,No,No,R2,P2PRXAX017,6G,LPI,,,,,,,T1B,133,160,-70,UL,STA56,,,,,,
P1,15,No,No,R2,P2PRXAX018,6G,LPI,,,,,,,T1C,133,160,-45,UL,STA63,,,,,,
P2,16,Yes,Yes,R2,COERXAX010,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,133,160,-79,UL,STA56,T3E,6,20,-70,DL,STA4
P1,17,No,Yes,R2,P2PRXAX019,6G,LPI,,,,,,,T2A,197,160,-79,UL,STA56,,,,,,
P1,17,No,No,R2,P2PRXAX020,6G,LPI,,,,,,,T1B,197,160,-70,UL,STA56,,,,,,
P1,17,No,No,R2,P2PRXAX021,6G,LPI,,,,,,,T1C,197,160,-45,UL,STA56,,,,,,
P2,18,Yes,Yes,R2,COERXAX011,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,197,160,-79,UL,STA56,T3E,6,20,-70,DL,STA4
P1,19,No,Yes,R2,P2PRXAX022,6G,SP,,,,,,,T2A,5,320,-79,UL,STA56,,,,,,
P1,19,No,No,R2,P2PRXAX023,6G,SP,,,,,,,T1B,5,320,-70,UL,STA56,,,,,,
P1,19,No,No,R2,P2PRXAX024,6G,SP,,,,,,,T1C,5,320,-45,UL,STA56,,,,,,
P2,20,Yes,Yes,R2,COERXAX012,6G,SP,T1F,161,80,-76,DL,STA63,T2A,5,320,-79,UL,STA56,T3E,6,20,-70,DL,STA4
P1,21,No,Yes,R2,P2PRXAX025,6G,LPI,,,,,,,T2A,133,320,-79,UL,STA56,,,,,,
P1,21,No,No,R2,P2PRXAX026,6G,LPI,,,,,,,T1B,133,320,-70,UL,STA56,,,,,,
P1,21,No,No,R2,P2PRXAX027,6G,LPI,,,,,,,T1C,133,320,-45,UL,STA56,,,,,,
P2,22,Yes,Yes,R2,COERXAX013,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,133,320,-79,UL,STA56,T3E,6,20,-70,DL,STA4
P1,23,No,Yes,R3,P2PRXAX028,2G,LPI,,,,,,,,,,,,,T3E,1,20,-70,UL,STA56
P1,23,No,No,R3,P2PRXAX029,2G,LPI,,,,,,,,,,,,,T1D,1,20,-60,UL,STA56
P1,23,No,No,R3,P2PRXAX030,2G,LPI,,,,,,,,,,,,,T1C,1,20,-45,UL,STA56
P2,24,Yes,Yes,R3,COERXAX014,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,5,160,-79,DL,STA63,T3E,1,20,-70,UL,STA56
P1,25,No,Yes,R3,P2PRXAX031,2G,LPI,,,,,,,,,,,,,T3E,6,20,-70,UL,STA56
P1,25,No,No,R3,P2PRXAX032,2G,LPI,,,,,,,,,,,,,T1D,6,20,-60,UL,STA56
P1,25,No,No,R3,P2PRXAX033,2G,LPI,,,,,,,,,,,,,T1C,6,20,-45,UL,STA56
P2,26,Yes,Yes,R3,COERXAX015,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,UL,STA56
P1,27,No,Yes,R3,P2PRXAX034,2G,LPI,,,,,,,,,,,,,T3E,11,20,-70,UL,STA56
P1,27,No,No,R3,P2PRXAX035,2G,LPI,,,,,,,,,,,,,T1D,11,20,-60,UL,STA56
P1,27,No,No,R3,P2PRXAX036,2G,LPI,,,,,,,,,,,,,T1C,11,20,-45,UL,STA56
P2,28,Yes,Yes,R3,COERXAX016,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,197,160,-79,DL,STA63,T3E,11,20,-70,UL,STA56
P1,1,No,No,R2,P2PRXAC001,5G,LPI,T1F,36,80,-76,UL,STA4,,,,,,,,,,,,
P1,1,No,No,R2,P2PRXAC002,5G,LPI,T1B,36,80,-67,UL,STA4,,,,,,,,,,,,
P1,1,No,No,R2,P2PRXAC003,5G,LPI,T1C,36,80,-45,UL,STA4,,,,,,,,,,,,
P1,2,No,Yes,R2,P2PRXAC004,5G,LPI,T1F,100,80,-76,UL,STA4,,,,,,,,,,,,
P1,2,No,No,R2,P2PRXAC005,5G,LPI,T1B,100,80,-67,UL,STA4,,,,,,,,,,,,
P1,2,No,No,R2,P2PRXAC006,5G,LPI,T1C,100,80,-45,UL,STA4,,,,,,,,,,,,
P2,3,Yes,Yes,R2,COERXAC001,5G,LPI,T1F,100,80,-76,UL,STA4,T2A,5,160,OFF,DL,STA63,T3E,6,20,-70,DL,STA56
P1,5,No,Yes,R2,P2PRXAC007,5G,LPI,T1F,161,80,-76,UL,STA4,,,,,,,,,,,,
P1,5,No,No,R2,P2PRXAC008,5G,LPI,T1B,161,80,-67,UL,STA4,,,,,,,,,,,,
P1,5,No,No,R2,P2PRXAC009,5G,LPI,T1C,161,80,-45,UL,STA4,,,,,,,,,,,,
P2,6,Yes,Yes,R2,COERXAC002,5G,LPI,T1F,161,80,-76,UL,STA4,T2A,133,160,OFF,DL,STA63,T3E,6,20,-70,DL,STA56
P1,14,No,Yes,R1,P2PRXAC010,2G,LPI,,,,,,,,,,,,,T3E,1,20,-70,UL,STA4
P1,14,No,No,R1,P2PRXAC011,2G,LPI,,,,,,,,,,,,,T1D,1,20,-60,UL,STA4
P1,14,No,No,R1,P2PRXAC012,2G,LPI,,,,,,,,,,,,,T1C,1,20,-45,UL,STA4
P2,15,Yes,Yes,R1,COERXAC003,2G,LPI,T1F,36,80,-76,DL,STA56,T2A,5,160,OFF,DL,STA63,T3E,1,20,-70,UL,STA4
P1,16,No,Yes,R1,P2PRXAC013,2G,LPI,,,,,,,,,,,,,T3E,6,20,-70,UL,STA4
P1,16,No,No,R1,P2PRXAC014,2G,LPI,,,,,,,,,,,,,T1D,6,20,-60,UL,STA4
P1,16,No,No,R1,P2PRXAC015,2G,LPI,,,,,,,,,,,,,T1C,6,20,-45,UL,STA4
P2,17,Yes,Yes,R1,COERXAC004,2G,LPI,T1F,36,80,-76,DL,STA56,T2A,133,160,OFF,DL,STA63,T3E,6,20,-70,UL,STA4
P1,18,No,Yes,R1,P2PRXAC016,2G,LPI,,,,,,,,,,,,,T3E,11,20,-70,UL,STA4
P1,18,No,No,R1,P2PRXAC017,2G,LPI,,,,,,,,,,,,,T1D,11,20,-60,UL,STA4
P1,18,No,No,R1,P2PRXAC018,2G,LPI,,,,,,,,,,,,,T1C,11,20,-45,UL,STA4
P2,19,Yes,Yes,R1,COERXAC005,2G,LPI,T1F,36,80,-76,DL,STA56,T2A,197,160,OFF,DL,STA63,T3E,11,20,-70,UL,STA4
P1,1,No,No,R3,P2PTXBE001,5G,LPI,T1F,36,80,-76,DL,STA63,,,,,,,,,,,,
P1,1,No,No,R3,P2PTXBE002,5G,LPI,T2B,36,80,-67,DL,STA63,,,,,,,,,,,,
P1,1,No,No,R3,P2PTXBE002,5G,LPI,T1B,36,80,-67,DL,STA63,,,,,,,,,,,,
P1,1,No,No,R3,P2PTXBE003,5G,LPI,T1C,36,80,-45,DL,STA63,,,,,,,,,,,,
P1,2,No,Yes,R3,P2PTXBE004,5G,LPI,T1F,100,80,-76,DL,STA63,,,,,,,,,,,,
P1,2,No,No,R3,P2PTXBE005,5G,LPI,T2B,100,80,-67,DL,STA63,,,,,,,,,,,,
P1,2,No,No,R3,P2PTXBE005,5G,LPI,T1B,100,80,-67,DL,STA63,,,,,,,,,,,,
P1,2,No,No,R3,P2PTXBE006,5G,LPI,T1C,100,80,-45,DL,STA63,,,,,,,,,,,,
P2,3,Yes,Yes,R3,COETXBE001,5G,LPI,T1F,100,80,-76,DL,STA63,T2A,5,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,4,Yes,Yes,R3,COETXBE002,5G,LPI,T1F,100,80,-76,DL,STA63,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P1,5,No,Yes,R3,P2PTXBE007,5G,LPI,T1F,161,80,-76,DL,STA63,,,,,,,,,,,,
P1,5,No,No,R3,P2PTXBE008,5G,LPI,T2B,161,80,-67,DL,STA63,,,,,,,,,,,,
P1,5,No,No,R3,P2PTXBE008,5G,LPI,T1B,161,80,-67,DL,STA63,,,,,,,,,,,,
P1,5,No,No,R3,P2PTXBE009,5G,LPI,T1C,161,80,-45,DL,STA63,,,,,,,,,,,,
P2,6,Yes,Yes,R3,COETXBE003,5G,LPI,T1F,161,80,-76,DL,STA63,T2A,5,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P2,7,Yes,Yes,R3,COETXBE004,5G,LPI,T1F,161,80,-76,DL,STA63,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P1,8,No,Yes,R3,P2PTXBE010,5G,LPI,T1F,100,160,-76,DL,STA63,,,,,,,,,,,,
P1,8,No,No,R3,P2PTXBE011,5G,LPI,T2B,100,160,-67,DL,STA63,,,,,,,,,,,,
P1,8,No,No,R3,P2PTXBE011,5G,LPI,T1B,100,160,-67,DL,STA63,,,,,,,,,,,,
P1,8,No,No,R3,P2PTXBE012,5G,LPI,T1C,100,160,-45,DL,STA63,,,,,,,,,,,,
P1,11,No,Yes,R3,P2PTXBE013,6G,LPI,T1F,5,160,-79,DL,STA63,,,,,,,,,,,,
P1,11,No,No,R1,P2PTXBE014,6G,LPI,T2B,5,160,-70,DL,STA63,,,,,,,,,,,,
P2,9,Yes,Yes,R3,COETXBE005,5G,SP,T1F,100,160,-76,DL,STA63,T2A,5,320,-79,DL,STA64,T3E,6,20,-70,DL,STA4
P2,10,Yes,Yes,R3,COETXBE006,5G,LPI,T1F,100,160,-76,DL,STA63,T2A,133,320,-79,DL,STA64,T3E,6,20,-70,DL,STA4
P1,11,No,Yes,R1,P2PTXBE013,6G,LPI,T1F,5,160,-79,DL,STA63,,,,,,,,,,,,
P1,11,No,No,R1,P2PTXBE014,6G,LPI,T1B,5,160,-70,DL,STA63,,,,,,,,,,,,
P1,11,No,No,R1,P2PTXBE015,6G,LPI,T1C,5,160,-45,DL,STA63,,,,,,,,,,,,
P2,12,Yes,Yes,R1,COETXBE007,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,5,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,13,No,Yes,R1,P2PTXBE016,6G,LPI,T1F,133,160,-79,DL,STA63,,,,,,,,,,,,
P1,13,No,No,R1,P2PTXBE017,6G,LPI,T2B,133,160,-70,DL,STA63,,,,,,,,,,,,
P1,13,No,No,R1,P2PTXBE017,6G,LPI,T1B,133,160,-70,DL,STA63,,,,,,,,,,,,
P1,13,No,No,R1,P2PTXBE018,6G,LPI,T1C,133,160,-45,DL,STA63,,,,,,,,,,,,
P2,14,Yes,Yes,R1,COETXBE008,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,15,No,Yes,R1,P2PTXBE019,6G,LPI,T1F,197,160,-79,DL,STA63,,,,,,,,,,,,
P1,15,No,No,R1,P2PTXBE020,6G,LPI,T2B,197,160,-70,DL,STA63,,,,,,,,,,,,
P1,15,No,No,R1,P2PTXBE020,6G,LPI,T1B,197,160,-70,DL,STA63,,,,,,,,,,,,
P1,15,No,No,R1,P2PTXBE021,6G,LPI,T1C,197,160,-45,DL,STA63,,,,,,,,,,,,
P2,16,Yes,Yes,R1,COETXBE009,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,197,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,17,No,Yes,R1,P2PTXBE022,6G,SP,T1F,5,320,-79,DL,STA63,,,,,,,,,,,,
P1,17,No,No,R1,P2PTXBE023,6G,SP,T2B,5,320,-70,DL,STA63,,,,,,,,,,,,
P1,17,No,No,R1,P2PTXBE023,6G,SP,T1B,5,320,-70,DL,STA63,,,,,,,,,,,,
P1,17,No,No,R1,P2PTXBE024,6G,SP,T1C,5,320,-45,DL,STA63,,,,,,,,,,,,
P2,18,Yes,Yes,R1,COETXBE010,6G,SP,T1F,161,80,-76,DL,STA56,T2A,5,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,19,No,Yes,R1,P2PTXBE025,6G,LPI,T1F,133,320,-79,DL,STA63,,,,,,,,,,,,
P1,19,No,No,R1,P2PTXBE026,6G,LPI,T2B,133,320,-70,DL,STA63,,,,,,,,,,,,
P1,19,No,No,R1,P2PTXBE026,6G,LPI,T1B,133,320,-70,DL,STA63,,,,,,,,,,,,
P1,19,No,No,R1,P2PTXBE027,6G,LPI,T1C,133,320,-45,DL,STA63,,,,,,,,,,,,
P2,20,Yes,Yes,R1,COETXBE011,6G,LPI,T1F,161,80,-76,DL,STA56,T2A,133,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,21,No,Yes,R2,P2PTXBE028,2G,LPI,T3E,1,20,-70,UL,STA63,,,,,,,,,,,,
P1,21,No,No,R2,P2PTXBE029,2G,LPI,T1D,1,20,-60,UL,STA63,,,,,,,,,,,,
P1,21,No,No,R2,P2PTXBE030,2G,LPI,T1C,1,20,-45,UL,STA63,,,,,,,,,,,,
P2,22,Yes,Yes,R2,COETXBE012,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,5,160,-79,DL,STA56,T3E,1,20,-70,DL,STA63
P1,23,No,Yes,R2,P2PTXBE031,2G,LPI,T3E,6,20,-70,UL,STA63,,,,,,,,,,,,
P1,23,No,No,R2,P2PTXBE032,2G,LPI,T1D,6,20,-60,UL,STA63,,,,,,,,,,,,
P1,23,No,No,R2,P2PTXBE033,2G,LPI,T1C,6,20,-45,UL,STA63,,,,,,,,,,,,
P2,24,Yes,Yes,R2,COETXBE013,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,DL,STA63
P1,25,No,Yes,R2,P2PTXBE034,2G,LPI,T3E,11,20,-70,UL,STA63,,,,,,,,,,,,
P1,25,No,No,R2,P2PTXBE035,2G,LPI,T1D,11,20,-60,UL,STA63,,,,,,,,,,,,
P1,25,No,No,R2,P2PTXBE036,2G,LPI,T1C,11,20,-45,UL,STA63,,,,,,,,,,,,
P2,26,Yes,Yes,R2,COETXBE014,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,197,160,-79,DL,STA56,T3E,11,20,-70,DL,STA63
P1,1,No,No,R1,P2PTXAX001,5G,LPI,T1F,36,80,-76,DL,STA56,,,,,,,,,,,,
P1,1,No,No,R1,P2PTXAX002,5G,LPI,T1B,36,80,-67,DL,STA56,,,,,,,,,,,,
P1,1,No,No,R1,P2PTXAX003,5G,LPI,T1C,36,80,-45,DL,STA56,,,,,,,,,,,,
P1,2,No,Yes,R1,P2PTXAX004,5G,LPI,T1F,100,80,-76,DL,STA56,,,,,,,,,,,,
P1,2,No,No,R1,P2PTXAX005,5G,LPI,T1B,100,80,-67,DL,STA56,,,,,,,,,,,,
P1,2,No,No,R1,P2PTXAX006,5G,LPI,T1C,100,80,-45,DL,STA56,,,,,,,,,,,,
P2,3,Yes,Yes,R1,COETXAX001,5G,LPI,T1F,100,80,-76,DL,STA56,T2A,5,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,4,Yes,Yes,R1,COETXAX002,5G,LPI,T1F,100,80,-76,DL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,5,No,Yes,R1,P2PTXAX007,5G,LPI,T1F,161,80,-76,DL,STA56,,,,,,,,,,,,
P1,5,No,No,R1,P2PTXAX008,5G,LPI,T1B,161,80,-67,DL,STA56,,,,,,,,,,,,
P1,5,No,No,R1,P2PTXAX009,5G,LPI,T1C,161,80,-45,DL,STA56,,,,,,,,,,,,
P2,6,Yes,Yes,R1,COETXAX003,5G,LPI,T1F,161,80,-76,DL,STA56,T2A,5,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,7,Yes,Yes,R1,COETXAX004,5G,LPI,T1F,161,80,-76,DL,STA56,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,8,No,Yes,R1,P2PTXAX010,5G,LPI,T1F,100,160,-76,DL,STA56,,,,,,,,,,,,
P1,8,No,No,R1,P2PTXAX011,5G,LPI,T1B,100,160,-67,DL,STA56,,,,,,,,,,,,
P1,8,No,No,R1,P2PTXAX012,5G,LPI,T1C,100,160,-45,DL,STA56,,,,,,,,,,,,
P2,9,Yes,Yes,R1,COETXAX005,5G,SP,T1F,100,160,-76,DL,STA56,T2A,5,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P2,10,Yes,Yes,R1,COETXAX006,5G,LPI,T1F,100,160,-76,DL,STA56,T2A,133,320,-79,DL,STA63,T3E,6,20,-70,DL,STA4
P1,11,No,Yes,R2,P2PTXAX013,6G,LPI,,,,,,,T2A,5,160,-79,DL,STA56,,,,,,
P1,11,No,No,R2,P2PTXAX014,6G,LPI,,,,,,,T1B,5,160,-70,DL,STA56,,,,,,
P1,11,No,No,R2,P2PTXAX015,6G,LPI,,,,,,,T1C,5,160,-45,DL,STA56,,,,,,
P2,12,Yes,Yes,R2,COETXAX007,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,5,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P1,13,No,Yes,R2,P2PTXAX016,6G,LPI,,,,,,,T2A,133,160,-79,DL,STA56,,,,,,
P1,13,No,No,R2,P2PTXAX017,6G,LPI,,,,,,,T1B,133,160,-70,DL,STA56,,,,,,
P1,13,No,No,R2,P2PTXAX018,6G,LPI,,,,,,,T1C,133,160,-45,DL,STA56,,,,,,
P2,14,Yes,Yes,R2,COETXAX008,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,133,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P1,15,No,Yes,R2,P2PTXAX019,6G,LPI,,,,,,,T2A,197,160,-79,DL,STA56,,,,,,
P1,15,No,No,R2,P2PTXAX020,6G,LPI,,,,,,,T1B,197,160,-70,DL,STA56,,,,,,
P1,15,No,No,R2,P2PTXAX021,6G,LPI,,,,,,,T1C,197,160,-45,DL,STA56,,,,,,
P2,16,Yes,Yes,R2,COETXAX009,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,197,160,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P1,17,No,Yes,R2,P2PTXAX022,6G,SP,,,,,,,T2A,5,320,-79,DL,STA56,,,,,,
P1,17,No,No,R2,P2PTXAX023,6G,SP,,,,,,,T1B,5,320,-70,DL,STA56,,,,,,
P1,17,No,No,R2,P2PTXAX024,6G,SP,,,,,,,T1C,5,320,-45,DL,STA56,,,,,,
P2,18,Yes,Yes,R2,COETXAX010,6G,SP,T1F,161,80,-76,DL,STA63,T2A,5,320,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P1,19,No,Yes,R2,P2PTXAX025,6G,LPI,,,,,,,T2A,133,320,-79,DL,STA56,,,,,,
P1,19,No,No,R2,P2PTXAX026,6G,LPI,,,,,,,T1B,133,320,-70,DL,STA56,,,,,,
P1,19,No,No,R2,P2PTXAX027,6G,LPI,,,,,,,T1C,133,320,-45,DL,STA56,,,,,,
P2,20,Yes,Yes,R2,COETXAX011,6G,LPI,T1F,161,80,-76,DL,STA63,T2A,133,320,-79,DL,STA56,T3E,6,20,-70,DL,STA4
P1,21,No,Yes,R3,P2PTXAX028,2G,LPI,,,,,,,,,,,,,T3E,1,20,-70,UL,STA56
P1,21,No,No,R3,P2PTXAX029,2G,LPI,,,,,,,,,,,,,T1D,1,20,-60,UL,STA56
P1,21,No,No,R3,P2PTXAX030,2G,LPI,,,,,,,,,,,,,T1C,1,20,-45,UL,STA56
P2,22,Yes,Yes,R3,COETXAX012,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,5,160,-79,DL,STA63,T3E,1,20,-70,DL,STA56
P1,23,No,Yes,R3,P2PTXAX031,2G,LPI,,,,,,,,,,,,,T3E,6,20,-70,UL,STA56
P1,23,No,No,R3,P2PTXAX032,2G,LPI,,,,,,,,,,,,,T1D,6,20,-60,UL,STA56
P1,23,No,No,R3,P2PTXAX033,2G,LPI,,,,,,,,,,,,,T1C,6,20,-45,UL,STA56
P2,24,Yes,Yes,R3,COETXAX013,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,133,160,-79,DL,STA63,T3E,6,20,-70,DL,STA56
P1,25,No,Yes,R3,P2PTXAX034,2G,LPI,,,,,,,,,,,,,T3E,11,20,-70,UL,STA56
P1,25,No,No,R3,P2PTXAX035,2G,LPI,,,,,,,,,,,,,T1D,11,20,-60,UL,STA56
P1,25,No,No,R3,P2PTXAX036,2G,LPI,,,,,,,,,,,,,T1C,11,20,-45,UL,STA56
P2,26,Yes,Yes,R3,COETXAX014,2G,LPI,T1F,36,80,-76,DL,STA4,T2A,197,160,-79,DL,STA63,T3E,11,20,-70,DL,STA56
P1,1,No,No,R2,P2PTXAC001,5G,LPI,T1F,36,80,-76,DL,STA4,,,,,,,,,,,,
P1,1,No,No,R2,P2PTXAC002,5G,LPI,T1B,36,80,-67,DL,STA4,,,,,,,,,,,,
P1,1,No,No,R2,P2PTXAC003,5G,LPI,T1C,36,80,-45,DL,STA4,,,,,,,,,,,,
P1,2,No,Yes,R2,P2PTXAC004,5G,LPI,T1F,100,80,-76,DL,STA4,,,,,,,,,,,,
P1,2,No,No,R2,P2PTXAC005,5G,LPI,T1B,100,80,-67,DL,STA4,,,,,,,,,,,,
P1,2,No,No,R2,P2PTXAC006,5G,LPI,T1C,100,80,-45,DL,STA4,,,,,,,,,,,,
P2,3,Yes,Yes,R2,COETXAC001,5G,LPI,T1F,100,80,-76,DL,STA4,T1K2A,5,160,OFF,DL,STA63,T3E,6,20,-70,DL,STA56
P1,5,No,Yes,R2,P2PTXAC007,5G,LPI,T1F,161,80,-76,DL,STA4,,,,,,,,,,,,
P1,5,No,No,R2,P2PTXAC008,5G,LPI,T1B,161,80,-67,DL,STA4,,,,,,,,,,,,
P1,5,No,No,R2,P2PTXAC009,5G,LPI,T1C,161,80,-45,DL,STA4,,,,,,,,,,,,
P2,6,Yes,Yes,R2,COETXAC002,5G,LPI,T1F,161,80,-76,DL,STA4,T1K2A,133,160,OFF,DL,STA63,T3E,6,20,-70,DL,STA56
P1,14,No,Yes,R1,P2PTXAC010,2G,LPI,,,,,,,,,,,,,T3E,1,20,-70,DL,STA4
P1,14,No,No,R1,P2PTXAC011,2G,LPI,,,,,,,,,,,,,T1D,1,20,-60,DL,STA4
P1,14,No,No,R1,P2PTXAC012,2G,LPI,,,,,,,,,,,,,T1C,1,20,-45,DL,STA4
P2,15,Yes,Yes,R1,COETXAC003,2G,LPI,T1F,36,80,-76,DL,STA56,T1K2A,5,160,OFF,DL,STA63,T3E,1,20,-70,DL,STA4
P1,16,No,Yes,R1,P2PTXAC013,2G,LPI,,,,,,,,,,,,,T3E,6,20,-70,DL,STA4
P1,16,No,No,R1,P2PTXAC014,2G,LPI,,,,,,,,,,,,,T1D,6,20,-60,DL,STA4
P1,16,No,No,R1,P2PTXAC015,2G,LPI,,,,,,,,,,,,,T1C,6,20,-45,DL,STA4
P2,17,Yes,Yes,R1,COETXAC004,2G,LPI,T1F,36,80,-76,DL,STA56,T1K2A,133,160,OFF,DL,STA63,T3E,6,20,-70,DL,STA4
P1,18,No,Yes,R1,P2PTXAC016,2G,LPI,,,,,,,,,,,,,T3E,11,20,-70,DL,STA4
P1,18,No,No,R1,P2PTXAC017,2G,LPI,,,,,,,,,,,,,T1D,11,20,-60,DL,STA4
P1,18,No,No,R1,P2PTXAC018,2G,LPI,,,,,,,,,,,,,T1C,11,20,-45,DL,STA4
P2,19,Yes,Yes,R1,COETXAC005,2G,LPI,T1F,36,80,-76,DL,STA56,T1K2A,5,320,OFF,DL,STA63,T3E,11,20,-70,DL,STA4
1 Priority Index Interferer COE Pair Rotation TC ID Victim Band 6GHz Power Mode 5G Test Point 5G_Test Point 5G Channel 5G_Channel 5G Bandwidth 5G_Bandwidth 5G RSSI 5G_RSSI 5G Direction 5G_Direction 5G STA 6G STA 5G_STA 6G Test Point 6G_Test Point 6G Channel 6G_Channel 6G Bandwidth 6G_Bandwidth 6G RSSI 6G_RSSI 6G Direction 6G_Direction 6G_STA 2G Test Point 2G_Test Point 2G Channel 2G_Channel 2G Bandwidth 2G_Bandwidth 2G RSSI 2G_RSSI 2G Direction 2G_Direction 2G STA 2G_STA
2 P2 P1 3 1 Yes No Yes No R2 R3 COERXAC001 P2PRXBE001 5G LPI T1F T1F 100 36 80 -76 -76 UL UL STA4 STA63 T2A 5 160 OFF DL T3E 6 20 -70 DL STA56
3 P2 P1 6 1 Yes No Yes No R2 R3 COERXAC002 P2PRXBE002 5G LPI T1F T1B 161 36 80 -76 -67 UL UL STA4 STA63 T2A 133 160 OFF DL T3E 6 20 -70 DL STA56
4 P2 P1 15 1 Yes No Yes No R1 R3 COERXAC003 P2PRXBE003 2G 5G LPI T1F T1C 36 36 80 -76 -45 DL UL STA56 STA63 T2A 5 160 OFF DL T3E 1 20 -70 UL STA4
5 P2 P1 17 2 Yes No Yes R1 R3 COERXAC004 P2PRXBE004 2G 5G LPI T1F T1F 36 100 80 -76 -76 DL UL STA56 STA63 T2A 133 160 OFF DL T3E 6 20 -70 UL STA4
6 P2 P1 19 2 Yes No Yes No R1 R3 COERXAC005 P2PRXBE005 2G 5G LPI T1F T1B 36 100 80 -76 -67 DL UL STA56 STA63 T2A 197 160 OFF DL T3E 11 20 -70 UL STA4
7 P2 P1 3 2 Yes No Yes No R1 R3 COERXAX001 P2PRXBE006 5G LPI T1F T1C 100 100 80 -76 -45 UL UL STA56 STA63 T2A 5 160 -79 DL T3E 6 20 -70 DL STA4
P2 4 Yes Yes R1 COERXAX002 5G LPI T1F 100 80 -76 UL STA56 STA63 T2A 133 160 -79 DL T3E 6 20 -70 DL STA4
P2 6 Yes Yes R1 COERXAX003 5G LPI T1F 161 80 -76 UL STA56 STA63 T2A 5 160 -79 DL T3E 6 20 -70 DL STA4
P2 7 Yes Yes R1 COERXAX004 5G LPI T1F 161 80 -76 UL STA56 STA63 T2A 133 160 -79 DL T3E 6 20 -70 DL STA4
P2 8 Yes Yes R1 COERXAX005 5G SP T1F 161 80 -76 UL STA56 STA63 T2A 5 320 -79 DL T3E 6 20 -70 DL STA4
P2 9 Yes Yes R1 COERXAX006 5G LPI T1F 161 80 -76 UL STA56 STA63 T2A 133 320 -79 DL T3E 6 20 -70 DL STA4
P2 11 Yes Yes R1 COERXAX007 5G SP T1F 100 160 -76 UL STA56 STA63 T2A 5 320 -79 DL T3E 6 20 -70 DL STA4
P2 12 Yes Yes R1 COERXAX008 5G LPI T1F 100 160 -76 UL STA56 STA63 T2A 133 320 -79 DL T3E 6 20 -70 DL STA4
P2 14 Yes Yes R2 COERXAX009 6G LPI T1F 161 80 -76 DL STA63 STA56 T2A 5 160 -79 UL T3E 6 20 -70 DL STA4
P2 16 Yes Yes R2 COERXAX010 6G LPI T1F 161 80 -76 DL STA63 STA56 T2A 133 160 -79 UL T3E 6 20 -70 DL STA4
P2 18 Yes Yes R2 COERXAX011 6G LPI T1F 161 80 -76 DL STA63 STA56 T2A 197 160 -79 UL T3E 6 20 -70 DL STA4
P2 20 Yes Yes R2 COERXAX012 6G SP T1F 161 80 -76 DL STA63 STA56 T2A 5 320 -79 UL T3E 6 20 -70 DL STA4
P2 22 Yes Yes R2 COERXAX013 6G LPI T1F 161 80 -76 DL STA63 STA56 T2A 133 320 -79 UL T3E 6 20 -70 DL STA4
P2 24 Yes Yes R3 COERXAX014 2G LPI T1F 36 80 -76 DL STA4 STA63 T2A 5 160 -79 DL T3E 1 20 -70 UL STA56
P2 26 Yes Yes R3 COERXAX015 2G LPI T1F 36 80 -76 DL STA4 STA63 T2A 133 160 -79 DL T3E 6 20 -70 UL STA56
P2 28 Yes Yes R3 COERXAX016 2G LPI T1F 36 80 -76 DL STA4 STA63 T2A 197 160 -79 DL T3E 11 20 -70 UL STA56
8 P2 3 Yes Yes R3 COERXBE001 5G LPI T1F T1F 100 100 80 -76 -76 UL UL STA63 STA56 STA63 T2A T2A 5 5 160 160 -79 -79 DL DL STA56 T3E T3E 6 6 20 20 -70 -70 DL DL STA4 STA4
9 P2 4 Yes Yes R3 COERXBE002 5G LPI T1F T1F 100 100 80 -76 -76 UL UL STA63 STA56 STA63 T2A T2A 133 133 160 160 -79 -79 DL DL STA56 T3E T3E 6 6 20 20 -70 -70 DL DL STA4 STA4
10 P1 5 No Yes R3 P2PRXBE007 5G LPI T1F 161 80 -76 UL STA63
11 P1 5 No No R3 P2PRXBE008 5G LPI T1B 161 80 -67 UL STA63
12 P1 5 No No R3 P2PRXBE009 5G LPI T1C 161 80 -45 UL STA63
13 P2 6 Yes Yes R3 COERXBE003 5G LPI T1F T1F 161 161 80 -76 -76 UL UL STA63 STA56 STA63 T2A T2A 5 5 160 160 -79 -79 DL DL STA56 T3E T3E 6 6 20 20 -70 -70 DL DL STA4 STA4
14 P2 7 Yes Yes R3 COERXBE004 5G LPI T1F T1F 161 161 80 -76 -76 UL UL STA63 STA56 STA63 T2A T2A 133 133 160 160 -79 -79 DL DL STA56 T3E T3E 6 6 20 20 -70 -70 DL DL STA4 STA4
15 P2 8 Yes Yes R3 COERXBE005 5G SP T1F T1F 161 161 80 -76 -76 UL UL STA63 STA64 STA63 T2A T2A 5 5 320 320 -79 -79 DL DL STA64 T3E T3E 6 6 20 20 -70 -70 DL DL STA4 STA4
16 P2 9 Yes Yes R3 COERXBE006 5G LPI T1F T1F 161 161 80 -76 -76 UL UL STA63 STA64 STA63 T2A T2A 133 133 320 320 -79 -79 DL DL STA64 T3E T3E 6 6 20 20 -70 -70 DL DL STA4 STA4
17 P1 10 No Yes R3 P2PRXBE010 5G LPI T1F 100 160 -76 UL STA63
18 P1 10 No No R3 P2PRXBE011 5G LPI T1B 100 160 -67 UL STA63
19 P1 10 No No R3 P2PRXBE012 5G LPI T1C 100 160 -45 UL STA63
20 P2 11 Yes Yes R3 COERXBE007 5G SP T1F T1F 100 100 160 -76 -76 UL UL STA63 STA64 STA63 T2A T2A 5 5 320 320 -79 -79 DL DL STA64 T3E T3E 6 6 20 20 -70 -70 DL DL STA4 STA4
21 P2 12 Yes Yes R3 COERXBE008 5G LPI T1F T1F 100 100 160 -76 -76 UL UL STA63 STA64 STA63 T2A T2A 133 133 320 320 -79 -79 DL DL STA64 T3E T3E 6 6 20 20 -70 -70 DL DL STA4 STA4
P2 14 Yes Yes R1 COERXBE009 6G LPI T1F 161 80 -76 DL STA56 STA63 T2A 5 160 -79 UL T3E 6 20 -70 DL STA4
P2 16 Yes Yes R1 COERXBE010 6G LPI T1F 161 80 -76 DL STA56 STA63 T2A 133 160 -79 UL T3E 6 20 -70 DL STA4
P2 18 Yes Yes R1 COERXBE011 6G LPI T1F 161 80 -76 DL STA56 STA63 T2A 197 160 -79 UL T3E 6 20 -70 DL STA4
P2 20 Yes Yes R1 COERXBE012 6G SP T1F 161 80 -76 DL STA56 STA63 T2A 5 320 -79 UL T3E 6 20 -70 DL STA4
P2 22 Yes Yes R1 COERXBE013 6G LPI T1F 161 80 -76 DL STA56 STA63 T2A 133 320 -79 UL T3E 6 20 -70 DL STA4
P2 24 Yes Yes R2 COERXBE014 2G LPI T1F 36 80 -76 DL STA4 STA56 T2A 5 160 -79 DL T3E 1 20 -70 UL STA63
P2 26 Yes Yes R2 COERXBE015 2G LPI T1F 36 80 -76 DL STA4 STA56 T2A 133 160 -79 DL T3E 6 20 -70 UL STA63
P2 28 Yes Yes R2 COERXBE016 2G LPI T1F 36 80 -76 DL STA4 STA56 T2A 197 160 -79 DL T3E 11 20 -70 UL STA63
P2 3 Yes Yes R2 COETXAC001 5G LPI T1F 100 80 -76 DL STA4 STA63 T1K2A 5 160 OFF DL T3E 6 20 -70 DL STA56
P2 6 Yes Yes R2 COETXAC002 5G LPI T1F 161 80 -76 DL STA4 STA63 T1K2A 133 160 OFF DL T3E 6 20 -70 DL STA56
P2 15 Yes Yes R1 COETXAC003 2G LPI T1F 36 80 -76 DL STA56 STA63 T1K2A 5 160 OFF DL T3E 1 20 -70 DL STA4
P2 17 Yes Yes R1 COETXAC004 2G LPI T1F 36 80 -76 DL STA56 STA63 T1K2A 133 160 OFF DL T3E 6 20 -70 DL STA4
P2 19 Yes Yes R1 COETXAC005 2G LPI T1F 36 80 -76 DL STA56 STA63 T1K2A 5 320 OFF DL T3E 11 20 -70 DL STA4
P2 3 Yes Yes R1 COETXAX001 5G LPI T1F 100 80 -76 DL STA56 STA63 T2A 5 160 -79 DL T3E 6 20 -70 DL STA4
P2 4 Yes Yes R1 COETXAX002 5G LPI T1F 100 80 -76 DL STA56 STA63 T2A 133 160 -79 DL T3E 6 20 -70 DL STA4
P2 6 Yes Yes R1 COETXAX003 5G LPI T1F 161 80 -76 DL STA56 STA63 T2A 5 160 -79 DL T3E 6 20 -70 DL STA4
P2 7 Yes Yes R1 COETXAX004 5G LPI T1F 161 80 -76 DL STA56 STA63 T2A 133 160 -79 DL T3E 6 20 -70 DL STA4
P2 9 Yes Yes R1 COETXAX005 5G SP T1F 100 160 -76 DL STA56 STA63 T2A 5 320 -79 DL T3E 6 20 -70 DL STA4
P2 10 Yes Yes R1 COETXAX006 5G LPI T1F 100 160 -76 DL STA56 STA63 T2A 133 320 -79 DL T3E 6 20 -70 DL STA4
P2 12 Yes Yes R2 COETXAX007 6G LPI T1F 161 80 -76 DL STA63 STA56 T2A 5 160 -79 DL T3E 6 20 -70 DL STA4
P2 14 Yes Yes R2 COETXAX008 6G LPI T1F 161 80 -76 DL STA63 STA56 T2A 133 160 -79 DL T3E 6 20 -70 DL STA4
P2 16 Yes Yes R2 COETXAX009 6G LPI T1F 161 80 -76 DL STA63 STA56 T2A 197 160 -79 DL T3E 6 20 -70 DL STA4
P2 18 Yes Yes R2 COETXAX010 6G SP T1F 161 80 -76 DL STA63 STA56 T2A 5 320 -79 DL T3E 6 20 -70 DL STA4
P2 20 Yes Yes R2 COETXAX011 6G LPI T1F 161 80 -76 DL STA63 STA56 T2A 133 320 -79 DL T3E 6 20 -70 DL STA4
P2 22 Yes Yes R3 COETXAX012 2G LPI T1F 36 80 -76 DL STA56 STA63 T2A 5 160 -79 DL T3E 1 20 -70 DL STA56
P2 24 Yes Yes R3 COETXAX013 2G LPI T1F 36 80 -76 DL STA56 STA63 T2A 133 160 -79 DL T3E 6 20 -70 DL STA56
P2 26 Yes Yes R3 COETXAX014 2G LPI T1F 36 80 -76 DL STA56 STA63 T2A 197 160 -79 DL T3E 11 20 -70 DL STA56
P2 3 Yes Yes R3 COETXBE001 5G LPI T1F 100 80 -76 DL STA63 STA56 T2A 5 160 -79 DL T3E 6 20 -70 DL STA4
P2 4 Yes Yes R3 COETXBE002 5G LPI T1F 100 80 -76 DL STA63 STA56 T2A 133 160 -79 DL T3E 6 20 -70 DL STA4
P2 6 Yes Yes R3 COETXBE003 5G LPI T1F 161 80 -76 DL STA63 STA56 T2A 5 160 -79 DL T3E 6 20 -70 DL STA4
P2 7 Yes Yes R3 COETXBE004 5G LPI T1F 161 80 -76 DL STA63 STA56 T2A 133 160 -79 DL T3E 6 20 -70 DL STA4
P2 9 Yes Yes R3 COETXBE005 5G SP T1F 100 160 -76 DL STA63 STA64 T2A 5 320 -79 DL T3E 6 20 -70 DL STA4
P2 10 Yes Yes R3 COETXBE006 5G LPI T1F 100 160 -76 DL STA63 STA64 T2A 133 320 -79 DL T3E 6 20 -70 DL STA4
P2 12 Yes Yes R1 COETXBE007 6G LPI T1F 161 80 -76 DL STA56 STA63 T2A 5 160 -79 DL T3E 6 20 -70 DL STA4
P2 14 Yes Yes R1 COETXBE008 6G LPI T1F 161 80 -76 DL STA56 STA63 T2A 133 160 -79 DL T3E 6 20 -70 DL STA4
P2 16 Yes Yes R1 COETXBE009 6G LPI T1F 161 80 -76 DL STA56 STA63 T2A 197 160 -79 DL T3E 6 20 -70 DL STA4
P2 18 Yes Yes R1 COETXBE010 6G SP T1F 161 80 -76 DL STA56 STA63 T2A 5 320 -79 DL T3E 6 20 -70 DL STA4
P2 20 Yes Yes R1 COETXBE011 6G LPI T1F 161 80 -76 DL STA56 STA63 T2A 133 320 -79 DL T3E 6 20 -70 DL STA4
P2 22 Yes Yes R2 COETXBE012 2G LPI T1F 36 80 -76 DL STA4 STA56 T2A 5 160 -79 DL T3E 1 20 -70 DL STA63
P2 24 Yes Yes R2 COETXBE013 2G LPI T1F 36 80 -76 DL STA4 STA56 T2A 133 160 -79 DL T3E 6 20 -70 DL STA63
P2 26 Yes Yes R2 COETXBE014 2G LPI T1F 36 80 -76 DL STA4 STA56 T2A 197 160 -79 DL T3E 11 20 -70 DL STA63
P1 1 No No R2 P2PRXAC001 5G LPI T1F 36 80 -76 UL STA4
P1 1 No No R2 P2PRXAC002 5G LPI T2B 36 80 -67 UL STA4
P1 1 No No R2 P2PRXAC003 5G LPI T1C 36 80 -45 UL STA4
P1 2 No Yes R2 P2PRXAC004 5G LPI T1F 100 80 -76 UL STA4
P1 2 No No R2 P2PRXAC005 5G LPI T2B 100 80 -67 UL STA4
P1 2 No No R2 P2PRXAC006 5G LPI T1C 100 80 -45 UL STA4
P1 5 No Yes R2 P2PRXAC007 5G LPI T1F 161 80 -76 UL STA4
P1 5 No No R2 P2PRXAC008 5G LPI T2B 161 80 -67 UL STA4
P1 5 No No R2 P2PRXAC009 5G LPI T1C 161 80 -45 UL STA4
P1 14 No Yes R1 P2PRXAC010 2G LPI T3E 1 20 -70 UL STA4
P1 14 No No R1 P2PRXAC011 2G LPI T1D 1 20 -60 UL STA4
P1 14 No No R1 P2PRXAC012 2G LPI T1C 1 20 -45 UL STA4
P1 16 No Yes R1 P2PRXAC013 2G LPI T3E 6 20 -70 UL STA4
P1 16 No No R1 P2PRXAC014 2G LPI T1D 6 20 -60 UL STA4
P1 16 No No R1 P2PRXAC015 2G LPI T1C 6 20 -45 UL STA4
P1 18 No Yes R1 P2PRXAC016 2G LPI T3E 11 20 -70 UL STA4
P1 18 No No R1 P2PRXAC017 2G LPI T1D 11 20 -60 UL STA4
P1 18 No No R1 P2PRXAC018 2G LPI T1C 11 20 -45 UL STA4
P1 1 No No R1 P2PRXAX001 5G LPI T1F 36 80 -76 UL STA56
P1 1 No No R1 P2PRXAX002 5G LPI T2B 36 80 -67 UL STA56
P1 1 No No R1 P2PRXAX003 5G LPI T1C 36 80 -45 UL STA56
P1 2 No Yes R1 P2PRXAX004 5G LPI T1F 100 80 -76 UL STA56
P1 2 No No R1 P2PRXAX005 5G LPI T2B 100 80 -67 UL STA56
P1 2 No No R1 P2PRXAX006 5G LPI T1C 100 80 -45 UL STA56
P1 5 No Yes R1 P2PRXAX007 5G LPI T1F 161 80 -76 UL STA56
P1 5 No No R1 P2PRXAX008 5G LPI T2B 161 80 -67 UL STA56
P1 5 No No R1 P2PRXAX009 5G LPI T1C 161 80 -45 UL STA56
P1 10 No Yes R1 P2PRXAX010 5G LPI T1F 100 160 -76 UL STA56
P1 10 No No R1 P2PRXAX011 5G LPI T2B 100 160 -67 UL STA56
P1 10 No No R1 P2PRXAX012 5G LPI T1C 100 160 -45 UL STA56
P1 13 No Yes R2 P2PRXAX013 6G LPI STA56 T2A 5 160 -79 UL
P1 13 No No R2 P2PRXAX014 6G LPI STA56 T2B 5 160 -70 UL
P1 13 No No R2 P2PRXAX015 6G LPI STA56 T1C 5 160 -45 UL
P1 15 No Yes R2 P2PRXAX016 6G LPI STA56 T2A 133 160 -79 UL
P1 15 No No R2 P2PRXAX017 6G LPI STA56 T2B 133 160 -70 UL
P1 15 No No R2 P2PRXAX018 6G LPI STA63 T1C 133 160 -45 UL
P1 17 No Yes R2 P2PRXAX019 6G LPI STA56 T2A 197 160 -79 UL
P1 17 No No R2 P2PRXAX020 6G LPI STA56 T2B 197 160 -70 UL
P1 17 No No R2 P2PRXAX021 6G LPI STA56 T1C 197 160 -45 UL
P1 19 No Yes R2 P2PRXAX022 6G SP STA56 T2A 5 320 -79 UL
P1 19 No No R2 P2PRXAX023 6G SP STA56 T2B 5 320 -70 UL
P1 19 No No R2 P2PRXAX024 6G SP STA56 T1C 5 320 -45 UL
P1 21 No Yes R2 P2PRXAX025 6G LPI STA56 T2A 133 320 -79 UL
P1 21 No No R2 P2PRXAX026 6G LPI STA56 T2B 133 320 -70 UL
P1 21 No No R2 P2PRXAX027 6G LPI STA56 T1C 133 320 -45 UL
P1 23 No Yes R3 P2PRXAX028 2G LPI T3E 1 20 -70 UL STA56
P1 23 No No R3 P2PRXAX029 2G LPI T1D 1 20 -60 UL STA56
P1 23 No No R3 P2PRXAX030 2G LPI T1C 1 20 -45 UL STA56
P1 25 No Yes R3 P2PRXAX031 2G LPI T3E 6 20 -70 UL STA56
P1 25 No No R3 P2PRXAX032 2G LPI T1D 6 20 -60 UL STA56
P1 25 No No R3 P2PRXAX033 2G LPI T1C 6 20 -45 UL STA56
P1 27 No Yes R3 P2PRXAX034 2G LPI T3E 11 20 -70 UL STA56
P1 27 No No R3 P2PRXAX035 2G LPI T1D 11 20 -60 UL STA56
P1 27 No No R3 P2PRXAX036 2G LPI T1C 11 20 -45 UL STA56
P1 1 No No R3 P2PRXBE001 5G LPI T1F 36 80 -76 UL STA63
P1 1 No No R3 P2PRXBE002 5G LPI T2B 36 80 -67 UL STA63
P1 1 No No R3 P2PRXBE003 5G LPI T1C 36 80 -45 UL STA63
P1 2 No Yes R3 P2PRXBE004 5G LPI T1F 100 80 -76 UL STA63
P1 2 No No R3 P2PRXBE005 5G LPI T2B 100 80 -67 UL STA63
P1 2 No No R3 P2PRXBE006 5G LPI T1C 100 80 -45 UL STA63
P1 5 No Yes R3 P2PRXBE007 5G LPI T1F 161 80 -76 UL STA63
P1 5 No No R3 P2PRXBE008 5G LPI T2B 161 80 -67 UL STA63
P1 5 No No R3 P2PRXBE009 5G LPI T1C 161 80 -45 UL STA63
P1 10 No Yes R3 P2PRXBE010 5G LPI T1F 100 160 -76 UL STA63
P1 10 No No R3 P2PRXBE011 5G LPI T2B 100 160 -67 UL STA63
P1 10 No No R3 P2PRXBE012 5G LPI T1C 100 160 -45 UL STA63
22 P1 13 No Yes R1 P2PRXBE013 6G LPI STA63 T2A T2A 5 5 160 160 -79 -79 UL UL STA63
23 P1 13 No No R1 P2PRXBE014 6G LPI STA63 T2B T1B 5 5 160 160 -70 -70 UL UL STA63
24 P1 13 No No R1 P2PRXBE015 6G LPI STA63 T1C T1C 5 5 160 160 -45 -45 UL UL STA63
25 P2 14 Yes Yes R1 COERXBE009 6G LPI T1F 161 80 -76 DL STA56 T2A 5 160 -79 UL STA63 T3E 6 20 -70 DL STA4
26 P1 15 No Yes R1 P2PRXBE016 6G LPI STA63 T2A T2A 133 133 160 160 -79 -79 UL UL STA63
27 P1 15 No No R1 P2PRXBE017 6G LPI STA63 T2B T1B 133 133 160 160 -70 -70 UL UL STA63
28 P1 15 No No R1 P2PRXBE018 6G LPI STA63 T1C T1C 133 133 160 160 -45 -45 UL UL STA63
29 P2 16 Yes Yes R1 COERXBE010 6G LPI T1F 161 80 -76 DL STA56 T2A 133 160 -79 UL STA63 T3E 6 20 -70 DL STA4
30 P1 17 No Yes R1 P2PRXBE019 6G LPI STA63 T2A T2A 197 197 160 160 -79 -79 UL UL STA63
31 P1 17 No No R1 P2PRXBE020 6G LPI STA63 T2B T1B 197 197 160 160 -70 -70 UL UL STA63
32 P1 17 No No R1 P2PRXBE021 6G LPI STA63 T1C T1C 197 197 160 160 -45 -45 UL UL STA63
33 P2 18 Yes Yes R1 COERXBE011 6G LPI T1F 161 80 -76 DL STA56 T2A 197 160 -79 UL STA63 T3E 6 20 -70 DL STA4
34 P1 19 No Yes R1 P2PRXBE022 6G SP STA63 T2A T2A 5 5 320 320 -79 -79 UL UL STA63
35 P1 19 No No R1 P2PRXBE023 6G SP STA63 T2B T1B 5 5 320 320 -70 -70 UL UL STA63
36 P1 19 No No R1 P2PRXBE024 6G SP STA63 T1C T1C 5 5 320 320 -45 -45 UL UL STA63
37 P2 20 Yes Yes R1 COERXBE012 6G SP T1F 161 80 -76 DL STA56 T2A 5 320 -79 UL STA63 T3E 6 20 -70 DL STA4
38 P1 21 No Yes R1 P2PRXBE025 6G LPI STA63 T2A T2A 133 133 320 320 -79 -79 UL UL STA63
39 P1 21 No No R1 P2PRXBE026 6G LPI STA63 T2B T1B 133 133 320 320 -70 -70 UL UL STA63
40 P1 21 No No R1 P2PRXBE027 6G LPI STA63 T1C T1C 133 133 320 320 -45 -45 UL UL STA63
41 P2 22 Yes Yes R1 COERXBE013 6G LPI T1F 161 80 -76 DL STA56 T2A 133 320 -79 UL STA63 T3E 6 20 -70 DL STA4
42 P1 23 No Yes R2 P2PRXBE028 2G LPI T3E T3E 1 1 20 20 -70 -70 UL UL STA63 STA63
43 P1 23 No No R2 P2PRXBE029 2G LPI T1D T1D 1 1 20 20 -60 -60 UL UL STA63 STA63
44 P1 23 No No R2 P2PRXBE030 2G LPI T1C T1C 1 1 20 20 -45 -45 UL UL STA63 STA63
45 P2 24 Yes Yes R2 COERXBE014 2G LPI T1F 36 80 -76 DL STA4 T2A 5 160 -79 DL STA56 T3E 1 20 -70 UL STA63
46 P1 25 No Yes R2 P2PRXBE031 2G LPI T3E T3E 6 6 20 20 -70 -70 UL UL STA63 STA63
47 P1 25 No No R2 P2PRXBE032 2G LPI T1D T1D 6 6 20 20 -60 -60 UL UL STA63 STA63
48 P1 25 No No R2 P2PRXBE033 2G LPI T1C T1C 6 6 20 20 -45 -45 UL UL STA63 STA63
49 P2 26 Yes Yes R2 COERXBE015 2G LPI T1F 36 80 -76 DL STA4 T2A 133 160 -79 DL STA56 T3E 6 20 -70 UL STA63
50 P1 27 No Yes R2 P2PRXBE034 2G LPI T3E T3E 11 11 20 20 -70 -70 UL UL STA63 STA63
51 P1 27 No No R2 P2PRXBE035 2G LPI T1D T1D 11 11 20 20 -60 -60 UL UL STA63 STA63
52 P1 27 No No R2 P2PRXBE036 2G LPI T1C T1C 11 11 20 20 -45 -45 UL UL STA63 STA63
53 P1 P2 1 28 No Yes No Yes R2 P2PTXAC001 COERXBE016 5G 2G LPI T1F T1F 36 36 80 -76 -76 DL DL STA4 STA4 T2A 197 160 -79 DL STA56 T3E 11 20 -70 UL STA63
54 P1 1 No No R2 R1 P2PTXAC002 P2PRXAX001 5G LPI T2B T1F 36 36 80 -67 -76 DL UL STA4 STA56
55 P1 1 No No R2 R1 P2PTXAC003 P2PRXAX002 5G LPI T1C T1B 36 36 80 -45 -67 DL UL STA4 STA56
56 P1 2 1 No Yes No R2 R1 P2PTXAC004 P2PRXAX003 5G LPI T1F T1C 100 36 80 -76 -45 DL UL STA4 STA56
57 P1 2 No No Yes R2 R1 P2PTXAC005 P2PRXAX004 5G LPI T2B T1F 100 100 80 -67 -76 DL UL STA4 STA56
58 P1 2 No No R2 R1 P2PTXAC006 P2PRXAX005 5G LPI T1C T1B 100 100 80 -45 -67 DL UL STA4 STA56
59 P1 5 2 No Yes No R2 R1 P2PTXAC007 P2PRXAX006 5G LPI T1F T1C 161 100 80 -76 -45 DL UL STA4 STA56
60 P1 P2 5 3 No Yes No Yes R2 R1 P2PTXAC008 COERXAX001 5G LPI T2B T1F 161 100 80 -67 -76 DL UL STA4 STA56 T2A 5 160 -79 DL STA63 T3E 6 20 -70 DL STA4
61 P1 P2 5 4 No Yes No Yes R2 R1 P2PTXAC009 COERXAX002 5G LPI T1C T1F 161 100 80 -45 -76 DL UL STA4 STA56 T2A 133 160 -79 DL STA63 T3E 6 20 -70 DL STA4
62 P1 14 5 No Yes R1 P2PTXAC010 P2PRXAX007 2G 5G LPI T1F 161 80 -76 UL STA56 T3E 1 20 -70 DL STA4
63 P1 14 5 No No R1 P2PTXAC011 P2PRXAX008 2G 5G LPI T1B 161 80 -67 UL STA56 T1D 1 20 -60 DL STA4
64 P1 14 5 No No R1 P2PTXAC012 P2PRXAX009 2G 5G LPI T1C 161 80 -45 UL STA56 T1C 1 20 -45 DL STA4
65 P1 P2 16 6 No Yes Yes R1 P2PTXAC013 COERXAX003 2G 5G LPI T1F 161 80 -76 UL STA56 T2A 5 160 -79 DL STA63 T3E T3E 6 6 20 20 -70 -70 DL DL STA4 STA4
66 P1 P2 16 7 No Yes No Yes R1 P2PTXAC014 COERXAX004 2G 5G LPI T1F 161 80 -76 UL STA56 T2A 133 160 -79 DL STA63 T1D T3E 6 6 20 20 -60 -70 DL DL STA4 STA4
67 P1 P2 16 8 No Yes No Yes R1 P2PTXAC015 COERXAX005 2G 5G LPI SP T1F 161 80 -76 UL STA56 T2A 5 320 -79 DL STA63 T1C T3E 6 6 20 20 -45 -70 DL DL STA4 STA4
68 P1 P2 18 9 No Yes Yes R1 P2PTXAC016 COERXAX006 2G 5G LPI T1F 161 80 -76 UL STA56 T2A 133 320 -79 DL STA63 T3E T3E 11 6 20 20 -70 -70 DL DL STA4 STA4
69 P1 18 10 No No Yes R1 P2PTXAC017 P2PRXAX010 2G 5G LPI T1F 100 160 -76 UL STA56 T1D 11 20 -60 DL STA4
70 P1 18 10 No No R1 P2PTXAC018 P2PRXAX011 2G 5G LPI T1B 100 160 -67 UL STA56 T1C 11 20 -45 DL STA4
71 P1 1 10 No No R1 P2PTXAX001 P2PRXAX012 5G LPI T1F T1C 36 100 80 160 -76 -45 DL UL STA56 STA56
72 P1 P2 1 11 No Yes No Yes R1 P2PTXAX002 COERXAX007 5G LPI SP T2B T1F 36 100 80 160 -67 -76 DL UL STA56 STA56 T2A 5 320 -79 DL STA63 T3E 6 20 -70 DL STA4
73 P1 P2 1 12 No Yes No Yes R1 P2PTXAX003 COERXAX008 5G LPI T1C T1F 36 100 80 160 -45 -76 DL UL STA56 STA56 T2A 133 320 -79 DL STA63 T3E 6 20 -70 DL STA4
74 P1 2 13 No Yes R1 R2 P2PTXAX004 P2PRXAX013 5G 6G LPI T1F 100 80 -76 DL STA56 T2A 5 160 -79 UL STA56
75 P1 2 13 No No R1 R2 P2PTXAX005 P2PRXAX014 5G 6G LPI T2B 100 80 -67 DL STA56 T1B 5 160 -70 UL STA56
76 P1 2 13 No No R1 R2 P2PTXAX006 P2PRXAX015 5G 6G LPI T1C 100 80 -45 DL STA56 T1C 5 160 -45 UL STA56
77 P1 P2 5 14 No Yes Yes R1 R2 P2PTXAX007 COERXAX009 5G 6G LPI T1F T1F 161 161 80 -76 -76 DL DL STA56 STA63 T2A 5 160 -79 UL STA56 T3E 6 20 -70 DL STA4
78 P1 5 15 No No Yes R1 R2 P2PTXAX008 P2PRXAX016 5G 6G LPI T2B 161 80 -67 DL STA56 T2A 133 160 -79 UL STA56
79 P1 5 15 No No R1 R2 P2PTXAX009 P2PRXAX017 5G 6G LPI T1C 161 80 -45 DL STA56 T1B 133 160 -70 UL STA56
80 P1 8 15 No Yes No R1 R2 P2PTXAX010 P2PRXAX018 5G 6G LPI T1F 100 160 -76 DL STA56 T1C 133 160 -45 UL STA63
81 P1 P2 8 16 No Yes No Yes R1 R2 P2PTXAX011 COERXAX010 5G 6G LPI T2B T1F 100 161 160 80 -67 -76 DL DL STA56 STA63 T2A 133 160 -79 UL STA56 T3E 6 20 -70 DL STA4
82 P1 8 17 No No Yes R1 R2 P2PTXAX012 P2PRXAX019 5G 6G LPI T1C 100 160 -45 DL STA56 T2A 197 160 -79 UL STA56
83 P1 11 17 No Yes No R2 P2PTXAX013 P2PRXAX020 6G LPI STA56 T2A T1B 5 197 160 160 -79 -70 DL UL STA56
84 P1 11 17 No No R2 P2PTXAX014 P2PRXAX021 6G LPI STA56 T2B T1C 5 197 160 160 -70 -45 DL UL STA56
85 P1 P2 11 18 No Yes No Yes R2 P2PTXAX015 COERXAX011 6G LPI T1F 161 80 -76 DL STA56 STA63 T1C T2A 5 197 160 160 -45 -79 DL UL STA56 T3E 6 20 -70 DL STA4
86 P1 13 19 No Yes R2 P2PTXAX016 P2PRXAX022 6G LPI SP STA56 T2A T2A 133 5 160 320 -79 -79 DL UL STA56
87 P1 13 19 No No R2 P2PTXAX017 P2PRXAX023 6G LPI SP STA56 T2B T1B 133 5 160 320 -70 -70 DL UL STA56
88 P1 13 19 No No R2 P2PTXAX018 P2PRXAX024 6G LPI SP STA56 T1C T1C 133 5 160 320 -45 -45 DL UL STA56
89 P1 P2 15 20 No Yes Yes R2 P2PTXAX019 COERXAX012 6G LPI SP T1F 161 80 -76 DL STA56 STA63 T2A T2A 197 5 160 320 -79 -79 DL UL STA56 T3E 6 20 -70 DL STA4
90 P1 15 21 No No Yes R2 P2PTXAX020 P2PRXAX025 6G LPI STA56 T2B T2A 197 133 160 320 -70 -79 DL UL STA56
91 P1 15 21 No No R2 P2PTXAX021 P2PRXAX026 6G LPI STA56 T1C T1B 197 133 160 320 -45 -70 DL UL STA56
92 P1 17 21 No Yes No R2 P2PTXAX022 P2PRXAX027 6G SP LPI STA56 T2A T1C 5 133 320 320 -79 -45 DL UL STA56
93 P1 P2 17 22 No Yes No Yes R2 P2PTXAX023 COERXAX013 6G SP LPI T1F 161 80 -76 DL STA56 STA63 T2B T2A 5 133 320 320 -70 -79 DL UL STA56 T3E 6 20 -70 DL STA4
94 P1 17 23 No No Yes R2 R3 P2PTXAX024 P2PRXAX028 6G 2G SP LPI STA56 T1C 5 320 -45 DL T3E 1 20 -70 UL STA56
95 P1 19 23 No Yes No R2 R3 P2PTXAX025 P2PRXAX029 6G 2G LPI STA56 T2A 133 320 -79 DL T1D 1 20 -60 UL STA56
96 P1 19 23 No No R2 R3 P2PTXAX026 P2PRXAX030 6G 2G LPI STA56 T2B 133 320 -70 DL T1C 1 20 -45 UL STA56
97 P1 P2 19 24 No Yes No Yes R2 R3 P2PTXAX027 COERXAX014 6G 2G LPI T1F 36 80 -76 DL STA56 STA4 T1C T2A 133 5 320 160 -45 -79 DL DL STA63 T3E 1 20 -70 UL STA56
98 P1 21 25 No Yes R3 P2PTXAX028 P2PRXAX031 2G LPI T3E T3E 1 6 20 20 -70 -70 UL UL STA56 STA56
99 P1 21 25 No No R3 P2PTXAX029 P2PRXAX032 2G LPI T1D T1D 1 6 20 20 -60 -60 UL UL STA56 STA56
100 P1 21 25 No No R3 P2PTXAX030 P2PRXAX033 2G LPI T1C T1C 1 6 20 20 -45 -45 UL UL STA56 STA56
101 P1 P2 23 26 No Yes Yes R3 P2PTXAX031 COERXAX015 2G LPI T1F 36 80 -76 DL STA4 T2A 133 160 -79 DL STA63 T3E T3E 6 6 20 20 -70 -70 UL UL STA56 STA56
102 P1 23 27 No No Yes R3 P2PTXAX032 P2PRXAX034 2G LPI T1D T3E 6 11 20 20 -60 -70 UL UL STA56 STA56
103 P1 23 27 No No R3 P2PTXAX033 P2PRXAX035 2G LPI T1C T1D 6 11 20 20 -45 -60 UL UL STA56 STA56
104 P1 25 27 No Yes No R3 P2PTXAX034 P2PRXAX036 2G LPI T3E T1C 11 11 20 20 -70 -45 UL UL STA56 STA56
105 P1 P2 25 28 No Yes No Yes R3 P2PTXAX035 COERXAX016 2G LPI T1F 36 80 -76 DL STA4 T2A 197 160 -79 DL STA63 T1D T3E 11 11 20 20 -60 -70 UL UL STA56 STA56
106 P1 25 1 No No R3 R2 P2PTXAX036 P2PRXAC001 2G 5G LPI T1F 36 80 -76 UL STA4 T1C 11 20 -45 UL STA56
107 P1 1 No No R2 P2PRXAC002 5G LPI T1B 36 80 -67 UL STA4
108 P1 1 No No R2 P2PRXAC003 5G LPI T1C 36 80 -45 UL STA4
109 P1 2 No Yes R2 P2PRXAC004 5G LPI T1F 100 80 -76 UL STA4
110 P1 2 No No R2 P2PRXAC005 5G LPI T1B 100 80 -67 UL STA4
111 P1 2 No No R2 P2PRXAC006 5G LPI T1C 100 80 -45 UL STA4
112 P2 3 Yes Yes R2 COERXAC001 5G LPI T1F 100 80 -76 UL STA4 T2A 5 160 OFF DL STA63 T3E 6 20 -70 DL STA56
113 P1 5 No Yes R2 P2PRXAC007 5G LPI T1F 161 80 -76 UL STA4
114 P1 5 No No R2 P2PRXAC008 5G LPI T1B 161 80 -67 UL STA4
115 P1 5 No No R2 P2PRXAC009 5G LPI T1C 161 80 -45 UL STA4
116 P2 6 Yes Yes R2 COERXAC002 5G LPI T1F 161 80 -76 UL STA4 T2A 133 160 OFF DL STA63 T3E 6 20 -70 DL STA56
117 P1 14 No Yes R1 P2PRXAC010 2G LPI T3E 1 20 -70 UL STA4
118 P1 14 No No R1 P2PRXAC011 2G LPI T1D 1 20 -60 UL STA4
119 P1 14 No No R1 P2PRXAC012 2G LPI T1C 1 20 -45 UL STA4
120 P2 15 Yes Yes R1 COERXAC003 2G LPI T1F 36 80 -76 DL STA56 T2A 5 160 OFF DL STA63 T3E 1 20 -70 UL STA4
121 P1 16 No Yes R1 P2PRXAC013 2G LPI T3E 6 20 -70 UL STA4
122 P1 16 No No R1 P2PRXAC014 2G LPI T1D 6 20 -60 UL STA4
123 P1 16 No No R1 P2PRXAC015 2G LPI T1C 6 20 -45 UL STA4
124 P2 17 Yes Yes R1 COERXAC004 2G LPI T1F 36 80 -76 DL STA56 T2A 133 160 OFF DL STA63 T3E 6 20 -70 UL STA4
125 P1 18 No Yes R1 P2PRXAC016 2G LPI T3E 11 20 -70 UL STA4
126 P1 18 No No R1 P2PRXAC017 2G LPI T1D 11 20 -60 UL STA4
127 P1 18 No No R1 P2PRXAC018 2G LPI T1C 11 20 -45 UL STA4
128 P2 19 Yes Yes R1 COERXAC005 2G LPI T1F 36 80 -76 DL STA56 T2A 197 160 OFF DL STA63 T3E 11 20 -70 UL STA4
129 P1 1 No No R3 P2PTXBE001 5G LPI T1F T1F 36 36 80 -76 -76 DL DL STA63 STA63
130 P1 1 No No R3 P2PTXBE002 5G LPI T2B T1B 36 36 80 -67 -67 DL DL STA63 STA63
131 P1 1 No No R3 P2PTXBE003 5G LPI T1C T1C 36 36 80 -45 -45 DL DL STA63 STA63
132 P1 2 No Yes R3 P2PTXBE004 5G LPI T1F T1F 100 100 80 -76 -76 DL DL STA63 STA63
133 P1 2 No No R3 P2PTXBE005 5G LPI T2B T1B 100 100 80 -67 -67 DL DL STA63 STA63
134 P1 2 No No R3 P2PTXBE006 5G LPI T1C T1C 100 100 80 -45 -45 DL DL STA63 STA63
135 P2 3 Yes Yes R3 COETXBE001 5G LPI T1F 100 80 -76 DL STA63 T2A 5 160 -79 DL STA56 T3E 6 20 -70 DL STA4
136 P2 4 Yes Yes R3 COETXBE002 5G LPI T1F 100 80 -76 DL STA63 T2A 133 160 -79 DL STA56 T3E 6 20 -70 DL STA4
137 P1 5 No Yes R3 P2PTXBE007 5G LPI T1F T1F 161 161 80 -76 -76 DL DL STA63 STA63
138 P1 5 No No R3 P2PTXBE008 5G LPI T2B T1B 161 161 80 -67 -67 DL DL STA63 STA63
139 P1 5 No No R3 P2PTXBE009 5G LPI T1C T1C 161 161 80 -45 -45 DL DL STA63 STA63
140 P2 6 Yes Yes R3 COETXBE003 5G LPI T1F 161 80 -76 DL STA63 T2A 5 160 -79 DL STA56 T3E 6 20 -70 DL STA4
141 P2 7 Yes Yes R3 COETXBE004 5G LPI T1F 161 80 -76 DL STA63 T2A 133 160 -79 DL STA56 T3E 6 20 -70 DL STA4
142 P1 8 No Yes R3 P2PTXBE010 5G LPI T1F T1F 100 100 160 -76 -76 DL DL STA63 STA63
143 P1 8 No No R3 P2PTXBE011 5G LPI T2B T1B 100 100 160 -67 -67 DL DL STA63 STA63
144 P1 8 No No R3 P2PTXBE012 5G LPI T1C T1C 100 100 160 -45 -45 DL DL STA63 STA63
145 P1 P2 11 9 No Yes Yes R3 P2PTXBE013 COETXBE005 6G 5G LPI SP T1F T1F 5 100 160 -79 -76 DL DL STA63 STA63 T2A 5 320 -79 DL STA64 T3E 6 20 -70 DL STA4
146 P1 P2 11 10 No Yes No Yes R1 R3 P2PTXBE014 COETXBE006 6G 5G LPI T2B T1F 5 100 160 -70 -76 DL DL STA63 STA63 T2A 133 320 -79 DL STA64 T3E 6 20 -70 DL STA4
147 P1 11 No Yes R1 P2PTXBE013 6G LPI T1F 5 160 -79 DL STA63
148 P1 11 No No R1 P2PTXBE014 6G LPI T1B 5 160 -70 DL STA63
149 P1 11 No No R1 P2PTXBE015 6G LPI T1C T1C 5 5 160 -45 -45 DL DL STA63 STA63
150 P2 12 Yes Yes R1 COETXBE007 6G LPI T1F 161 80 -76 DL STA56 T2A 5 160 -79 DL STA63 T3E 6 20 -70 DL STA4
151 P1 13 No Yes R1 P2PTXBE016 6G LPI T1F T1F 133 133 160 -79 -79 DL DL STA63 STA63
152 P1 13 No No R1 P2PTXBE017 6G LPI T2B T1B 133 133 160 -70 -70 DL DL STA63 STA63
153 P1 13 No No R1 P2PTXBE018 6G LPI T1C T1C 133 133 160 -45 -45 DL DL STA63 STA63
154 P2 14 Yes Yes R1 COETXBE008 6G LPI T1F 161 80 -76 DL STA56 T2A 133 160 -79 DL STA63 T3E 6 20 -70 DL STA4
155 P1 15 No Yes R1 P2PTXBE019 6G LPI T1F T1F 197 197 160 -79 -79 DL DL STA63 STA63
156 P1 15 No No R1 P2PTXBE020 6G LPI T2B T1B 197 197 160 -70 -70 DL DL STA63 STA63
157 P1 15 No No R1 P2PTXBE021 6G LPI T1C T1C 197 197 160 -45 -45 DL DL STA63 STA63
158 P2 16 Yes Yes R1 COETXBE009 6G LPI T1F 161 80 -76 DL STA56 T2A 197 160 -79 DL STA63 T3E 6 20 -70 DL STA4
159 P1 17 No Yes R1 P2PTXBE022 6G SP T1F T1F 5 5 320 -79 -79 DL DL STA63 STA63
160 P1 17 No No R1 P2PTXBE023 6G SP T2B T1B 5 5 320 -70 -70 DL DL STA63 STA63
161 P1 17 No No R1 P2PTXBE024 6G SP T1C T1C 5 5 320 -45 -45 DL DL STA63 STA63
162 P2 18 Yes Yes R1 COETXBE010 6G SP T1F 161 80 -76 DL STA56 T2A 5 320 -79 DL STA63 T3E 6 20 -70 DL STA4
163 P1 19 No Yes R1 P2PTXBE025 6G LPI T1F T1F 133 133 320 -79 -79 DL DL STA63 STA63
164 P1 19 No No R1 P2PTXBE026 6G LPI T2B T1B 133 133 320 -70 -70 DL DL STA63 STA63
165 P1 19 No No R1 P2PTXBE027 6G LPI T1C T1C 133 133 320 -45 -45 DL DL STA63 STA63
166 P2 20 Yes Yes R1 COETXBE011 6G LPI T1F 161 80 -76 DL STA56 T2A 133 320 -79 DL STA63 T3E 6 20 -70 DL STA4
167 P1 21 No Yes R2 P2PTXBE028 2G LPI T3E T3E 1 1 20 -70 -70 UL UL STA63 STA63
168 P1 21 No No R2 P2PTXBE029 2G LPI T1D T1D 1 1 20 -60 -60 UL UL STA63 STA63
169 P1 21 No No R2 P2PTXBE030 2G LPI T1C T1C 1 1 20 -45 -45 UL UL STA63 STA63
170 P2 22 Yes Yes R2 COETXBE012 2G LPI T1F 36 80 -76 DL STA4 T2A 5 160 -79 DL STA56 T3E 1 20 -70 DL STA63
171 P1 23 No Yes R2 P2PTXBE031 2G LPI T3E T3E 6 6 20 -70 -70 UL UL STA63 STA63
172 P1 23 No No R2 P2PTXBE032 2G LPI T1D T1D 6 6 20 -60 -60 UL UL STA63 STA63
173 P1 23 No No R2 P2PTXBE033 2G LPI T1C T1C 6 6 20 -45 -45 UL UL STA63 STA63
174 P2 24 Yes Yes R2 COETXBE013 2G LPI T1F 36 80 -76 DL STA4 T2A 133 160 -79 DL STA56 T3E 6 20 -70 DL STA63
175 P1 25 No Yes R2 P2PTXBE034 2G LPI T3E T3E 11 11 20 -70 -70 UL UL STA63 STA63
176 P1 25 No No R2 P2PTXBE035 2G LPI T1D T1D 11 11 20 -60 -60 UL UL STA63 STA63
177 P1 25 No No R2 P2PTXBE036 2G LPI T1C T1C 11 11 20 -45 -45 UL UL STA63 STA63
178 P2 26 Yes Yes R2 COETXBE014 2G LPI T1F 36 80 -76 DL STA4 T2A 197 160 -79 DL STA56 T3E 11 20 -70 DL STA63
179 P1 1 No No R1 P2PTXAX001 5G LPI T1F 36 80 -76 DL STA56
180 P1 1 No No R1 P2PTXAX002 5G LPI T1B 36 80 -67 DL STA56
181 P1 1 No No R1 P2PTXAX003 5G LPI T1C 36 80 -45 DL STA56
182 P1 2 No Yes R1 P2PTXAX004 5G LPI T1F 100 80 -76 DL STA56
183 P1 2 No No R1 P2PTXAX005 5G LPI T1B 100 80 -67 DL STA56
184 P1 2 No No R1 P2PTXAX006 5G LPI T1C 100 80 -45 DL STA56
185 P2 3 Yes Yes R1 COETXAX001 5G LPI T1F 100 80 -76 DL STA56 T2A 5 160 -79 DL STA63 T3E 6 20 -70 DL STA4
186 P2 4 Yes Yes R1 COETXAX002 5G LPI T1F 100 80 -76 DL STA56 T2A 133 160 -79 DL STA63 T3E 6 20 -70 DL STA4
187 P1 5 No Yes R1 P2PTXAX007 5G LPI T1F 161 80 -76 DL STA56
188 P1 5 No No R1 P2PTXAX008 5G LPI T1B 161 80 -67 DL STA56
189 P1 5 No No R1 P2PTXAX009 5G LPI T1C 161 80 -45 DL STA56
190 P2 6 Yes Yes R1 COETXAX003 5G LPI T1F 161 80 -76 DL STA56 T2A 5 160 -79 DL STA63 T3E 6 20 -70 DL STA4
191 P2 7 Yes Yes R1 COETXAX004 5G LPI T1F 161 80 -76 DL STA56 T2A 133 160 -79 DL STA63 T3E 6 20 -70 DL STA4
192 P1 8 No Yes R1 P2PTXAX010 5G LPI T1F 100 160 -76 DL STA56
193 P1 8 No No R1 P2PTXAX011 5G LPI T1B 100 160 -67 DL STA56
194 P1 8 No No R1 P2PTXAX012 5G LPI T1C 100 160 -45 DL STA56
195 P2 9 Yes Yes R1 COETXAX005 5G SP T1F 100 160 -76 DL STA56 T2A 5 320 -79 DL STA63 T3E 6 20 -70 DL STA4
196 P2 10 Yes Yes R1 COETXAX006 5G LPI T1F 100 160 -76 DL STA56 T2A 133 320 -79 DL STA63 T3E 6 20 -70 DL STA4
197 P1 11 No Yes R2 P2PTXAX013 6G LPI T2A 5 160 -79 DL STA56
198 P1 11 No No R2 P2PTXAX014 6G LPI T1B 5 160 -70 DL STA56
199 P1 11 No No R2 P2PTXAX015 6G LPI T1C 5 160 -45 DL STA56
200 P2 12 Yes Yes R2 COETXAX007 6G LPI T1F 161 80 -76 DL STA63 T2A 5 160 -79 DL STA56 T3E 6 20 -70 DL STA4
201 P1 13 No Yes R2 P2PTXAX016 6G LPI T2A 133 160 -79 DL STA56
202 P1 13 No No R2 P2PTXAX017 6G LPI T1B 133 160 -70 DL STA56
203 P1 13 No No R2 P2PTXAX018 6G LPI T1C 133 160 -45 DL STA56
204 P2 14 Yes Yes R2 COETXAX008 6G LPI T1F 161 80 -76 DL STA63 T2A 133 160 -79 DL STA56 T3E 6 20 -70 DL STA4
205 P1 15 No Yes R2 P2PTXAX019 6G LPI T2A 197 160 -79 DL STA56
206 P1 15 No No R2 P2PTXAX020 6G LPI T1B 197 160 -70 DL STA56
207 P1 15 No No R2 P2PTXAX021 6G LPI T1C 197 160 -45 DL STA56
208 P2 16 Yes Yes R2 COETXAX009 6G LPI T1F 161 80 -76 DL STA63 T2A 197 160 -79 DL STA56 T3E 6 20 -70 DL STA4
209 P1 17 No Yes R2 P2PTXAX022 6G SP T2A 5 320 -79 DL STA56
210 P1 17 No No R2 P2PTXAX023 6G SP T1B 5 320 -70 DL STA56
211 P1 17 No No R2 P2PTXAX024 6G SP T1C 5 320 -45 DL STA56
212 P2 18 Yes Yes R2 COETXAX010 6G SP T1F 161 80 -76 DL STA63 T2A 5 320 -79 DL STA56 T3E 6 20 -70 DL STA4
213 P1 19 No Yes R2 P2PTXAX025 6G LPI T2A 133 320 -79 DL STA56
214 P1 19 No No R2 P2PTXAX026 6G LPI T1B 133 320 -70 DL STA56
215 P1 19 No No R2 P2PTXAX027 6G LPI T1C 133 320 -45 DL STA56
216 P2 20 Yes Yes R2 COETXAX011 6G LPI T1F 161 80 -76 DL STA63 T2A 133 320 -79 DL STA56 T3E 6 20 -70 DL STA4
217 P1 21 No Yes R3 P2PTXAX028 2G LPI T3E 1 20 -70 UL STA56
218 P1 21 No No R3 P2PTXAX029 2G LPI T1D 1 20 -60 UL STA56
219 P1 21 No No R3 P2PTXAX030 2G LPI T1C 1 20 -45 UL STA56
220 P2 22 Yes Yes R3 COETXAX012 2G LPI T1F 36 80 -76 DL STA4 T2A 5 160 -79 DL STA63 T3E 1 20 -70 DL STA56
221 P1 23 No Yes R3 P2PTXAX031 2G LPI T3E 6 20 -70 UL STA56
222 P1 23 No No R3 P2PTXAX032 2G LPI T1D 6 20 -60 UL STA56
223 P1 23 No No R3 P2PTXAX033 2G LPI T1C 6 20 -45 UL STA56
224 P2 24 Yes Yes R3 COETXAX013 2G LPI T1F 36 80 -76 DL STA4 T2A 133 160 -79 DL STA63 T3E 6 20 -70 DL STA56
225 P1 25 No Yes R3 P2PTXAX034 2G LPI T3E 11 20 -70 UL STA56
226 P1 25 No No R3 P2PTXAX035 2G LPI T1D 11 20 -60 UL STA56
227 P1 25 No No R3 P2PTXAX036 2G LPI T1C 11 20 -45 UL STA56
228 P2 26 Yes Yes R3 COETXAX014 2G LPI T1F 36 80 -76 DL STA4 T2A 197 160 -79 DL STA63 T3E 11 20 -70 DL STA56
229 P1 1 No No R2 P2PTXAC001 5G LPI T1F 36 80 -76 DL STA4
230 P1 1 No No R2 P2PTXAC002 5G LPI T1B 36 80 -67 DL STA4
231 P1 1 No No R2 P2PTXAC003 5G LPI T1C 36 80 -45 DL STA4
232 P1 2 No Yes R2 P2PTXAC004 5G LPI T1F 100 80 -76 DL STA4
233 P1 2 No No R2 P2PTXAC005 5G LPI T1B 100 80 -67 DL STA4
234 P1 2 No No R2 P2PTXAC006 5G LPI T1C 100 80 -45 DL STA4
235 P2 3 Yes Yes R2 COETXAC001 5G LPI T1F 100 80 -76 DL STA4 T1K2A 5 160 OFF DL STA63 T3E 6 20 -70 DL STA56
236 P1 5 No Yes R2 P2PTXAC007 5G LPI T1F 161 80 -76 DL STA4
237 P1 5 No No R2 P2PTXAC008 5G LPI T1B 161 80 -67 DL STA4
238 P1 5 No No R2 P2PTXAC009 5G LPI T1C 161 80 -45 DL STA4
239 P2 6 Yes Yes R2 COETXAC002 5G LPI T1F 161 80 -76 DL STA4 T1K2A 133 160 OFF DL STA63 T3E 6 20 -70 DL STA56
240 P1 14 No Yes R1 P2PTXAC010 2G LPI T3E 1 20 -70 DL STA4
241 P1 14 No No R1 P2PTXAC011 2G LPI T1D 1 20 -60 DL STA4
242 P1 14 No No R1 P2PTXAC012 2G LPI T1C 1 20 -45 DL STA4
243 P2 15 Yes Yes R1 COETXAC003 2G LPI T1F 36 80 -76 DL STA56 T1K2A 5 160 OFF DL STA63 T3E 1 20 -70 DL STA4
244 P1 16 No Yes R1 P2PTXAC013 2G LPI T3E 6 20 -70 DL STA4
245 P1 16 No No R1 P2PTXAC014 2G LPI T1D 6 20 -60 DL STA4
246 P1 16 No No R1 P2PTXAC015 2G LPI T1C 6 20 -45 DL STA4
247 P2 17 Yes Yes R1 COETXAC004 2G LPI T1F 36 80 -76 DL STA56 T1K2A 133 160 OFF DL STA63 T3E 6 20 -70 DL STA4
248 P1 18 No Yes R1 P2PTXAC016 2G LPI T3E 11 20 -70 DL STA4
249 P1 18 No No R1 P2PTXAC017 2G LPI T1D 11 20 -60 DL STA4
250 P1 18 No No R1 P2PTXAC018 2G LPI T1C 11 20 -45 DL STA4
251 P2 19 Yes Yes R1 COETXAC005 2G LPI T1F 36 80 -76 DL STA56 T1K2A 5 320 OFF DL STA63 T3E 11 20 -70 DL STA4
@@ -1,81 +1,89 @@
Priority,Index,Throttled,Rotation,TC ID,Band,6GHz Power Mode,Station 1 Test Point,Station 1 Channel,Station 1 Bandwidth,Station 1 RSSI,Station 1 Direction,Station 1 Rate,Station 1 STA,Station 2 Test Point,Station 2 Channel,Station 2 Bandwidth,Station 2 RSSI,Station 2 Direction,Station 2 Rate,Station 2 STA,Station 3 Test Point,Station 3 Channel,Station 3 Bandwidth,Station 3 RSSI,Station 3 Direction,Station 3 Rate,Station 3 STA
Priority,Index,Throttled,Rotation,TC ID,Band,6GHz Power Mode,STATION 1_Test Point,STATION 1_Channel,STATION 1_Bandwidth,STATION 1_RSSI,STATION 1_Direction,STATION 1_Rate,STATION 1_STA,STATION 2_Test Point,STATION 2_Channel,STATION 2_Bandwidth,STATION 2_RSSI,STATION 2_Direction,STATION 2_Rate,STATION 2_STA,STATION 3_Test Point,STATION 3_Channel,STATION 3_Bandwidth,STATION 3_RSSI,STATION 3_Direction,STATION 3_Rate,STATION 3_STA
P2,1,Yes,R3,P3PRXBETH001,5G,LPI,T1F,36,80,-76,UL,22Mbps,STA63,T1I,36,80,-76,UL,22Mbps,STA64,T2J,36,80,-76,UL,11Mbps,STA65
P2,2,Yes,R3,P3PRXBETH002,5G,LPI,T1F,100,80,-76,UL,22Mbps,STA63,T1I,100,80,-76,UL,22Mbps,STA64,T2J,100,80,-76,UL,11Mbps,STA65
P2,3,Yes,R3,P3PRXBETH003,5G,LPI,T1F,161,80,-76,UL,22Mbps,STA63,T1I,161,80,-76,UL,22Mbps,STA64,T2J,161,80,-76,UL,11Mbps,STA65
P2,4,Yes,R1,P3PRXBETH004,6G,LPI,T2A,5,160,-79,UL,22Mbps,STA63,T1L,5,160,-79,UL,22Mbps,STA64,T2O,5,160,-79,UL,11Mbps,STA65
P2,5,Yes,R1,P3PRXBETH005,6G,LPI,T2A,133,160,-79,UL,22Mbps,STA63,T1L,133,160,-79,UL,22Mbps,STA64,T2O,133,160,-79,UL,11Mbps,STA65
P2,6,Yes,R1,P3PRXBETH006,6G,LPI,T2A,197,160,-79,UL,22Mbps,STA63,T1L,197,160,-79,UL,22Mbps,STA64,T2O,197,160,-79,UL,11Mbps,STA65
P2,7,Yes,R2,P3PRXBETH007,2G,LPI,T3E,6,20,-70,UL,22Mbps,STA63,T1P,6,20,-70,UL,22Mbps,STA64,T2Q,6,20,-70,UL,11Mbps,STA65
P2,8,Yes,R1,P3PRXBETH008,6G,SP,T2A,5,320,-79,UL,22Mbps,STA63,T1L,5,320,-79,UL,22Mbps,STA64,T2O,5,320,-79,UL,11Mbps,STA65
P2,9,Yes,R1,P3PRXBETH009,6G,LPI,T2A,133,320,-79,UL,22Mbps,STA63,T1L,133,320,-79,UL,22Mbps,STA64,T2O,133,320,-79,UL,11Mbps,STA65
P2,1,No,R3,P3PRXBEUT001,5G,LPI,T1F,36,80,-76,UL,Unlimited,STA63,T1I,36,80,-76,UL,Unlimited,STA64,T2J,36,80,-76,UL,Unlimited,STA65
P2,2,No,R3,P3PRXBEUT002,5G,LPI,T1F,100,80,-76,UL,Unlimited,STA63,T1I,100,80,-76,UL,Unlimited,STA64,T2J,100,80,-76,UL,Unlimited,STA65
P2,3,No,R3,P3PRXBEUT003,5G,LPI,T1F,161,80,-76,UL,Unlimited,STA63,T1I,161,80,-76,UL,Unlimited,STA64,T2J,161,80,-76,UL,Unlimited,STA65
P2,4,No,R1,P3PRXBEUT004,6G,LPI,T2A,5,160,-79,UL,Unlimited,STA63,T1L,5,160,-79,UL,Unlimited,STA64,T2O,5,160,-79,UL,Unlimited,STA65
P2,5,No,R1,P3PRXBEUT005,6G,LPI,T2A,133,160,-79,UL,Unlimited,STA63,T1L,133,160,-79,UL,Unlimited,STA64,T2O,133,160,-79,UL,Unlimited,STA65
P2,6,No,R1,P3PRXBEUT006,6G,LPI,T2A,197,160,-79,UL,Unlimited,STA63,T1L,197,160,-79,UL,Unlimited,STA64,T2O,197,160,-79,UL,Unlimited,STA65
P2,7,No,R2,P3PRXBEUT007,2G,LPI,T3E,6,20,-70,UL,Unlimited,STA63,T1P,6,20,-70,UL,Unlimited,STA64,T2Q,6,20,-70,UL,Unlimited,STA65
P2,8,No,R1,P3PRXBEUT008,6G,SP,T2A,5,320,-79,UL,Unlimited,STA63,T1L,5,320,-79,UL,Unlimited,STA64,T2O,5,320,-79,UL,Unlimited,STA65
P2,9,No,R1,P3PRXBEUT009,6G,LPI,T2A,133,320,-79,UL,Unlimited,STA63,T1L,133,320,-79,UL,Unlimited,STA64,T2O,133,320,-79,UL,Unlimited,STA65
P2,1,Yes,R3,P3PTXBETH001,5G,LPI,T1F,36,80,-76,DL,22Mbps,STA63,T1I,36,80,-76,DL,22Mbps,STA64,T2J,36,80,-76,DL,11Mbps,STA65
P2,2,Yes,R3,P3PTXBETH002,5G,LPI,T1F,100,80,-76,DL,22Mbps,STA63,T1I,100,80,-76,DL,22Mbps,STA64,T2J,100,80,-76,DL,11Mbps,STA65
P2,3,Yes,R3,P3PTXBETH003,5G,LPI,T1F,161,80,-76,DL,22Mbps,STA63,T1I,161,80,-76,DL,22Mbps,STA64,T2J,161,80,-76,DL,11Mbps,STA65
P2,4,Yes,R1,P3PTXBETH004,6G,LPI,T2A,5,160,-79,DL,22Mbps,STA63,T1L,5,160,-79,DL,22Mbps,STA64,T2O,5,160,-79,DL,11Mbps,STA65
P2,5,Yes,R1,P3PTXBETH005,6G,LPI,T2A,133,160,-79,DL,22Mbps,STA63,T1L,133,160,-79,DL,22Mbps,STA64,T2O,133,160,-79,DL,11Mbps,STA65
P2,6,Yes,R1,P3PTXBETH006,6G,LPI,T2A,197,160,-79,DL,22Mbps,STA63,T1L,197,160,-79,DL,22Mbps,STA64,T2O,197,160,-79,DL,11Mbps,STA65
P2,7,Yes,R2,P3PTXBETH007,2G,LPI,T3E,6,20,-70,DL,22Mbps,STA63,T1P,6,20,-70,DL,22Mbps,STA64,T2Q,6,20,-70,DL,11Mbps,STA65
P2,8,Yes,R1,P3PTXBETH008,6G,SP,T2A,5,320,-79,DL,22Mbps,STA63,T1L,5,320,-79,DL,22Mbps,STA64,T2O,5,320,-79,DL,11Mbps,STA65
P2,9,Yes,R1,P3PTXBETH009,6G,LPI,T2A,133,320,-79,DL,22Mbps,STA63,T1L,133,320,-79,DL,22Mbps,STA64,T2O,133,320,-79,DL,11Mbps,STA65
P2,1,No,R3,P3PTXBEUT001,5G,LPI,T1F,36,80,-76,DL,Unlimited,STA63,T1I,36,80,-76,DL,Unlimited,STA64,T2J,36,80,-76,DL,Unlimited,STA65
P2,2,No,R3,P3PTXBEUT002,5G,LPI,T1F,100,80,-76,DL,Unlimited,STA63,T1I,100,80,-76,DL,Unlimited,STA64,T2J,100,80,-76,DL,Unlimited,STA65
P2,3,No,R3,P3PTXBEUT003,5G,LPI,T1F,161,80,-76,DL,Unlimited,STA63,T1I,161,80,-76,DL,Unlimited,STA64,T2J,161,80,-76,DL,Unlimited,STA65
P2,4,No,R1,P3PTXBEUT004,6G,LPI,T2A,5,160,-79,DL,Unlimited,STA63,T1L,5,160,-79,DL,Unlimited,STA64,T2O,5,160,-79,DL,Unlimited,STA65
P2,5,No,R1,P3PTXBEUT005,6G,LPI,T2A,133,160,-79,DL,Unlimited,STA63,T1L,133,160,-79,DL,Unlimited,STA64,T2O,133,160,-79,DL,Unlimited,STA65
P2,6,No,R1,P3PTXBEUT006,6G,LPI,T2A,197,160,-79,DL,Unlimited,STA63,T1L,197,160,-79,DL,Unlimited,STA64,T2O,197,160,-79,DL,Unlimited,STA65
P2,7,No,R2,P3PTXBEUT007,2G,LPI,T3E,6,20,-70,DL,Unlimited,STA63,T1P,6,20,-70,DL,Unlimited,STA64,T2Q,6,20,-70,DL,Unlimited,STA65
P2,8,No,R1,P3PTXBEUT008,6G,SP,T2A,5,320,-79,DL,Unlimited,STA63,T1L,5,320,-79,DL,Unlimited,STA64,T2O,5,320,-79,DL,Unlimited,STA65
P2,9,No,R1,P3PTXBEUT009,6G,LPI,T2A,133,320,-79,DL,Unlimited,STA63,T1L,133,320,-79,DL,Unlimited,STA64,T2O,133,320,-79,DL,Unlimited,STA65
P2,1,Yes,R1,P3PRXAXTH001,5G,LPI,T1F,36,80,-76,UL,22Mbps,STA56,T1I,36,80,-76,UL,22Mbps,STA58,T2J,36,80,-76,UL,11Mbps,STA59
P2,2,Yes,R1,P3PRXAXTH002,5G,LPI,T1F,100,80,-76,UL,22Mbps,STA56,T1I,100,80,-76,UL,22Mbps,STA58,T2J,100,80,-76,UL,11Mbps,STA59
P2,3,Yes,R1,P3PRXAXTH003,5G,LPI,T1F,161,80,-76,UL,22Mbps,STA56,T1I,161,80,-76,UL,22Mbps,STA58,T2J,161,80,-76,UL,11Mbps,STA59
P2,1,No,R1,P3PRXAXUT001,5G,LPI,T1F,36,80,-76,UL,Unlimited,STA56,T1I,36,80,-76,UL,Unlimited,STA58,T2J,36,80,-76,UL,Unlimited,STA59
P2,2,No,R1,P3PRXAXUT002,5G,LPI,T1F,100,80,-76,UL,Unlimited,STA56,T1I,100,80,-76,UL,Unlimited,STA58,T2J,100,80,-76,UL,Unlimited,STA59
P2,3,No,R1,P3PRXAXUT003,5G,LPI,T1F,161,80,-76,UL,Unlimited,STA56,T1I,161,80,-76,UL,Unlimited,STA58,T2J,161,80,-76,UL,Unlimited,STA59
P2,1,Yes,R1,P3PTXAXTH001,5G,LPI,T1F,36,80,-76,DL,22Mbps,STA56,T1I,36,80,-76,DL,22Mbps,STA58,T2J,36,80,-76,DL,11Mbps,STA59
P2,2,Yes,R1,P3PTXAXTH002,5G,LPI,T1F,100,80,-76,DL,22Mbps,STA56,T1I,100,80,-76,DL,22Mbps,STA58,T2J,100,80,-76,DL,11Mbps,STA59
P2,3,Yes,R1,P3PTXAXTH003,5G,LPI,T1F,161,80,-76,DL,22Mbps,STA56,T1I,161,80,-76,DL,22Mbps,STA58,T2J,161,80,-76,DL,11Mbps,STA59
P2,1,No,R1,P3PTXAXUT001,5G,LPI,T1F,36,80,-76,DL,Unlimited,STA56,T1I,36,80,-76,DL,Unlimited,STA58,T2J,36,80,-76,DL,Unlimited,STA59
P2,2,No,R1,P3PTXAXUT002,5G,LPI,T1F,100,80,-76,DL,Unlimited,STA56,T1I,100,80,-76,DL,Unlimited,STA58,T2J,100,80,-76,DL,Unlimited,STA59
P2,3,No,R1,P3PTXAXUT003,5G,LPI,T1F,161,80,-76,DL,Unlimited,STA56,T1I,161,80,-76,DL,Unlimited,STA58,T2J,161,80,-76,DL,Unlimited,STA59
P2,4,Yes,R1,P3PRXACTH004,2G,LPI,T3E,6,20,-70,UL,22Mbps,STA4,T1P,6,20,-70,UL,22Mbps,STA5,T2Q,6,20,-70,UL,11Mbps,STA6
P2,4,No,R1,P3PRXACUT004,2G,LPI,T3E,6,20,-70,UL,Unlimited,STA4,T1P,6,20,-70,UL,Unlimited,STA5,T2Q,6,20,-70,UL,Unlimited,STA6
P2,4,Yes,R1,P3PTXACTH004,2G,LPI,T3E,6,20,-70,DL,22Mbps,STA4,T1P,6,20,-70,DL,22Mbps,STA5,T2Q,6,20,-70,DL,11Mbps,STA6
P2,4,No,R1,P3PTXACUT004,2G,LPI,T3E,6,20,-70,DL,Unlimited,STA4,T1P,6,20,-70,DL,Unlimited,STA5,T2Q,6,20,-70,DL,Unlimited,STA6
P2,7,Yes,R2,P3PRXBETH007,2G,LPI,T3E,6,20,-70,UL,22Mbps,STA63,T1P,6,20,-70,UL,22Mbps,STA64,T2Q,6,20,-70,UL,11Mbps,STA65
P2,7,No,R2,P3PRXBEUT007,2G,LPI,T3E,6,20,-70,UL,Unlimited,STA63,T1P,6,20,-70,UL,Unlimited,STA64,T2Q,6,20,-70,UL,Unlimited,STA65
P2,7,Yes,R2,P3PTXBETH007,2G,LPI,T3E,6,20,-70,DL,22Mbps,STA63,T1P,6,20,-70,DL,22Mbps,STA64,T2Q,6,20,-70,DL,11Mbps,STA65
P2,7,No,R2,P3PTXBEUT007,2G,LPI,T3E,6,20,-70,DL,Unlimited,STA63,T1P,6,20,-70,DL,Unlimited,STA64,T2Q,6,20,-70,DL,Unlimited,STA65
P2,4,Yes,R2,P3PRXAXTH004,6G,LPI,T2A,5,160,-79,UL,22Mbps,STA56,T1L,5,160,-79,UL,22Mbps,STA58,T2O,5,160,-79,UL,11Mbps,STA59
P2,5,Yes,R2,P3PRXAXTH005,6G,LPI,T2A,133,160,-79,UL,22Mbps,STA56,T1L,133,160,-79,UL,22Mbps,STA58,T2O,133,160,-79,UL,11Mbps,STA59
P2,6,Yes,R2,P3PRXAXTH006,6G,LPI,T2A,197,160,-79,UL,22Mbps,STA56,T1L,197,160,-79,UL,22Mbps,STA58,T2O,197,160,-79,UL,11Mbps,STA59
P2,7,Yes,R3,P3PRXAXTH007,2G,LPI,T3E,6,20,-70,UL,22Mbps,STA56,T1P,6,20,-70,UL,22Mbps,STA58,T2Q,6,20,-70,UL,11Mbps,STA59
P2,8,Yes,R2,P3PRXAXTH008,6G,SP,T2A,5,320,-79,UL,22Mbps,STA56,T1L,5,320,-79,UL,22Mbps,STA58,T2O,5,320,-79,UL,11Mbps,STA59
P2,9,Yes,R2,P3PRXAXTH009,6G,LPI,T2A,133,320,-79,UL,22Mbps,STA56,T1L,133,320,-79,UL,22Mbps,STA58,T2O,133,320,-79,UL,11Mbps,STA59
P2,1,No,R1,P3PRXAXUT001,5G,LPI,T1F,36,80,-76,UL,Unlimited,STA56,T1I,36,80,-76,UL,Unlimited,STA58,T2J,36,80,-76,UL,Unlimited,STA59
P2,2,No,R1,P3PRXAXUT002,5G,LPI,T1F,100,80,-76,UL,Unlimited,STA56,T1I,100,80,-76,UL,Unlimited,STA58,T2J,100,80,-76,UL,Unlimited,STA59
P2,3,No,R1,P3PRXAXUT003,5G,LPI,T1F,161,80,-76,UL,Unlimited,STA56,T1I,161,80,-76,UL,Unlimited,STA58,T2J,161,80,-76,UL,Unlimited,STA59
P2,4,No,R2,P3PRXAXUT004,6G,LPI,T2A,5,160,-79,UL,Unlimited,STA56,T1L,5,160,-79,UL,Unlimited,STA58,T2O,5,160,-79,UL,Unlimited,STA59
P2,5,No,R2,P3PRXAXUT005,6G,LPI,T2A,133,160,-79,UL,Unlimited,STA56,T1L,133,160,-79,UL,Unlimited,STA58,T2O,133,160,-79,UL,Unlimited,STA59
P2,6,No,R2,P3PRXAXUT006,6G,LPI,T2A,197,160,-79,UL,Unlimited,STA56,T1L,197,160,-79,UL,Unlimited,STA58,T2O,197,160,-79,UL,Unlimited,STA59
P2,7,No,R3,P3PRXAXUT007,2G,LPI,T3E,6,20,-70,UL,Unlimited,STA56,T1P,6,20,-70,UL,Unlimited,STA58,T2Q,6,20,-70,UL,Unlimited,STA59
P2,8,No,R2,P3PRXAXUT008,6G,SP,T2A,5,320,-79,UL,Unlimited,STA56,T1L,5,320,-79,UL,Unlimited,STA58,T2O,5,320,-79,UL,Unlimited,STA59
P2,9,No,R2,P3PRXAXUT009,6G,LPI,T2A,133,320,-79,UL,Unlimited,STA56,T1L,133,320,-79,UL,Unlimited,STA58,T2O,133,320,-79,UL,Unlimited,STA59
P2,1,Yes,R1,P3PTXAXTH001,5G,LPI,T1F,36,80,-76,DL,22Mbps,STA56,T1I,36,80,-76,DL,22Mbps,STA58,T2J,36,80,-76,DL,11Mbps,STA59
P2,2,Yes,R1,P3PTXAXTH002,5G,LPI,T1F,100,80,-76,DL,22Mbps,STA56,T1I,100,80,-76,DL,22Mbps,STA58,T2J,100,80,-76,DL,11Mbps,STA59
P2,3,Yes,R1,P3PTXAXTH003,5G,LPI,T1F,161,80,-76,DL,22Mbps,STA56,T1I,161,80,-76,DL,22Mbps,STA58,T2J,161,80,-76,DL,11Mbps,STA59
P2,4,Yes,R2,P3PTXAXTH004,6G,LPI,T2A,5,160,-79,DL,22Mbps,STA56,T1L,5,160,-79,DL,22Mbps,STA58,T2O,5,160,-79,DL,11Mbps,STA59
P2,5,Yes,R2,P3PTXAXTH005,6G,LPI,T2A,133,160,-79,DL,22Mbps,STA56,T1L,133,160,-79,DL,22Mbps,STA58,T2O,133,160,-79,DL,11Mbps,STA59
P2,6,Yes,R2,P3PTXAXTH006,6G,LPI,T2A,197,160,-79,DL,22Mbps,STA56,T1L,197,160,-79,DL,22Mbps,STA58,T2O,197,160,-79,DL,11Mbps,STA59
P2,7,Yes,R3,P3PTXAXTH007,2G,LPI,T3E,6,20,-70,DL,22Mbps,STA56,T1P,6,20,-70,DL,22Mbps,STA58,T2Q,6,20,-70,DL,11Mbps,STA59
P2,8,Yes,R2,P3PTXAXTH008,6G,SP,T2A,5,320,-79,DL,22Mbps,STA56,T1L,5,320,-79,DL,22Mbps,STA58,T2O,5,320,-79,DL,11Mbps,STA59
P2,9,Yes,R2,P3PTXAXTH009,6G,LPI,T2A,133,320,-79,DL,22Mbps,STA56,T1L,133,320,-79,DL,22Mbps,STA58,T2O,133,320,-79,DL,11Mbps,STA59
P2,1,No,R1,P3PTXAXUT001,5G,LPI,T1F,36,80,-76,DL,Unlimited,STA56,T1I,36,80,-76,DL,Unlimited,STA58,T2J,36,80,-76,DL,Unlimited,STA59
P2,2,No,R1,P3PTXAXUT002,5G,LPI,T1F,100,80,-76,DL,Unlimited,STA56,T1I,100,80,-76,DL,Unlimited,STA58,T2J,100,80,-76,DL,Unlimited,STA59
P2,3,No,R1,P3PTXAXUT003,5G,LPI,T1F,161,80,-76,DL,Unlimited,STA56,T1I,161,80,-76,DL,Unlimited,STA58,T2J,161,80,-76,DL,Unlimited,STA59
P2,4,No,R2,P3PTXAXUT004,6G,LPI,T2A,5,160,-79,DL,Unlimited,STA56,T1L,5,160,-79,DL,Unlimited,STA58,T2O,5,160,-79,DL,Unlimited,STA59
P2,5,No,R2,P3PTXAXUT005,6G,LPI,T2A,133,160,-79,DL,Unlimited,STA56,T1L,133,160,-79,DL,Unlimited,STA58,T2O,133,160,-79,DL,Unlimited,STA59
P2,6,No,R2,P3PTXAXUT006,6G,LPI,T2A,197,160,-79,DL,Unlimited,STA56,T1L,197,160,-79,DL,Unlimited,STA58,T2O,197,160,-79,DL,Unlimited,STA59
P2,7,No,R3,P3PTXAXUT007,2G,LPI,T3E,6,20,-70,DL,Unlimited,STA56,T1P,6,20,-70,DL,Unlimited,STA58,T2Q,6,20,-70,DL,Unlimited,STA59
P2,8,No,R2,P3PTXAXUT008,6G,SP,T2A,5,320,-79,DL,Unlimited,STA56,T1L,5,320,-79,DL,Unlimited,STA58,T2O,5,320,-79,DL,Unlimited,STA59
P2,9,No,R2,P3PTXAXUT009,6G,LPI,T2A,133,320,-79,DL,Unlimited,STA56,T1L,133,320,-79,DL,Unlimited,STA58,T2O,133,320,-79,DL,Unlimited,STA59
P2,1,Yes,R2,P3PRXACTH001,5G,LPI,T1F,36,80,-76,UL,22Mbps,STA4,T1I,36,80,-76,UL,22Mbps,STA5,T2J,36,80,-76,UL,11Mbps,STA6
P2,2,Yes,R2,P3PRXACTH002,5G,LPI,T1F,100,80,-76,UL,22Mbps,STA4,T1I,100,80,-76,UL,22Mbps,STA5,T2J,100,80,-76,UL,11Mbps,STA6
P2,3,Yes,R2,P3PRXACTH003,5G,LPI,T1F,161,80,-76,UL,22Mbps,STA4,T1I,161,80,-76,UL,22Mbps,STA5,T2J,161,80,-76,UL,11Mbps,STA6
P2,4,Yes,R1,P3PRXACTH004,2G,LPI,T3E,6,20,-70,UL,22Mbps,STA4,T1P,6,20,-70,UL,22Mbps,STA5,T2Q,6,20,-70,UL,11Mbps,STA6
P2,1,No,R2,P3PRXACUT001,5G,LPI,T1F,36,80,-76,UL,Unlimited,STA4,T1I,36,80,-76,UL,Unlimited,STA5,T2J,36,80,-76,UL,Unlimited,STA6
P2,2,No,R2,P3PRXACUT002,5G,LPI,T1F,100,80,-76,UL,Unlimited,STA4,T1I,100,80,-76,UL,Unlimited,STA5,T2J,100,80,-76,UL,Unlimited,STA6
P2,3,No,R2,P3PRXACUT003,5G,LPI,T1F,161,80,-76,UL,Unlimited,STA4,T1I,161,80,-76,UL,Unlimited,STA5,T2J,161,80,-76,UL,Unlimited,STA6
P2,4,No,R1,P3PRXACUT004,2G,LPI,T3E,6,20,-70,UL,Unlimited,STA4,T1P,6,20,-70,UL,Unlimited,STA5,T2Q,6,20,-70,UL,Unlimited,STA6
P2,1,Yes,R2,P3PTXACTH001,5G,LPI,T1F,36,80,-76,DL,22Mbps,STA4,T1I,36,80,-76,DL,22Mbps,STA5,T2J,36,80,-76,DL,11Mbps,STA6
P2,2,Yes,R2,P3PTXACTH002,5G,LPI,T1F,100,80,-76,DL,22Mbps,STA4,T1I,100,80,-76,DL,22Mbps,STA5,T2J,100,80,-76,DL,11Mbps,STA6
P2,3,Yes,R2,P3PTXACTH003,5G,LPI,T1F,161,80,-76,DL,22Mbps,STA4,T1I,161,80,-76,DL,22Mbps,STA5,T2J,161,80,-76,DL,11Mbps,STA6
P2,4,Yes,R1,P3PTXACTH004,2G,LPI,T3E,6,20,-70,DL,22Mbps,STA4,T1P,6,20,-70,DL,22Mbps,STA5,T2Q,6,20,-70,DL,11Mbps,STA6
P2,1,No,R2,P3PTXACUT001,5G,LPI,T1F,36,80,-76,DL,Unlimited,STA4,T1I,36,80,-76,DL,Unlimited,STA5,T2J,36,80,-76,DL,Unlimited,STA6
P2,2,No,R2,P3PTXACUT002,5G,LPI,T1F,100,80,-76,DL,Unlimited,STA4,T1I,100,80,-76,DL,Unlimited,STA5,T2J,100,80,-76,DL,Unlimited,STA6
P2,3,No,R2,P3PTXACUT003,5G,LPI,T1F,161,80,-76,DL,Unlimited,STA4,T1I,161,80,-76,DL,Unlimited,STA5,T2J,161,80,-76,DL,Unlimited,STA6
P2,1,Yes,R3,P3PRXBETH001,5G,LPI,T1F,36,80,-76,UL,22Mbps,STA63,T1I,36,80,-76,UL,22Mbps,STA64,T2J,36,80,-76,UL,11Mbps,STA65
P2,2,Yes,R3,P3PRXBETH002,5G,LPI,T1F,100,80,-76,UL,22Mbps,STA63,T1I,100,80,-76,UL,22Mbps,STA64,T2J,100,80,-76,UL,11Mbps,STA65
P2,3,Yes,R3,P3PRXBETH003,5G,LPI,T1F,161,80,-76,UL,22Mbps,STA63,T1I,161,80,-76,UL,22Mbps,STA64,T2J,161,80,-76,UL,11Mbps,STA65
P2,1,No,R3,P3PRXBEUT001,5G,LPI,T1F,36,80,-76,UL,Unlimited,STA63,T1I,36,80,-76,UL,Unlimited,STA64,T2J,36,80,-76,UL,Unlimited,STA65
P2,2,No,R3,P3PRXBEUT002,5G,LPI,T1F,100,80,-76,UL,Unlimited,STA63,T1I,100,80,-76,UL,Unlimited,STA64,T2J,100,80,-76,UL,Unlimited,STA65
P2,3,No,R3,P3PRXBEUT003,5G,LPI,T1F,161,80,-76,UL,Unlimited,STA63,T1I,161,80,-76,UL,Unlimited,STA64,T2J,161,80,-76,UL,Unlimited,STA65
P2,1,Yes,R3,P3PTXBETH001,5G,LPI,T1F,36,80,-76,DL,22Mbps,STA63,T1I,36,80,-76,DL,22Mbps,STA64,T2J,36,80,-76,DL,11Mbps,STA65
P2,2,Yes,R3,P3PTXBETH002,5G,LPI,T1F,100,80,-76,DL,22Mbps,STA63,T1I,100,80,-76,DL,22Mbps,STA64,T2J,100,80,-76,DL,11Mbps,STA65
P2,3,Yes,R3,P3PTXBETH003,5G,LPI,T1F,161,80,-76,DL,22Mbps,STA63,T1I,161,80,-76,DL,22Mbps,STA64,T2J,161,80,-76,DL,11Mbps,STA65
P2,1,No,R3,P3PTXBEUT001,5G,LPI,T1F,36,80,-76,DL,Unlimited,STA63,T1I,36,80,-76,DL,Unlimited,STA64,T2J,36,80,-76,DL,Unlimited,STA65
P2,2,No,R3,P3PTXBEUT002,5G,LPI,T1F,100,80,-76,DL,Unlimited,STA63,T1I,100,80,-76,DL,Unlimited,STA64,T2J,100,80,-76,DL,Unlimited,STA65
P2,3,No,R3,P3PTXBEUT003,5G,LPI,T1F,161,80,-76,DL,Unlimited,STA63,T1I,161,80,-76,DL,Unlimited,STA64,T2J,161,80,-76,DL,Unlimited,STA65
P2,7,Yes,R3,P3PRXAXTH007,2G,LPI,T1D,6,20,-70,UL,22Mbps,STA56,T1P,6,20,-70,UL,22Mbps,STA58,T2Q,6,20,-70,UL,11Mbps,STA59
P2,7,No,R3,P3PRXAXUT007,2G,LPI,T1D,6,20,-70,UL,Unlimited,STA56,T1P,6,20,-70,UL,Unlimited,STA58,T2Q,6,20,-70,UL,Unlimited,STA59
P2,7,Yes,R3,P3PTXAXTH007,2G,LPI,T1D,6,20,-70,DL,22Mbps,STA56,T1P,6,20,-70,DL,22Mbps,STA58,T2Q,6,20,-70,DL,11Mbps,STA59
P2,7,No,R3,P3PTXAXUT007,2G,LPI,T1D,6,20,-70,DL,Unlimited,STA56,T1P,6,20,-70,DL,Unlimited,STA58,T2Q,6,20,-70,DL,Unlimited,STA59
P2,4,No,R1,P3PTXACUT004,2G,LPI,T3E,6,20,-70,DL,Unlimited,STA4,T1P,6,20,-70,DL,Unlimited,STA5,T2Q,6,20,-70,DL,Unlimited,STA6
1 Priority Index Throttled Rotation TC ID Band 6GHz Power Mode Station 1 Test Point STATION 1_Test Point Station 1 Channel STATION 1_Channel Station 1 Bandwidth STATION 1_Bandwidth Station 1 RSSI STATION 1_RSSI Station 1 Direction STATION 1_Direction Station 1 Rate STATION 1_Rate Station 1 STA STATION 1_STA Station 2 Test Point STATION 2_Test Point Station 2 Channel STATION 2_Channel Station 2 Bandwidth STATION 2_Bandwidth Station 2 RSSI STATION 2_RSSI Station 2 Direction STATION 2_Direction Station 2 Rate STATION 2_Rate Station 2 STA STATION 2_STA Station 3 Test Point STATION 3_Test Point Station 3 Channel STATION 3_Channel Station 3 Bandwidth STATION 3_Bandwidth Station 3 RSSI STATION 3_RSSI Station 3 Direction STATION 3_Direction Station 3 Rate STATION 3_Rate Station 3 STA STATION 3_STA
2 P2 1 Yes R3 P3PRXBETH001 5G LPI T1F 36 80 -76 UL 22Mbps STA63 T1I 36 80 -76 UL 22Mbps STA64 T2J 36 80 -76 UL 11Mbps STA65
3 P2 2 Yes R3 P3PRXBETH002 5G LPI T1F 100 80 -76 UL 22Mbps STA63 T1I 100 80 -76 UL 22Mbps STA64 T2J 100 80 -76 UL 11Mbps STA65
4 P2 3 Yes R3 P3PRXBETH003 5G LPI T1F 161 80 -76 UL 22Mbps STA63 T1I 161 80 -76 UL 22Mbps STA64 T2J 161 80 -76 UL 11Mbps STA65
5 P2 4 Yes R1 P3PRXBETH004 6G LPI T2A T2A 5 5 160 160 -79 -79 UL 22Mbps 22Mbps STA63 T1L T1L 5 5 160 160 -79 -79 UL 22Mbps 22Mbps STA64 T2O T2O 5 5 160 160 -79 -79 UL 11Mbps 11Mbps STA65
6 P2 5 Yes R1 P3PRXBETH005 6G LPI T2A T2A 133 133 160 160 -79 -79 UL 22Mbps 22Mbps STA63 T1L T1L 133 133 160 160 -79 -79 UL 22Mbps 22Mbps STA64 T2O T2O 133 133 160 160 -79 -79 UL 11Mbps 11Mbps STA65
7 P2 6 Yes R1 P3PRXBETH006 6G LPI T2A T2A 197 197 160 160 -79 -79 UL 22Mbps 22Mbps STA63 T1L T1L 197 197 160 160 -79 -79 UL 22Mbps 22Mbps STA64 T2O T2O 197 197 160 160 -79 -79 UL 11Mbps 11Mbps STA65
8 P2 7 Yes R2 P3PRXBETH007 2G LPI T3E 6 20 -70 UL 22Mbps STA63 T1P 6 20 -70 UL 22Mbps STA64 T2Q 6 20 -70 UL 11Mbps STA65
9 P2 8 Yes R1 P3PRXBETH008 6G SP T2A 5 320 -79 UL 22Mbps STA63 T1L 5 320 -79 UL 22Mbps STA64 T2O 5 320 -79 UL 11Mbps STA65
10 P2 9 Yes R1 P3PRXBETH009 6G LPI T2A T2A 133 133 320 320 -79 -79 UL 22Mbps 22Mbps STA63 T1L T1L 133 133 320 320 -79 -79 UL 22Mbps 22Mbps STA64 T2O T2O 133 133 320 320 -79 -79 UL 11Mbps 11Mbps STA65
11 P2 1 No R3 P3PRXBEUT001 5G LPI T1F 36 80 -76 UL Unlimited STA63 T1I 36 80 -76 UL Unlimited STA64 T2J 36 80 -76 UL Unlimited STA65
12 P2 2 No R3 P3PRXBEUT002 5G LPI T1F 100 80 -76 UL Unlimited STA63 T1I 100 80 -76 UL Unlimited STA64 T2J 100 80 -76 UL Unlimited STA65
13 P2 3 No R3 P3PRXBEUT003 5G LPI T1F 161 80 -76 UL Unlimited STA63 T1I 161 80 -76 UL Unlimited STA64 T2J 161 80 -76 UL Unlimited STA65
14 P2 4 No R1 P3PRXBEUT004 6G LPI T2A T2A 5 5 160 160 -79 -79 UL Unlimited Unlimited STA63 T1L T1L 5 5 160 160 -79 -79 UL Unlimited Unlimited STA64 T2O T2O 5 5 160 160 -79 -79 UL Unlimited Unlimited STA65
15 P2 5 No R1 P3PRXBEUT005 6G LPI T2A T2A 133 133 160 160 -79 -79 UL Unlimited Unlimited STA63 T1L T1L 133 133 160 160 -79 -79 UL Unlimited Unlimited STA64 T2O T2O 133 133 160 160 -79 -79 UL Unlimited Unlimited STA65
16 P2 6 No R1 P3PRXBEUT006 6G LPI T2A T2A 197 197 160 160 -79 -79 UL Unlimited Unlimited STA63 T1L T1L 197 197 160 160 -79 -79 UL Unlimited Unlimited STA64 T2O T2O 197 197 160 160 -79 -79 UL Unlimited Unlimited STA65
17 P2 7 No R2 P3PRXBEUT007 2G LPI T3E 6 20 -70 UL Unlimited STA63 T1P 6 20 -70 UL Unlimited STA64 T2Q 6 20 -70 UL Unlimited STA65
18 P2 8 No R1 P3PRXBEUT008 6G SP T2A 5 320 -79 UL Unlimited STA63 T1L 5 320 -79 UL Unlimited STA64 T2O 5 320 -79 UL Unlimited STA65
19 P2 9 No R1 P3PRXBEUT009 6G LPI T2A T2A 133 133 320 320 -79 -79 UL Unlimited Unlimited STA63 T1L T1L 133 133 320 320 -79 -79 UL Unlimited Unlimited STA64 T2O T2O 133 133 320 320 -79 -79 UL Unlimited Unlimited STA65
20 P2 1 Yes R3 P3PTXBETH001 5G LPI T1F 36 80 -76 DL 22Mbps STA63 T1I 36 80 -76 DL 22Mbps STA64 T2J 36 80 -76 DL 11Mbps STA65
21 P2 2 Yes R3 P3PTXBETH002 5G LPI T1F 100 80 -76 DL 22Mbps STA63 T1I 100 80 -76 DL 22Mbps STA64 T2J 100 80 -76 DL 11Mbps STA65
22 P2 3 Yes R3 P3PTXBETH003 5G LPI T1F 161 80 -76 DL 22Mbps STA63 T1I 161 80 -76 DL 22Mbps STA64 T2J 161 80 -76 DL 11Mbps STA65
23 P2 4 Yes R1 P3PTXBETH004 6G LPI T2A T2A 5 5 160 160 -79 -79 DL 22Mbps 22Mbps STA63 T1L T1L 5 5 160 160 -79 -79 DL 22Mbps 22Mbps STA64 T2O T2O 5 5 160 160 -79 -79 DL 11Mbps 11Mbps STA65
24 P2 5 Yes R1 P3PTXBETH005 6G LPI T2A T2A 133 133 160 160 -79 -79 DL 22Mbps 22Mbps STA63 T1L T1L 133 133 160 160 -79 -79 DL 22Mbps 22Mbps STA64 T2O T2O 133 133 160 160 -79 -79 DL 11Mbps 11Mbps STA65
25 P2 6 Yes R1 P3PTXBETH006 6G LPI T2A T2A 197 197 160 160 -79 -79 DL 22Mbps 22Mbps STA63 T1L T1L 197 197 160 160 -79 -79 DL 22Mbps 22Mbps STA64 T2O T2O 197 197 160 160 -79 -79 DL 11Mbps 11Mbps STA65
26 P2 7 Yes R2 P3PTXBETH007 2G LPI T3E 6 20 -70 DL 22Mbps STA63 T1P 6 20 -70 DL 22Mbps STA64 T2Q 6 20 -70 DL 11Mbps STA65
27 P2 8 Yes R1 P3PTXBETH008 6G SP T2A 5 320 -79 DL 22Mbps STA63 T1L 5 320 -79 DL 22Mbps STA64 T2O 5 320 -79 DL 11Mbps STA65
28 P2 9 Yes R1 P3PTXBETH009 6G LPI T2A T2A 133 133 320 320 -79 -79 DL 22Mbps 22Mbps STA63 T1L T1L 133 133 320 320 -79 -79 DL 22Mbps 22Mbps STA64 T2O T2O 133 133 320 320 -79 -79 DL 11Mbps 11Mbps STA65
29 P2 1 No R3 P3PTXBEUT001 5G LPI T1F 36 80 -76 DL Unlimited STA63 T1I 36 80 -76 DL Unlimited STA64 T2J 36 80 -76 DL Unlimited STA65
30 P2 2 No R3 P3PTXBEUT002 5G LPI T1F 100 80 -76 DL Unlimited STA63 T1I 100 80 -76 DL Unlimited STA64 T2J 100 80 -76 DL Unlimited STA65
31 P2 3 No R3 P3PTXBEUT003 5G LPI T1F 161 80 -76 DL Unlimited STA63 T1I 161 80 -76 DL Unlimited STA64 T2J 161 80 -76 DL Unlimited STA65
32 P2 4 No R1 P3PTXBEUT004 6G LPI T2A T2A 5 5 160 160 -79 -79 DL Unlimited Unlimited STA63 T1L T1L 5 5 160 160 -79 -79 DL Unlimited Unlimited STA64 T2O T2O 5 5 160 160 -79 -79 DL Unlimited Unlimited STA65
33 P2 5 No R1 P3PTXBEUT005 6G LPI T2A T2A 133 133 160 160 -79 -79 DL Unlimited Unlimited STA63 T1L T1L 133 133 160 160 -79 -79 DL Unlimited Unlimited STA64 T2O T2O 133 133 160 160 -79 -79 DL Unlimited Unlimited STA65
34 P2 6 No R1 P3PTXBEUT006 6G LPI T2A T2A 197 197 160 160 -79 -79 DL Unlimited Unlimited STA63 T1L T1L 197 197 160 160 -79 -79 DL Unlimited Unlimited STA64 T2O T2O 197 197 160 160 -79 -79 DL Unlimited Unlimited STA65
35 P2 7 No R2 P3PTXBEUT007 2G LPI T3E 6 20 -70 DL Unlimited STA63 T1P 6 20 -70 DL Unlimited STA64 T2Q 6 20 -70 DL Unlimited STA65
36 P2 8 No R1 P3PTXBEUT008 6G SP T2A 5 320 -79 DL Unlimited STA63 T1L 5 320 -79 DL Unlimited STA64 T2O 5 320 -79 DL Unlimited STA65
37 P2 9 No R1 P3PTXBEUT009 6G LPI T2A T2A 133 133 320 320 -79 -79 DL Unlimited Unlimited STA63 T1L T1L 133 133 320 320 -79 -79 DL Unlimited Unlimited STA64 T2O T2O 133 133 320 320 -79 -79 DL Unlimited Unlimited STA65
38 P2 1 Yes R1 P3PRXAXTH001 5G LPI T1F T1F 36 36 80 80 -76 -76 UL 22Mbps 22Mbps STA56 T1I T1I 36 36 80 80 -76 -76 UL 22Mbps 22Mbps STA58 T2J T2J 36 36 80 80 -76 -76 UL 11Mbps 11Mbps STA59
39 P2 2 Yes R1 P3PRXAXTH002 5G LPI T1F T1F 100 100 80 80 -76 -76 UL 22Mbps 22Mbps STA56 T1I T1I 100 100 80 80 -76 -76 UL 22Mbps 22Mbps STA58 T2J T2J 100 100 80 80 -76 -76 UL 11Mbps 11Mbps STA59
40 P2 3 Yes R1 P3PRXAXTH003 5G LPI T1F T1F 161 161 80 80 -76 -76 UL 22Mbps 22Mbps STA56 T1I T1I 161 161 80 80 -76 -76 UL 22Mbps 22Mbps STA58 T2J T2J 161 161 80 80 -76 -76 UL 11Mbps 11Mbps STA59
P2 1 No R1 P3PRXAXUT001 5G LPI T1F 36 80 -76 UL Unlimited STA56 T1I 36 80 -76 UL Unlimited STA58 T2J 36 80 -76 UL Unlimited STA59
P2 2 No R1 P3PRXAXUT002 5G LPI T1F 100 80 -76 UL Unlimited STA56 T1I 100 80 -76 UL Unlimited STA58 T2J 100 80 -76 UL Unlimited STA59
P2 3 No R1 P3PRXAXUT003 5G LPI T1F 161 80 -76 UL Unlimited STA56 T1I 161 80 -76 UL Unlimited STA58 T2J 161 80 -76 UL Unlimited STA59
P2 1 Yes R1 P3PTXAXTH001 5G LPI T1F 36 80 -76 DL 22Mbps STA56 T1I 36 80 -76 DL 22Mbps STA58 T2J 36 80 -76 DL 11Mbps STA59
P2 2 Yes R1 P3PTXAXTH002 5G LPI T1F 100 80 -76 DL 22Mbps STA56 T1I 100 80 -76 DL 22Mbps STA58 T2J 100 80 -76 DL 11Mbps STA59
P2 3 Yes R1 P3PTXAXTH003 5G LPI T1F 161 80 -76 DL 22Mbps STA56 T1I 161 80 -76 DL 22Mbps STA58 T2J 161 80 -76 DL 11Mbps STA59
P2 1 No R1 P3PTXAXUT001 5G LPI T1F 36 80 -76 DL Unlimited STA56 T1I 36 80 -76 DL Unlimited STA58 T2J 36 80 -76 DL Unlimited STA59
P2 2 No R1 P3PTXAXUT002 5G LPI T1F 100 80 -76 DL Unlimited STA56 T1I 100 80 -76 DL Unlimited STA58 T2J 100 80 -76 DL Unlimited STA59
P2 3 No R1 P3PTXAXUT003 5G LPI T1F 161 80 -76 DL Unlimited STA56 T1I 161 80 -76 DL Unlimited STA58 T2J 161 80 -76 DL Unlimited STA59
P2 4 Yes R1 P3PRXACTH004 2G LPI T3E 6 20 -70 UL 22Mbps STA4 T1P 6 20 -70 UL 22Mbps STA5 T2Q 6 20 -70 UL 11Mbps STA6
P2 4 No R1 P3PRXACUT004 2G LPI T3E 6 20 -70 UL Unlimited STA4 T1P 6 20 -70 UL Unlimited STA5 T2Q 6 20 -70 UL Unlimited STA6
P2 4 Yes R1 P3PTXACTH004 2G LPI T3E 6 20 -70 DL 22Mbps STA4 T1P 6 20 -70 DL 22Mbps STA5 T2Q 6 20 -70 DL 11Mbps STA6
P2 4 No R1 P3PTXACUT004 2G LPI T3E 6 20 -70 DL Unlimited STA4 T1P 6 20 -70 DL Unlimited STA5 T2Q 6 20 -70 DL Unlimited STA6
P2 7 Yes R2 P3PRXBETH007 2G LPI T3E 6 20 -70 UL 22Mbps STA63 T1P 6 20 -70 UL 22Mbps STA64 T2Q 6 20 -70 UL 11Mbps STA65
P2 7 No R2 P3PRXBEUT007 2G LPI T3E 6 20 -70 UL Unlimited STA63 T1P 6 20 -70 UL Unlimited STA64 T2Q 6 20 -70 UL Unlimited STA65
P2 7 Yes R2 P3PTXBETH007 2G LPI T3E 6 20 -70 DL 22Mbps STA63 T1P 6 20 -70 DL 22Mbps STA64 T2Q 6 20 -70 DL 11Mbps STA65
P2 7 No R2 P3PTXBEUT007 2G LPI T3E 6 20 -70 DL Unlimited STA63 T1P 6 20 -70 DL Unlimited STA64 T2Q 6 20 -70 DL Unlimited STA65
41 P2 4 Yes R2 P3PRXAXTH004 6G LPI T2A T2A 5 5 160 160 -79 -79 UL 22Mbps 22Mbps STA56 T1L T1L 5 5 160 160 -79 -79 UL 22Mbps 22Mbps STA58 T2O T2O 5 5 160 160 -79 -79 UL 11Mbps 11Mbps STA59
42 P2 5 Yes R2 P3PRXAXTH005 6G LPI T2A T2A 133 133 160 160 -79 -79 UL 22Mbps 22Mbps STA56 T1L T1L 133 133 160 160 -79 -79 UL 22Mbps 22Mbps STA58 T2O T2O 133 133 160 160 -79 -79 UL 11Mbps 11Mbps STA59
43 P2 6 Yes R2 P3PRXAXTH006 6G LPI T2A T2A 197 197 160 160 -79 -79 UL 22Mbps 22Mbps STA56 T1L T1L 197 197 160 160 -79 -79 UL 22Mbps 22Mbps STA58 T2O T2O 197 197 160 160 -79 -79 UL 11Mbps 11Mbps STA59
44 P2 7 Yes R3 P3PRXAXTH007 2G LPI T3E 6 20 -70 UL 22Mbps STA56 T1P 6 20 -70 UL 22Mbps STA58 T2Q 6 20 -70 UL 11Mbps STA59
45 P2 8 Yes R2 P3PRXAXTH008 6G SP T2A 5 320 -79 UL 22Mbps STA56 T1L 5 320 -79 UL 22Mbps STA58 T2O 5 320 -79 UL 11Mbps STA59
46 P2 9 Yes R2 P3PRXAXTH009 6G LPI T2A T2A 133 133 320 320 -79 -79 UL 22Mbps 22Mbps STA56 T1L T1L 133 133 320 320 -79 -79 UL 22Mbps 22Mbps STA58 T2O T2O 133 133 320 320 -79 -79 UL 11Mbps 11Mbps STA59
47 P2 1 No R1 P3PRXAXUT001 5G LPI T1F 36 80 -76 UL Unlimited STA56 T1I 36 80 -76 UL Unlimited STA58 T2J 36 80 -76 UL Unlimited STA59
48 P2 2 No R1 P3PRXAXUT002 5G LPI T1F 100 80 -76 UL Unlimited STA56 T1I 100 80 -76 UL Unlimited STA58 T2J 100 80 -76 UL Unlimited STA59
49 P2 3 No R1 P3PRXAXUT003 5G LPI T1F 161 80 -76 UL Unlimited STA56 T1I 161 80 -76 UL Unlimited STA58 T2J 161 80 -76 UL Unlimited STA59
50 P2 4 No R2 P3PRXAXUT004 6G LPI T2A T2A 5 5 160 160 -79 -79 UL Unlimited Unlimited STA56 T1L T1L 5 5 160 160 -79 -79 UL Unlimited Unlimited STA58 T2O T2O 5 5 160 160 -79 -79 UL Unlimited Unlimited STA59
51 P2 5 No R2 P3PRXAXUT005 6G LPI T2A T2A 133 133 160 160 -79 -79 UL Unlimited Unlimited STA56 T1L T1L 133 133 160 160 -79 -79 UL Unlimited Unlimited STA58 T2O T2O 133 133 160 160 -79 -79 UL Unlimited Unlimited STA59
52 P2 6 No R2 P3PRXAXUT006 6G LPI T2A T2A 197 197 160 160 -79 -79 UL Unlimited Unlimited STA56 T1L T1L 197 197 160 160 -79 -79 UL Unlimited Unlimited STA58 T2O T2O 197 197 160 160 -79 -79 UL Unlimited Unlimited STA59
53 P2 7 No R3 P3PRXAXUT007 2G LPI T3E 6 20 -70 UL Unlimited STA56 T1P 6 20 -70 UL Unlimited STA58 T2Q 6 20 -70 UL Unlimited STA59
54 P2 8 No R2 P3PRXAXUT008 6G SP T2A 5 320 -79 UL Unlimited STA56 T1L 5 320 -79 UL Unlimited STA58 T2O 5 320 -79 UL Unlimited STA59
55 P2 9 No R2 P3PRXAXUT009 6G LPI T2A T2A 133 133 320 320 -79 -79 UL Unlimited Unlimited STA56 T1L T1L 133 133 320 320 -79 -79 UL Unlimited Unlimited STA58 T2O T2O 133 133 320 320 -79 -79 UL Unlimited Unlimited STA59
56 P2 1 Yes R1 P3PTXAXTH001 5G LPI T1F 36 80 -76 DL 22Mbps STA56 T1I 36 80 -76 DL 22Mbps STA58 T2J 36 80 -76 DL 11Mbps STA59
57 P2 2 Yes R1 P3PTXAXTH002 5G LPI T1F 100 80 -76 DL 22Mbps STA56 T1I 100 80 -76 DL 22Mbps STA58 T2J 100 80 -76 DL 11Mbps STA59
58 P2 3 Yes R1 P3PTXAXTH003 5G LPI T1F 161 80 -76 DL 22Mbps STA56 T1I 161 80 -76 DL 22Mbps STA58 T2J 161 80 -76 DL 11Mbps STA59
59 P2 4 Yes R2 P3PTXAXTH004 6G LPI T2A T2A 5 5 160 160 -79 -79 DL 22Mbps 22Mbps STA56 T1L T1L 5 5 160 160 -79 -79 DL 22Mbps 22Mbps STA58 T2O T2O 5 5 160 160 -79 -79 DL 11Mbps 11Mbps STA59
60 P2 5 Yes R2 P3PTXAXTH005 6G LPI T2A T2A 133 133 160 160 -79 -79 DL 22Mbps 22Mbps STA56 T1L T1L 133 133 160 160 -79 -79 DL 22Mbps 22Mbps STA58 T2O T2O 133 133 160 160 -79 -79 DL 11Mbps 11Mbps STA59
61 P2 6 Yes R2 P3PTXAXTH006 6G LPI T2A T2A 197 197 160 160 -79 -79 DL 22Mbps 22Mbps STA56 T1L T1L 197 197 160 160 -79 -79 DL 22Mbps 22Mbps STA58 T2O T2O 197 197 160 160 -79 -79 DL 11Mbps 11Mbps STA59
62 P2 7 Yes R3 P3PTXAXTH007 2G LPI T3E 6 20 -70 DL 22Mbps STA56 T1P 6 20 -70 DL 22Mbps STA58 T2Q 6 20 -70 DL 11Mbps STA59
63 P2 8 Yes R2 P3PTXAXTH008 6G SP T2A 5 320 -79 DL 22Mbps STA56 T1L 5 320 -79 DL 22Mbps STA58 T2O 5 320 -79 DL 11Mbps STA59
64 P2 9 Yes R2 P3PTXAXTH009 6G LPI T2A T2A 133 133 320 320 -79 -79 DL 22Mbps 22Mbps STA56 T1L T1L 133 133 320 320 -79 -79 DL 22Mbps 22Mbps STA58 T2O T2O 133 133 320 320 -79 -79 DL 11Mbps 11Mbps STA59
65 P2 1 No R1 P3PTXAXUT001 5G LPI T1F 36 80 -76 DL Unlimited STA56 T1I 36 80 -76 DL Unlimited STA58 T2J 36 80 -76 DL Unlimited STA59
66 P2 2 No R1 P3PTXAXUT002 5G LPI T1F 100 80 -76 DL Unlimited STA56 T1I 100 80 -76 DL Unlimited STA58 T2J 100 80 -76 DL Unlimited STA59
67 P2 3 No R1 P3PTXAXUT003 5G LPI T1F 161 80 -76 DL Unlimited STA56 T1I 161 80 -76 DL Unlimited STA58 T2J 161 80 -76 DL Unlimited STA59
68 P2 4 No R2 P3PTXAXUT004 6G LPI T2A T2A 5 5 160 160 -79 -79 DL Unlimited Unlimited STA56 T1L T1L 5 5 160 160 -79 -79 DL Unlimited Unlimited STA58 T2O T2O 5 5 160 160 -79 -79 DL Unlimited Unlimited STA59
69 P2 5 No R2 P3PTXAXUT005 6G LPI T2A T2A 133 133 160 160 -79 -79 DL Unlimited Unlimited STA56 T1L T1L 133 133 160 160 -79 -79 DL Unlimited Unlimited STA58 T2O T2O 133 133 160 160 -79 -79 DL Unlimited Unlimited STA59
70 P2 6 No R2 P3PTXAXUT006 6G LPI T2A T2A 197 197 160 160 -79 -79 DL Unlimited Unlimited STA56 T1L T1L 197 197 160 160 -79 -79 DL Unlimited Unlimited STA58 T2O T2O 197 197 160 160 -79 -79 DL Unlimited Unlimited STA59
71 P2 7 No R3 P3PTXAXUT007 2G LPI T3E 6 20 -70 DL Unlimited STA56 T1P 6 20 -70 DL Unlimited STA58 T2Q 6 20 -70 DL Unlimited STA59
72 P2 8 No R2 P3PTXAXUT008 6G SP T2A 5 320 -79 DL Unlimited STA56 T1L 5 320 -79 DL Unlimited STA58 T2O 5 320 -79 DL Unlimited STA59
73 P2 9 No R2 P3PTXAXUT009 6G LPI T2A T2A 133 133 320 320 -79 -79 DL Unlimited Unlimited STA56 T1L T1L 133 133 320 320 -79 -79 DL Unlimited Unlimited STA58 T2O T2O 133 133 320 320 -79 -79 DL Unlimited Unlimited STA59
74 P2 1 Yes R2 P3PRXACTH001 5G LPI T1F T1F 36 36 80 80 -76 -76 UL 22Mbps 22Mbps STA4 T1I T1I 36 36 80 80 -76 -76 UL 22Mbps 22Mbps STA5 T2J T2J 36 36 80 80 -76 -76 UL 11Mbps 11Mbps STA6
75 P2 2 Yes R2 P3PRXACTH002 5G LPI T1F T1F 100 100 80 80 -76 -76 UL 22Mbps 22Mbps STA4 T1I T1I 100 100 80 80 -76 -76 UL 22Mbps 22Mbps STA5 T2J T2J 100 100 80 80 -76 -76 UL 11Mbps 11Mbps STA6
76 P2 3 Yes R2 P3PRXACTH003 5G LPI T1F T1F 161 161 80 80 -76 -76 UL 22Mbps 22Mbps STA4 T1I T1I 161 161 80 80 -76 -76 UL 22Mbps 22Mbps STA5 T2J T2J 161 161 80 80 -76 -76 UL 11Mbps 11Mbps STA6
77 P2 4 Yes R1 P3PRXACTH004 2G LPI T3E 6 20 -70 UL 22Mbps STA4 T1P 6 20 -70 UL 22Mbps STA5 T2Q 6 20 -70 UL 11Mbps STA6
78 P2 1 No R2 P3PRXACUT001 5G LPI T1F T1F 36 36 80 80 -76 -76 UL Unlimited Unlimited STA4 T1I T1I 36 36 80 80 -76 -76 UL Unlimited Unlimited STA5 T2J T2J 36 36 80 80 -76 -76 UL Unlimited Unlimited STA6
79 P2 2 No R2 P3PRXACUT002 5G LPI T1F T1F 100 100 80 80 -76 -76 UL Unlimited Unlimited STA4 T1I T1I 100 100 80 80 -76 -76 UL Unlimited Unlimited STA5 T2J T2J 100 100 80 80 -76 -76 UL Unlimited Unlimited STA6
80 P2 3 No R2 P3PRXACUT003 5G LPI T1F T1F 161 161 80 80 -76 -76 UL Unlimited Unlimited STA4 T1I T1I 161 161 80 80 -76 -76 UL Unlimited Unlimited STA5 T2J T2J 161 161 80 80 -76 -76 UL Unlimited Unlimited STA6
81 P2 4 No R1 P3PRXACUT004 2G LPI T3E 6 20 -70 UL Unlimited STA4 T1P 6 20 -70 UL Unlimited STA5 T2Q 6 20 -70 UL Unlimited STA6
82 P2 1 Yes R2 P3PTXACTH001 5G LPI T1F T1F 36 36 80 80 -76 -76 DL 22Mbps 22Mbps STA4 T1I T1I 36 36 80 80 -76 -76 DL 22Mbps 22Mbps STA5 T2J T2J 36 36 80 80 -76 -76 DL 11Mbps 11Mbps STA6
83 P2 2 Yes R2 P3PTXACTH002 5G LPI T1F T1F 100 100 80 80 -76 -76 DL 22Mbps 22Mbps STA4 T1I T1I 100 100 80 80 -76 -76 DL 22Mbps 22Mbps STA5 T2J T2J 100 100 80 80 -76 -76 DL 11Mbps 11Mbps STA6
84 P2 3 Yes R2 P3PTXACTH003 5G LPI T1F T1F 161 161 80 80 -76 -76 DL 22Mbps 22Mbps STA4 T1I T1I 161 161 80 80 -76 -76 DL 22Mbps 22Mbps STA5 T2J T2J 161 161 80 80 -76 -76 DL 11Mbps 11Mbps STA6
85 P2 4 Yes R1 P3PTXACTH004 2G LPI T3E 6 20 -70 DL 22Mbps STA4 T1P 6 20 -70 DL 22Mbps STA5 T2Q 6 20 -70 DL 11Mbps STA6
86 P2 1 No R2 P3PTXACUT001 5G LPI T1F T1F 36 36 80 80 -76 -76 DL Unlimited Unlimited STA4 T1I T1I 36 36 80 80 -76 -76 DL Unlimited Unlimited STA5 T2J T2J 36 36 80 80 -76 -76 DL Unlimited Unlimited STA6
87 P2 2 No R2 P3PTXACUT002 5G LPI T1F T1F 100 100 80 80 -76 -76 DL Unlimited Unlimited STA4 T1I T1I 100 100 80 80 -76 -76 DL Unlimited Unlimited STA5 T2J T2J 100 100 80 80 -76 -76 DL Unlimited Unlimited STA6
88 P2 3 No R2 P3PTXACUT003 5G LPI T1F T1F 161 161 80 80 -76 -76 DL Unlimited Unlimited STA4 T1I T1I 161 161 80 80 -76 -76 DL Unlimited Unlimited STA5 T2J T2J 161 161 80 80 -76 -76 DL Unlimited Unlimited STA6
89 P2 1 4 Yes No R3 R1 P3PRXBETH001 P3PTXACUT004 5G 2G LPI T1F T3E 36 6 80 20 -76 -70 UL DL 22Mbps Unlimited STA63 STA4 T1I T1P 36 6 80 20 -76 -70 UL DL 22Mbps Unlimited STA64 STA5 T2J T2Q 36 6 80 20 -76 -70 UL DL 11Mbps Unlimited STA65 STA6
P2 2 Yes R3 P3PRXBETH002 5G LPI T1F 100 80 -76 UL 22Mbps STA63 T1I 100 80 -76 UL 22Mbps STA64 T2J 100 80 -76 UL 11Mbps STA65
P2 3 Yes R3 P3PRXBETH003 5G LPI T1F 161 80 -76 UL 22Mbps STA63 T1I 161 80 -76 UL 22Mbps STA64 T2J 161 80 -76 UL 11Mbps STA65
P2 1 No R3 P3PRXBEUT001 5G LPI T1F 36 80 -76 UL Unlimited STA63 T1I 36 80 -76 UL Unlimited STA64 T2J 36 80 -76 UL Unlimited STA65
P2 2 No R3 P3PRXBEUT002 5G LPI T1F 100 80 -76 UL Unlimited STA63 T1I 100 80 -76 UL Unlimited STA64 T2J 100 80 -76 UL Unlimited STA65
P2 3 No R3 P3PRXBEUT003 5G LPI T1F 161 80 -76 UL Unlimited STA63 T1I 161 80 -76 UL Unlimited STA64 T2J 161 80 -76 UL Unlimited STA65
P2 1 Yes R3 P3PTXBETH001 5G LPI T1F 36 80 -76 DL 22Mbps STA63 T1I 36 80 -76 DL 22Mbps STA64 T2J 36 80 -76 DL 11Mbps STA65
P2 2 Yes R3 P3PTXBETH002 5G LPI T1F 100 80 -76 DL 22Mbps STA63 T1I 100 80 -76 DL 22Mbps STA64 T2J 100 80 -76 DL 11Mbps STA65
P2 3 Yes R3 P3PTXBETH003 5G LPI T1F 161 80 -76 DL 22Mbps STA63 T1I 161 80 -76 DL 22Mbps STA64 T2J 161 80 -76 DL 11Mbps STA65
P2 1 No R3 P3PTXBEUT001 5G LPI T1F 36 80 -76 DL Unlimited STA63 T1I 36 80 -76 DL Unlimited STA64 T2J 36 80 -76 DL Unlimited STA65
P2 2 No R3 P3PTXBEUT002 5G LPI T1F 100 80 -76 DL Unlimited STA63 T1I 100 80 -76 DL Unlimited STA64 T2J 100 80 -76 DL Unlimited STA65
P2 3 No R3 P3PTXBEUT003 5G LPI T1F 161 80 -76 DL Unlimited STA63 T1I 161 80 -76 DL Unlimited STA64 T2J 161 80 -76 DL Unlimited STA65
P2 7 Yes R3 P3PRXAXTH007 2G LPI T1D 6 20 -70 UL 22Mbps STA56 T1P 6 20 -70 UL 22Mbps STA58 T2Q 6 20 -70 UL 11Mbps STA59
P2 7 No R3 P3PRXAXUT007 2G LPI T1D 6 20 -70 UL Unlimited STA56 T1P 6 20 -70 UL Unlimited STA58 T2Q 6 20 -70 UL Unlimited STA59
P2 7 Yes R3 P3PTXAXTH007 2G LPI T1D 6 20 -70 DL 22Mbps STA56 T1P 6 20 -70 DL 22Mbps STA58 T2Q 6 20 -70 DL 11Mbps STA59
P2 7 No R3 P3PTXAXUT007 2G LPI T1D 6 20 -70 DL Unlimited STA56 T1P 6 20 -70 DL Unlimited STA58 T2Q 6 20 -70 DL Unlimited STA59
+79 -78
View File
@@ -6,7 +6,7 @@ from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterator
from graph import build_station_testpoint_map
from test_config import serialize_station_testpoint_map
APP_ROOT = Path(__file__).resolve().parent
@@ -123,13 +123,6 @@ def init_db(db_path: str | Path = DB_PATH) -> None:
minutes INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS graph_cache (
name TEXT PRIMARY KEY,
payload_json TEXT NOT NULL,
test_count INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS rerun_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
detected_date TEXT NOT NULL,
@@ -166,14 +159,38 @@ def init_db(db_path: str | Path = DB_PATH) -> None:
def _serialize_station_testpoint_map(config: dict[str, dict[str, str | None]]) -> str:
"""Compute and serialize the station-to-testpoint map from a test config."""
station_map = build_station_testpoint_map(config)
return json.dumps(station_map)
return serialize_station_testpoint_map(config)
def upsert_tests(records: list[TestRecord], db_path: str | Path = DB_PATH) -> int:
if not records:
return 0
values: list[tuple[Any, ...]] = []
for r in records:
station_testpoint_map = _serialize_station_testpoint_map(r.config)
values.append(
(
r.test_id,
r.device,
r.test_type,
r.rotation,
r.rx_tx,
int(r.has_coe_pair),
json.dumps(r.coe_pairing or []),
json.dumps(r.config),
r.priority,
r.victim_band,
int(r.throttled),
r.estimated_minutes,
r.status,
int(r.excluded),
json.dumps(r.raw_payload or {}),
station_testpoint_map,
)
)
with get_connection(db_path) as conn:
conn.executemany(
"""
@@ -200,27 +217,7 @@ def upsert_tests(records: list[TestRecord], db_path: str | Path = DB_PATH) -> in
station_testpoint_map = excluded.station_testpoint_map,
updated_at = CURRENT_TIMESTAMP
""",
[
(
r.test_id,
r.device,
r.test_type,
r.rotation,
r.rx_tx,
int(r.has_coe_pair),
json.dumps(r.coe_pairing or []),
json.dumps(r.config),
r.priority,
r.victim_band,
int(r.throttled),
r.estimated_minutes,
r.status,
int(r.excluded),
json.dumps(r.raw_payload or {}),
_serialize_station_testpoint_map(r.config),
)
for r in records
],
values,
)
return len(records)
@@ -253,52 +250,6 @@ def read_settings(db_path: str | Path = DB_PATH) -> dict[str, Any]:
return {row["key"]: json.loads(row["value_json"]) for row in rows}
def save_graph_cache(
name: str,
payload: dict[str, list[str]],
test_count: int,
db_path: str | Path = DB_PATH,
) -> None:
with get_connection(db_path) as conn:
conn.execute(
"""
INSERT INTO graph_cache(name, payload_json, test_count, updated_at)
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT(name) DO UPDATE SET
payload_json = excluded.payload_json,
test_count = excluded.test_count,
updated_at = CURRENT_TIMESTAMP
""",
(name, json.dumps(payload), int(test_count)),
)
def load_graph_cache(name: str, db_path: str | Path = DB_PATH) -> dict[str, Any] | None:
with get_connection(db_path) as conn:
row = conn.execute(
"""
SELECT payload_json, test_count, updated_at
FROM graph_cache
WHERE name = ?
""",
(name,),
).fetchone()
if row is None:
return None
return {
"payload": json.loads(row["payload_json"] or "{}"),
"test_count": int(row["test_count"]),
"updated_at": row["updated_at"],
}
def delete_graph_cache(name: str, db_path: str | Path = DB_PATH) -> None:
with get_connection(db_path) as conn:
conn.execute("DELETE FROM graph_cache WHERE name = ?", (name,))
def _parse_rule_tokens(rule: str | None) -> list[str]:
if not rule:
return []
@@ -345,7 +296,7 @@ def _match_any_band_value(record: TestRecord, key: str, token_suffix: str) -> bo
target_num = _extract_signed_int(token_suffix)
target_compact = _normalize_compact(token_suffix)
keys = ("Station 1", "Station 2", "Station 3") if record.test_type == "P3P" else ("5G", "6G", "2G")
keys = ("STATION 1", "STATION 2", "STATION 3") if record.test_type == "P3P" else ("5G", "6G", "2G")
for band in keys:
entry = _band_entry(record.config, band)
@@ -579,6 +530,56 @@ def get_schedule_week(start_date: str, db_path: str | Path = DB_PATH) -> list[Sc
return result
def get_schedule_rows_for_latest_version(db_path: str | Path = DB_PATH) -> list[ScheduleRow]:
with get_connection(db_path) as conn:
version_row = conn.execute("SELECT MAX(schedule_version) AS latest FROM schedules").fetchone()
latest = version_row["latest"]
if latest is None:
return []
rows = conn.execute(
"""
SELECT
s.test_id,
s.device,
s.scheduled_date,
s.shift_index,
s.sequence_in_shift,
t.test_type,
t.rotation,
t.config_json,
t.status,
t.priority,
t.estimated_minutes
FROM schedules s
JOIN tests t ON t.test_id = s.test_id AND t.device = s.device
WHERE s.schedule_version = ?
ORDER BY s.scheduled_date, s.shift_index, s.sequence_in_shift
""",
(latest,),
).fetchall()
result: list[ScheduleRow] = []
for row in rows:
result.append(
ScheduleRow(
test_id=row["test_id"],
device=row["device"],
scheduled_date=row["scheduled_date"],
shift_index=int(row["shift_index"]),
sequence_in_shift=int(row["sequence_in_shift"]),
test_type=row["test_type"],
rotation=row["rotation"],
config=json.loads(row["config_json"] or "{}"),
status=row["status"],
priority=int(row["priority"]),
estimated_minutes=int(row["estimated_minutes"]),
)
)
return result
def mark_tests_completed(test_ids_with_device: list[tuple[str, str]], db_path: str | Path = DB_PATH) -> None:
if not test_ids_with_device:
return
-179
View File
@@ -1,179 +0,0 @@
from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Any
# Immutable-in-practice graph state for the loaded DUT test set.
_TESTS_BY_ID: dict[str, Any] = {}
_GRAPH: dict[str, set[str]] = {}
DUT = os.getenv("DUT", "CGW453").strip()
APP_ROOT = Path(__file__).resolve().parent
DB_PATH = APP_ROOT / "scheduler.db"
GRAPH_CACHE_NAME = "dut_compatibility"
def reset_graph_state() -> None:
global _TESTS_BY_ID, _GRAPH
_TESTS_BY_ID = {}
_GRAPH = {}
def _get_station_map(test: Any) -> dict[str, str]:
"""Get station testpoint map from test record or compute it from config."""
# Try to get from serialized map first (cached from DB)
if hasattr(test, 'station_testpoint_map') and test.station_testpoint_map:
try:
return json.loads(test.station_testpoint_map)
except (json.JSONDecodeError, TypeError):
pass
# Safeguard: return empty map
return {}
def is_graph_built() -> bool:
return bool(_GRAPH)
def build_graph_once(tests: list[Any]) -> dict[str, set[str]]:
"""Build DUT-only compatibility adjacency graph once per load lifecycle."""
global _TESTS_BY_ID, _GRAPH
if _GRAPH:
return _GRAPH
# Keep only one representative per DUT test_id.
_TESTS_BY_ID = {str(t.test_id): t for t in tests if getattr(t, "test_id", None)}
_GRAPH = _build_graph(_TESTS_BY_ID)
return _GRAPH
def serialize_graph(graph: dict[str, set[str]]) -> dict[str, list[str]]:
return {str(test_id): sorted(str(neighbor) for neighbor in neighbors) for test_id, neighbors in graph.items()}
def deserialize_graph(data: dict[str, list[str]]) -> dict[str, set[str]]:
deserialized: dict[str, set[str]] = {}
for test_id, neighbors in (data or {}).items():
deserialized[str(test_id)] = {str(neighbor) for neighbor in (neighbors or [])}
return deserialized
def _persist_graph(db_path: str | Path = DB_PATH) -> None:
import db as db_module
db_module.save_graph_cache(
name=GRAPH_CACHE_NAME,
payload=serialize_graph(_GRAPH),
test_count=len(_GRAPH),
db_path=db_path,
)
def _load_graph_from_db(db_path: str | Path = DB_PATH) -> bool:
global _GRAPH
import db as db_module
cached = db_module.load_graph_cache(GRAPH_CACHE_NAME, db_path)
if cached is None:
return False
_GRAPH = deserialize_graph(cached.get("payload") or {})
return True
def build_and_persist_graph(tests: list[Any], db_path: str | Path = DB_PATH) -> dict[str, set[str]]:
graph = build_graph_once(tests)
_persist_graph(db_path)
return graph
def get_graph(
db_path: str | Path = DB_PATH,
dut_tests: list[Any] | None = None,
) -> dict[str, set[str]]:
if _GRAPH:
return _GRAPH
if _load_graph_from_db(db_path):
return _GRAPH
if dut_tests is None:
import db as db_module
dut_tests = db_module.list_tests_for_device(DUT, db_path)
build_and_persist_graph(dut_tests or [], db_path)
return _GRAPH
def _build_graph(tests: dict[str, Any]) -> dict[str, set[str]]:
test_ids = list(tests.keys())
graph = {test_id: set() for test_id in test_ids}
for i in range(len(test_ids)):
for j in range(i + 1, len(test_ids)):
a_id = test_ids[i]
b_id = test_ids[j]
a = tests[a_id]
b = tests[b_id]
if _compatible(a, b):
graph[a_id].add(b_id)
graph[b_id].add(a_id)
return graph
def _compatible(a: Any, b: Any) -> bool:
station_map_a = _get_station_map(a)
station_map_b = _get_station_map(b)
if _same_station_to_testpoint(station_map_a, station_map_b, a, b) and _same_testpoint_to_station(station_map_a, station_map_b, a, b):
return True
return False
def _same_station_to_testpoint(
station_map_a: dict[str, str],
station_map_b: dict[str, str], test_a: Any, test_b: Any
) -> bool:
overlap_stations = set(station_map_a.keys()) & set(station_map_b.keys())
for station in overlap_stations:
if station_map_a[station] != station_map_b[station]:
return False
return True
def _same_testpoint_to_station(
station_map_a: dict[str, str],
station_map_b: dict[str, str], test_a: Any, test_b: Any
) -> bool:
overlap_testpoints = set(station_map_a.values()) & set(station_map_b.values())
for testpoint in overlap_testpoints:
stations_a = {s for s, t in station_map_a.items() if t == testpoint}
stations_b = {s for s, t in station_map_b.items() if t == testpoint}
if stations_a != stations_b:
return False
return True
def build_station_testpoint_map(config: dict[str, dict[str, str | None]]) -> dict[str, str]:
station_to_testpoint: dict[str, str] = {}
def _add_entry(entry: dict[str, str | None]) -> None:
testpoint = _norm(entry.get("test_point"))
sta_raw = _norm(entry.get("sta"))
if not testpoint or not sta_raw:
return
for sta in sta_raw.split(","):
sta_clean = _norm(sta)
if sta_clean:
station_to_testpoint[sta_clean] = testpoint
for band in ("5G", "6G", "2G"):
entry = config.get(band) or {}
_add_entry(entry)
for station_key in ("Station 1", "Station 2", "Station 3"):
entry = config.get(station_key) or {}
_add_entry(entry)
return station_to_testpoint
def _norm(value: Any) -> str:
if value is None:
return ""
return " ".join(str(value).strip().upper().split())
+95 -87
View File
@@ -21,24 +21,24 @@ P2P_COE_REQUIRED_COLUMNS = [
"TC ID",
"Victim Band",
"6GHz Power Mode",
"5G Test Point",
"5G Channel",
"5G Bandwidth",
"5G RSSI",
"5G Direction",
"5G STA",
"6G Test Point",
"6G Channel",
"6G Bandwidth",
"6G RSSI",
"6G Direction",
"6G STA",
"2G Test Point",
"2G Channel",
"2G Bandwidth",
"2G RSSI",
"2G Direction",
"2G STA",
"5G_Test Point",
"5G_Channel",
"5G_Bandwidth",
"5G_RSSI",
"5G_Direction",
"5G_STA",
"6G_Test Point",
"6G_Channel",
"6G_Bandwidth",
"6G_RSSI",
"6G_Direction",
"6G_STA",
"2G_Test Point",
"2G_Channel",
"2G_Bandwidth",
"2G_RSSI",
"2G_Direction",
"2G_STA",
]
P3P_REQUIRED_COLUMNS = [
@@ -49,27 +49,27 @@ P3P_REQUIRED_COLUMNS = [
"TC ID",
"Band",
"6GHz Power Mode",
"Station 1 Test Point",
"Station 1 Channel",
"Station 1 Bandwidth",
"Station 1 RSSI",
"Station 1 Direction",
"Station 1 Rate",
"Station 1 STA",
"Station 2 Test Point",
"Station 2 Channel",
"Station 2 Bandwidth",
"Station 2 RSSI",
"Station 2 Direction",
"Station 2 Rate",
"Station 2 STA",
"Station 3 Test Point",
"Station 3 Channel",
"Station 3 Bandwidth",
"Station 3 RSSI",
"Station 3 Direction",
"Station 3 Rate",
"Station 3 STA",
"STATION 1_Test Point",
"STATION 1_Channel",
"STATION 1_Bandwidth",
"STATION 1_RSSI",
"STATION 1_Direction",
"STATION 1_Rate",
"STATION 1_STA",
"STATION 2_Test Point",
"STATION 2_Channel",
"STATION 2_Bandwidth",
"STATION 2_RSSI",
"STATION 2_Direction",
"STATION 2_Rate",
"STATION 2_STA",
"STATION 3_Test Point",
"STATION 3_Channel",
"STATION 3_Bandwidth",
"STATION 3_RSSI",
"STATION 3_Direction",
"STATION 3_Rate",
"STATION 3_STA",
]
RUNTIME_DEFAULTS = {
@@ -230,7 +230,6 @@ def _detect_csv_format(normalized_fieldnames: set[str]) -> str:
f"{', '.join(p2p_coe_missing)}; missing columns for P3P format: {', '.join(p3p_missing)}"
)
def parse_target_csv(
csv_path: str | Path | list[str | Path] | tuple[str | Path, ...],
smb_credentials: dict[str, Any] | None = None,
@@ -476,11 +475,20 @@ def _victim_band_signature(row: dict[str, str]) -> tuple[str, ...] | None:
if band is None:
return None
prefix = f"{band} "
test_point = _normalize_value(_row_get(row, f"{prefix}Test Point"))
channel = _normalize_value(_row_get(row, f"{prefix}Channel"))
rssi = _normalize_value(_row_get(row, f"{prefix}RSSI"))
bandwidth = _normalize_value(_row_get(row, f"{prefix}Bandwidth"))
# Source CSVs may use either "5G_Test Point" or "5G Test Point" style headers.
prefixes = (f"{band}_", f"{band} ")
def _pick_value(suffix: str) -> str:
for prefix in prefixes:
value = _row_get(row, f"{prefix}{suffix}")
if _normalize_value(value):
return _normalize_value(value)
return ""
test_point = _pick_value("Test Point")
channel = _pick_value("Channel")
rssi = _pick_value("RSSI")
bandwidth = _pick_value("Bandwidth")
# Direction is intentionally ignored for COE pairing matching.
if not all([test_point, channel, rssi, bandwidth]):
@@ -515,61 +523,61 @@ def _normalize_yes_no(value: str | None) -> bool:
def _build_legacy_config(row: dict[str, str]) -> dict[str, dict[str, str | None]]:
return {
"5G": {
"test_point": _empty_to_none(_row_get(row, "5G Test Point")),
"channel": _empty_to_none(_row_get(row, "5G Channel")),
"bandwidth": _empty_to_none(_row_get(row, "5G Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "5G RSSI")),
"direction": _empty_to_none(_row_get(row, "5G Direction")),
"sta": _empty_to_none(_row_get(row, "5G STA")),
"test_point": _empty_to_none(_row_get(row, "5G_Test Point")),
"channel": _empty_to_none(_row_get(row, "5G_Channel")),
"bandwidth": _empty_to_none(_row_get(row, "5G_Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "5G_RSSI")),
"direction": _empty_to_none(_row_get(row, "5G_Direction")),
"sta": _empty_to_none(_row_get(row, "5G_STA")),
},
"6G": {
"power_mode": _empty_to_none(_row_get(row, "6GHz Power Mode")),
"test_point": _empty_to_none(_row_get(row, "6G Test Point")),
"channel": _empty_to_none(_row_get(row, "6G Channel")),
"bandwidth": _empty_to_none(_row_get(row, "6G Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "6G RSSI")),
"direction": _empty_to_none(_row_get(row, "6G Direction")),
"sta": _empty_to_none(_row_get(row, "6G STA")),
"test_point": _empty_to_none(_row_get(row, "6G_Test Point")),
"channel": _empty_to_none(_row_get(row, "6G_Channel")),
"bandwidth": _empty_to_none(_row_get(row, "6G_Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "6G_RSSI")),
"direction": _empty_to_none(_row_get(row, "6G_Direction")),
"sta": _empty_to_none(_row_get(row, "6G_STA")),
},
"2G": {
"test_point": _empty_to_none(_row_get(row, "2G Test Point")),
"channel": _empty_to_none(_row_get(row, "2G Channel")),
"bandwidth": _empty_to_none(_row_get(row, "2G Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "2G RSSI")),
"direction": _empty_to_none(_row_get(row, "2G Direction")),
"sta": _empty_to_none(_row_get(row, "2G STA")),
"test_point": _empty_to_none(_row_get(row, "2G_Test Point")),
"channel": _empty_to_none(_row_get(row, "2G_Channel")),
"bandwidth": _empty_to_none(_row_get(row, "2G_Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "2G_RSSI")),
"direction": _empty_to_none(_row_get(row, "2G_Direction")),
"sta": _empty_to_none(_row_get(row, "2G_STA")),
},
}
def _build_p3p_config(row: dict[str, str]) -> dict[str, dict[str, str | None]]:
return {
"Station 1": {
"test_point": _empty_to_none(_row_get(row, "Station 1 Test Point")),
"channel": _empty_to_none(_row_get(row, "Station 1 Channel")),
"bandwidth": _empty_to_none(_row_get(row, "Station 1 Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "Station 1 RSSI")),
"direction": _empty_to_none(_row_get(row, "Station 1 Direction")),
"rate": _empty_to_none(_row_get(row, "Station 1 Rate")),
"sta": _empty_to_none(_row_get(row, "Station 1 STA")),
"STATION1": {
"test_point": _empty_to_none(_row_get(row, "STATION 1_Test Point")),
"channel": _empty_to_none(_row_get(row, "STATION 1_Channel")),
"bandwidth": _empty_to_none(_row_get(row, "STATION 1_Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "STATION 1_RSSI")),
"direction": _empty_to_none(_row_get(row, "STATION 1_Direction")),
"rate": _empty_to_none(_row_get(row, "STATION 1_Rate")),
"sta": _empty_to_none(_row_get(row, "STATION 1_STA")),
},
"Station 2": {
"test_point": _empty_to_none(_row_get(row, "Station 2 Test Point")),
"channel": _empty_to_none(_row_get(row, "Station 2 Channel")),
"bandwidth": _empty_to_none(_row_get(row, "Station 2 Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "Station 2 RSSI")),
"direction": _empty_to_none(_row_get(row, "Station 2 Direction")),
"rate": _empty_to_none(_row_get(row, "Station 2 Rate")),
"sta": _empty_to_none(_row_get(row, "Station 2 STA")),
"STATION2": {
"test_point": _empty_to_none(_row_get(row, "STATION 2_Test Point")),
"channel": _empty_to_none(_row_get(row, "STATION 2_Channel")),
"bandwidth": _empty_to_none(_row_get(row, "STATION 2_Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "STATION 2_RSSI")),
"direction": _empty_to_none(_row_get(row, "STATION 2_Direction")),
"rate": _empty_to_none(_row_get(row, "STATION 2_Rate")),
"sta": _empty_to_none(_row_get(row, "STATION 2_STA")),
},
"Station 3": {
"test_point": _empty_to_none(_row_get(row, "Station 3 Test Point")),
"channel": _empty_to_none(_row_get(row, "Station 3 Channel")),
"bandwidth": _empty_to_none(_row_get(row, "Station 3 Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "Station 3 RSSI")),
"direction": _empty_to_none(_row_get(row, "Station 3 Direction")),
"rate": _empty_to_none(_row_get(row, "Station 3 Rate")),
"sta": _empty_to_none(_row_get(row, "Station 3 STA")),
"STATION3": {
"test_point": _empty_to_none(_row_get(row, "STATION 3_Test Point")),
"channel": _empty_to_none(_row_get(row, "STATION 3_Channel")),
"bandwidth": _empty_to_none(_row_get(row, "STATION 3_Bandwidth")),
"rssi": _empty_to_none(_row_get(row, "STATION 3_RSSI")),
"direction": _empty_to_none(_row_get(row, "STATION 3_Direction")),
"rate": _empty_to_none(_row_get(row, "STATION 3_Rate")),
"sta": _empty_to_none(_row_get(row, "STATION 3_STA")),
},
}
+283 -607
View File
@@ -2,616 +2,292 @@ from __future__ import annotations
import os
from dataclasses import dataclass
from datetime import date, datetime, timedelta
from typing import Any
import graph
# Bundle priority tiers for scheduling order (lower number = higher priority)
BUNDLE_PRIORITY_FAILED = 0 # Failed tests requiring rerun
BUNDLE_PRIORITY_P2P_WITH_COE = 1 # P2P tests with COE pairs
BUNDLE_PRIORITY_P2P_ONLY = 2 # P2P tests without COE pairs (RX/TX bundled)
BUNDLE_PRIORITY_COE_ONLY = 3 # COE tests without P2P pairing (should be rare/unschedulable)
BUNDLE_PRIORITY_P3P = 4 # P3P tests
DUT = os.getenv("DUT", "CGW453").strip()
REF = os.getenv("REF", "CGW452").strip()
TestKey = tuple[str, str]
@dataclass(frozen=True)
class SchedulerTest:
test_id: str
device: str
test_type: str
rotation: str | None
rx_tx: str | None
has_coe_pair: bool
coe_pairing: list[str]
config: dict[str, dict[str, str | None]]
throttled: bool
estimated_minutes: int
priority: int
raw_payload: dict[str, Any]
from datetime import date
from test_window import get_shift_sequence_with_capacity, next_window_start_date
from test_bundle import Test, TestBundle, build_test_bundles, bundle_pair_lookup
DUT = (os.getenv("DUT") or "DUT").strip()
REF = (os.getenv("REF") or "REF").strip()
@dataclass(frozen=True)
class ScheduleEntry:
test_id: str
device: str
scheduled_date: str
shift_index: int
sequence_in_shift: int
@dataclass(frozen=True)
class TestBundle:
"""A bundle of tests that must be run together in sequence."""
test_ids: list[TestKey]
priority_tier: int
total_minutes: int
# Global scheduler state. Graph is built once per loaded test set.
_TESTS_BY_ID: dict[TestKey, SchedulerTest] = {}
_ACTIVE_DUT: dict[TestKey, int] = {}
_ACTIVE_REF: dict[TestKey, int] = {}
def reset_scheduler_state() -> None:
global _TESTS_BY_ID, _ACTIVE_DUT, _ACTIVE_REF
_TESTS_BY_ID = {}
_ACTIVE_DUT = {}
_ACTIVE_REF = {}
def initialize_scheduler_state(tests: list[SchedulerTest]) -> None:
"""Initialize active scheduler state once for the current loaded schedulable dataset."""
global _TESTS_BY_ID, _ACTIVE_DUT, _ACTIVE_REF
if _TESTS_BY_ID:
return
_TESTS_BY_ID = {(t.test_id, t.device): t for t in tests}
_ACTIVE_DUT = {
test_id: _derive_priority(test, set(), set(), tests)
for test_id, test in _TESTS_BY_ID.items() if test.device == DUT
}
_ACTIVE_REF = {
test_id: _derive_priority(test, set(), set(), tests)
for test_id, test in _TESTS_BY_ID.items() if test.device == REF
}
def set_user_priorities(top_priority_tests: set[str], lowest_priority_tests: set[str]) -> None:
"""Update active priority map in-place using user overrides."""
if not _TESTS_BY_ID:
return
all_tests = list(_TESTS_BY_ID.values())
for key in list(_ACTIVE_DUT.keys()):
test = _TESTS_BY_ID.get(key)
if test is None:
continue
_ACTIVE_DUT[key] = _derive_priority(test, top_priority_tests, lowest_priority_tests, all_tests)
for key in list(_ACTIVE_REF.keys()):
test = _TESTS_BY_ID.get(key)
if test is None:
continue
_ACTIVE_REF[key] = _derive_priority(test, top_priority_tests, lowest_priority_tests, all_tests)
def remove_from_active(completed_test_ids: set[str]) -> None:
"""Remove completed/invalid tests from active list without rebuilding graph."""
for test_id in completed_test_ids:
_ACTIVE_DUT.pop((test_id, DUT), None)
_ACTIVE_REF.pop((test_id, REF), None)
def compile_schedule(
tests: list[SchedulerTest],
start_date: str | None,
holiday_dates: set[str],
top_priority_tests: set[str],
lowest_priority_tests: set[str],
daytime_testing_today: bool,
) -> tuple[list[ScheduleEntry], str | None]:
"""Compile an optimized schedule for the given tests.
Uses the greedy algorithm respecting shift sequences per design:
- Mon-Thu: shift 3 (5pm-1am) then shift 1 next day (1am-10am)
- Friday: shift 3 through Monday shift 1 (4-day window)
Returns a list of ScheduleEntry objects and the completion date.
"""
if not tests:
return [], None
initialize_scheduler_state(tests)
set_user_priorities(top_priority_tests, lowest_priority_tests)
# Use a local working copy for this compile run. Global active remains until result processing removes IDs.
dut_active_priority: dict[TestKey, int] = dict(_ACTIVE_DUT)
ref_active_priority: dict[TestKey, int] = dict(_ACTIVE_REF)
entries: list[ScheduleEntry] = []
window_start_date = _parse_date(start_date)
current_date = window_start_date
last_date: str | None = None
daytime_shift2_window_pending = daytime_testing_today
date_device_lock: dict[str, str] = {}
while dut_active_priority or ref_active_priority:
is_special_daytime_shift2_window = (
daytime_shift2_window_pending
and current_date == window_start_date
and current_date.weekday() < 5
and current_date.isoformat() not in holiday_dates
)
# Get the shift sequence for current date (respects Mon/Fri/weekend rules)
shift_sequence = _get_shift_sequence(
current_date,
holiday_dates,
daytime_shift2_only=is_special_daytime_shift2_window,
)
if not shift_sequence:
break # No valid shift sequence
dut_active_test_ids: set[TestKey] = set(dut_active_priority.keys())
ref_active_test_ids: set[TestKey] = set(ref_active_priority.keys())
bundles = _create_bundles(dut_active_test_ids, ref_active_test_ids, _TESTS_BY_ID, top_priority_tests)
shift_capacities = {
(date_obj, shift_idx): _shift_capacity_for_date(
current_date=date_obj,
holiday_dates=holiday_dates,
daytime_shift2_only=is_special_daytime_shift2_window and date_obj == current_date,
).get(shift_idx, 0)
for date_obj, shift_idx in shift_sequence
}
forced_device: str | None = None
for date_obj, _shift_idx in shift_sequence:
locked_device = date_device_lock.get(date_obj.isoformat())
if locked_device is None:
continue
if forced_device is None:
forced_device = locked_device
elif forced_device != locked_device:
raise ValueError(f"Conflicting device locks in shift sequence for {date_obj.isoformat()}")
placed_entries, placed_test_ids, placed_last_date, window_device = _fit_bundles_to_shifts(
bundles=bundles,
shift_sequence=shift_sequence,
tests=_TESTS_BY_ID,
graph_by_test_id=graph.get_graph(),
shift_capacities=shift_capacities,
forced_device=forced_device,
)
entries.extend(placed_entries)
if window_device is not None:
for date_obj, _shift_idx in shift_sequence:
date_device_lock.setdefault(date_obj.isoformat(), window_device)
for key in placed_test_ids:
dut_active_priority.pop(key, None)
ref_active_priority.pop(key, None)
if placed_last_date is not None:
last_date = placed_last_date
# Daytime testing special handling applies only to the first scheduling window.
daytime_shift2_window_pending = False
# Move to next scheduling window start date
# After shift 1 (1am-9am), there's shift 2 if daytime testing, then shift 3 (5pm)
# After shift 3 (5pm), shift 1 is next day (1am)
if shift_sequence:
last_sequence_date = shift_sequence[-1][0]
last_shift_index = shift_sequence[-1][1]
if last_shift_index == 1:
# Last shift was 1 (1am-9am); next shift 3 is same day (5pm)
current_date = last_sequence_date
elif last_shift_index == 2:
# Last shift was 2 (9am-5pm); next shift 3 is same day (5pm)
current_date = last_sequence_date
else: # last_shift_index == 3
# Last shift was 3 (5pm-1am); next shift 1 is next day (1am)
current_date = last_sequence_date + timedelta(days=1)
else:
current_date = current_date + timedelta(days=1)
return entries, last_date
def format_schedule_for_frontend(
entries: list[ScheduleEntry],
) -> dict[str, dict[int, list[SchedulerTest]]]:
"""Convert flat ScheduleEntry list to nested format for frontend.
Returns: {date_string: {shift_index: [SchedulerTest, ...]}, ...}
"""
schedule: dict[str, dict[int, list[SchedulerTest]]] = {}
for entry in entries:
if entry.scheduled_date not in schedule:
schedule[entry.scheduled_date] = {1: [], 2: [], 3: []}
test = _TESTS_BY_ID.get((entry.test_id, entry.device))
if test:
schedule[entry.scheduled_date][entry.shift_index].append(test)
return schedule
def _derive_priority(
test: SchedulerTest,
top_priority_tests: set[str],
lowest_priority_tests: set[str],
all_tests: list[SchedulerTest],
) -> int:
if test.test_id in top_priority_tests:
return 1
if test.test_id in lowest_priority_tests:
return 5
return test.priority
def _fit_bundles_to_shifts(
bundles: list[TestBundle],
shift_sequence: list[tuple[date, int]],
tests: dict[TestKey, SchedulerTest],
graph_by_test_id: dict[str, set[str]],
shift_capacities: dict[tuple[date, int], int],
forced_device: str | None = None,
) -> tuple[list[ScheduleEntry], set[TestKey], str | None, str | None]:
"""Fit bundles into a shift sequence window (e.g., one day or one weekend).
Returns:
- List of ScheduleEntry for placed tests
- Set of test_ids that were placed
- Last scheduled date
- Device used for this window, if any bundle was placed
Bundles are placed in sequence order and may span multiple shifts inside the same window.
"""
entries: list[ScheduleEntry] = []
placed_test_ids: set[TestKey] = set()
last_date: str | None = None
window_device: str | None = None
# Track state per shift
shift_state: dict[tuple[date, int], dict] = {}
for date_shift in shift_sequence:
shift_state[date_shift] = {
'remaining_minutes': shift_capacities.get(date_shift, 0),
'sequence_counter': 1,
}
shift_positions = {date_shift: idx for idx, date_shift in enumerate(shift_sequence)}
current_shift_pos = 0
window_placed_test_ids: set[str] = set()
window_placed_devices: set[str] = set()
for bundle in bundles:
# Skip bundles that are already fully placed.
if any(test_id in placed_test_ids for test_id in bundle.test_ids):
continue
if not bundle.test_ids:
continue
bundle_devices = {test_key[1] for test_key in bundle.test_ids}
if len(bundle_devices) > 1:
# A bundle cannot span devices because each window is single-device only.
continue
if forced_device is not None and forced_device not in bundle_devices:
continue
# Keep all tests in the same window pairwise-compatible.
if window_placed_test_ids:
is_compatible_with_window = True
bundle_test_ids_only = {test_key[0] for test_key in bundle.test_ids}
for placed_test_id in window_placed_test_ids:
placed_neighbors = graph_by_test_id.get(placed_test_id, set())
if not all(current_test_id in placed_neighbors for current_test_id in bundle_test_ids_only):
is_compatible_with_window = False
break
if not is_compatible_with_window:
continue
if window_placed_devices and not bundle_devices.issubset(window_placed_devices):
continue
# Ensure the whole bundle can still fit somewhere in the remaining window.
remaining_window = sum(
shift_state[date_shift]['remaining_minutes'] for date_shift in shift_sequence[current_shift_pos:]
)
if remaining_window < bundle.total_minutes:
continue
bundle_start_shift_pos = current_shift_pos
placed_bundle_tests: list[tuple[TestKey, tuple[date, int]]] = []
failed = False
for key in bundle.test_ids:
test = tests[key]
prev_remaining = 0
while current_shift_pos < len(shift_sequence):
date_shift = shift_sequence[current_shift_pos]
state = shift_state[date_shift]
state["remaining_minutes"] += prev_remaining # Add back any leftover from previous shift
if state['remaining_minutes'] >= test.estimated_minutes:
#print(f"remaining minutes for {date_shift}: {state['remaining_minutes']} - placing {key} ({test.estimated_minutes}m)")
entries.append(
ScheduleEntry(
test_id=test.test_id,
device=test.device,
scheduled_date=date_shift[0].isoformat(),
shift_index=date_shift[1],
sequence_in_shift=state['sequence_counter'],
)
)
state['sequence_counter'] += 1
state['remaining_minutes'] -= test.estimated_minutes
placed_test_ids.add(key)
placed_bundle_tests.append((key, date_shift))
last_date = date_shift[0].isoformat()
break
# Move to the next shift in the sequence and keep the bundle contiguous.
current_shift_pos += 1
prev_remaining = state['remaining_minutes']
if current_shift_pos >= len(shift_sequence):
failed = True
break
if failed:
break
if failed:
# Roll back any partially placed tests from this bundle.
for key, date_shift in reversed(placed_bundle_tests):
state = shift_state[date_shift]
state['remaining_minutes'] += tests[key].estimated_minutes
state['sequence_counter'] -= 1
entries.pop()
placed_test_ids.discard(key)
last_date = None
current_shift_pos = bundle_start_shift_pos
continue
if placed_bundle_tests:
window_placed_test_ids.update(test_key[0] for test_key in bundle.test_ids)
window_placed_devices.update(bundle_devices)
if window_device is None:
window_device = next(iter(bundle_devices))
return entries, placed_test_ids, last_date, window_device
def _create_bundles(
dut_active_test_ids: set[TestKey],
ref_active_test_ids: set[TestKey],
tests: dict[TestKey, SchedulerTest],
top_priority_tests: set[str] | None = None,
) -> list[TestBundle]:
"""Create bundles of tests that must run together, sorted by priority tier and efficiency.
Bundling rules:
- P2P with COE pairs: [P2P, COE1, COE2, ...]
- P2P without COE: [P2P_RX, P2P_TX] if both active
- COE without a linked active P2P: [COE] (standalone bundle)
- P3P: individual test (not bundled)
Returned list is sorted by (priority_tier, total_minutes) to place small/high-priority bundles first.
"""
if top_priority_tests is None:
top_priority_tests = set()
bundles: list[TestBundle] = []
processed: set[TestKey] = set()
dut_by_id: dict[str, TestKey] = {test_id: key for test_id, _device in dut_active_test_ids for key in [(test_id, DUT)] if key in dut_active_test_ids}
ref_by_id: dict[str, TestKey] = {test_id: key for test_id, _device in ref_active_test_ids for key in [(test_id, REF)] if key in ref_active_test_ids}
all_ids = sorted(set(dut_by_id.keys()) | set(ref_by_id.keys()))
def _active_key(test_id: str, device: str) -> TestKey | None:
key = (test_id, device)
if key in dut_active_test_ids or key in ref_active_test_ids:
return key
return None
for test_id in all_ids:
seed_keys = [key for key in (_active_key(test_id, DUT), _active_key(test_id, REF)) if key is not None and key not in processed]
if not seed_keys:
continue
representative_key = seed_keys[0]
test = tests[representative_key]
if test.test_type == "P2P":
if test.has_coe_pair:
for device in (DUT, REF):
bundle_test_ids: list[TestKey] = []
base_key = _active_key(test_id, device)
if base_key is not None and base_key not in processed:
bundle_test_ids.append(base_key)
for coe_id in sorted(test.coe_pairing):
coe_key = _active_key(coe_id, device)
if coe_key is not None and coe_key not in processed:
bundle_test_ids.append(coe_key)
if not bundle_test_ids:
continue
processed.update(bundle_test_ids)
# Check if any test in bundle is top priority
bundle_test_ids_only = {key[0] for key in bundle_test_ids}
if bundle_test_ids_only & top_priority_tests:
priority_tier = BUNDLE_PRIORITY_FAILED # Highest priority (0)
else:
priority_tier = BUNDLE_PRIORITY_P2P_WITH_COE
bundles.append(TestBundle(
test_ids=bundle_test_ids,
priority_tier=priority_tier,
total_minutes=sum(tests[key].estimated_minutes for key in bundle_test_ids)
))
else:
for device in (DUT, REF):
bundle_test_ids: list[TestKey] = []
base_key = _active_key(test_id, device)
if base_key is None or base_key in processed:
continue
bundle_test_ids.append(base_key)
pair_id = _rx_tx_pair_id(test_id, set(k[0] for k in (dut_active_test_ids if device == DUT else ref_active_test_ids)))
if pair_id:
pair_key = _active_key(pair_id, device)
if pair_key is not None and pair_key not in processed and pair_key not in bundle_test_ids:
bundle_test_ids.append(pair_key)
processed.update(bundle_test_ids)
# Check if any test in bundle is top priority
bundle_test_ids_only = {key[0] for key in bundle_test_ids}
if bundle_test_ids_only & top_priority_tests:
priority_tier = BUNDLE_PRIORITY_FAILED # Highest priority (0)
else:
priority_tier = BUNDLE_PRIORITY_P2P_ONLY
bundles.append(TestBundle(
test_ids=bundle_test_ids,
priority_tier=priority_tier,
total_minutes=sum(tests[key].estimated_minutes for key in bundle_test_ids)
))
elif test.test_type == "P3P":
for device in (DUT, REF):
key = _active_key(test_id, device)
if key is None or key in processed:
continue
bundle_test_ids = [key]
# Check if any test in bundle is top priority
if key[0] in top_priority_tests:
priority_tier = BUNDLE_PRIORITY_FAILED # Highest priority (0)
else:
priority_tier = BUNDLE_PRIORITY_P3P
bundles.append(TestBundle(
test_ids=bundle_test_ids,
priority_tier=priority_tier,
total_minutes=sum(tests[item].estimated_minutes for item in bundle_test_ids)
))
processed.update(bundle_test_ids)
# Second pass to catch any active tests left uncovered by first-pass grouping.
all_active_keys = sorted(dut_active_test_ids | ref_active_test_ids)
for key in all_active_keys:
if key in processed:
continue
test = tests[key]
bundle_test_ids = [key]
# Check if this test is top priority
if key[0] in top_priority_tests:
priority_tier = BUNDLE_PRIORITY_FAILED # Highest priority (0)
else:
priority_tier = BUNDLE_PRIORITY_COE_ONLY
bundles.append(TestBundle(
test_ids=bundle_test_ids,
priority_tier=priority_tier,
total_minutes=sum(tests[item].estimated_minutes for item in bundle_test_ids)
))
processed.update(bundle_test_ids)
# Sort by priority tier (lower first), then by total minutes (smaller first for efficiency)
bundles.sort(key=lambda b: (b.priority_tier, b.total_minutes))
return bundles
def _rx_tx_pair_id(test_id: str, active: set[str]) -> str | None:
if "RX" in test_id:
candidate = test_id.replace("RX", "TX", 1)
return candidate if candidate in active else None
if "TX" in test_id:
candidate = test_id.replace("TX", "RX", 1)
return candidate if candidate in active else None
return None
def _shift_capacity_for_date(
current_date: date,
holiday_dates: set[str],
daytime_shift2_only: bool,
) -> dict[int, int]:
iso = current_date.isoformat()
if iso in holiday_dates:
return {1: 480, 2: 480, 3: 480}
is_weekend = current_date.weekday() >= 5
if is_weekend:
return {1: 480, 2: 480, 3: 480}
if daytime_shift2_only:
return {1: 0, 2: 480, 3: 0}
return {1: 600, 2: 0, 3: 420}
def _parse_date(value: str | None) -> date:
if not value:
return date.today()
return datetime.strptime(value, "%Y-%m-%d").date()
def _is_off_day(day: date, holiday_dates: set[str]) -> bool:
"""Return True for weekend days and configured holidays."""
return day.weekday() >= 5 or day.isoformat() in holiday_dates
def _get_shift_sequence(
start_date: date,
holiday_dates: set[str],
daytime_shift2_only: bool = False,
) -> list[tuple[date, int]]:
"""Generate the sequence of (date, shift_index) tuples for a scheduling window.
Rules per design:
- Daytime testing first window (weekday only): [2] (shift 2 today only)
- Monday-Thursday: [3, 1] (shift 3 today, shift 1 next day) = 16 hours
- Friday: [3, 1, 2, 3, 1, 2, 3, 1] (Fri-Mon) = 24 hours continuous
- Saturday/Sunday/Holiday weekday: all 3 shifts
Weekday default starts on shift 3 unless daytime testing shift-2-only is enabled.
Returns ordered list of (date, shift_index) pairs.
"""
iso = start_date.isoformat()
is_holiday = iso in holiday_dates
weekday = start_date.weekday() # 0=Mon, 4=Fri, 5=Sat, 6=Sun
shifts: list[tuple[date, int]] = []
if daytime_shift2_only and weekday < 5 and not is_holiday:
shifts.append((start_date, 2))
return shifts
# Any off-day (weekend/holiday) uses full daytime+night shifts for that date.
if _is_off_day(start_date, holiday_dates):
shifts.append((start_date, 1))
shifts.append((start_date, 2))
shifts.append((start_date, 3))
return shifts
# Working-day windows always start at shift 3.
shifts.append((start_date, 3))
next_day = start_date + timedelta(days=1)
# If tomorrow starts an off-day chain (holiday/weekend), extend the window
# through all off-days and end at shift 1 of the next working day.
if _is_off_day(next_day, holiday_dates):
cursor = next_day
while _is_off_day(cursor, holiday_dates):
shifts.append((cursor, 1))
shifts.append((cursor, 2))
shifts.append((cursor, 3))
cursor += timedelta(days=1)
shifts.append((cursor, 1))
return shifts
# Default working-day pair: tonight shift 3 + next day shift 1.
shifts.append((next_day, 1))
return shifts
test_id: str
device: str
scheduled_date: str
shift_index: int
sequence_in_shift: int
@dataclass
class ScheduleWindow:
index: int
shifts: list[tuple[date, int]]
capacity_minutes: int
remaining_minutes: int
assigned_device: str | None = None
assigned_config: tuple[str, ...] | None = None
class Scheduler:
def __init__(
self,
tests: list[Test],
top_priority_tests: set[tuple[str, str]],
start_date: str | None = None,
holiday_dates: set[str] = set(),
daytime_testing_today: bool = False,
priority_weight: int = 100,
):
self.top_priority_tests = top_priority_tests
self.tests = tests
self.active_dut: dict[str, Test] = {test.test_id: test for test in tests if test.device == DUT}
self.active_ref: dict[str, Test] = {test.test_id: test for test in tests if test.device == REF}
self.start_date = date.fromisoformat(start_date) if start_date else date.today()
self.holiday_dates = holiday_dates
self.daytime_testing_today = daytime_testing_today
self.priority_weight = priority_weight
self.pending_dut_mirror: list[TestBundle] = []
self.pending_ref_mirror: list[TestBundle] = []
self.schedule: list[ScheduleEntry] = []
self.scheduled_bundle_keys: set[tuple[int, str]] = set()
self.scheduled_test_ids: set[str] = set()
def compile_schedule(self) -> str | None:
bundles = build_test_bundles(self.active_dut, self.active_ref, self.top_priority_tests)
if not bundles:
return f"Error: No test bundles could be created from the provided tests."
active_dut_bundles = [b for b in bundles if b.device == DUT]
active_ref_bundles = [b for b in bundles if b.device == REF]
# Group by individual TC values (from config tuples)
all_tcs = set()
for bundle in bundles:
all_tcs.update(bundle.config if bundle.config else [None])
# Create dict: TC -> bundles supporting that TC
bundles_by_tc: dict = {}
# Sort with None last
sorted_tcs = sorted([tc for tc in all_tcs if tc is not None]) + ([None] if None in all_tcs else [])
for tc in sorted_tcs:
if tc is None:
bundles_by_tc[tc] = [b for b in bundles if not b.config]
else:
bundles_by_tc[tc] = [b for b in bundles if tc in b.config]
# Sort by priority, then index
bundles_by_tc[tc].sort(key=lambda b: (b.priority, b.index))
window_index = 0
cursor_date = self.start_date
# Sort TC keys by bundle count (largest first); tie-break by TC name.
sorted_tc_items = sorted(
[(tc, bundles_by_tc[tc]) for tc in bundles_by_tc if tc is not None],
key=lambda x: (-len(x[1]), x[0]),
)
if None in bundles_by_tc:
sorted_tc_items.append((None, bundles_by_tc[None]))
for tc, tc_bundles in sorted_tc_items:
window_device = DUT
dut_unscheduled = [b for b in tc_bundles if b.device == DUT]
ref_unscheduled = [b for b in tc_bundles if b.device == REF]
while dut_unscheduled or ref_unscheduled:
active_unscheduled = dut_unscheduled if window_device == DUT else ref_unscheduled
if len(active_unscheduled) == 0:
# If no unscheduled bundles for the current device, switch to the other device
window_device = REF if window_device == DUT else DUT
active_unscheduled = dut_unscheduled if window_device == DUT else ref_unscheduled
shifts, capacity = get_shift_sequence_with_capacity(cursor_date, self.holiday_dates, self.daytime_testing_today)
# Evaluate both devices without mutating queue state, then commit once.
mirrored_bundles, knapsack_bundles, remaining_time = self._select_bundles(
window_device,
capacity,
active_unscheduled,
tc,
mutate=True,
)
selected_bundles = mirrored_bundles + knapsack_bundles
if len(selected_bundles) == 0:
window_device = REF if window_device == DUT else DUT
mirrored_bundles, knapsack_bundles, remaining_time = self._select_bundles(
window_device,
capacity,
active_unscheduled,
tc,
mutate=True,
)
selected_bundles = mirrored_bundles + knapsack_bundles
if len(selected_bundles) == 0:
break
# Create a mirror of the other device for the next window
bundle_pairs = bundle_pair_lookup(knapsack_bundles, dut_unscheduled, ref_unscheduled)
if window_device == DUT:
pending_target = self.pending_ref_mirror
else:
pending_target = self.pending_dut_mirror
existing_pending = {(b.index, b.device) for b in pending_target}
for pair_bundle in bundle_pairs:
pair_key = (pair_bundle.index, pair_bundle.device)
if pair_key in self.scheduled_bundle_keys or pair_key in existing_pending:
continue
pending_target.append(pair_bundle)
existing_pending.add(pair_key)
window = ScheduleWindow(
index=window_index,
shifts=shifts,
capacity_minutes=capacity,
remaining_minutes=remaining_time,
assigned_config=tc,
assigned_device=window_device,
)
self._place_bundles_in_window(window, selected_bundles)
# Mark bundles as scheduled and remove from ALL TC buckets
selected_keys = {(b.index, b.device) for b in selected_bundles}
# Remove scheduled bundles from ALL TC buckets globally
for all_tc in bundles_by_tc:
bundles_by_tc[all_tc] = [b for b in bundles_by_tc[all_tc] if (b.index, b.device) not in selected_keys]
# Rebuild current TC unscheduled lists
dut_unscheduled = [b for b in bundles_by_tc[tc] if b.device == DUT]
ref_unscheduled = [b for b in bundles_by_tc[tc] if b.device == REF]
window_index += 1
window_device = REF if window_device == DUT else DUT
cursor_date = next_window_start_date(shifts)
return None
def _select_bundles(
self,
device: str,
capacity: int,
unscheduled: list[TestBundle],
tc,
mutate: bool,
) -> tuple[list[TestBundle], list[TestBundle], int]:
# Consume pending mirrored bundles only while there is room.
pending = self.pending_dut_mirror if device == DUT else self.pending_ref_mirror
mirrored_bundles: list[TestBundle] = []
still_pending: list[TestBundle] = [] # Bundles that couldn't fit in the remaining capacity
remaining_capacity = capacity
for bundle in pending:
bundle_key = (bundle.index, bundle.device)
if bundle_key in self.scheduled_bundle_keys:
continue
if bundle.device != device or (tc is not None and tc not in bundle.config) or (tc is None and bundle.config):
still_pending.append(bundle)
continue
if bundle.total_minutes <= remaining_capacity:
mirrored_bundles.append(bundle)
remaining_capacity -= bundle.total_minutes
else:
still_pending.append(bundle)
if mutate:
pending[:] = still_pending
# Run knapsack selection for remaining capacity
candidates = [
b
for b in unscheduled
if b not in mirrored_bundles
and b.device == device
and (b.index, b.device) not in self.scheduled_bundle_keys
]
knapsack_bundles = self._knapsack_select(remaining_capacity, candidates)
remaining_capacity -= sum(bundle.total_minutes for bundle in knapsack_bundles)
print(f"Selected {len(mirrored_bundles)} mirrored bundles and {len(knapsack_bundles)} knapsack bundles for device {device} with remaining capacity {remaining_capacity} minutes.")
return mirrored_bundles, knapsack_bundles, remaining_capacity
def get_schedule(self) -> list[ScheduleEntry]:
return self.schedule
def _knapsack_select(
self,
capacity: int,
candidates: list[TestBundle],
) -> list[TestBundle]:
if capacity <= 0 or not candidates:
return []
# Weights are total minutes of each bundle
weights = [bundle.total_minutes for bundle in candidates]
# Values are based on priority, lower priority number means higher value
values = [self.priority_weight - bundle.priority for bundle in candidates]
# Implement dynamic programming knapsack algorithm to select bundles
n = len(candidates)
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(capacity + 1):
if weights[i - 1] <= w:
dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1])
else:
dp[i][w] = dp[i - 1][w]
# Traceback to find selected bundles
w = capacity
selected_indices = []
for i in range(n, 0, -1):
if w <= 0:
break
if dp[i][w] != dp[i - 1][w]:
selected_indices.append(i - 1)
w -= weights[i - 1]
# Return the selected bundles in the order they were added
return [candidates[i] for i in reversed(selected_indices)]
def _place_bundles_in_window(
self,
window: ScheduleWindow,
bundles: list[TestBundle],
) -> None:
for bundle in bundles:
bundle_key = (bundle.index, bundle.device)
if bundle_key in self.scheduled_bundle_keys:
continue
self.scheduled_bundle_keys.add(bundle_key)
for idx, test in enumerate(bundle.tests):
schedule_key = f"{test}:{bundle.device}"
if schedule_key in self.scheduled_test_ids:
continue
self.scheduled_test_ids.add(schedule_key)
self.schedule.append(
ScheduleEntry(
test_id=test,
device=bundle.device,
scheduled_date=str(window.shifts[0][0]),
shift_index=window.shifts[0][1],
sequence_in_shift=idx + 1,
)
)
window.remaining_minutes -= self._get_test_minutes(test, bundle.device)
def _get_test_minutes(self, test_id: str, device: str) -> int:
if device == DUT and test_id in self.active_dut:
return self.active_dut[test_id].estimated_minutes
if device == REF and test_id in self.active_ref:
return self.active_ref[test_id].estimated_minutes
return 0
+270
View File
@@ -0,0 +1,270 @@
from __future__ import annotations
from dataclasses import dataclass
import os
from test_config import build_bundle_test_configs
@dataclass(frozen=True)
class Test:
test_id: str
device: str
test_type: str
rotation: str | None
rx_tx: str | None
has_coe_pair: bool
coe_pairing: list[str]
config: dict[str, dict[str, str | None]]
throttled: bool
estimated_minutes: int
# Bundle priority tiers for scheduling order (lower number = higher priority)
BUNDLE_PRIORITY_TOP = 0 # Failed tests requiring rerun
BUNDLE_PRIORITY_P2P_WITH_COE = 1 # P2P tests with COE pairs
BUNDLE_PRIORITY_P2P_ONLY = 2 # P2P tests without COE pairs (RX/TX bundled)
BUNDLE_PRIORITY_COE_ONLY = 3 # COE tests without P2P pairing (should be rare/unschedulable)
BUNDLE_PRIORITY_P3P = 4 # P3P tests
DUT = (os.getenv("DUT") or "DUT").strip()
REF = (os.getenv("REF") or "REF").strip()
@dataclass(frozen=True)
class TestBundle:
index: int
tests: list[str]
total_minutes: int
device: str
config: tuple[str, ...]
priority: int
def build_test_bundles(active_dut: dict[str, Test], active_ref: dict[str, Test], top_priority_tests: set[tuple[str, str]]) -> list[TestBundle]:
"""Build deterministic bundles for DP scheduling."""
processed_dut: set[str] = set()
processed_ref: set[str] = set()
test_bundles: list[TestBundle] = []
bundle_index = 0
def dedupe_preserve_order(test_ids: list[str]) -> list[str]:
seen: set[str] = set()
deduped: list[str] = []
for test_id in test_ids:
if test_id in seen:
continue
seen.add(test_id)
deduped.append(test_id)
return deduped
dut_top_priority: list[str] = []
ref_top_priority: list[str] = []
for test_id, device in top_priority_tests:
if device == DUT and test_id in active_dut:
dut_top_priority.append(test_id)
elif device == REF and test_id in active_ref:
ref_top_priority.append(test_id)
if dut_top_priority:
test_bundles.append(create_bundle(dut_top_priority, DUT, BUNDLE_PRIORITY_TOP, bundle_index, active_dut, active_ref))
processed_dut.update(dut_top_priority)
bundle_index += 1
if ref_top_priority:
test_bundles.append(create_bundle(ref_top_priority, REF, BUNDLE_PRIORITY_TOP, bundle_index, active_dut, active_ref))
processed_ref.update(ref_top_priority)
bundle_index += 1
# Phase 1: DUT P2P bundles
for dut_test_id, dut_test in active_dut.items():
if dut_test_id in processed_dut or dut_test.test_type != "P2P":
continue
dut_bundled_tests = [dut_test_id]
processed_dut.add(dut_test_id)
active_coe_pairings = [
test_id
for test_id in dut_test.coe_pairing
if test_id in active_dut and test_id not in processed_dut
]
if active_coe_pairings:
dut_bundled_tests.extend(active_coe_pairings)
processed_dut.update(active_coe_pairings)
priority = BUNDLE_PRIORITY_P2P_WITH_COE
else:
rx_tx_pair_id = get_rx_tx_pair_id(dut_test, DUT, active_dut, active_ref)
if rx_tx_pair_id and rx_tx_pair_id not in processed_dut:
dut_bundled_tests.append(rx_tx_pair_id)
processed_dut.add(rx_tx_pair_id)
priority = BUNDLE_PRIORITY_P2P_ONLY
dut_bundled_tests = dedupe_preserve_order(dut_bundled_tests)
test_bundles.append(
create_bundle(dut_bundled_tests, DUT, priority, bundle_index, active_dut, active_ref)
)
ref_bundled_tests = [
test_id
for test_id in dut_bundled_tests
if test_id in active_ref and test_id not in processed_ref
]
if ref_bundled_tests:
ref_bundled_tests = dedupe_preserve_order(ref_bundled_tests)
processed_ref.update(ref_bundled_tests)
test_bundles.append(
create_bundle(ref_bundled_tests, REF, priority, bundle_index, active_dut, active_ref)
)
bundle_index += 1
# Phase 2: DUT P3P bundles
for dut_test_id, dut_test in active_dut.items():
if dut_test_id in processed_dut or dut_test.test_type != "P3P":
continue
dut_bundled_tests = [dut_test_id]
processed_dut.add(dut_test_id)
th_ut_pair_id = get_th_ut_pair_id(dut_test, DUT, active_dut, active_ref)
if th_ut_pair_id and th_ut_pair_id not in processed_dut:
dut_bundled_tests.append(th_ut_pair_id)
processed_dut.add(th_ut_pair_id)
dut_bundled_tests = dedupe_preserve_order(dut_bundled_tests)
test_bundles.append(
create_bundle(dut_bundled_tests, DUT, BUNDLE_PRIORITY_P3P, bundle_index, active_dut, active_ref)
)
ref_bundled_tests = [
test_id
for test_id in dut_bundled_tests
if test_id in active_ref and test_id not in processed_ref
]
if ref_bundled_tests:
ref_bundled_tests = dedupe_preserve_order(ref_bundled_tests)
processed_ref.update(ref_bundled_tests)
test_bundles.append(
create_bundle(ref_bundled_tests, REF, BUNDLE_PRIORITY_P3P, bundle_index, active_dut, active_ref)
)
bundle_index += 1
# Phase 3: DUT leftovers (including COE-only)
for dut_test_id in active_dut:
if dut_test_id in processed_dut:
continue
test_bundles.append(
create_bundle([dut_test_id], DUT, BUNDLE_PRIORITY_COE_ONLY, bundle_index, active_dut, active_ref)
)
bundle_index += 1
processed_dut.add(dut_test_id)
# Phase 4: unmatched REF P2P bundles
for ref_test_id, ref_test in active_ref.items():
if ref_test_id in processed_ref or ref_test.test_type != "P2P":
continue
ref_bundled_tests = [ref_test_id]
processed_ref.add(ref_test_id)
active_coe_pairings = [
test_id
for test_id in ref_test.coe_pairing
if test_id in active_ref and test_id not in processed_ref
]
if active_coe_pairings:
ref_bundled_tests.extend(active_coe_pairings)
processed_ref.update(active_coe_pairings)
priority = BUNDLE_PRIORITY_P2P_WITH_COE
else:
rx_tx_pair_id = get_rx_tx_pair_id(ref_test, REF, active_dut, active_ref)
if rx_tx_pair_id and rx_tx_pair_id not in processed_ref:
ref_bundled_tests.append(rx_tx_pair_id)
processed_ref.add(rx_tx_pair_id)
priority = BUNDLE_PRIORITY_P2P_ONLY
ref_bundled_tests = dedupe_preserve_order(ref_bundled_tests)
test_bundles.append(
create_bundle(ref_bundled_tests, REF, priority, bundle_index, active_dut, active_ref)
)
bundle_index += 1
# Phase 5: unmatched REF P3P bundles
for ref_test_id, ref_test in active_ref.items():
if ref_test_id in processed_ref or ref_test.test_type != "P3P":
continue
ref_bundled_tests = [ref_test_id]
processed_ref.add(ref_test_id)
th_ut_pair_id = get_th_ut_pair_id(ref_test, REF, active_dut, active_ref)
if th_ut_pair_id and th_ut_pair_id not in processed_ref:
ref_bundled_tests.append(th_ut_pair_id)
processed_ref.add(th_ut_pair_id)
ref_bundled_tests = dedupe_preserve_order(ref_bundled_tests)
test_bundles.append(
create_bundle(ref_bundled_tests, REF, BUNDLE_PRIORITY_P3P, bundle_index, active_dut, active_ref)
)
bundle_index += 1
# Phase 6: REF leftovers (including COE-only)
for ref_test_id in active_ref:
if ref_test_id in processed_ref:
continue
test_bundles.append(
create_bundle([ref_test_id], REF, BUNDLE_PRIORITY_COE_ONLY, bundle_index, active_dut, active_ref)
)
bundle_index += 1
processed_ref.add(ref_test_id)
test_bundles.sort(key=lambda b: (b.priority, b.index, b.device, b.config))
return test_bundles
def get_rx_tx_pair_id(test: Test, device: str, active_dut: dict[str, Test], active_ref: dict[str, Test]) -> str | None:
active_tests = active_dut if device == DUT else active_ref
test_id = test.test_id
pair_id = test_id.replace("RX", "TX", 1) if "RX" in test_id else test_id.replace("TX", "RX", 1)
return pair_id if pair_id in active_tests else None
def get_th_ut_pair_id(test: Test, device: str, active_dut: dict[str, Test], active_ref: dict[str, Test]) -> str | None:
active_tests = active_dut if device == DUT else active_ref
test_id = test.test_id
pair_id = test_id.replace("TH", "UT", 1) if "TH" in test_id else test_id.replace("UT", "TH", 1)
return pair_id if pair_id in active_tests else None
def create_bundle(tests: list[str], device: str, priority: int, index: int, active_dut: dict[str, Test], active_ref: dict[str, Test]) -> TestBundle:
if not tests:
raise ValueError("Cannot create bundle with no tests")
if device == DUT:
active_tests = active_dut
else:
active_tests = active_ref
total_minutes = sum(active_tests[test_id].estimated_minutes for test_id in tests)
bundle_tests = [active_tests[test_id] for test_id in tests]
config = tuple(build_bundle_test_configs(bundle_tests))
return TestBundle(
index=index,
tests=tests,
total_minutes=total_minutes,
device=device,
config=config,
priority=priority,
)
def bundle_pair_lookup(bundles: list[TestBundle], dut_bundles: list[TestBundle], ref_bundles: list[TestBundle]) -> list[TestBundle]:
device = bundles[0].device if bundles else None
active_bundle_ids = {b.index for b in dut_bundles} if device == REF else {b.index for b in ref_bundles}
bundle_indexes = [b.index for b in bundles if b.index in active_bundle_ids]
if device == DUT:
return [b for b in ref_bundles if b.index in bundle_indexes]
elif device == REF:
return [b for b in dut_bundles if b.index in bundle_indexes]
return []
+159
View File
@@ -0,0 +1,159 @@
from __future__ import annotations
import json
from typing import Any
TEST_CONFIG_DEFINITION = {
"TC1": {"T1D": "STA5", "T1F": "STA56", "T1I": "STA58", "T1L": "STA64", "T2A":"STA63", "T2O":"STA65", "T2J": "STA59", "T2E": "STA6", "T3E": "STA4"},
"TC2": {"T1D": "STA64", "T1F": "STA4", "T1I": "STA5", "T1L": "STA58", "T2A":"STA56", "T2O":"STA59", "T2J": "STA6", "T2E": "STA65", "T3E": "STA63"},
"TC3": {"T1D": "STA58", "T1F": "STA63", "T1I": "STA64", "T2J": "STA65", "T2E": "STA59", "T3E": "STA56"},
"TC4": {"T1B": "STA56", "T1C": "STA4", "T1D": "STA63"},
"TC5": {"T1B": "STA4", "T1C": "STA63", "T1D": "STA56"},
"TC6": {"T1B": "STA63", "T1C": "STA56", "T1D": "STA4"},
"TC7": {"T1F": "STA4", "T2A": "STA63", "T3E": "STA56"},
"TC8": {"T1F": "STA63", "T2A": "STA56", "T3E": "STA4"},
"TC9": {"T1F": "STA63", "T2A": "STA64", "T3E": "STA4"},
"TC10": {"T1F": "STA4", "T3E": "STA56", "T1K2A": "STA63"},
"TC11": {"T1F": "STA56", "T3E": "STA63", "T1K2A": "STA4"},
"TC12": {"T1F": "STA56", "T3E": "STA4", "T1K2A": "STA63"},
}
def _norm(value: Any) -> str:
if value is None:
return ""
return " ".join(str(value).strip().upper().split())
def _get_test_config(test: Any) -> dict[str, dict[str, str | None]]:
if isinstance(test, dict):
config = test.get("config") or {}
else:
config = getattr(test, "config", None) or {}
return config if isinstance(config, dict) else {}
def _testpoint_to_station_sets(config: dict[str, dict[str, str | None]]) -> dict[str, set[str]]:
result: dict[str, set[str]] = {}
def _add_entry(entry: dict[str, str | None]) -> None:
testpoint = _norm(entry.get("test_point") or entry.get("Test Point"))
sta_raw = _norm(entry.get("sta") or entry.get("STA"))
if not testpoint or not sta_raw:
return
stations = {_norm(sta) for sta in sta_raw.split(",") if _norm(sta)}
if not stations:
return
result.setdefault(testpoint, set()).update(stations)
for entry in config.values():
if isinstance(entry, dict):
_add_entry(entry)
return result
def build_station_testpoint_map(config: dict[str, dict[str, str | None]]) -> dict[str, str]:
"""Build station->testpoint map used by legacy graph serialization."""
station_to_testpoint: dict[str, str] = {}
for testpoint, stations in _testpoint_to_station_sets(config).items():
for station in stations:
station_to_testpoint[station] = testpoint
return station_to_testpoint
def resolve_test_config_keys(config: dict[str, dict[str, str | None]]) -> list[str]:
"""Return all TC keys whose definitions contain all testpoint->station mappings in config."""
testpoint_stations = _testpoint_to_station_sets(config)
if not testpoint_stations:
return []
matches: list[str] = []
for tc_key, tc_mapping in TEST_CONFIG_DEFINITION.items():
is_match = True
for testpoint, stations in testpoint_stations.items():
# TC definitions map one testpoint to exactly one station.
if len(stations) != 1:
is_match = False
break
expected_station = tc_mapping.get(testpoint)
if expected_station is None or expected_station not in stations:
is_match = False
break
if is_match:
matches.append(tc_key)
return matches
def build_config_rows(tests: list[dict[str, Any]]) -> list[dict[str, Any]]:
by_sta: dict[str, set[str]] = {}
for test in tests:
config = test.get("config") or {}
if not isinstance(config, dict):
continue
for entry in config.values():
if not isinstance(entry, dict):
continue
sta_raw = entry.get("sta") or entry.get("STA")
test_point = entry.get("test_point") or entry.get("Test Point")
if not sta_raw or not test_point:
continue
for sta_part in str(sta_raw).split(","):
sta = sta_part.strip().upper()
if not sta:
continue
by_sta.setdefault(sta, set()).add(str(test_point))
return [
{
"sta": sta,
"testPoints": sorted(points),
}
for sta, points in sorted(by_sta.items())
]
def build_bundle_test_configs(tests: list[Any]) -> list[str]:
merged_by_testpoint: dict[str, set[str]] = {}
for test in tests:
config = _get_test_config(test)
for entry in config.values():
if not isinstance(entry, dict):
continue
testpoint_raw = entry.get("test_point") or entry.get("Test Point")
sta_raw = entry.get("sta") or entry.get("STA")
if not testpoint_raw or not sta_raw:
continue
testpoint = " ".join(str(testpoint_raw).strip().upper().split())
if not testpoint:
continue
stations = {
" ".join(str(sta).strip().upper().split())
for sta in str(sta_raw).split(",")
if " ".join(str(sta).strip().upper().split())
}
if not stations:
continue
merged_by_testpoint.setdefault(testpoint, set()).update(stations)
merged_config: dict[str, dict[str, str | None]] = {}
for idx, (testpoint, stations) in enumerate(sorted(merged_by_testpoint.items()), start=1):
merged_config[f"Station {idx}"] = {
"test_point": testpoint,
"sta": ",".join(sorted(stations)),
}
return resolve_test_config_keys(merged_config)
def serialize_station_testpoint_map(config: dict[str, dict[str, str | None]]) -> str:
return json.dumps(build_station_testpoint_map(config))
+67
View File
@@ -0,0 +1,67 @@
from __future__ import annotations
from datetime import date, timedelta
def is_off_day(current_date: date, holiday_dates: set[str]) -> bool:
return current_date.weekday() >= 5 or current_date.isoformat() in holiday_dates
def get_shift_sequence(
current_date: date,
holiday_dates: set[str],
daytime_shift2_only: bool = False,
) -> list[tuple[date, int]]:
is_holiday = current_date.isoformat() in holiday_dates
weekday = current_date.weekday()
shifts: list[tuple[date, int]] = []
if daytime_shift2_only and weekday < 5 and not is_holiday:
return [(current_date, 2)]
if is_off_day(current_date, holiday_dates):
return [(current_date, 1), (current_date, 2), (current_date, 3)]
shifts.append((current_date, 3))
next_day = current_date + timedelta(days=1)
if is_off_day(next_day, holiday_dates):
cursor = next_day
while is_off_day(cursor, holiday_dates):
shifts.extend([(cursor, 1), (cursor, 2), (cursor, 3)])
cursor += timedelta(days=1)
shifts.append((cursor, 1))
return shifts
shifts.append((next_day, 1))
return shifts
def get_shift_capacity_for_date(
current_date: date,
holiday_dates: set[str],
daytime_shift2_only: bool = False,
) -> dict[int, int]:
if daytime_shift2_only:
return {1: 0, 2: 480, 3: 0}
if is_off_day(current_date, holiday_dates):
return {1: 480, 2: 480, 3: 480}
return {1: 480, 2: 0, 3: 480}
def get_shift_sequence_with_capacity(
current_date: date,
holiday_dates: set[str],
daytime_shift2_only: bool = False,
) -> tuple[list[tuple[date, int]], int]:
shifts = get_shift_sequence(current_date, holiday_dates, daytime_shift2_only)
capacity = sum(
get_shift_capacity_for_date(day, holiday_dates, daytime_shift2_only).get(shift_index, 0)
for day, shift_index in shifts
)
return shifts, capacity
def next_window_start_date(shifts: list[tuple[date, int]]) -> date:
last_date, last_shift = shifts[-1]
if last_shift in (1, 2):
return last_date
return last_date + timedelta(days=1)
+2
View File
@@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
/data
+53 -12
View File
@@ -1,9 +1,10 @@
import { useState, useEffect, useCallback, useMemo } from 'react'
import Header from './components/Header'
import FailedBanner from './components/FailedBanner'
import Calendar from './components/Calendar'
import RightPanel from './components/RightPanel'
import SettingsModal from './components/SettingsModal'
import Header from './components/Header'
import FailedBanner from './components/FailedBanner'
import Calendar from './components/Calendar'
import RightPanel from './components/RightPanel'
import SettingsModal from './components/SettingsModal'
import TestWindowDetailsPanel from './components/TestWindowDetailsPanel'
import { api, groupScheduleItems } from './api'
// ---------------------------------------------------------------------------
@@ -123,19 +124,37 @@ export default function App() {
const [topPriority, setTopPriority] = useState('')
const [lowestPriority, setLowestPriority] = useState('')
const [startDateOverride, setStartDateOverride] = useState('')
const [failedTests, setFailedTests] = useState([])
const [scheduleData, setScheduleData] = useState({})
const [completionDate, setCompletionDate] = useState(null)
const [weekStart, setWeekStart] = useState(() => getMondayOfWeek(new Date()))
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [failedTests, setFailedTests] = useState([])
const [scheduleData, setScheduleData] = useState({})
const [scheduleWindows, setScheduleWindows] = useState([])
const [selectedWindowId, setSelectedWindowId] = useState(null)
const [completionDate, setCompletionDate] = useState(null)
const [weekStart, setWeekStart] = useState(() => getMondayOfWeek(new Date()))
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const tonightConfigRows = useMemo(() => toTonightConfigRows(scheduleData), [scheduleData])
const windowLookup = useMemo(() => {
const lookup = new Map()
for (const windowDetails of scheduleWindows) {
for (const segment of windowDetails.segments ?? []) {
lookup.set(`${segment.date}::shift${segment.shift_index}`, windowDetails)
}
}
return lookup
}, [scheduleWindows])
const selectedWindow = useMemo(
() => scheduleWindows.find((windowDetails) => windowDetails.window_id === selectedWindowId) ?? null,
[scheduleWindows, selectedWindowId],
)
// Fetch schedule for the given weekStart (Monday)
const fetchSchedule = useCallback(async (start) => {
try {
const data = await api.getScheduleWeek(toKey(start))
setScheduleData(groupScheduleItems(data.items))
setScheduleWindows(data.windows ?? [])
} catch (e) {
console.error('Failed to fetch schedule:', e)
}
@@ -268,6 +287,20 @@ export default function App() {
setFailedTests([])
}
useEffect(() => {
if (selectedWindowId && !selectedWindow) {
setSelectedWindowId(null)
}
}, [selectedWindow, selectedWindowId])
function handleWindowSelect(windowDetails) {
setSelectedWindowId(windowDetails?.window_id ?? null)
}
function handleCloseWindowDetails() {
setSelectedWindowId(null)
}
// -------------------------------------------------------------------------
return (
@@ -286,13 +319,15 @@ export default function App() {
onDecision={handleRerunDecision}
/>
<main className="flex flex-1 gap-4 p-4 overflow-hidden">
<main className="relative flex flex-1 gap-4 p-4 overflow-hidden">
<div className="flex-1 min-w-0 flex flex-col">
<Calendar
scheduleData={scheduleData}
daytimeDateKey={daytimeEnabled ? toKey(new Date()) : null}
weekStart={weekStart}
onWeekChange={setWeekStart}
windowLookup={windowLookup}
onWindowSelect={handleWindowSelect}
/>
</div>
@@ -310,6 +345,12 @@ export default function App() {
loading={loading}
tonightConfigRows={tonightConfigRows}
/>
<TestWindowDetailsPanel
isOpen={Boolean(selectedWindow)}
windowDetails={selectedWindow}
onClose={handleCloseWindowDetails}
/>
</main>
<SettingsModal
Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

+30 -2
View File
@@ -1,4 +1,5 @@
import DayColumn from './DayColumn'
import { getDeviceAccentClass } from './TestCard'
function addDays(date, n) {
const d = new Date(date)
@@ -47,7 +48,14 @@ function getNextTestWindow() {
}
// scheduleData: { "YYYY-MM-DD": { shift1: [...], shift2: [...], shift3: [...] }, ... }
export default function Calendar({ scheduleData = {}, daytimeDateKey = null, weekStart, onWeekChange }) {
export default function Calendar({
scheduleData = {},
daytimeDateKey = null,
weekStart,
onWeekChange,
windowLookup = new Map(),
onWindowSelect,
}) {
const today = new Date()
today.setHours(0, 0, 0, 0)
@@ -58,6 +66,18 @@ export default function Calendar({ scheduleData = {}, daytimeDateKey = null, wee
// Reorder columns: Sun Mon Tue Wed Thu Fri Sat
// weekStart is Monday, so days[0]=Mon ... days[6]=Sun → put Sunday first
const orderedDays = [ ...days.slice(0, 7)]
const deviceLegendItems = Array.from(
new Set(
Object.values(scheduleData)
.flatMap((day) => [
...(day?.shift1 ?? []),
...(day?.shift2 ?? []),
...(day?.shift3 ?? []),
])
.map((test) => test?.device)
.filter(Boolean),
),
).sort((a, b) => String(a).localeCompare(String(b)))
return (
<div className="flex flex-col gap-3 h-full">
@@ -112,16 +132,24 @@ export default function Calendar({ scheduleData = {}, daytimeDateKey = null, wee
activeShifts={activeShifts}
shifts={shifts}
showShift2={isWeekend || (daytimeDateKey !== null && key === daytimeDateKey)}
windowLookup={windowLookup}
onWindowSelect={onWindowSelect}
/>
)
})}
</div>
{/* Legend */}
<div className="flex gap-4 text-xs text-gray-400">
<div className="flex flex-wrap gap-x-6 gap-y-2 text-xs text-gray-400">
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-sm bg-gray-500 inline-block" />Pending</span>
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-sm bg-green-700 inline-block" />Completed</span>
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-sm bg-red-700 inline-block" />Rerun Required</span>
{deviceLegendItems.map((device) => (
<span key={device} className="flex items-center gap-1.5">
<span className={`w-2.5 h-2.5 rounded-sm inline-block ${getDeviceAccentClass(device)}`} />
{device}
</span>
))}
</div>
</div>
)
+39 -8
View File
@@ -2,14 +2,24 @@ import ShiftSlot from './ShiftSlot'
const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
export default function DayColumn({ date, isToday, activeShifts = new Set(), shifts = {}, showShift2 = false }) {
export default function DayColumn({
date,
isToday,
activeShifts = new Set(),
shifts = {},
showShift2 = false,
windowLookup = new Map(),
onWindowSelect,
}) {
const dayName = DAY_NAMES[date.getDay()]
const dayNum = date.getDate()
const isWeekend = date.getDay() === 0 || date.getDay() === 6
const hasActive = activeShifts.size > 0
const dateKey = date.toISOString().slice(0, 10)
// Shift 2 is visible if: weekend, holiday (showShift2), or daytime testing on
const shift2Visible = isWeekend || showShift2
function getWindowForShift(shiftIndex) {
return windowLookup.get(`${dateKey}::shift${shiftIndex}`) ?? null
}
const shift2Visible = showShift2 || shifts.shift2.length > 0 || Boolean(getWindowForShift(2))
return (
<div className="flex flex-col min-w-0 flex-1 rounded-lg border border-gray-700 bg-gray-800/40">
@@ -29,9 +39,30 @@ export default function DayColumn({ date, isToday, activeShifts = new Set(), shi
{/* Shifts */}
<div className="flex flex-col flex-1 px-0.5 py-1">
<ShiftSlot label="12AM9AM" tests={shifts.shift1} visible={true} active={activeShifts.has(1)} />
<ShiftSlot label="9AM5PM" tests={shifts.shift2} visible={true} active={activeShifts.has(2)} />
<ShiftSlot label="5PM12AM" tests={shifts.shift3} visible={true} active={activeShifts.has(3)} />
<ShiftSlot
label="12AM9AM"
tests={shifts.shift1}
visible={true}
active={activeShifts.has(1)}
windowDetails={getWindowForShift(1)}
onWindowSelect={onWindowSelect}
/>
<ShiftSlot
label="9AM5PM"
tests={shifts.shift2}
visible={true}
active={activeShifts.has(2)}
windowDetails={getWindowForShift(2)}
onWindowSelect={onWindowSelect}
/>
<ShiftSlot
label="5PM12AM"
tests={shifts.shift3}
visible={true}
active={activeShifts.has(3)}
windowDetails={getWindowForShift(3)}
onWindowSelect={onWindowSelect}
/>
</div>
</div>
)
+1 -1
View File
@@ -2,7 +2,7 @@ export default function Header({ onOpenSettings }) {
return (
<header className="flex items-center justify-between px-6 py-3 bg-gray-900 border-b border-gray-700 shrink-0">
<h1 className="text-white font-semibold text-lg tracking-wide">
CGW453 Scheduler
NJTH Scheduler
</h1>
<button
onClick={onOpenSettings}
+3 -3
View File
@@ -196,12 +196,12 @@ export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
<p className="text-[11px] font-bold uppercase tracking-widest text-gray-500 mb-3">
Test Exclusion
</p>
<Field label="Excluded Test IDs" hint="(comma-separated)">
<Field label="Exclusions" hint="(comma-separated)">
<input
type="text"
className={INPUT_CLS}
placeholder="P2PRXAX001, COERXBE002…"
value={form.testExclusion ?? ''}
placeholder="SP, BW320,..."
value={form.testExclusion?? ''}
onChange={(e) => set('testExclusion', e.target.value)}
/>
</Field>
+36 -9
View File
@@ -1,17 +1,44 @@
import TestCard from './TestCard'
export default function ShiftSlot({ label, tests = [], visible = true, active = false }) {
export default function ShiftSlot({
label,
tests = [],
visible = true,
active = false,
windowDetails = null,
onWindowSelect,
}) {
if (!visible) return null
const clickable = Boolean(windowDetails && onWindowSelect)
return (
<div className={`border-t pt-1 pb-1.5 ${
active ? 'border-blue-500/50 bg-blue-950/20 rounded' : 'border-gray-700/60'
}`}>
<p className={`text-[10px] font-semibold uppercase tracking-wider mb-1 px-1 ${
active ? 'text-blue-400' : 'text-gray-500'
}`}>
{label}
</p>
<div
role={clickable ? 'button' : undefined}
tabIndex={clickable ? 0 : undefined}
onClick={clickable ? () => onWindowSelect(windowDetails) : undefined}
onKeyDown={clickable ? (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
onWindowSelect(windowDetails)
}
} : undefined}
className={`border-t pt-1 pb-1.5 transition-colors ${
active ? 'border-blue-500/50 bg-blue-950/20 rounded' : 'border-gray-700/60'
} ${clickable ? 'cursor-pointer hover:bg-gray-700/25 focus:outline-none focus:ring-1 focus:ring-cyan-400/70' : ''}`}
>
<div className="flex items-center justify-between gap-2 px-1 mb-1">
<p className={`text-[10px] font-semibold uppercase tracking-wider ${
active ? 'text-blue-400' : 'text-gray-500'
}`}>
{label}
</p>
{clickable && (
<span className="text-[9px] font-semibold uppercase tracking-[0.2em] text-cyan-400/80">
</span>
)}
</div>
<div className="flex flex-col gap-0.5 px-1 min-h-4">
{tests.length === 0 ? (
<span className="text-[10px] text-gray-600 italic"></span>
+51 -7
View File
@@ -1,10 +1,12 @@
import { useRef, useEffect, useState } from 'react'
const STATUS_STYLES = {
pending: 'bg-gray-600/60 text-gray-200 border-gray-500',
completed: 'bg-green-800/60 text-green-200 border-green-600',
rerun: 'bg-red-800/60 text-red-200 border-red-600',
}
const DEVICE_ACCENT_CLASSES = [
export const DEVICE_ACCENT_CLASSES = [
'bg-sky-400',
'bg-emerald-400',
'bg-amber-400',
@@ -15,7 +17,7 @@ const DEVICE_ACCENT_CLASSES = [
'bg-fuchsia-400',
]
function getDeviceAccentClass(device) {
export function getDeviceAccentClass(device) {
if (!device) return 'bg-gray-500'
let hash = 0
@@ -47,9 +49,46 @@ function formatConfig(config) {
export default function TestCard({ test }) {
const style = STATUS_STYLES[test.status] ?? STATUS_STYLES.pending
const deviceAccentClass = getDeviceAccentClass(test.device)
const cardRef = useRef(null)
const [tooltipPos, setTooltipPos] = useState({ top: '0px', left: '0px' })
const [isHovered, setIsHovered] = useState(false)
useEffect(() => {
const cardElement = cardRef.current
if (!cardElement) return
const handleMouseMove = (e) => {
// Position tooltip near the cursor, with 10px offset
const top = e.clientY + 10
const left = e.clientX + 10
setTooltipPos({
top: `${top}px`,
left: `${left}px`,
})
}
const handleMouseEnter = () => {
setIsHovered(true)
}
const handleMouseLeave = () => {
setIsHovered(false)
}
cardElement.addEventListener('mouseenter', handleMouseEnter)
cardElement.addEventListener('mouseleave', handleMouseLeave)
cardElement.addEventListener('mousemove', handleMouseMove)
return () => {
cardElement.removeEventListener('mouseenter', handleMouseEnter)
cardElement.removeEventListener('mouseleave', handleMouseLeave)
cardElement.removeEventListener('mousemove', handleMouseMove)
}
}, [])
return (
<div className="relative group">
<div ref={cardRef} className="relative">
<div
className={`relative px-1.5 py-0.5 pl-3 rounded border text-xs font-mono truncate cursor-default select-none ${style}`}
style={{ maxWidth: '100%' }}
@@ -57,9 +96,14 @@ export default function TestCard({ test }) {
<span className={`absolute inset-y-0 left-0 w-1 rounded-l ${deviceAccentClass}`} aria-hidden="true" />
{test.test_id}
</div>
{/* Hover tooltip */}
<div className="absolute z-50 bottom-full left-0 mb-1 hidden group-hover:block min-w-max">
<div className="bg-gray-800 border border-gray-600 rounded-md px-2.5 py-1.5 text-xs text-gray-200 shadow-lg max-w-md">
{/* Hover tooltip — follows cursor */}
{isHovered && (
<div className="fixed z-50 bg-gray-800 border border-gray-600 rounded-md px-2.5 py-1.5 text-xs text-gray-200 shadow-lg max-w-md pointer-events-none"
style={{
top: tooltipPos.top,
left: tooltipPos.left,
}}
>
<p><span className="text-gray-400">Type:</span> {test.test_type ?? '—'}</p>
<p><span className="text-gray-400">Rotation:</span> {test.rotation ?? '—'}</p>
<p className="flex items-center gap-1.5">
@@ -69,7 +113,7 @@ export default function TestCard({ test }) {
</p>
<p className="mt-1 pt-1 border-t border-gray-600"><span className="text-gray-400">Config:</span> {formatConfig(test.config)}</p>
</div>
</div>
)}
</div>
)
}
@@ -0,0 +1,295 @@
import { useEffect, useMemo, useState } from 'react'
const STATUS_STYLES = {
pending: 'bg-gray-700 text-gray-200 border-gray-600',
completed: 'bg-green-900/60 text-green-200 border-green-700',
rerun: 'bg-red-900/60 text-red-200 border-red-700',
}
function formatWindowHeader(windowDetails) {
if (!windowDetails?.start_at || !windowDetails?.end_at) return 'Window details'
const start = new Date(windowDetails.start_at)
const end = new Date(windowDetails.end_at)
const startLabel = start.toLocaleString('en-US', {
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true,
}).replace(':00', '').replace(' AM', 'am').replace(' PM', 'pm')
const endLabel = end.toLocaleString('en-US', {
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true,
}).replace(':00', '').replace(' AM', 'am').replace(' PM', 'pm')
return `${startLabel} - ${endLabel}`
}
function formatHours(minutes) {
if (!Number.isFinite(minutes) || minutes <= 0) return '0 hrs'
const hours = minutes / 60
const rounded = Number.isInteger(hours) ? String(hours) : hours.toFixed(1)
return `${rounded} hrs`
}
function formatShiftLabel(test) {
const shiftLabels = {
1: '12AM9AM',
2: '9AM5PM',
3: '5PM12AM',
}
const date = test?.scheduled_date
const label = shiftLabels[test?.shift_index] ?? 'Unknown shift'
return date ? `${date} ${label}` : label
}
function getWindowTestConfig(windowDetails) {
if (Array.isArray(windowDetails?.bundle_test_configs)) {
return windowDetails.bundle_test_configs
.map((value) => (typeof value === 'string' ? value.trim() : ''))
.filter(Boolean)
}
const tests = windowDetails?.tests ?? []
const allKeys = new Set()
for (const test of tests) {
if (Array.isArray(test?.bundle_test_configs)) {
for (const key of test.bundle_test_configs) {
if (typeof key === 'string' && key.trim()) {
allKeys.add(key.trim())
}
}
continue
}
if (typeof test?.test_config === 'string' && test.test_config.trim()) {
allKeys.add(test.test_config.trim())
}
}
return Array.from(allKeys).sort()
}
function formatConfigEntries(config) {
if (!config || typeof config !== 'object') return []
return Object.entries(config)
.map(([band, data]) => {
if (!data || typeof data !== 'object') return null
const testPoint = data.test_point || data['Test Point'] || '—'
const sta = data.sta || data.STA || '—'
return { band, testPoint, sta }
})
.filter(Boolean)
}
function TestRow({ test }) {
const [expanded, setExpanded] = useState(false)
const statusStyle = STATUS_STYLES[test.status] ?? STATUS_STYLES.pending
const configEntries = useMemo(() => formatConfigEntries(test.config), [test.config])
return (
<div className="rounded-xl border border-gray-700 bg-gray-900/70 overflow-hidden">
<button
type="button"
onClick={() => setExpanded((value) => !value)}
className="w-full px-4 py-3 text-left hover:bg-gray-800/80 transition-colors"
>
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-sm font-semibold text-white">{test.test_id}</p>
<p className="mt-1 text-xs text-gray-400">{test.device ?? '—'} · {formatShiftLabel(test)}</p>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className={`px-2 py-1 rounded-full border text-[11px] font-semibold uppercase tracking-wide ${statusStyle}`}>
{test.status ?? 'pending'}
</span>
<span className="text-xs text-gray-400">{formatHours(test.estimated_minutes)}</span>
</div>
</div>
</button>
{expanded && (
<div className="px-4 pb-4 pt-1 border-t border-gray-800 bg-gray-950/60">
<div className="grid grid-cols-2 gap-3 text-xs text-gray-300">
<div>
<p className="text-gray-500 uppercase tracking-wide text-[11px] mb-1">Type</p>
<p>{test.test_type ?? '—'}</p>
</div>
<div>
<p className="text-gray-500 uppercase tracking-wide text-[11px] mb-1">Rotation</p>
<p>{test.rotation ?? '—'}</p>
</div>
<div>
<p className="text-gray-500 uppercase tracking-wide text-[11px] mb-1">Priority</p>
<p>{test.priority ?? '—'}</p>
</div>
<div>
<p className="text-gray-500 uppercase tracking-wide text-[11px] mb-1">Sequence</p>
<p>{test.sequence_in_shift ?? '—'}</p>
</div>
</div>
<div className="mt-4">
<p className="text-gray-500 uppercase tracking-wide text-[11px] mb-2">Config</p>
{configEntries.length === 0 ? (
<p className="text-xs text-gray-500">No config details available.</p>
) : (
<div className="rounded-lg border border-gray-800 overflow-hidden">
<table className="w-full text-xs">
<thead className="bg-gray-900/90">
<tr>
<th className="text-left px-3 py-2 font-semibold text-gray-400 border-b border-gray-800">Band</th>
<th className="text-left px-3 py-2 font-semibold text-gray-400 border-b border-gray-800">Testpoint</th>
<th className="text-left px-3 py-2 font-semibold text-gray-400 border-b border-gray-800">STA</th>
</tr>
</thead>
<tbody>
{configEntries.map((entry) => (
<tr key={`${test.test_id}-${entry.band}`} className="odd:bg-gray-950 even:bg-gray-900/60">
<td className="px-3 py-2 text-gray-200 border-b border-gray-800">{entry.band}</td>
<td className="px-3 py-2 text-gray-300 border-b border-gray-800">{entry.testPoint}</td>
<td className="px-3 py-2 text-gray-300 border-b border-gray-800">{entry.sta}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
)}
</div>
)
}
export default function TestWindowDetailsPanel({ isOpen, windowDetails, onClose }) {
const windowTestConfig = useMemo(() => getWindowTestConfig(windowDetails), [windowDetails])
useEffect(() => {
if (!isOpen) return undefined
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
onClose()
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [isOpen, onClose])
return (
<div
className={`absolute inset-0 z-40 transition-opacity duration-300 ${
isOpen ? 'pointer-events-auto opacity-100' : 'pointer-events-none opacity-0'
}`}
aria-hidden={!isOpen}
>
<button
type="button"
onClick={onClose}
className="absolute inset-0 bg-gray-950/45 backdrop-blur-[1px]"
aria-label="Close test window details"
/>
<aside
className={`absolute inset-y-0 right-0 w-full max-w-[30rem] border-l border-gray-700 bg-gray-950/96 shadow-2xl transition-transform duration-300 ${
isOpen ? 'translate-x-0' : 'translate-x-full'
}`}
>
<div className="flex h-full flex-col">
<div className="flex items-start justify-between gap-4 border-b border-gray-800 px-5 py-5">
<div>
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-cyan-400">Test Window</p>
<h2 className="mt-2 text-xl font-semibold text-white">
{windowDetails ? formatWindowHeader(windowDetails) : 'Window details'}
</h2>
</div>
<button
type="button"
onClick={onClose}
className="rounded-full border border-gray-700 p-2 text-gray-400 hover:border-gray-500 hover:text-white transition-colors"
aria-label="Close panel"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{windowDetails && (
<div className="flex-1 overflow-y-auto px-5 py-5">
<div className="grid grid-cols-2 gap-3">
<div className="rounded-2xl border border-gray-800 bg-gray-900/80 px-4 py-3">
<p className="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Available Run Time</p>
<p className="mt-2 text-2xl font-semibold text-white">{formatHours(windowDetails.available_runtime_minutes)}</p>
</div>
<div className="rounded-2xl border border-gray-800 bg-gray-900/80 px-4 py-3">
<p className="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Estimated Run Time</p>
<p className="mt-2 text-2xl font-semibold text-white">{formatHours(windowDetails.estimated_runtime_minutes)}</p>
</div>
</div>
<div className="mt-5 rounded-2xl border border-gray-800 bg-gray-900/80 p-4">
<div className="flex items-center justify-between gap-3">
<p className="text-sm font-semibold text-white">Config</p>
<span className="rounded-full border border-cyan-700/60 bg-cyan-900/30 px-2 py-1 text-[11px] font-semibold uppercase tracking-wide text-cyan-200">
{windowTestConfig.length > 0 ? windowTestConfig.join(', ') : 'N/A'}
</span>
</div>
{windowDetails.configRows?.length ? (
<div className="mt-3 overflow-hidden rounded-xl border border-gray-800">
<table className="w-full text-xs">
<thead className="bg-gray-950/90">
<tr>
<th className="px-3 py-2 text-left font-semibold text-gray-400 border-b border-gray-800">STA</th>
<th className="px-3 py-2 text-left font-semibold text-gray-400 border-b border-gray-800">Testpoint</th>
</tr>
</thead>
<tbody>
{windowDetails.configRows.map((row, index) => (
<tr key={`${row.sta}-${index}`} className="odd:bg-gray-950 even:bg-gray-900/60">
<td className="px-3 py-2 border-b border-gray-800 font-semibold text-gray-200">{row.sta}</td>
<td className="px-3 py-2 border-b border-gray-800 text-gray-300">{row.testPoints.join(', ')}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p className="mt-3 text-xs text-gray-500">No STA placement mapping available for this window.</p>
)}
</div>
<div className="mt-5">
<div className="flex items-center justify-between gap-3 mb-3">
<p className="text-sm font-semibold text-white">Tests Scheduled In This Window</p>
<span className="text-xs text-gray-500">{windowDetails.tests?.length ?? 0} tests</span>
</div>
{windowDetails.tests?.length ? (
<div className="flex flex-col gap-3">
{windowDetails.tests.map((test) => (
<TestRow key={`${test.test_id}-${test.device}-${test.scheduled_date}-${test.shift_index}`} test={test} />
))}
</div>
) : (
<div className="rounded-2xl border border-dashed border-gray-800 bg-gray-900/50 px-4 py-6 text-sm text-gray-500">
No tests are currently scheduled in this window.
</div>
)}
</div>
</div>
)}
</div>
</aside>
</div>
)
}