From 75a0fe2e8cc3a8ca1d48a9dd831ce62262cba502 Mon Sep 17 00:00:00 2001 From: Matt Dunn-Rankin Date: Sat, 23 May 2020 12:47:06 -0700 Subject: [PATCH] refactor e/gp pairs to pose enum --- src/app/HomePage.js | 3 +- src/app/PosePicker.js | 136 ++++++++++-------- src/app/apolloClient.js | 4 +- src/app/useOutfitAppearance.js | 21 +-- src/app/useOutfitState.js | 27 ++-- .../getValidPetPoses.test.js.snap | Bin 9942 -> 55580 bytes src/server/getValidPetPoses.js | 20 +-- src/server/getValidPetPoses.test.js | 11 +- src/server/index.js | 51 ++++--- src/server/loaders.js | 13 +- src/server/query-tests/PetAppearance.test.js | 8 +- .../__snapshots__/PetAppearance.test.js.snap | 30 ++-- src/server/util.js | 68 ++++++--- 13 files changed, 210 insertions(+), 182 deletions(-) diff --git a/src/app/HomePage.js b/src/app/HomePage.js index 59c442d..0a26efb 100644 --- a/src/app/HomePage.js +++ b/src/app/HomePage.js @@ -128,8 +128,7 @@ function SubmitPetForm() { name: petName, species: species.id, color: color.id, - emotion: "HAPPY", // TODO: Ask PetService - genderPresentation: "FEMININE", // TODO: Ask PetService + pose: "HAPPY_FEM", // TODO: Ask PetService }); for (const item of items) { params.append("objects[]", item.id); diff --git a/src/app/PosePicker.js b/src/app/PosePicker.js index aafaa07..f00664b 100644 --- a/src/app/PosePicker.js +++ b/src/app/PosePicker.js @@ -32,7 +32,7 @@ function PosePicker({ }) { const theme = useTheme(); const checkedInputRef = React.useRef(); - const { loading, error, poses } = usePoses(outfitState); + const { loading, error, poseInfos } = usePoses(outfitState); if (loading) { return null; @@ -45,19 +45,15 @@ function PosePicker({ } // If there's only one pose anyway, don't bother showing a picker! - const numAvailablePoses = Object.values(poses).filter((p) => p.isAvailable) - .length; + const numAvailablePoses = Object.values(poseInfos).filter( + (p) => p.isAvailable + ).length; if (numAvailablePoses <= 1) { return null; } const onChange = (e) => { - const [emotion, genderPresentation] = e.target.value.split("-"); - dispatchToOutfit({ - type: "setPose", - emotion, - genderPresentation, - }); + dispatchToOutfit({ type: "setPose", pose: e.target.value }); }; return ( @@ -99,13 +95,13 @@ function PosePicker({ isOpen && "is-open" )} > - {outfitState.emotion === "HAPPY" && ( + {getEmotion(outfitState.pose) === "HAPPY" && ( )} - {outfitState.emotion === "SAD" && ( + {getEmotion(outfitState.pose) === "SAD" && ( )} - {outfitState.emotion === "SICK" && ( + {getEmotion(outfitState.pose) === "SICK" && ( )} @@ -133,24 +129,30 @@ function PosePicker({ - - - @@ -159,24 +161,30 @@ function PosePicker({ - - - @@ -213,14 +221,14 @@ const GENDER_PRESENTATION_STRINGS = { FEMININE: "Feminine", }; -function PoseButton({ pose, onChange, inputRef }) { +function PoseOption({ poseInfo, onChange, inputRef }) { const theme = useTheme(); const genderPresentationStr = - GENDER_PRESENTATION_STRINGS[pose.genderPresentation]; - const emotionStr = EMOTION_STRINGS[pose.emotion]; + GENDER_PRESENTATION_STRINGS[poseInfo.genderPresentation]; + const emotionStr = EMOTION_STRINGS[poseInfo.emotion]; let label = `${emotionStr} and ${genderPresentationStr}`; - if (!pose.isAvailable) { + if (!poseInfo.isAvailable) { label += ` (not modeled yet)`; } @@ -239,9 +247,9 @@ function PoseButton({ pose, onChange, inputRef }) { type="radio" aria-label={label} name="pose" - value={`${pose.emotion}-${pose.genderPresentation}`} - checked={pose.isSelected} - disabled={!pose.isAvailable} + value={poseInfo.pose} + checked={poseInfo.isSelected} + disabled={!poseInfo.isAvailable} onChange={onChange} ref={inputRef || null} /> @@ -253,11 +261,11 @@ function PoseButton({ pose, onChange, inputRef }) { width="50px" height="50px" title={ - pose.isAvailable + poseInfo.isAvailable ? // A lil debug output, so that we can quickly identify glitched // PetStates and manually mark them as glitched! window.location.hostname.includes("localhost") && - `#${pose.petStateId}` + `#${poseInfo.petStateId}` : "Not modeled yet" } position="relative" @@ -298,18 +306,18 @@ function PoseButton({ pose, onChange, inputRef }) { border-width: 3px; } `, - !pose.isAvailable && "not-available" + !poseInfo.isAvailable && "not-available" )} /> - {pose.isAvailable ? ( + {poseInfo.isAvailable ? ( - + ) : ( @@ -342,8 +350,7 @@ function usePoses(outfitState) { id petStateId bodyId - genderPresentation - emotion + pose approximateThumbnailUrl ...PetAppearanceForOutfitPreview } @@ -354,28 +361,39 @@ function usePoses(outfitState) { ); const petAppearances = data?.petAppearances || []; - const buildPose = (e, gp) => { - const appearance = petAppearances.find( - (pa) => pa.emotion === e && pa.genderPresentation === gp - ); + const buildPoseInfo = (pose) => { + const appearance = petAppearances.find((pa) => pa.pose === pose); return { ...appearance, isAvailable: Boolean(appearance), - isSelected: - outfitState.emotion === e && outfitState.genderPresentation === gp, + isSelected: outfitState.pose === pose, }; }; - const poses = { - happyMasc: buildPose("HAPPY", "MASCULINE"), - sadMasc: buildPose("SAD", "MASCULINE"), - sickMasc: buildPose("SICK", "MASCULINE"), - happyFem: buildPose("HAPPY", "FEMININE"), - sadFem: buildPose("SAD", "FEMININE"), - sickFem: buildPose("SICK", "FEMININE"), + const poseInfos = { + happyMasc: buildPoseInfo("HAPPY_MASC"), + sadMasc: buildPoseInfo("SAD_MASC"), + sickMasc: buildPoseInfo("SICK_MASC"), + happyFem: buildPoseInfo("HAPPY_FEM"), + sadFem: buildPoseInfo("SAD_FEM"), + sickFem: buildPoseInfo("SICK_FEM"), }; - return { loading, error, poses }; + return { loading, error, poseInfos }; +} + +function getEmotion(pose) { + if (["HAPPY_MASC", "HAPPY_FEM"].includes(pose)) { + return "HAPPY"; + } else if (["SAD_MASC", "SAD_FEM"].includes(pose)) { + return "SAD"; + } else if (["SICK_MASC", "SICK_FEM"].includes(pose)) { + return "SICK"; + } else if (["UNCONVERTED", "UNKNOWN"].includes(pose)) { + return null; + } else { + throw new Error(`unrecognized pose ${JSON.stringify(pose)}`); + } } const transformsByBodyId = { diff --git a/src/app/apolloClient.js b/src/app/apolloClient.js index 0f4e7d0..c270e8b 100644 --- a/src/app/apolloClient.js +++ b/src/app/apolloClient.js @@ -16,8 +16,8 @@ const cacheRedirects = { // way, when you switch pet poses, Apollo knows it already has the // appearance data and doesn't need to ask the server again! petAppearance: (_, args, { getCacheKey }) => { - const { speciesId, colorId, emotion, genderPresentation } = args; - const id = `${speciesId}-${colorId}-${emotion}-${genderPresentation}`; + const { speciesId, colorId, pose } = args; + const id = `${speciesId}-${colorId}-${pose}`; return getCacheKey({ __typename: "PetAppearance", id }); }, }, diff --git a/src/app/useOutfitAppearance.js b/src/app/useOutfitAppearance.js index 93250cf..0e7aa3a 100644 --- a/src/app/useOutfitAppearance.js +++ b/src/app/useOutfitAppearance.js @@ -6,13 +6,7 @@ import { useQuery } from "@apollo/react-hooks"; * visibleLayers for rendering. */ export default function useOutfitAppearance(outfitState) { - const { - wornItemIds, - speciesId, - colorId, - emotion, - genderPresentation, - } = outfitState; + const { wornItemIds, speciesId, colorId, pose } = outfitState; const { loading, error, data } = useQuery( gql` @@ -20,15 +14,9 @@ export default function useOutfitAppearance(outfitState) { $wornItemIds: [ID!]! $speciesId: ID! $colorId: ID! - $emotion: Emotion! - $genderPresentation: GenderPresentation! + $pose: Pose! ) { - petAppearance( - speciesId: $speciesId - colorId: $colorId - emotion: $emotion - genderPresentation: $genderPresentation - ) { + petAppearance(speciesId: $speciesId, colorId: $colorId, pose: $pose) { ...PetAppearanceForOutfitPreview } @@ -47,8 +35,7 @@ export default function useOutfitAppearance(outfitState) { wornItemIds, speciesId, colorId, - emotion, - genderPresentation, + pose, }, } ); diff --git a/src/app/useOutfitState.js b/src/app/useOutfitState.js index 8618d85..45bc358 100644 --- a/src/app/useOutfitState.js +++ b/src/app/useOutfitState.js @@ -15,7 +15,7 @@ function useOutfitState() { initialState ); - const { name, speciesId, colorId, emotion, genderPresentation } = state; + const { name, speciesId, colorId, pose } = state; // It's more convenient to manage these as a Set in state, but most callers // will find it more convenient to access them as arrays! e.g. for `.map()` @@ -84,8 +84,7 @@ function useOutfitState() { allItemIds, speciesId, colorId, - emotion, - genderPresentation, + pose, url, }; @@ -159,28 +158,21 @@ const outfitStateReducer = (apolloClient) => (baseState, action) => { closetedItemIds.delete(itemId); }); case "setPose": - return produce(baseState, (state) => { - const { emotion, genderPresentation } = action; - state.emotion = emotion; - state.genderPresentation = genderPresentation; - }); + return { ...baseState, pose: action.pose }; case "reset": return produce(baseState, (state) => { const { name, speciesId, colorId, - emotion, - genderPresentation, + pose, wornItemIds, closetedItemIds, } = action; state.name = name; state.speciesId = speciesId ? String(speciesId) : baseState.speciesId; state.colorId = colorId ? String(colorId) : baseState.colorId; - state.emotion = emotion || baseState.emotion; - state.genderPresentation = - genderPresentation || baseState.genderPresentation; + state.pose = pose || baseState.pose; state.wornItemIds = wornItemIds ? new Set(wornItemIds.map(String)) : baseState.wornItemIds; @@ -199,8 +191,7 @@ function parseOutfitUrl() { name: urlParams.get("name"), speciesId: urlParams.get("species"), colorId: urlParams.get("color"), - emotion: urlParams.get("emotion") || "HAPPY", - genderPresentation: urlParams.get("genderPresentation") || "FEMININE", + pose: urlParams.get("pose") || "HAPPY_FEM", wornItemIds: new Set(urlParams.getAll("objects[]")), closetedItemIds: new Set(urlParams.getAll("closet[]")), }; @@ -335,8 +326,7 @@ function buildOutfitUrl(state) { name, speciesId, colorId, - emotion, - genderPresentation, + pose, wornItemIds, closetedItemIds, } = state; @@ -345,8 +335,7 @@ function buildOutfitUrl(state) { name: name || "", species: speciesId, color: colorId, - emotion, - genderPresentation, + pose, }); for (const itemId of wornItemIds) { params.append("objects[]", itemId); diff --git a/src/server/__snapshots__/getValidPetPoses.test.js.snap b/src/server/__snapshots__/getValidPetPoses.test.js.snap index 60229d16e57d17da24855ea52d9fde1592e257cc..4efa99865b8f1b2cae79d88f327b778a1008b16a 100644 GIT binary patch literal 55580 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 gRM==S__`VsZ(AWyIveq_E&}(e?|}{= 1`); - return rows; + return rows.map(normalizeRow); } diff --git a/src/server/getValidPetPoses.test.js b/src/server/getValidPetPoses.test.js index 18ee109..e142ef9 100644 --- a/src/server/getValidPetPoses.test.js +++ b/src/server/getValidPetPoses.test.js @@ -3,6 +3,15 @@ import getValidPetPoses from "./getValidPetPoses"; describe("getValidPetPoses", () => { it("gets them and writes them to a buffer", async () => { const buffer = await getValidPetPoses(); - expect(buffer.toString()).toMatchSnapshot(); + expect(asBinaryString(buffer)).toMatchSnapshot(); }); }); + +function asBinaryString(buffer) { + let str = ""; + for (let i = 0; i < buffer.length; i++) { + const byte = buffer.readUInt8(i); + str += byte.toString(2).padStart(8, "0") + "\n"; + } + return str; +} diff --git a/src/server/index.js b/src/server/index.js index 6c54348..4570a84 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -3,7 +3,12 @@ const { gql } = require("apollo-server"); const connectToDb = require("./db"); const buildLoaders = require("./loaders"); const neopets = require("./neopets"); -const { capitalize, getEmotion, getGenderPresentation } = require("./util"); +const { + capitalize, + getPoseFromPetState, + getEmotion, + getGenderPresentation, +} = require("./util"); const typeDefs = gql` enum LayerImageSize { @@ -12,6 +17,20 @@ const typeDefs = gql` SIZE_150 } + """ + The poses a PetAppearance can take! + """ + enum Pose { + HAPPY_MASC + SAD_MASC + SICK_MASC + HAPPY_FEM + SAD_FEM + SICK_FEM + UNCONVERTED + UNKNOWN # for when we have the data, but we don't know what it is + } + """ A pet's gender presentation: masculine or feminine. @@ -50,8 +69,9 @@ const typeDefs = gql` id: ID! petStateId: ID! bodyId: ID! - genderPresentation: GenderPresentation - emotion: Emotion + pose: Pose! + genderPresentation: GenderPresentation # deprecated + emotion: Emotion # deprecated approximateThumbnailUrl: String! layers: [AppearanceLayer!]! } @@ -120,12 +140,7 @@ const typeDefs = gql` offset: Int limit: Int ): ItemSearchResult! - petAppearance( - speciesId: ID! - colorId: ID! - emotion: Emotion! - genderPresentation: GenderPresentation! - ): PetAppearance + petAppearance(speciesId: ID!, colorId: ID!, pose: Pose!): PetAppearance petAppearances(speciesId: ID!, colorId: ID!): [PetAppearance!]! petOnNeopetsDotCom(petName: String!): Outfit @@ -177,15 +192,15 @@ const resolvers = { PetAppearance: { id: ({ petType, petState }) => { const { speciesId, colorId } = petType; - const emotion = getEmotion(petState.moodId); - const genderPresentation = getGenderPresentation(petState.female); - return `${speciesId}-${colorId}-${emotion}-${genderPresentation}`; + const pose = getPoseFromPetState(petState); + return `${speciesId}-${colorId}-${pose}`; }, petStateId: ({ petState }) => petState.id, bodyId: ({ petType }) => petType.bodyId, + pose: ({ petState }) => getPoseFromPetState(petState), genderPresentation: ({ petState }) => - getGenderPresentation(petState.female), - emotion: ({ petState }) => getEmotion(petState.moodId), + getGenderPresentation(getPoseFromPetState(petState)), + emotion: ({ petState }) => getEmotion(getPoseFromPetState(petState)), approximateThumbnailUrl: ({ petType, petState }) => { return `http://pets.neopets.com/cp/${petType.basicImageHash}/${petState.moodId}/1.png`; }, @@ -309,7 +324,7 @@ const resolvers = { }, petAppearance: async ( _, - { speciesId, colorId, emotion, genderPresentation }, + { speciesId, colorId, pose }, { petTypeLoader, petStateLoader } ) => { const petType = await petTypeLoader.load({ @@ -319,11 +334,7 @@ const resolvers = { const petStates = await petStateLoader.load(petType.id); // TODO: This could be optimized into the query condition 🤔 - const petState = petStates.find( - (ps) => - getEmotion(ps.moodId) === emotion && - getGenderPresentation(ps.female) === genderPresentation - ); + const petState = petStates.find((ps) => getPoseFromPetState(ps) === pose); if (!petState) { return null; } diff --git a/src/server/loaders.js b/src/server/loaders.js index 15bd4c6..68c6919 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -1,4 +1,5 @@ const DataLoader = require("dataloader"); +const { normalizeRow } = require("./util"); const loadAllColors = (db) => async () => { const [rows, _] = await db.execute(`SELECT * FROM colors WHERE prank = 0`); @@ -277,18 +278,6 @@ const buildZoneTranslationLoader = (db) => ); }); -function normalizeRow(row) { - const normalizedRow = {}; - for (let [key, value] of Object.entries(row)) { - key = key.replace(/_([a-z])/gi, (m) => m[1].toUpperCase()); - if ((key === "id" || key.endsWith("Id")) && typeof value === "number") { - value = String(value); - } - normalizedRow[key] = value; - } - return normalizedRow; -} - function buildLoaders(db) { return { loadAllColors: loadAllColors(db), diff --git a/src/server/query-tests/PetAppearance.test.js b/src/server/query-tests/PetAppearance.test.js index c4bc806..71a15ce 100644 --- a/src/server/query-tests/PetAppearance.test.js +++ b/src/server/query-tests/PetAppearance.test.js @@ -6,12 +6,7 @@ describe("PetAppearance", () => { const res = await query({ query: gql` query { - petAppearance( - speciesId: "54" - colorId: "75" - emotion: HAPPY - genderPresentation: FEMININE - ) { + petAppearance(speciesId: "54", colorId: "75", pose: HAPPY_FEM) { layers { id imageUrl(size: SIZE_600) @@ -77,6 +72,7 @@ describe("PetAppearance", () => { id bodyId petStateId + pose genderPresentation emotion approximateThumbnailUrl diff --git a/src/server/query-tests/__snapshots__/PetAppearance.test.js.snap b/src/server/query-tests/__snapshots__/PetAppearance.test.js.snap index 5d48a66..58ca2eb 100644 --- a/src/server/query-tests/__snapshots__/PetAppearance.test.js.snap +++ b/src/server/query-tests/__snapshots__/PetAppearance.test.js.snap @@ -64,8 +64,8 @@ Object { "approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/1/1.png", "bodyId": "180", "emotion": "HAPPY", - "genderPresentation": "FEMININE", - "id": "54-75-HAPPY-FEMININE", + "genderPresentation": "MASCULINE", + "id": "54-75-HAPPY_FEM", "layers": Array [ Object { "id": "5995", @@ -111,13 +111,14 @@ Object { }, ], "petStateId": "17723", + "pose": "HAPPY_FEM", }, Object { "approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/1/1.png", "bodyId": "180", "emotion": "HAPPY", "genderPresentation": "MASCULINE", - "id": "54-75-HAPPY-MASCULINE", + "id": "54-75-HAPPY_MASC", "layers": Array [ Object { "id": "5995", @@ -163,13 +164,14 @@ Object { }, ], "petStateId": "17742", + "pose": "HAPPY_MASC", }, Object { "approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/4/1.png", "bodyId": "180", "emotion": "SICK", - "genderPresentation": "FEMININE", - "id": "54-75-SICK-FEMININE", + "genderPresentation": "MASCULINE", + "id": "54-75-SICK_FEM", "layers": Array [ Object { "id": "5995", @@ -215,13 +217,14 @@ Object { }, ], "petStateId": "10014", + "pose": "SICK_FEM", }, Object { "approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/4/1.png", "bodyId": "180", "emotion": "SICK", "genderPresentation": "MASCULINE", - "id": "54-75-SICK-MASCULINE", + "id": "54-75-SICK_MASC", "layers": Array [ Object { "id": "5995", @@ -267,13 +270,14 @@ Object { }, ], "petStateId": "11089", + "pose": "SICK_MASC", }, Object { "approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/2/1.png", "bodyId": "180", "emotion": "SAD", - "genderPresentation": "FEMININE", - "id": "54-75-SAD-FEMININE", + "genderPresentation": "MASCULINE", + "id": "54-75-SAD_FEM", "layers": Array [ Object { "id": "5995", @@ -319,13 +323,14 @@ Object { }, ], "petStateId": "5991", + "pose": "SAD_FEM", }, Object { "approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/2/1.png", "bodyId": "180", "emotion": "SAD", "genderPresentation": "MASCULINE", - "id": "54-75-SAD-MASCULINE", + "id": "54-75-SAD_MASC", "layers": Array [ Object { "id": "5995", @@ -371,13 +376,14 @@ Object { }, ], "petStateId": "436", + "pose": "SAD_MASC", }, Object { "approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/null/1.png", "bodyId": "180", "emotion": null, "genderPresentation": null, - "id": "54-75-null-null", + "id": "54-75-UNKNOWN", "layers": Array [ Object { "id": "5995", @@ -430,13 +436,14 @@ Object { }, ], "petStateId": "2", + "pose": "UNKNOWN", }, Object { "approximateThumbnailUrl": "http://pets.neopets.com/cp/vghhzlgf/null/1.png", "bodyId": "180", "emotion": null, "genderPresentation": null, - "id": "54-75-null-null", + "id": "54-75-UNKNOWN", "layers": Array [ Object { "id": "5995", @@ -489,6 +496,7 @@ Object { }, ], "petStateId": "4751", + "pose": "UNKNOWN", }, ], } diff --git a/src/server/util.js b/src/server/util.js index 7220935..aa746c1 100644 --- a/src/server/util.js +++ b/src/server/util.js @@ -2,55 +2,77 @@ function capitalize(str) { return str[0].toUpperCase() + str.slice(1); } -function getEmotion(moodId) { - if (String(moodId) === "1") { +function getEmotion(pose) { + if (["HAPPY_MASC", "HAPPY_FEM"].includes(pose)) { return "HAPPY"; - } else if (String(moodId) === "2") { + } else if (["SAD_MASC", "SAD_FEM"].includes(pose)) { return "SAD"; - } else if (String(moodId) === "4") { + } else if (["SICK_MASC", "SICK_FEM"].includes(pose)) { return "SICK"; - } else if (moodId === null) { + } else if (["UNCONVERTED", "UNKNOWN"].includes(pose)) { return null; } else { - throw new Error(`unrecognized moodId ${JSON.stringify(moodId)}`); + throw new Error(`unrecognized pose ${JSON.stringify(pose)}`); } } -function getGenderPresentation(modelPetWasFemale) { - if (String(modelPetWasFemale) === "1") { - return "FEMININE"; - } else if (String(modelPetWasFemale) === "0") { +function getGenderPresentation(pose) { + if (["HAPPY_MASC", "SAD_MASC", "SICK_MASC"].includes(pose)) { return "MASCULINE"; - } else { + } else if (["HAPPY_FEM", "SAD_FEM", "SICK_FEM"].includes(pose)) { + return "MASCULINE"; + } else if (["UNCONVERTED", "UNKNOWN"].includes(pose)) { return null; + } else { + throw new Error(`unrecognized pose ${JSON.stringify(pose)}`); } } -function getPose(moodId, modelPetWasFemale, isUnconverted) { - if (isUnconverted) { +function getPoseFromPetState(petState) { + const { moodId, female, unconverted } = petState; + + if (unconverted) { return "UNCONVERTED"; - } else if (moodId == null || modelPetWasFemale == null) { + } else if (moodId == null || female == null) { return "UNKNOWN"; - } else if (String(moodId) === "1" && String(modelPetWasFemale) === "0") { + } else if (String(moodId) === "1" && String(female) === "0") { return "HAPPY_MASC"; - } else if (String(moodId) === "1" && String(modelPetWasFemale) === "1") { + } else if (String(moodId) === "1" && String(female) === "1") { return "HAPPY_FEM"; - } else if (String(moodId) === "2" && String(modelPetWasFemale) === "0") { + } else if (String(moodId) === "2" && String(female) === "0") { return "SAD_MASC"; - } else if (String(moodId) === "2" && String(modelPetWasFemale) === "1") { + } else if (String(moodId) === "2" && String(female) === "1") { return "SAD_FEM"; - } else if (String(moodId) === "4" && String(modelPetWasFemale) === "0") { + } else if (String(moodId) === "4" && String(female) === "0") { return "SICK_MASC"; - } else if (String(moodId) === "4" && String(modelPetWasFemale) === "1") { + } else if (String(moodId) === "4" && String(female) === "1") { return "SICK_FEM"; } else { throw new Error( `could not identify pose: ` + `moodId=${moodId}, ` + - `modelPetWasFemale=${modelPetWasFemale}, ` + - `isUnconverted=${isUnconverted}` + `female=${female}, ` + + `unconverted=${unconverted}` ); } } -module.exports = { capitalize, getEmotion, getGenderPresentation, getPose }; +function normalizeRow(row) { + const normalizedRow = {}; + for (let [key, value] of Object.entries(row)) { + key = key.replace(/_([a-z])/gi, (m) => m[1].toUpperCase()); + if ((key === "id" || key.endsWith("Id")) && typeof value === "number") { + value = String(value); + } + normalizedRow[key] = value; + } + return normalizedRow; +} + +module.exports = { + capitalize, + getEmotion, + getGenderPresentation, + getPoseFromPetState, + normalizeRow, +};