Skip to content

Commit

Permalink
Merge pull request #537 from Open-Earth-Foundation/feat/connect-inven…
Browse files Browse the repository at this point in the history
…tory-v2

Connect add data pages
  • Loading branch information
lemilonkh committed Jun 25, 2024
2 parents 7723f5f + f53ef1a commit f16a0d8
Show file tree
Hide file tree
Showing 15 changed files with 1,894 additions and 828 deletions.
6 changes: 5 additions & 1 deletion app/seeders/20231114094254-emissions-factors.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ module.exports = {
"DataSourceEmissionsFactor",
folder,
);
const emissionsFactors = await parseFile("EmissionsFactor", folder);

const emissionsFactorsStationaryEnergy = await parseFile("EmissionsFactor_Stationary_Energy", folder);
const emissionsFactorsStationaryEnergyScope1 = await parseFile("EmissionsFactor_Stationary_Energy_Scope1", folder);
const emissionsFactors = emissionsFactorsStationaryEnergy.concat(emissionsFactorsStationaryEnergyScope1);

const publishers = await parseFile("Publisher", folder);

await bulkUpsert(
Expand Down
2,470 changes: 1,663 additions & 807 deletions app/src/app/[lng]/[inventory]/data/[step]/[subsector]/page.tsx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/src/app/[lng]/[inventory]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export default function Home({ params: { lng } }: { params: { lng: string } }) {
{ cityId: inventory?.cityId!, year: inventory?.year! },
{ skip: !inventory?.cityId || !inventory?.year },
);

let totalProgress = 0,
thirdPartyProgress = 0,
uploadedProgress = 0;
Expand Down
17 changes: 13 additions & 4 deletions app/src/app/api/v0/city/[city]/population/[year]/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import UserService from "@/backend/UserService";
import { db } from "@/models";
import { apiHandler } from "@/util/api";
import { PopulationEntry, findClosestYearToInventory } from "@/util/helpers";
import { NextResponse } from "next/server";

export const GET = apiHandler(async (_req: Request, { session, params }) => {
const city = await UserService.findUserCity(params.city, session);
const population = await db.models.Population.findOne({
where: { cityId: city?.cityId, year: params.year },
const population = await db.models.Population.findAll({
where: { cityId: city?.cityId },
});
return NextResponse.json({ data: population });
});
const populations: PopulationEntry[] = population.map(({population, year})=>{
return {
year,
population: population!
}
})

const closestYearPopulationData = findClosestYearToInventory(populations, parseInt(params.year))
return NextResponse.json({ data: closestYearPopulationData });
});
28 changes: 20 additions & 8 deletions app/src/app/api/v0/inventory/[inventory]/activity-value/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,21 @@ export const POST = apiHandler(async (req, { params, session }) => {
export const GET = apiHandler(async (req, { params, session }) => {
// extract and validate query params
const subCategoryIdsParam = req.nextUrl.searchParams.get("subCategoryIds");
if (!subCategoryIdsParam || subCategoryIdsParam.length === 0) {
const subSectorId = req.nextUrl.searchParams.get("subSectorId");

let subCategoryIds;
if (subCategoryIdsParam && subCategoryIdsParam.length > 0) {
subCategoryIds = subCategoryIdsParam.split(",");
} else if (subSectorId && subSectorId.length > 0) {
const subCategories = await db.models.SubCategory.findAll({
where: { subsectorId: subSectorId! },
});
subCategoryIds = subCategories.map((sc) => sc.subcategoryId);
} else {
throw new createHttpError.BadRequest(
"Query parameter subCategoryIds is required!",
"Query parameter subCategoryIds or subSectorId is required!",
);
}
const subCategoryIds = subCategoryIdsParam.split(",");

// optional filter for a specific methodology
const methodologyId = req.nextUrl.searchParams.get("methodologyId");
Expand All @@ -104,16 +113,19 @@ export const GET = apiHandler(async (req, { params, session }) => {
session,
);

const query: any = {
subCategoryId: { [Op.in]: subCategoryIds },
inventoryId: inventory.inventoryId,
};
if (methodologyId) {
query.methodologyId = methodologyId;
}
const activityValues = await db.models.ActivityValue.findAll({
include: [
{
model: db.models.InventoryValue,
as: "inventoryValue",
where: {
subCategoryId: { [Op.in]: subCategoryIds },
inventoryId: inventory.inventoryId,
methodologyId: methodologyId ?? undefined,
},
where: query,
},
{ model: db.models.DataSource, as: "dataSource" },
{
Expand Down
2 changes: 1 addition & 1 deletion app/src/backend/CDPService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,4 @@ export default class CDPService {
logger.debug(`Response: ${res.statusText}`);
return true;
}
}
}
19 changes: 14 additions & 5 deletions app/src/components/Cards/suggested-activities-card.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { AddIcon } from "@chakra-ui/icons";
import { Box, Button, Card, Checkbox, Text } from "@chakra-ui/react";
import { Box, Button, Card, Checkbox, Text, useTheme } from "@chakra-ui/react";
import { TFunction } from "i18next";
import React, { FC } from "react";

interface SuggestedActivityCardProps {
name: string;
t: TFunction;
onAddActivity: () => void;
isSelected: boolean;
onActivityAdded?: () => void;
}

const SuggestedActivityCard: FC<SuggestedActivityCardProps> = ({ name, t, onAddActivity }) => {
const SuggestedActivityCard: FC<SuggestedActivityCardProps> = ({
name,
t,
isSelected,
onActivityAdded,
}) => {
const themeColors = useTheme().colors;

return (
<Card
display="flex"
Expand All @@ -23,15 +31,15 @@ const SuggestedActivityCard: FC<SuggestedActivityCardProps> = ({ name, t, onAddA
borderColor="border.overlay"
cursor="pointer"
_hover={{ shadow: "md", borderWidth: '1px', borderColor: "content.link" }}
onClick={onAddActivity}
onClick={onActivityAdded}
>
<Box display="flex" alignItems="center">
<Checkbox
borderRadius="full"
__css={{
"& .chakra-checkbox__control": {
bg: "white",
color: "#2351DC", // TODO: figure out how to include theme variable in css
color: themeColors.brand,
borderRadius: "full",
borderColor: "#D7D8FA",
h: "24px",
Expand Down Expand Up @@ -86,6 +94,7 @@ const SuggestedActivityCard: FC<SuggestedActivityCardProps> = ({ name, t, onAddA
fontSize="button.md"
color="content.link"
gap="8px"
onClick={onActivityAdded}
>
{t("add-activity")}
</Button>
Expand Down
5 changes: 4 additions & 1 deletion app/src/components/Modals/change-methodology.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ import { Trans } from "react-i18next";

interface ChangeMethodologyProps {
isOpen: boolean;
onClose: any;
onClose: () => void;
onChangeClicked: () => void;
t: TFunction;
}

const ChangeMethodology: FC<ChangeMethodologyProps> = ({
isOpen,
onClose,
onChangeClicked,
t,
}) => {
return (
Expand Down Expand Up @@ -120,6 +122,7 @@ const ChangeMethodology: FC<ChangeMethodologyProps> = ({
type="submit"
p={0}
m={0}
onClick={onChangeClicked}
>
{t("change-methodology")}
</Button>
Expand Down
9 changes: 9 additions & 0 deletions app/src/models/ActivityValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Sequelize from "sequelize";
import { DataTypes, Model, Optional } from "sequelize";
import type { GasValue, GasValueId } from "./GasValue";
import type { InventoryValue, InventoryValueId } from "./InventoryValue";
import { DataSource, DataSourceId } from "./DataSource";

export interface ActivityValueAttributes {
id: string;
Expand Down Expand Up @@ -64,6 +65,14 @@ export class ActivityValue
InventoryValueId
>;
createInventoryValue!: Sequelize.BelongsToCreateAssociationMixin<InventoryValue>;
// ActivityValue belongsTo DataSource via DataSourceId
dataSource!: DataSource;
getDataSource!: Sequelize.BelongsToGetAssociationMixin<DataSource>;
setDataSource!: Sequelize.BelongsToSetAssociationMixin<
DataSource,
DataSourceId
>;
createDataSource!: Sequelize.BelongsToCreateAssociationMixin<DataSource>;

static initModel(sequelize: Sequelize.Sequelize): typeof ActivityValue {
return ActivityValue.init(
Expand Down
33 changes: 33 additions & 0 deletions app/src/models/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type { Sector, SectorId } from "./Sector";
import type { SubCategory, SubCategoryId } from "./SubCategory";
import type { SubSector, SubSectorId } from "./SubSector";
import { InventoryValue, InventoryValueId } from "./InventoryValue";
import { ActivityValue, ActivityValueId } from "./ActivityValue";

export interface DataSourceAttributes {
datasourceId: string;
Expand Down Expand Up @@ -723,6 +724,38 @@ export class DataSource
InventoryValue,
InventoryValueId
>;
// DataSource hasMany ActivityValue via datasourceId
activityValues!: ActivityValue[];
getActivityValues!: Sequelize.HasManyGetAssociationsMixin<ActivityValue>;
setActivityValues!: Sequelize.HasManySetAssociationsMixin<
ActivityValue,
ActivityValueId
>;
addActivityValue!: Sequelize.HasManyAddAssociationMixin<
ActivityValue,
ActivityValueId
>;
addActivityValues!: Sequelize.HasManyAddAssociationsMixin<
ActivityValue,
ActivityValueId
>;
createActivityValue!: Sequelize.HasManyCreateAssociationMixin<ActivityValue>;
removeActivityValue!: Sequelize.HasManyRemoveAssociationMixin<
ActivityValue,
ActivityValueId
>;
removeActivityValues!: Sequelize.HasManyRemoveAssociationsMixin<
ActivityValue,
ActivityValueId
>;
hasActivityValue!: Sequelize.HasManyHasAssociationMixin<
ActivityValue,
ActivityValueId
>;
hasActivityValues!: Sequelize.HasManyHasAssociationsMixin<
ActivityValue,
ActivityValueId
>;

static initModel(sequelize: Sequelize.Sequelize): typeof DataSource {
return DataSource.init(
Expand Down
8 changes: 8 additions & 0 deletions app/src/models/init-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,14 @@ export function initModels(sequelize: Sequelize) {
as: "gasValues",
foreignKey: "activityValueId",
});
ActivityValue.belongsTo(DataSource, {
as: "dataSource",
foreignKey: "datasourceId",
});
DataSource.hasMany(ActivityValue, {
as: "activityValues",
foreignKey: "datasourceId",
});
ActivityValue.belongsTo(InventoryValue, {
as: "inventoryValue",
foreignKey: "inventoryValueId",
Expand Down
46 changes: 46 additions & 0 deletions app/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const api = createApi({
"UserInventories",
"SubSectorValue",
"InventoryValue",
"ActivityValue",
"UserData",
"FileData",
"CityData",
Expand Down Expand Up @@ -402,6 +403,51 @@ export const api = createApi({
},
transformResponse: (response: { data: [] }) => response.data,
}),

// ActivityValue CRUD
getActivityValues: builder.query({
query: ({inventoryId, subCategoryIds, subSectorId, methodologyId}: {inventoryId: string, subCategoryIds?: string[], subSectorId?: string, methodologyId?: string }) => ({
url: `/inventory/${inventoryId}/activity-value`,
params: { subCategoryIds: subCategoryIds?.join(",") ?? undefined, subSectorId: subSectorId ?? undefined },
method: "GET",
}),
transformResponse: (response: any) => response.data,
providesTags: ["ActivityValue"],
}),
createActivityValue: builder.mutation({
query: (data) => ({
method: "POST",
url: `/inventory/${data.inventoryId}/activity-value`,
body: data.data,
}),
transformResponse: (response: any) => response.data,
invalidatesTags: ["ActivityValue"],
}),
getActivityValue: builder.query({
query: (data: { inventoryId: string; valueId: string }) => ({
method: "GET",
url: `/inventory/${data.inventoryId}/activity-value/${data.valueId}`,
}),
transformResponse: (response: any) => response.data,
providesTags: ["ActivityValue"],
}),
updateActivityValue: builder.mutation({
query: (data) => ({
method: "PATCH",
url: `/inventory/${data.inventoryId}/activity-value/${data.valueId}`,
body: data.data,
}),
transformResponse: (response: any) => response.data,
invalidatesTags: ["ActivityValue"],
}),
deleteActivityValue: builder.mutation({
query: (data: { activityValueId: string; inventoryId: string }) => ({
method: "DELETE",
url: `/inventory/${data.inventoryId}/activity-value/${data.activityValueId}`,
}),
transformResponse: (response: any) => response.data,
invalidatesTags: ["ActivityValue"],
}),
}),
});

Expand Down
35 changes: 35 additions & 0 deletions app/src/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,38 @@ export function findClosestYear(
null as PopulationEntry | null,
);
}

export function findClosestYearToInventory(
populationData: PopulationEntry[] | undefined,
year: number,
maxYearDifference: number = 10
): PopulationEntry | null {
if (!populationData || populationData.length === 0) {
return null;
}

let closestEntry = null;
let closestDistance = Infinity; // Initialize with a large number

populationData.forEach(entry => {
// Ensure the entry has a valid population value
if (entry.population !== null && entry.population !== undefined) {
const currentDistance = Math.abs(entry.year - year);
// Update closestEntry if the current entry is closer than the previously stored one
if (currentDistance < closestDistance) {
closestEntry = entry;
closestDistance = currentDistance;
}
}
});

// After identifying the closest entry, check if it's within the allowable range
if (closestEntry && closestDistance <= maxYearDifference) {
return closestEntry;
} else if (closestEntry) {
// If no entry is within the maxYearDifference, return the closest available entry
return closestEntry;
}

return null; // In case all entries are outside the maxYearDifference and no closest entry was found
}
Loading

0 comments on commit f16a0d8

Please sign in to comment.