見出し画像

アレクサスキル サンプルコード メタボチェッカー

What's メタボチェッカー

身長と体重からBMIの判定と体重管理のアドバイスを聞く事ができます

サンプルコード

/* *
 * This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
 * Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
 * session persistence, api calls, and more.
 メタボチェッカーを開いて
 * */
const Alexa = require('ask-sdk-core');
let speakAdvice = '';
let speakJudge = '';

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speakOutput = ''
        + 'あなたの体重と身長から、世界保健機関の規定に沿ってBMIの判定をします。まずは体重から'
        + '教えて下さい。';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

const AnswerIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AnswerIntent';
    },
    handle(handlerInput) {
        //値・単位取得
        const HeightValue = handlerInput.requestEnvelope.request.intent.slots.Height.value;
        const HeightUnit =  handlerInput.requestEnvelope.request.intent.slots.UnitHeight.value;
        let HeightCoefficient = 1;
        
        //単位換算係数
        if (HeightUnit == ('センチ' || 'センチメートル')){
            HeightCoefficient = 1;
        }
        if (HeightUnit == 'ミリ'){
            HeightCoefficient = 0.1;
        }
        if (HeightUnit == 'ミリメートル'){
            HeightCoefficient = 0.1;
        }       
        if (HeightUnit == 'メートル'){
            HeightCoefficient = 100;
        }
        if (HeightUnit == 'フィート'){
            HeightCoefficient = 30.48;
        }
        
        //身長値決定
        const Height = HeightValue * HeightCoefficient;
        console.log ('HeightValue;', HeightValue, 'HeightUnit;', HeightUnit, 'Height;', Height);
        
        
        //体重取得・単位取得
        const WeightValue = handlerInput.requestEnvelope.request.intent.slots.Weight.value;
        const WeightUnit =  handlerInput.requestEnvelope.request.intent.slots.UnitWeight.value;
        let WeightCoefficient = 1;
        
        //重さ単位換算係数
        if (WeightUnit == ('キロ'||'キログラム')){
            WeightCoefficient = 1;
        }
        if (WeightUnit == 'グラム'){
            WeightCoefficient = 0.001;
        }
        if (WeightUnit == 'トン'){
            WeightCoefficient = 1000;
        }
        if (WeightUnit == 'ポンド'){
            WeightCoefficient = 0.453592;
        }
    
        //体重値決定
        const Weight = WeightValue * WeightCoefficient;
        console.log ('weightValue;', WeightValue, 'WeightUnit;', WeightUnit, 'Weight;', Weight);
    
        //適正体重算出
        let WeightIdeal = ((Height/100)^2) * 22;
        let WeightDifference = WeightIdeal - Weight;
    
        //BMI算出
        let BMI_raw = Weight / ((Height/100)^2);
        let BMI = parseFloat(BMI_raw.toFixed(1))
        
        //診断1
        let diagnoseTop = '';
        const diagnoseTop_1 = 'あなたのBMIは標準よりも小さく、痩せています。肥満は健康の大敵ですが、もちろん痩せすぎも良くありません。';
        const diagnoseTop_2 = 'あなたのBMIは標準の範囲内です。今の体重をキープするように心がけてください。ただし、体重は標準でも体脂肪率の高い「隠れ肥満」の可能性もあります。';
        const diagnoseTop_3 = 'あなたのBMIは標準よりも高く、肥満気味です。肥満はさまざまな生活習慣を引き起こす、健康の大敵です。';
        //診断2
        let diagnoseEnd = '';
        const diagnoseEnd_1 = 'きちんと食べて適度に運動するように心がけてください。';
        const diagnoseEnd_2 = '適度に運動して自己管理に努めてください。';
        const diagnoseEnd_3 = '食生活を見直し、適度な運動を取り入れて自己管理に努めてください。';
        
        //体型判定
        if (BMI < 16){
            speakJudge = '痩せすぎです。' ;
            diagnoseTop = diagnoseTop_1;
            diagnoseEnd = diagnoseEnd_1;
        } 
        if (BMI >= 16 && BMI < 17){
            speakJudge = '痩せです。' ;
            diagnoseTop = diagnoseTop_1;
            diagnoseEnd = diagnoseEnd_1;
        } 
        if (BMI >= 17 && BMI < 18.5){
            speakJudge = '痩せぎみです。' ;
            diagnoseTop = diagnoseTop_1;
            diagnoseEnd = diagnoseEnd_1;
        } 
        if (BMI >= 18.5 && BMI < 25){
            speakJudge = '適正体重です。' ;
            diagnoseTop = diagnoseTop_2;
            diagnoseEnd = diagnoseEnd_2;
        } 
        if (BMI >= 25 && BMI < 30){
            speakJudge = '肥満度1の過体重です。' ;
            diagnoseTop = diagnoseTop_3;
            diagnoseEnd = diagnoseEnd_3;
        } 
        if (BMI >= 30 && BMI < 35){
            speakJudge = '肥満度2の肥満予備軍です。' ;
            diagnoseTop = diagnoseTop_3;
            diagnoseEnd = diagnoseEnd_3;
        } 
        if (BMI >= 35 && BMI < 40){
            speakJudge = '肥満度3の高度肥満です。' ;
            diagnoseTop = diagnoseTop_3;
            diagnoseEnd = diagnoseEnd_3;
        } 
        if (BMI >= 40){
            speakJudge = '肥満度4の高度肥満です。' ;
            diagnoseTop = diagnoseTop_3;
            diagnoseEnd = diagnoseEnd_3;
        } 
        
        //体重アドバイス
        if (WeightDifference > 3){
            speakAdvice = 'きちんと食べて適度に運動しながら体重を'+ WeightDifference + 'キロ増やしましょう。' 
        }
        if (WeightDifference >= 3 && WeightDifference <= 3 ){
            speakAdvice = 'きちんと食べて適度に運動しながら体重をキープしましょう。' 
        }       
        if (WeightDifference < 3){
            speakAdvice = '食生活を見直し、適度な運動を取り入れて体重を'+ Math.abs(WeightDifference) + 'キロ、減らしましょう。' 
        } 
        
        
        //スクリプト生成
        const speakOutput = '身長'+ Height+'センチ。体重' + Weight + 'キロのあなたのBMIは'
        + BMI +'。判定は、'
        +'<audio src="soundbank://soundlibrary/musical/amzn_sfx_drum_comedy_02"/>'
        +'<break time="1s"/>'
        +speakJudge
        +'<break time="1s"/>'
        +diagnoseTop
        +'あなたの適正体重、' + WeightIdeal + 'キロに向けて、'
        + speakAdvice
        +'<break time="1s"/>'
        +'さて、明日から健やかな毎日を過ごしてください。'
        +'<say-as interpret-as="interjection">それでは</say-as>.'
        ;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};

const HelpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'あなたの体重と身長から、世界保健機関の規定に沿ってBMIの判定をします。まずは体重から教えて下さい。';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

const CancelAndStopIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
                || Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
    },
    handle(handlerInput) {
        const speakOutput = 'またいつでも聞いて下さい。';

        return handlerInput.responseBuilder
            //.speak(speakOutput)
            .getResponse();
    }
};
/* *
 * FallbackIntent triggers when a customer says something that doesn’t map to any intents in your skill
 * It must also be defined in the language model (if the locale supports it)
 * This handler can be safely added but will be ingnored in locales that do not support it yet 
 * */
const FallbackIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.FallbackIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'Sorry, I don\'t know about that. Please try again.';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};
/* *
 * SessionEndedRequest notifies that a session was ended. This handler will be triggered when a currently open 
 * session is closed for one of the following reasons: 1) The user says "exit" or "quit". 2) The user does not 
 * respond or says something that does not match an intent defined in your voice model. 3) An error occurs 
 * */
const SessionEndedRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
    },
    handle(handlerInput) {
        console.log(`~~~~ Session ended: ${JSON.stringify(handlerInput.requestEnvelope)}`);
        // Any cleanup logic goes here.
        return handlerInput.responseBuilder.getResponse(); // notice we send an empty response
    }
};
/* *
 * The intent reflector is used for interaction model testing and debugging.
 * It will simply repeat the intent the user said. You can create custom handlers for your intents 
 * by defining them above, then also adding them to the request handler chain below 
 * */
const IntentReflectorHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
    },
    handle(handlerInput) {
        const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
        const speakOutput = `You just triggered ${intentName}`;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};
