Skip to content

Commit

Permalink
loa: switch to new data source
Browse files Browse the repository at this point in the history
  • Loading branch information
globin committed Feb 17, 2025
1 parent 2b5e643 commit f814212
Show file tree
Hide file tree
Showing 15 changed files with 1,138 additions and 782 deletions.
1,123 changes: 665 additions & 458 deletions atciss-frontend/package-lock.json

Large diffs are not rendered by default.

16 changes: 0 additions & 16 deletions atciss-frontend/src/app/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -707,19 +707,3 @@ export const FIR_SETTINGS: Record<string, FIR> = {
},
},
}
export const FIR_TO_VATGLASSES: Record<string, string> = {
EDMM: "ed",
EDUU: "ed",
EDGG: "ed",
EDWW: "ed",
EDYY: "ed",
EBBU: "eb-el",
EHAA: "eh",
EKDK: "ek",
ESMM: "es",
LIPP: "li",
LKAA: "lk",
LOVV: "lo",
EPWW: "ep",
LSAS: "ls",
}
77 changes: 50 additions & 27 deletions atciss-frontend/src/components/atciss/LoaRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { FIR_TO_VATGLASSES } from "app/config"
import { useAppSelector } from "app/hooks"
import { selectOwner, selectSelectedPosition } from "services/activePositions"
import { LoaItem } from "types/loa"
Expand All @@ -12,45 +11,69 @@ const PositionInformation = ({
}) => {
const selectedPosition = useAppSelector(selectSelectedPosition)

if (!loa[`${from_to}_sector`]) {
return loa[`${from_to}_fir`]
}
const fir = loa[`${from_to}_fir`]
const sector = loa[`${from_to}_sector`]
const position = useAppSelector((state) =>
selectOwner(state, `${FIR_TO_VATGLASSES[fir]}/${sector}`),
)
const displaySector = sector.replace(/.*\//, "")
const position = useAppSelector((state) => selectOwner(state, sector))
const frequency =
position && position.id !== selectedPosition
? ` (${position?.frequency})`
: ""
return position &&
position.id !== selectedPosition &&
position.id !== `${FIR_TO_VATGLASSES[fir]}/${sector}`
? `${sector} by ${position?.name}${frequency}`
: `${sector}${frequency}`
return position && position.id !== selectedPosition && position.id !== sector
? `${displaySector} by ${position?.name}${frequency}`
: `${displaySector}${frequency}`
}

export const LoaRow = ({
loa,
showCop = true,
}: {
loa: LoaItem
showCop?: boolean
}) => {
export const LoaRow = ({ loa }: { loa: LoaItem }) => {
return (
<tr>
{showCop && <td>{loa.cop}</td>}
<td>
{loa.adep_ades === "ADEP" && <>&#x02197;</>}
{loa.adep_ades === "ADES" && <>&#x02198;</>} {loa.aerodrome}
{loa.route_before} <strong>{loa.cop ?? "LoR"}</strong> {loa.route_after}
</td>
<td>
{loa.adep && loa.adep.length > 0 && (
<>&#x02197; {loa.adep.join(", ")}</>
)}
{loa.ades && loa.ades.length > 0 && (
<>&#x02197; {loa.ades.join(", ")}</>
)}
</td>
<td>
{loa.transfer_type === "D" && <>&darr;</>}
{loa.transfer_type === "C" && <>&uarr;</>}

{loa.sfl && <>{loa.qnh ? `${loa.sfl}00ft` : `FL${loa.sfl}`}-</>}
<strong>
{loa.level === null
? "indiv. coord."
: loa.qnh
? `${loa.level}00ft`
: `FL${loa.level}`}
</strong>
{loa.qnh && <>, QNH {loa.qnh}</>}
</td>
<td>
{loa.xc === "A" && <>&uarr;</>}
{loa.xc === "B" && <>&darr;</>}
{loa.feet ? `${loa.level}00ft` : `FL${loa.level}`}
{[
loa.level_at &&
(loa.level_at[0] > 0
? `${loa.level_at[0]}nm after `
: loa.level_at[0] < 0
? `${Math.abs(loa.level_at[0])}nm prior `
: "") + `${loa.level_at[1]} at level`,
loa.releases && `released ${loa.releases}`,
/* TODO: filter by detected ATIS runway*/
loa.runway &&
loa.runway.length > 0 &&
`Runways ${loa.runway.join(", ")}`,
/* TODO: filter by active areas*/
loa.areas &&
loa.areas.length > 0 &&
`Active Areas ${loa.areas.join(", ")}`,
loa.rfl && `RFL: FL${loa.rfl}`,
loa.remarks,
]
.filter((val) => val)
.join("; ")}
</td>
<td>{loa.special_conditions}</td>
<td>
<PositionInformation loa={loa} from_to="from" />
</td>
Expand Down
7 changes: 3 additions & 4 deletions atciss-frontend/src/components/atciss/map/LoaNavaidMarker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const LoaNavaidMarker = ({ designator }: { designator: string }) => {
>
<thead>
<tr>
<th sx={{ pt: 0 }}>ROUTE</th>
<th sx={{ pt: 0 }}>ADEP/ADES</th>
<th sx={{ pt: 0 }}>FL</th>
<th sx={{ pt: 0 }}>REMARK</th>
Expand All @@ -48,9 +49,8 @@ export const LoaNavaidMarker = ({ designator }: { designator: string }) => {
<tbody>
{xloasByNavaid.map((loa, idx) => (
<LoaRow
key={`${loa.cop}-${loa.aerodrome}-${loa.adep_ades}-${loa.from_sector}-${loa.to_sector}-${idx}`}
key={`${loa.cop}-${(loa.adep ?? []).join("_")}-${(loa.ades ?? []).join("_")}-${loa.from_sector}-${loa.to_sector}-${idx}`}
loa={loa}
showCop={false}
/>
))}
</tbody>
Expand All @@ -66,9 +66,8 @@ export const LoaNavaidMarker = ({ designator }: { designator: string }) => {
<tbody>
{nloasByNavaid?.map((loa, idx) => (
<LoaRow
key={`${loa.cop}-${loa.aerodrome}-${loa.adep_ades}-${loa.from_sector}-${loa.to_sector}-${idx}`}
key={`${loa.cop}-${(loa.adep ?? []).join("_")}-${(loa.ades ?? []).join("_")}-${loa.from_sector}-${loa.to_sector}-${idx}`}
loa={loa}
showCop={false}
/>
))}
</tbody>
Expand Down
2 changes: 1 addition & 1 deletion atciss-frontend/src/services/activePositions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export const selectOwnedSectors = createSelector(
: (Object.entries(sectors).reduce(
(acc, [id, s]) =>
ownerFromSectorsActivePositions(s, activePositions) === pos
? [...acc, id.replace(/.*\//, "")] // FIXME check
? [...acc, id] // FIXME check
: acc,
[] as string[],
) ?? []),
Expand Down
2 changes: 1 addition & 1 deletion atciss-frontend/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const api = createApi({
loaBySectors: builder.query<LoaItem[], string[]>({
query: (sectors) => ({
url: "loa",
params: sectors.map((sector) => ["sector", sector.replace(/.*\//, "")]),
params: sectors.map((sector) => ["sector", sector]),
}),
}),

Expand Down
26 changes: 14 additions & 12 deletions atciss-frontend/src/services/loaApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const selectRelevantExitLoas = createSelector(
(relevantLoas, ownedSectors) =>
relevantLoas
.filter((loa) => ownedSectors.includes(loa.from_sector))
.sort(sortBy(["from_sector", "cop", "to_sector", "to_fir", "adep_ades"])),
.sort(sortBy(["from_sector", "cop", "to_sector", "adep", "ades"])),
)

export const selectRelevantEntryLoas = createSelector(
Expand All @@ -52,34 +52,36 @@ export const selectRelevantEntryLoas = createSelector(
(relevantLoas, ownedSectors) =>
relevantLoas
.filter((loa) => ownedSectors.includes(loa.to_sector))
.sort(
sortBy(["to_sector", "cop", "from_sector", "from_fir", "adep_ades"]),
),
.sort(sortBy(["to_sector", "cop", "from_sector", "adep", "ades"])),
)

const filterFn = (filter: string, to_from: "to" | "from") => (loa: LoaItem) =>
loa.aerodrome.toLowerCase().includes(filter.toLowerCase()) ||
const filterFn = (filter: string) => (loa: LoaItem) =>
(loa.adep ?? []).some((ad) => ad.includes(filter.toUpperCase())) ||
(loa.ades ?? []).some((ad) => ad.includes(filter.toUpperCase())) ||
loa.from_sector.toLowerCase().includes(filter.toLowerCase()) ||
loa.to_sector.toLowerCase().includes(filter.toLowerCase()) ||
loa[`${to_from}_fir`].toLowerCase().includes(filter.toLowerCase()) ||
loa.special_conditions.toLowerCase().includes(filter.toLowerCase()) ||
loa.cop.toLowerCase().includes(filter.toLowerCase())
loa.remarks?.toLowerCase().includes(filter.toLowerCase()) ||
loa.cop?.toLowerCase().includes(filter.toLowerCase())

export const selectFilteredExitLoas = createSelector(
selectRelevantExitLoas,
(_state: RootState, filter: string) => filter,
(loas, filter) => loas.filter(filterFn(filter, "to")),
(loas, filter) => loas.filter(filterFn(filter)),
)

export const selectFilteredEntryLoas = createSelector(
selectRelevantEntryLoas,
(_state: RootState, filter: string) => filter,
(loas, filter) => loas.filter(filterFn(filter, "from")),
(loas, filter) => loas.filter(filterFn(filter)),
)

export const selectLoaCops = createSelector(
selectRelevantLoas,
(relevantLoas) => [...new Set(relevantLoas.map((loa) => loa.cop))],
(relevantLoas) => [
...new Set(
relevantLoas.map((loa) => loa.cop).filter((cop) => cop !== null),
),
],
)

export const selectExitLoasByNavaid = createSelector(
Expand Down
24 changes: 15 additions & 9 deletions atciss-frontend/src/types/loa.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
export interface LoaItem {
aerodrome: string // FIXME: should be string[]
adep_ades: "ADEP" | "ADES" | null
cop: string
level: number
feet: boolean
xc: string | null
special_conditions: string
from_sector: string
to_sector: string
from_fir: string
to_fir: string
ades: string[] | null
adep: string[] | null
cop: string | null
runway: string[] | null
route_before: string | null
route_after: string | null
level: number
sfl: number | null
qnh: string | null
level_at: [number, string] | null
transfer_type: "C" | "D" | null
releases: "F" | "T" | "C" | "D" | null
remarks: string | null
areas: string[] | null
rfl: string | null
}
4 changes: 2 additions & 2 deletions atciss-frontend/src/views/atciss/LOA.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const LOA = ({ sx }: { sx?: ThemeUIStyleObject }) => {
<tbody>
{xLoas?.map((loa, idx) => (
<LoaRow
key={`${loa.cop}-${loa.aerodrome}-${loa.adep_ades}-${loa.from_sector}-${loa.to_sector}-${idx}`}
key={`${loa.cop}-${loa.adep?.join("_")}-${loa.ades?.join("_")}-${loa.from_sector}-${loa.to_sector}-${idx}`}
loa={loa}
/>
))}
Expand All @@ -61,7 +61,7 @@ export const LOA = ({ sx }: { sx?: ThemeUIStyleObject }) => {
<tbody>
{nLoas?.map((loa, idx) => (
<LoaRow
key={`${loa.cop}-${loa.aerodrome}-${loa.adep_ades}-${loa.from_sector}-${loa.to_sector}-${idx}`}
key={`${loa.cop}-${loa.adep?.join("_")}-${loa.ades?.join("_")}-${loa.from_sector}-${loa.to_sector}-${idx}`}
loa={loa}
/>
))}
Expand Down
22 changes: 10 additions & 12 deletions atciss/app/controllers/loa.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from typing import Annotated, cast
from typing import Annotated

from fastapi import APIRouter, Depends, Query
from fastapi.responses import ORJSONResponse
from pydantic import TypeAdapter
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import select

from atciss.app.controllers.auth import get_user
from atciss.app.utils.redis import Redis, get_redis
from atciss.app.utils.db import get_session
from atciss.app.views.loa import LoaItem

router = APIRouter()
Expand All @@ -18,15 +19,12 @@
async def metar_get(
sector: Annotated[list[str], Query(...)],
cid: Annotated[str, Depends(get_user)],
redis: Annotated[Redis, Depends(get_redis)],
db_session: Annotated[AsyncSession, Depends(get_session)],
) -> list[LoaItem]:
"""Get LOA for sector."""
loaitems = []
for s in sector:
s = s.upper()
loaitems_json = cast("str | None", await redis.get(f"loa:{s}"))
if loaitems_json is None:
continue
loaitems.extend(TypeAdapter(list[LoaItem]).validate_json(loaitems_json))

return loaitems
items = await db_session.scalars(
select(LoaItem).where(LoaItem.from_sector.in_(sector) | LoaItem.to_sector.in_(sector))
)

return items
36 changes: 21 additions & 15 deletions atciss/app/views/loa.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
from dataclasses import dataclass
from typing import Literal
import sqlalchemy
from loa import Agreement
from loa.agreement import ReleaseTypes, TransferTypes
from sqlmodel import JSON, Column, Field, SQLModel, String


@dataclass
class LoaItem:
aerodrome: str # FIXME: should be List[str]
adep_ades: Literal["ADEP", "ADES"] | None
cop: str
level: int
feet: bool
xc: str | None
special_conditions: str
from_sector: str
to_sector: str
from_fir: str
to_fir: str
class LoaItem(Agreement, SQLModel, table=True):
id: int = Field(primary_key=True)
adep: list[str] | None = Field(
default=None, sa_column=Column(sqlalchemy.dialects.postgresql.ARRAY(String()))
)
ades: list[str] | None = Field(
default=None, sa_column=Column(sqlalchemy.dialects.postgresql.ARRAY(String()))
)
runway: list[str] | None = Field(
default=None, sa_column=Column(sqlalchemy.dialects.postgresql.ARRAY(String()))
)
level_at: tuple[int, str] | None = Field(sa_column=Column(JSON), default=None)
areas: list[str] = Field(
default_factory=list, sa_column=Column(sqlalchemy.dialects.postgresql.ARRAY(String()))
)
transfer_type: TransferTypes | None = Field(default=None, sa_column=Column(String))
releases: ReleaseTypes | None = Field(default=None, sa_column=Column(String))
Loading

0 comments on commit f814212

Please sign in to comment.