Клиентские библиотеки и виджеты
У сервиса две ручки: нормализация — привести марку и модель к записи каталога, и подсказки — дополнить ввод по мере набора. Библиотеки для Python, C#, Ruby, PHP, Elixir и JavaScript закрывают обе. Отдельно — React-компоненты, если поиск нужен прямо в интерфейсе.
Виджеты
Работают против API этого стенда, данные настоящие. Подключается один файл
mm-widgets.js: React лежит внутри него, ставить ничего не надо.
Обе ручки вместе
MakeModelDemo — подсказки на вводе, а после выбора карточка
записи: значение каждого поля и откуда оно взялось. Наберите «камр» или
«тайота камри».
Размер каталога
CatalogStats — счётчики из /v1/public/stats.
Только подсказки
SuggestInput — поле ввода без карточки, если распознавание
вы делаете у себя. Управляется с клавиатуры: стрелки, Enter, Escape;
скринридер слышит, сколько вариантов нашлось.
Выберите вариант — здесь появится его идентификатор.
<script src="https://ЭТОТ-СТЕНД/ui/sdk/mm-widgets.js" defer></script>
<div data-mm-widget="demo"
data-mm-base-url=""
data-mm-targets="broker"
data-mm-paths='{"resolve":["/demo-api/resolve","/v1/resolve"],
"decode":["/demo-api/decode","/v1/decode"]}'></div>
Установка и первый запрос
Ниже один и тот же пример на всех языках: отправить «Тайота / камри» и получить запись каталога.
from make_model_sdk import MakeModelClient, QualityCode
with MakeModelClient("https://ЭТОТ-СТЕНД", api_key="…") as client:
res = client.resolve("Тайота", "камри", target="broker")
res.quality_code # "NO_MAPPING_FOR_TARGET"
res.primary_id # "N-TOYOTA-CAMRY"
res.field_value("mark") # "Toyota"
res.is_ok # нашлась ли запись
res.outbound_for("broker") # OutboundValue | None
# Свободный текст, стратегия по каждому полю
res = client.resolve_request({
"query": "тайота камри 2.5 автомат",
"want": {"mark": "exact", "modification": "confidence"},
"targets": ["broker"],
})
# Запись уже выбрана — decode вместо повторного распознавания
card = client.decode("N-TOYOTA-CAMRY", fields=["mark", "model"])
client.suggest("камр", limit=7) # list[SuggestItem]
client.stats() # размеры каталога
using MakeModel.Sdk;
using var client = new MakeModelClient("https://ЭТОТ-СТЕНД", apiKey: "…");
var res = await client.ResolvePairAsync("Тайота", "камри", target: "broker");
res.Result; // "NO_MAPPING_FOR_TARGET"
res.PrimaryId; // "N-TOYOTA-CAMRY"
res.FieldValue(V2Fields.Mark); // "Toyota"
res.IsOk; // нашлась ли запись
res.OutboundFor("broker"); // OutboundValue?
// Свободный текст, стратегия по каждому полю
res = await client.ResolveAsync(new ResolveRequest
{
Query = "тайота камри 2.5 автомат",
Want = new() { ["mark"] = FieldStrategies.Exact },
Targets = new() { "broker" },
});
var card = await client.DecodeAsync(new DecodeRequest
{
PrimaryId = "N-TOYOTA-CAMRY",
Fields = new() { "mark", "model" },
});
await client.SuggestAsync("камр");
await client.StatsAsync();
require "make_model"
client = MakeModel.client(base_url: "https://ЭТОТ-СТЕНД", api_key: "…")
res = client.resolve_pair("Тайота", "камри", target: "broker")
res.result # "NO_MAPPING_FOR_TARGET"
res.primary_id # "N-TOYOTA-CAMRY"
res.field_value(:mark) # "Toyota"
res.ok? # нашлась ли запись
res.outbound_for("broker") # MakeModel::OutboundValue | nil
# Свободный текст, стратегия по каждому полю
res = client.resolve(
query: "тайота камри 2.5 автомат",
want: { mark: "exact", modification: "confidence" },
targets: ["broker"]
)
card = client.decode("N-TOYOTA-CAMRY", fields: %w[mark model])
client.suggest("камр", limit: 7) # [MakeModel::SuggestItem]
client.stats.marks # размер каталога
use Ofm\MakeModel\Client;
use Ofm\MakeModel\V2Field;
$client = new Client('https://ЭТОТ-СТЕНД', '…');
$res = $client->resolvePair('Тайота', 'камри', 'broker');
$res->result(); // 'NO_MAPPING_FOR_TARGET'
$res->primaryId(); // 'N-TOYOTA-CAMRY'
$res->fieldValue(V2Field::MARK); // 'Toyota'
$res->isOk(); // нашлась ли запись
$res->outboundFor('broker'); // OutboundValue|null
// Свободный текст, стратегия по каждому полю
$res = $client->resolve([
'query' => 'тайота камри 2.5 автомат',
'want' => ['mark' => 'exact'],
'targets' => ['broker'],
]);
$card = $client->decode('N-TOYOTA-CAMRY', [], ['mark', 'model']);
$client->suggest('камр', 7);
$client->stats()->marks();
client = MakeModel.client("https://ЭТОТ-СТЕНД", api_key: "…")
{:ok, res} = MakeModel.Client.resolve_pair(client, "Тайота", "камри", "broker")
MakeModel.Result.result(res) # "NO_MAPPING_FOR_TARGET"
MakeModel.Result.primary_id(res) # "N-TOYOTA-CAMRY"
MakeModel.Result.field_value(res, :mark) # "Toyota"
MakeModel.Result.ok?(res) # нашлась ли запись
MakeModel.Result.outbound_for(res, "broker")
# Свободный текст, стратегия по каждому полю
{:ok, res} =
MakeModel.Client.resolve(client, %{
query: "тайота камри 2.5 автомат",
want: %{mark: "exact", modification: "confidence"},
targets: ["broker"]
})
{:ok, card} =
MakeModel.Client.decode(client, "N-TOYOTA-CAMRY", fields: ["mark", "model"])
{:ok, items} = MakeModel.Client.suggest(client, "камр")
{:ok, stats} = MakeModel.Client.stats(client)
import { MakeModelClient, isOk, outboundFor, fieldValue } from '@ofm/make-model-sdk';
const client = new MakeModelClient({ baseUrl: 'https://ЭТОТ-СТЕНД', apiKey: '…' });
const res = await client.resolvePair('Тайота', 'камри', 'broker');
res.result; // 'NO_MAPPING_FOR_TARGET'
res.primary_ref?.primary_id; // 'N-TOYOTA-CAMRY'
fieldValue(res, 'mark'); // 'Toyota'
isOk(res.result); // нашлась ли запись
outboundFor(res, 'broker'); // OutboundValue | undefined
// Свободный текст, стратегия по каждому полю
await client.resolve({
query: 'тайота камри 2.5 автомат',
want: { mark: 'exact', modification: 'confidence' },
targets: ['broker'],
});
await client.decode({ primary_id: 'N-TOYOTA-CAMRY', fields: ['mark', 'model'] });
await client.suggest('камр', 7);
await client.stats();
import { MakeModelClient } from '@ofm/make-model-sdk';
import { MakeModelDemo, SuggestInput, ResolveCard, QualityBadge, CatalogStats,
useResolve, useSuggest } from '@ofm/make-model-sdk/react';
const client = new MakeModelClient({ baseUrl: '' });
// Всё сразу: подсказки и карточка
<MakeModelDemo client={client} targets={['broker']} />
// Или по частям, если вёрстка своя
function Picker() {
const { data, loading, error, decode } = useResolve(client);
return (
<>
<SuggestInput client={client} onSelect={(i) => decode(i.primary_id, [], ['mark', 'model'])} />
<ResolveCard result={data} loading={loading} error={error} />
{data ? <QualityBadge code={data.result} /> : null}
</>
);
}
Две ручки
Названия методов в библиотеках пишутся по правилам своего языка
(resolve_pair, ResolvePairAsync,
resolvePair), но зовут они одни и те же эндпоинты.
| Ручка | Метод | HTTP | Что делает |
|---|---|---|---|
| Нормализация | resolve |
POST /v1/resolve |
Приводит произвольный ввод к записи каталога и возвращает поля с указанием источника каждого значения. Принимает inputs, query, vin, pts, want, targets. |
decode |
POST /v1/decode |
То же, когда запись уже выбрана — например, пользователь ткнул в подсказку. Распознавать заново не нужно, поля перечисляются в fields. |
|
| Подсказки | suggest |
GET /v1/suggest |
Дополняет ввод по мере набора. Только чтение, ничего не меняет. limit — от 1 до 20. |
Есть ещё stats (GET /v1/public/stats) — сколько в
каталоге марок, моделей и алиасов. Это служебная сводка для витрин, к
распознаванию отношения не имеет.
-
«Не нашли» — это не ошибка.
NO_MATCH,NO_MAPPING_FOR_TARGETиOK_GREYприходят с кодом HTTP 200, в полеresult. Ошибка HTTP значит, что не в порядке запрос или сам сервис. -
В
inputsиwantшесть ключей:mark,model,modification,body_type,year,segment. Остальные дают 422 — чаще всего так ловитсяbrandвместоmark. - Повторы запроса. Библиотеки сами повторяют запрос трижды, если сервис ответил 5xx или оборвалось соединение; пауза между попытками растёт с 0,2 секунды. На 4xx повторов нет: если запрос составлен неверно, второй раз он не станет верным.
-
Токен нужен не всем методам.
/v1/resolveи/v1/decodeзакрыты,/v1/suggestи/v1/public/statsоткрыты. На этом стенде токен подставляет nginx на путях/demo-api/*— поэтому в коде страницы его нет. -
Ответ воспроизводим. Тот же запрос при той же
ruleset_versionдаёт тот же результат. Эту версию иcatalog_epochсервис возвращает каждый раз — их стоит писать в лог рядом с ответом.