/**
 * Generic error handling to capture any syntax or routing errors. If you receive an error
 * stating the request handler chain is not found, you have not implemented a handler for
 * the intent being invoked or included it in the skill builder below 
 * */
const ErrorHandler = {
    canHandle() {
        return true;
    },
    handle(handlerInput, error) {
        const speakOutput = 'Sorry, I had trouble doing what you asked. Please try again.';
        console.log(`~~~~ Error handled: ${JSON.stringify(error)}`);

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

/**
 * This handler acts as the entry point for your skill, routing all request and response
 * payloads to the handlers above. Make sure any new handlers or interceptors you've
 * defined are included below. The order matters - they're processed top to bottom 
 * */
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        AnswerIntentHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        FallbackIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler)
    .addErrorHandlers(
        ErrorHandler)
    .withCustomUserAgent('sample/hello-world/v1.2')
    .lambda();

工夫ポイント

スキルの申請のフィードバックをもとにcm・kg以外の単位にも対応しています。(xxフィート。xxポンドと返答してもスキル内で単位換算してBMIを計算します)

//値・単位取得
        const HeightValue = handlerInput.requestEnvelope.request.intent.slots.Height.value;
        const HeightUnit =  handlerInput.requestEnvelope.request.intent.slots.UnitHeight.value;
        let HeightCoefficient = 1;
        
        //単位換算係数
        if (HeightUnit == ('センチ' || 'センチメートル')){
            HeightCoefficient = 1;
        }
        if (HeightUnit == 'ミリ'){
            HeightCoefficient = 0.1;
        }
        if (HeightUnit == 'ミリメートル'){
            HeightCoefficient = 0.1;
        }       
        if (HeightUnit == 'メートル'){
            HeightCoefficient = 100;
        }
        if (HeightUnit == 'フィート'){
            HeightCoefficient = 30.48;
        }
        
        //身長値決定
        const Height = HeightValue * HeightCoefficient;
        console.log ('HeightValue;', HeightValue, 'HeightUnit;', HeightUnit, 'Height;', Height);
        
        
        //体重取得・単位取得
        const WeightValue = handlerInput.requestEnvelope.request.intent.slots.Weight.value;
        const WeightUnit =  handlerInput.requestEnvelope.request.intent.slots.UnitWeight.value;
        let WeightCoefficient = 1;
        
        //重さ単位換算係数
        if (WeightUnit == ('キロ'||'キログラム')){
            WeightCoefficient = 1;
        }
        if (WeightUnit == 'グラム'){
            WeightCoefficient = 0.001;
        }
        if (WeightUnit == 'トン'){
            WeightCoefficient = 1000;
        }
        if (WeightUnit == 'ポンド'){
            WeightCoefficient = 0.453592;
        }

Intent

身長と体重と単位を取得するインテントです。

身長・体重を取得するインテント
数値だけでなく単位を取得する為にUnitHeight / UnitWeight を設定


身長の単位を取得する為のスロット値

あとがき

当初は身長も体重もcm・kgだけで回答される事を前提として、単位は無視して数値だけを取得するシンプルな構成でしたが、スキル申請の際に「フィートやポンドで回答してもスキルを正しく応答できること」というフィードバックにより審査NGとなりました。日本人で身長・体重を答える時にcm・kg以外を使う人はあまりイメージつきませんが、Amazonのスキル審査の方達は想定外使用の範囲までテストをしているんですね。ちょっと感心しました。

とはいえ、単位の問題を解決しない事にはスキルを公開できませんので、単位を取得するスロットを追加して、単位を換算するロジックを書いてと少々苦労しましたが、このなんやかんややっている時が面白く、やっとの事で様々な単位をもアレクサが正しく認識して応答できるようになった時はちょっとした達成感を感じる事ができました。

今回は数値も単位も「スロット」を駆使してスキルを実装した訳ですが、人間とシステムを対話させる仕組みとして、一定の制約・ルールがありながらも、組み合わにより多様な機能を実装できる「スロット」という概念・機能は本当に良く出来ているなと理解が深まると共に、実業務にも大いなる示唆を得る事が出来たスキル開発でした。

この記事が気に入ったらサポートをしてみませんか?