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 60229d1..4efa998 100644 Binary files a/src/server/__snapshots__/getValidPetPoses.test.js.snap and b/src/server/__snapshots__/getValidPetPoses.test.js.snap differ diff --git a/src/server/getValidPetPoses.js b/src/server/getValidPetPoses.js index c1b97b7..0cd3972 100644 --- a/src/server/getValidPetPoses.js +++ b/src/server/getValidPetPoses.js @@ -1,25 +1,25 @@ import connectToDb from "./db"; -import { getPose } from "./util"; +import { getPoseFromPetState, normalizeRow } from "./util"; export default async function getValidPetPoses() { const db = await connectToDb(); const numSpeciesPromise = getNumSpecies(db); const numColorsPromise = getNumColors(db); - const poseTuplesPromise = getPoseTuples(db); + const distinctPetStatesPromise = getDistinctPetStates(db); - const [numSpecies, numColors, poseTuples] = await Promise.all([ + const [numSpecies, numColors, distinctPetStates] = await Promise.all([ numSpeciesPromise, numColorsPromise, - poseTuplesPromise, + distinctPetStatesPromise, ]); const poseStrs = new Set(); - for (const poseTuple of poseTuples) { - const { species_id, color_id, mood_id, female, unconverted } = poseTuple; - const pose = getPose(mood_id, female, unconverted); - const poseStr = `${species_id}-${color_id}-${pose}`; + for (const petState of distinctPetStates) { + const { speciesId, colorId } = petState; + const pose = getPoseFromPetState(petState); + const poseStr = `${speciesId}-${colorId}-${pose}`; poseStrs.add(poseStr); } @@ -77,11 +77,11 @@ async function getNumColors(db) { return rows[0]["count(*)"]; } -async function getPoseTuples(db) { +async function getDistinctPetStates(db) { const [rows, _] = await db.query(` SELECT DISTINCT species_id, color_id, mood_id, female, unconverted FROM pet_states INNER JOIN pet_types ON pet_types.id = pet_states.pet_type_id WHERE glitched IS false AND color_id >= 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, +};