// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/quick_answers/search_result_parsers/unit_conversion_result_parser.h"
#include <string>
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chromeos/components/quick_answers/utils/quick_answers_utils.h"
#include "chromeos/components/quick_answers/utils/unit_conversion_constants.h"
#include "chromeos/components/quick_answers/utils/unit_converter.h"
namespace quick_answers {
namespace {
using base::Value;
constexpr double kPreferredRatioRange = 100;
constexpr int kMaxAlternativeUnitsNumber = 4;
std::optional<ConversionRule> CreateConversionRule(
const Value::Dict& unit,
const std::string& category) {
const std::string* unit_name = unit.FindStringByDottedPath(kNamePath);
if (!unit_name) {
return std::nullopt;
}
return ConversionRule::Create(category, *unit_name,
unit.FindDouble(kConversionToSiAPath),
unit.FindDouble(kConversionToSiBPath),
unit.FindDouble(kConversionToSiCPath));
}
std::vector<UnitConversion> ParseAlternativeUnitConversions(
const Value::Dict& result,
const UnitConversion& unit_conversion) {
std::vector<UnitConversion> alternative_unit_conversions;
const Value::List* rule = result.FindListByDottedPath(kRuleSetPath);
if (!rule) {
return alternative_unit_conversions;
}
UnitConverter converter(*rule);
const Value::List* possible_units =
converter.GetPossibleUnitsForCategory(unit_conversion.category());
if (!possible_units) {
return alternative_unit_conversions;
}
for (const Value& unit : *possible_units) {
const Value::Dict& unit_dict = unit.GetDict();
const std::string* unit_name = unit_dict.FindStringByDottedPath(kNamePath);
// Filter out the source and destination units.
if (!unit_name || *unit_name == unit_conversion.source_rule().unit_name() ||
*unit_name == unit_conversion.dest_rule().unit_name()) {
continue;
}
std::optional<ConversionRule> alternative_dest_rule =
CreateConversionRule(unit_dict, unit_conversion.category());
if (!alternative_dest_rule) {
continue;
}
std::optional<UnitConversion> alternative_unit_conversion =
UnitConversion::Create(unit_conversion.source_rule(),
alternative_dest_rule.value());
if (!alternative_unit_conversion) {
continue;
}
alternative_unit_conversions.push_back(alternative_unit_conversion.value());
}
// Sort |alternative_unit_conversions| from lowest to highest linear
// conversion rates, then limit the vector size to
// |kMaxAlternativeUnitsNumber| results.
base::ranges::sort(alternative_unit_conversions);
if (alternative_unit_conversions.size() > kMaxAlternativeUnitsNumber) {
alternative_unit_conversions.erase(
alternative_unit_conversions.begin() + kMaxAlternativeUnitsNumber,
alternative_unit_conversions.end());
}
return alternative_unit_conversions;
}
} // namespace
// Extract |quick_answer| from unit conversion result.
bool UnitConversionResultParser::Parse(const Value::Dict& result,
QuickAnswer* quick_answer) {
std::unique_ptr<StructuredResult> structured_result =
ParseInStructuredResult(result);
if (!structured_result) {
return false;
}
return PopulateQuickAnswer(*structured_result, quick_answer);
}
std::unique_ptr<StructuredResult>
UnitConversionResultParser::ParseInStructuredResult(const Value::Dict& result) {
std::unique_ptr<UnitConversionResult> unit_conversion_result =
std::make_unique<UnitConversionResult>();
const std::string* category =
result.FindStringByDottedPath(kResultCategoryPath);
if (!category) {
LOG(ERROR) << "Failed to get the category for the conversion.";
return nullptr;
}
unit_conversion_result->category = *category;
const std::string* source_text =
result.FindStringByDottedPath(kSourceTextPath);
if (!source_text) {
LOG(ERROR) << "Failed to get the source amount and unit text.";
return nullptr;
}
unit_conversion_result->source_text = *source_text;
const std::optional<double> source_amount =
result.FindDoubleByDottedPath(kSourceAmountPath);
if (!source_amount) {
LOG(ERROR) << "Failed to get the source amount.";
return nullptr;
}
unit_conversion_result->source_amount = source_amount.value();
std::optional<ConversionRule> source_rule;
const Value::Dict* source_unit = result.FindDictByDottedPath(kSourceUnitPath);
if (source_unit) {
source_rule = CreateConversionRule(*source_unit, *category);
}
std::optional<ConversionRule> dest_rule;
const Value::Dict* dest_unit = result.FindDictByDottedPath(kDestUnitPath);
if (dest_unit) {
dest_rule = CreateConversionRule(*dest_unit, *category);
}
std::string result_string;
// If the conversion ratio is not within the preferred range, try to find a
// better destination unit type.
// This only works if we have a valid source unit.
if (source_unit) {
const std::optional<double> dest_amount =
result.FindDoubleByDottedPath(kDestAmountPath);
const std::optional<double> ratio = GetRatio(source_amount, dest_amount);
if (ratio && ratio.value() > kPreferredRatioRange) {
const Value::List* rule = result.FindListByDottedPath(kRuleSetPath);
if (rule) {
UnitConverter converter(*rule);
dest_unit =
converter.FindProperDestinationUnit(*source_unit, ratio.value());
if (dest_unit) {
result_string = converter.Convert(source_amount.value(), *source_unit,
*dest_unit);
// If a valid result is found, update the `dest_rule` value
// accordingly to get the ConversionRule for the new `dest_unit`.
if (!result_string.empty()) {
dest_rule = CreateConversionRule(*dest_unit, *category);
}
}
}
}
}
// Fallback to the existing result.
if (result_string.empty()) {
const std::string* dest_text = result.FindStringByDottedPath(kDestTextPath);
if (!dest_text) {
LOG(ERROR) << "Failed to get the conversion result.";
return nullptr;
}
result_string = *dest_text;
}
unit_conversion_result->result_text = result_string;
// Both source and destination unit ConversionRules must be valid for there to
// be valid unit conversions.
if (source_rule && dest_rule) {
std::optional<UnitConversion> unit_conversion =
UnitConversion::Create(source_rule.value(), dest_rule.value());
unit_conversion_result->source_to_dest_unit_conversion = unit_conversion;
if (unit_conversion) {
std::vector<UnitConversion> alternative_unit_conversions_list =
ParseAlternativeUnitConversions(result, unit_conversion.value());
unit_conversion_result->alternative_unit_conversions_list =
alternative_unit_conversions_list;
}
}
std::unique_ptr<StructuredResult> structured_result =
std::make_unique<StructuredResult>();
structured_result->unit_conversion_result = std::move(unit_conversion_result);
return structured_result;
}
bool UnitConversionResultParser::PopulateQuickAnswer(
const StructuredResult& structured_result,
QuickAnswer* quick_answer) {
UnitConversionResult* unit_conversion_result =
structured_result.unit_conversion_result.get();
if (!unit_conversion_result) {
DLOG(ERROR) << "Unable to find unit_conversion_result.";
return false;
}
quick_answer->result_type = ResultType::kUnitConversionResult;
quick_answer->first_answer_row.push_back(
std::make_unique<QuickAnswerResultText>(
unit_conversion_result->result_text));
return true;
}
bool UnitConversionResultParser::SupportsNewInterface() const {
return true;
}
} // namespace quick_answers