using System.Globalization;
using Amazon.Lambda.Core;
using Amazon.Lambda.LexEvents;
namespace BlueprintBaseName._1;
public class BookHotelIntentProcessor : AbstractIntentProcessor
{
public const string LOCATION_SLOT = "Location";
public const string CHECK_IN_DATE_SLOT = "CheckInDate";
public const string NIGHTS_SLOT = "Nights";
public const string ROOM_TYPE_SLOT = "RoomType";
///
/// Performs dialog management and fulfillment for booking a hotel.
///
/// 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 = "Hotel",
Location = slots.ContainsKey(LOCATION_SLOT) ? slots[LOCATION_SLOT] : null,
CheckInDate = slots.ContainsKey(CHECK_IN_DATE_SLOT) ? slots[CHECK_IN_DATE_SLOT] : null,
Nights = slots.ContainsKey(NIGHTS_SLOT) ? slots[NIGHTS_SLOT] : null,
RoomType = slots.ContainsKey(ROOM_TYPE_SLOT) ? slots[ROOM_TYPE_SLOT] : null
};
sessionAttributes[CURRENT_RESERVATION_SESSION_ATTRIBUTE] = SerializeReservation(reservation);
if (string.Equals(lexEvent.InvocationSource, "DialogCodeHook", StringComparison.Ordinal))
{
var validateResult = Validate(reservation);
// 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);
}
// Otherwise, let native DM rules determine how to elicit for slots and prompt for confirmation. Pass price
// back in sessionAttributes once it can be calculated; otherwise clear any setting from sessionAttributes.
if (reservation.HasRequiredHotelFields && validateResult.IsValid)
{
var price = GeneratePrice(reservation);
context.Logger.LogInformation($"Generated price: {price}");
sessionAttributes[CURRENT_RESERVATION_PRICE_SESSION_ATTRIBUTE] = price.ToString(CultureInfo.InvariantCulture);
}
else
{
sessionAttributes.Remove(CURRENT_RESERVATION_PRICE_SESSION_ATTRIBUTE);
}
return Delegate(sessionAttributes, slots);
}
// Booking the hotel. In a real application, this would likely involve a call to a backend service.
context.Logger.LogInformation($"Book hotel at = {SerializeReservation(reservation)}");
if (sessionAttributes.ContainsKey(CURRENT_RESERVATION_PRICE_SESSION_ATTRIBUTE))
{
context.Logger.LogInformation($"Book hotel 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 hotel reservation."
}
);
}
///
/// Verifies that any values for slots in the intent are valid.
///
///
///
private ValidationResult Validate(Reservation reservation)
{
if (!string.IsNullOrEmpty(reservation.Location) && !TypeValidators.IsValidCity(reservation.Location))
{
return new ValidationResult(false, LOCATION_SLOT,
$"We currently do not support {reservation.Location} as a valid destination. Can you try a different city?");
}
if (!string.IsNullOrEmpty(reservation.CheckInDate))
{
DateTime checkinDate = DateTime.MinValue;
if (!DateTime.TryParse(reservation.CheckInDate, out checkinDate))
{
return new ValidationResult(false, CHECK_IN_DATE_SLOT,
"I did not understand your check in date. When would you like to check in?");
}
if (checkinDate < DateTime.Today)
{
return new ValidationResult(false, CHECK_IN_DATE_SLOT,
"Reservations must be scheduled at least one day in advance. Can you try a different date?");
}
}
if (!string.IsNullOrEmpty(reservation.Nights))
{
int nights;
if (!int.TryParse(reservation.Nights, out nights))
{
return new ValidationResult(false, NIGHTS_SLOT,
"I did not understand the number of nights. Can you enter the number of nights again again?");
}
if (nights < 1 || nights > 30)
{
return new ValidationResult(false, NIGHTS_SLOT,
"You can make a reservations for from one to thirty nights. How many nights would you like to stay for?");
}
}
return ValidationResult.VALID_RESULT;
}
///
/// Generates a number within a reasonable range that might be expected for a hotel.
/// The price is fixed for a pair of location and roomType.
///
///
///
private double GeneratePrice(Reservation reservation)
{
double costOfLiving = 0;
foreach (char c in reservation.Location!)
{
costOfLiving += (c - 97);
}
int roomTypeIndex = 0;
for (int i = 0; i < TypeValidators.VALID_ROOM_TYPES.Length; i++)
{
if (string.Equals(TypeValidators.VALID_ROOM_TYPES[i], reservation.CarType, StringComparison.Ordinal))
{
roomTypeIndex = i + 1;
break;
}
}
return int.Parse(reservation.Nights!, CultureInfo.InvariantCulture) * (100 + costOfLiving + (100 + roomTypeIndex));
}
}