Skip to content

Commit

Permalink
feat: protected feature API
Browse files Browse the repository at this point in the history
- a new API, `/features/{protector-feature-id}/protected-by`, which returns list of adaptation options, by feature and layer, for all features protected by `protector-feature-id`.
- a new recoil selector, which runs the query with the current selected feature ID.
- new recoil selectors that filter the results by RCP and protection level.
  • Loading branch information
eatyourgreens committed Nov 29, 2024
1 parent 4f41593 commit af7ab15
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 2 deletions.
32 changes: 32 additions & 0 deletions backend/backend/app/routers/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,35 @@ def read_sorted_features(
).order_by(desc("value"))

return paginate(q, page_params)


@router.get(
"/{protector_id}/protected-by",
response_model=list[schemas.ProtectedFeatureListItem],
)
def read_protected_features(
protector_id: int,
db: Session = Depends(get_db),
):
adaptation_options = db.query(
models.Feature.id.label("id"),
models.Feature.string_id.label("string_id"),
models.Feature.layer.label("layer"),
models.AdaptationCostBenefit.adaptation_cost.label("adaptation_cost"),
models.AdaptationCostBenefit.adaptation_protection_level.label("adaptation_protection_level"),
models.AdaptationCostBenefit.adaptation_name.label("adaptation_name"),
models.AdaptationCostBenefit.avoided_ead_mean.label("avoided_ead_mean"),
models.AdaptationCostBenefit.avoided_eael_mean.label("avoided_eael_mean"),
models.AdaptationCostBenefit.rcp.label("rcp"),
models.AdaptationCostBenefit.hazard.label("hazard"),
).select_from(
models.Feature
).join(
models.FeatureLayer
).join(
models.Feature.adaptation
).filter(
models.AdaptationCostBenefit.adaptation_name == "Flood defence around asset"
)
print(adaptation_options)
return adaptation_options.all()
22 changes: 21 additions & 1 deletion backend/backend/app/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ class ReturnPeriodDamagesVariables(DataVariables):
loss_amax: float


class ReturnPeriodDamage(ReturnPeriodDamagesDimensions, ReturnPeriodDamagesVariables):
class ReturnPeriodDamage(
ReturnPeriodDamagesDimensions,
ReturnPeriodDamagesVariables
):
model_config = ConfigDict(from_attributes=True)


Expand Down Expand Up @@ -172,3 +175,20 @@ class FeatureListItemOut(BaseModel, Generic[SortFieldT]):
AttributeT = TypeVar("AttributeT")

AttributeLookup = dict[int, AttributeT]

# Protected Features


class ProtectedFeatureListItem(BaseModel):
id: int
string_id: str
layer: str
adaptation_name: str
adaptation_protection_level: float
adaptation_cost: float
avoided_ead_mean: float
avoided_eael_mean: float
hazard: str
rcp: float

model_config = ConfigDict(from_attributes=True)
5 changes: 5 additions & 0 deletions backend/backend/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ class AdaptationCostBenefit(Base):
primary_key=True,
index=True
)
protector_feature_id = Column(
Integer,
primary_key=True,
index=True
)

hazard = Column(String(8), nullable=False, primary_key=True)
rcp = Column(String(8), nullable=False, primary_key=True)
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/lib/api-client/services/FeaturesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,26 @@ export class FeaturesService {
});
}

/**
* Read Protected Features
* @returns any Successful Response
* @throws ApiError
*/
public featuresReadProtectedFeatures({
protectorId,
}: {
protectorId: number,
}): CancelablePromise<any> {
return this.httpRequest.request({
method: 'GET',
url: '/features/{protector_id}/protected-by',
path: {
'protector_id': protectorId,
},
errors: {
422: `Validation Error`,
},
});
}

}
19 changes: 19 additions & 0 deletions frontend/src/lib/data-map/DataMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { useMap } from 'react-map-gl/maplibre';
import { FC, useRef } from 'react';
import { useRecoilValue } from 'recoil';

import {
protectedFeatureDetailsState,
protectedFeatureProtectionLevelState,
protectedFeatureRCPState,
protectedFeatureDetailsQuery,
} from 'lib/state/interactions/interaction-state';
import { useInteractions } from 'lib/state/interactions/use-interactions';
import { useDataLoadTrigger } from 'lib/data-map/use-data-load-trigger';
import { InteractionGroupConfig } from 'lib/data-map/types';
Expand Down Expand Up @@ -71,6 +77,19 @@ export const DataMap: FC<{
const viewLayersParams = useRecoilValue(viewLayersParamsState);
const saveViewLayers = useSaveViewLayers();

const protectedFeatures = useRecoilValue(protectedFeatureDetailsState);
const protectedFeatureProtectionLevel = useRecoilValue(protectedFeatureProtectionLevelState);
const protectedFeatureRCP = useRecoilValue(protectedFeatureRCPState);
const protectedFeatureDetails = useRecoilValue(
protectedFeatureDetailsQuery({ rcp: 2.6, protectionLevel: 1 }),
);
console.log({
protectedFeatures,
protectedFeatureProtectionLevel,
protectedFeatureRCP,
protectedFeatureDetails,
});

useTrigger(viewLayers);

const { onHover, onClick, layerFilter, pickingRadius } = useInteractions(
Expand Down
41 changes: 40 additions & 1 deletion frontend/src/lib/state/interactions/interaction-state.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import forEach from 'lodash/forEach';
import { atom, atomFamily, selector, selectorFamily } from 'recoil';

import { InteractionLayer } from 'lib/data-map/types';
import { InteractionLayer, VectorTarget } from 'lib/data-map/types';
import { isReset } from 'lib/recoil/is-reset';
import { ApiClient } from 'lib/api-client';

Expand Down Expand Up @@ -90,6 +90,45 @@ export const selectedAssetDetails = selectorFamily({
},
});

export const protectedFeatureDetailsState = selector({
key: 'protectedFeatureDetails',
get: async ({ get }) => {
const selection = get(selectionState('assets'));
const target = selection?.target as VectorTarget;
if (!target?.feature?.id) {
return null;
}
const featureDetails = await apiClient.features.featuresReadProtectedFeatures({
protectorId: target.feature.id,
});
return featureDetails;
},
});

export const protectedFeatureRCPState = selector({
key: 'protectedFeatureRCP',
get: ({ get }) => new Set(get(protectedFeatureDetailsState)?.map((feature) => feature.rcp)),
});

export const protectedFeatureProtectionLevelState = selector({
key: 'protectedFeatureProtectionLevel',
get: ({ get }) =>
new Set(
get(protectedFeatureDetailsState)?.map((feature) => feature.adaptation_protection_level),
),
});

type ProtectedFeatureDetailsQuery = { rcp: number; protectionLevel: number };
export const protectedFeatureDetailsQuery = selectorFamily({
key: 'protectedFeatureDetailsQuery',
get:
({ rcp, protectionLevel }: ProtectedFeatureDetailsQuery) =>
({ get }) =>
get(protectedFeatureDetailsState).filter(
(item) => item.rcp === rcp && item.adaptation_protection_level === protectionLevel,
),
});

type AllowedGroupLayers = Record<string, string[]>;

const allowedGroupLayersImpl = atom<AllowedGroupLayers>({
Expand Down

0 comments on commit af7ab15

Please sign in to comment.