using Amazon.Lambda.Core;
using Amazon.Lambda.LexEvents;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace BlueprintBaseName._1
{
public class BookCarIntentProcessor : AbstractIntentProcessor
{
public const string PICK_UP_CITY_SLOT = "PickUpCity";
public const string PICK_UP_DATE_SLOT = "PickUpDate";
public const string RETURN_DATE_SLOT = "ReturnDate";
public const string DRIVER_AGE_SLOT = "DriverAge";
public const string CAR_TYPE_SLOT = "CarType";
///
/// Performs dialog management and fulfillment for booking a car.
///
/// Beyond fulfillment, the implementation for this intent demonstrates the following:
/// 1) Use of elicitSlot in slot validation and re-prompting
/// 2) Use of sessionAttributes to pass information that can be used to guide the conversation
///
///
///
public override LexResponse Process(LexEvent lexEvent, ILambdaContext context)
{
var slots = lexEvent.CurrentIntent.Slots;
var sessionAttributes = lexEvent.SessionAttributes ?? new Dictionary();
Reservation reservation = new Reservation
{
ReservationType = "Car",
PickUpCity = slots.ContainsKey(PICK_UP_CITY_SLOT) ? slots[PICK_UP_CITY_SLOT] : null,
PickUpDate = slots.ContainsKey(PICK_UP_DATE_SLOT) ? slots[PICK_UP_DATE_SLOT] : null,
ReturnDate = slots.ContainsKey(RETURN_DATE_SLOT) ? slots[RETURN_DATE_SLOT] : null,
DriverAge = slots.ContainsKey(DRIVER_AGE_SLOT) ? slots[DRIVER_AGE_SLOT] : null,
CarType = slots.ContainsKey(CAR_TYPE_SLOT) ? slots[CAR_TYPE_SLOT] : null,
};
string confirmationStaus = lexEvent.CurrentIntent.ConfirmationStatus;
Reservation lastConfirmedReservation = null;
if (slots.ContainsKey(LAST_CONFIRMED_RESERVATION_SESSION_ATTRIBUTE))
{
lastConfirmedReservation = DeserializeReservation(slots[LAST_CONFIRMED_RESERVATION_SESSION_ATTRIBUTE]);
}
string confirmationContext = sessionAttributes.ContainsKey("confirmationContext") ? sessionAttributes["confirmationContext"] : null;
sessionAttributes[CURRENT_RESERVATION_SESSION_ATTRIBUTE] = SerializeReservation(reservation);
var validateResult = Validate(reservation);
context.Logger.LogLine($"Has required fields: {reservation.HasRequiredCarFields}, Has valid values {validateResult.IsValid}");
if(!validateResult.IsValid)
{
context.Logger.LogLine($"Slot {validateResult.ViolationSlot} is invalid: {validateResult.Message?.Content}");
}
if (reservation.HasRequiredCarFields && validateResult.IsValid)
{
var price = GeneratePrice(reservation);
context.Logger.LogLine($"Generated price: {price}");
sessionAttributes[CURRENT_RESERVATION_PRICE_SESSION_ATTRIBUTE] = price.ToString(CultureInfo.InvariantCulture);
}
else
{
sessionAttributes.Remove(CURRENT_RESERVATION_PRICE_SESSION_ATTRIBUTE);
}
if (string.Equals(lexEvent.InvocationSource, "DialogCodeHook", StringComparison.Ordinal))
{
// If any slots are invalid, re-elicit for their value
if (!validateResult.IsValid)
{
slots[validateResult.ViolationSlot] = null;
return ElicitSlot(sessionAttributes, lexEvent.CurrentIntent.Name, slots, validateResult.ViolationSlot, validateResult.Message);
}
// Determine if the intent (and current slot settings) have been denied. The messaging will be different
// if the user is denying a reservation they initiated or an auto-populated suggestion.
if (string.Equals(lexEvent.CurrentIntent.ConfirmationStatus, "Denied", StringComparison.Ordinal))
{
sessionAttributes.Remove("confirmationContext");
sessionAttributes.Remove(CURRENT_RESERVATION_SESSION_ATTRIBUTE);
if (string.Equals(confirmationContext, "AutoPopulate", StringComparison.Ordinal))
{
return ElicitSlot(sessionAttributes,
lexEvent.CurrentIntent.Name,
new Dictionary
{
{PICK_UP_CITY_SLOT, null },
{PICK_UP_DATE_SLOT, null },
{RETURN_DATE_SLOT, null },
{DRIVER_AGE_SLOT, null },
{CAR_TYPE_SLOT, null }
},
PICK_UP_CITY_SLOT,
new LexResponse.LexMessage
{
ContentType = MESSAGE_CONTENT_TYPE,
Content = "Where would you like to make your car reservation?"
}
);
}
return Delegate(sessionAttributes, slots);
}
if (string.Equals(lexEvent.CurrentIntent.ConfirmationStatus, "None", StringComparison.Ordinal))
{
// If we are currently auto-populating but have not gotten confirmation, keep requesting for confirmation.
if ((!string.IsNullOrEmpty(reservation.PickUpCity)
&& !string.IsNullOrEmpty(reservation.PickUpDate)
&& !string.IsNullOrEmpty(reservation.ReturnDate)
&& !string.IsNullOrEmpty(reservation.DriverAge)
&& !string.IsNullOrEmpty(reservation.CarType)) || string.Equals(confirmationContext, "AutoPopulate", StringComparison.Ordinal))
{
if (lastConfirmedReservation != null &&
string.Equals(lastConfirmedReservation.ReservationType, "Hotel", StringComparison.Ordinal))
{
// If the user's previous reservation was a hotel - prompt for a rental with
// auto-populated values to match this reservation.
sessionAttributes["confirmationContext"] = "AutoPopulate";
return ConfirmIntent(
sessionAttributes,
lexEvent.CurrentIntent.Name,
new Dictionary
{
{PICK_UP_CITY_SLOT, lastConfirmedReservation.PickUpCity },
{PICK_UP_DATE_SLOT, lastConfirmedReservation.CheckInDate },
{RETURN_DATE_SLOT, DateTime.Parse(lastConfirmedReservation.CheckInDate).AddDays(int.Parse(lastConfirmedReservation.Nights)).ToUniversalTime().ToString(CultureInfo.InvariantCulture) },
{CAR_TYPE_SLOT, null },
{DRIVER_AGE_SLOT, null },
},
new LexResponse.LexMessage
{
ContentType = MESSAGE_CONTENT_TYPE,
Content = $"Is this car rental for your {lastConfirmedReservation.Nights} night stay in {lastConfirmedReservation.Location} on {lastConfirmedReservation.CheckInDate}?"
}
);
}
}
// Otherwise, let native DM rules determine how to elicit for slots and/or drive confirmation.
return Delegate(sessionAttributes, slots);
}
// If confirmation has occurred, continue filling any unfilled slot values or pass to fulfillment.
if (string.Equals(lexEvent.CurrentIntent.ConfirmationStatus, "Confirmed", StringComparison.Ordinal))
{
// Remove confirmationContext from sessionAttributes so it does not confuse future requests
sessionAttributes.Remove("confirmationContext");
if (string.Equals(confirmationContext, "AutoPopulate", StringComparison.Ordinal))
{
if (!string.IsNullOrEmpty(reservation.DriverAge))
{
return ElicitSlot(sessionAttributes,
lexEvent.CurrentIntent.Name,
slots,
DRIVER_AGE_SLOT,
new LexResponse.LexMessage
{
ContentType = MESSAGE_CONTENT_TYPE,
Content = "How old is the driver of this car rental?"
}
);
}
else if (string.IsNullOrEmpty(reservation.CarType))
{
return ElicitSlot(sessionAttributes,
lexEvent.CurrentIntent.Name,
slots,
CAR_TYPE_SLOT,
new LexResponse.LexMessage
{
ContentType = MESSAGE_CONTENT_TYPE,
Content = "What type of car would you like? Popular models are economy, midsize, and luxury."
}
);
}
}
return Delegate(sessionAttributes, slots);
}
}
context.Logger.LogLine($"Book car at = {SerializeReservation(reservation)}");
if (sessionAttributes.ContainsKey(CURRENT_RESERVATION_PRICE_SESSION_ATTRIBUTE))
{
context.Logger.LogLine($"Book car price = {sessionAttributes[CURRENT_RESERVATION_PRICE_SESSION_ATTRIBUTE]}");
}
sessionAttributes.Remove(CURRENT_RESERVATION_PRICE_SESSION_ATTRIBUTE);
sessionAttributes.Remove(CURRENT_RESERVATION_SESSION_ATTRIBUTE);
sessionAttributes[LAST_CONFIRMED_RESERVATION_SESSION_ATTRIBUTE] = SerializeReservation(reservation);
return Close(
sessionAttributes,
"Fulfilled",
new LexResponse.LexMessage
{
ContentType = MESSAGE_CONTENT_TYPE,
Content = "Thanks, I have placed your reservation."
}
);
}
///
/// Verifies that any values for slots in the intent are valid.
///
///
///
private ValidationResult Validate(Reservation reservation)
{
if (!string.IsNullOrEmpty(reservation.PickUpCity) && !TypeValidators.IsValidCity(reservation.PickUpCity))
{
return new ValidationResult(false, PICK_UP_CITY_SLOT,
$"We currently do not support {reservation.PickUpCity} as a valid destination. Can you try a different city?");
}
DateTime pickupDate = DateTime.MinValue;
if (!string.IsNullOrEmpty(reservation.PickUpDate))
{
if (!DateTime.TryParse(reservation.PickUpDate, out pickupDate))
{
return new ValidationResult(false, PICK_UP_DATE_SLOT,
"I did not understand your departure date. When would you like to pick up your car rental?");
}
if (pickupDate < DateTime.Today)
{
return new ValidationResult(false, PICK_UP_DATE_SLOT,
"Your pick up date is in the past! Can you try a different date?");
}
}
DateTime returnDate = DateTime.MinValue;
if (!string.IsNullOrEmpty(reservation.ReturnDate))
{
if (!DateTime.TryParse(reservation.ReturnDate, out returnDate))
{
return new ValidationResult(false, RETURN_DATE_SLOT,
"I did not understand your return date. When would you like to return your car rental?");
}
}
if (pickupDate != DateTime.MinValue && returnDate != DateTime.MinValue)
{
if (returnDate <= pickupDate)
{
return new ValidationResult(false, RETURN_DATE_SLOT,
"Your return date must be after your pick up date. Can you try a different return date?");
}
var ts = returnDate.Date - pickupDate.Date;
if (ts.Days > 30)
{
return new ValidationResult(false, RETURN_DATE_SLOT,
"You can reserve a car for up to thirty days. Can you try a different return date?");
}
}
int age = 0;
if (!string.IsNullOrEmpty(reservation.DriverAge))
{
if (!int.TryParse(reservation.DriverAge, out age))
{
return new ValidationResult(false, DRIVER_AGE_SLOT,
"I did not understand the driver's age. Can you enter the driver's age again?");
}
if (age < 18)
{
return new ValidationResult(false, DRIVER_AGE_SLOT,
"Your driver must be at least eighteen to rent a car. Can you provide the age of a different driver?");
}
}
if (!string.IsNullOrEmpty(reservation.CarType) && !TypeValidators.IsValidCarType(reservation.CarType))
{
return new ValidationResult(false, CAR_TYPE_SLOT,
"I did not recognize that model. What type of car would you like to rent? " +
"Popular cars are economy, midsize, or luxury");
}
return ValidationResult.VALID_RESULT;
}
///
/// Generates a number within a reasonable range that might be expected for a flight.
/// The price is fixed for a given pair of locations.
///
///
///
private double GeneratePrice(Reservation reservation)
{
double baseLocationCost = 0;
foreach (char c in reservation.PickUpCity)
{
baseLocationCost += (c - 97);
}
double ageMultiplier = int.Parse(reservation.DriverAge) < 25 ? 1.1 : 1.0;
var carTypeIndex = 0;
for (int i = 0; i < TypeValidators.VALID_CAR_TYPES.Length; i++)
{
if (string.Equals(TypeValidators.VALID_CAR_TYPES[i], reservation.CarType, StringComparison.Ordinal))
{
carTypeIndex = i + 1;
break;
}
}
int days = (DateTime.Parse(reservation.ReturnDate).Date - DateTime.Parse(reservation.PickUpDate).Date).Days;
return days * ((100 + baseLocationCost) + ((carTypeIndex * 50) * ageMultiplier));
}
}
}