Qamqor Match
Next.js-сервис подбирает до трёх event-подрядчиков из официального CSV (66 профилей) по городу, дате, формату, категории и бюджету, объясняет выбор…
О проекте
Команда: Qamqor
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Доведённый продукт
Задача кейса: «#79-lite»: подобрать до 3 подрядчиков для мероприятия и объяснить выбор
Суть
Next.js-сервис подбирает до трёх event-подрядчиков из официального CSV (66 профилей) по городу, дате, формату, категории и бюджету, объясняет выбор и помогает собрать смету события.
Как устроено
POST /api/match читает CSV на сервере. Детерминированный matcher фильтрует по занятости, бюджету, формату, языку и длительности (считая причины исключения), затем ранжирует по формуле score v1: остаток бюджета, язык, запас часов, совпавший признак из описания, полнота данных. Сортировка стабильна по id. LLM (gpt-4o-mini через Responses API) не выбирает и не переставляет подрядчиков: по фактам кандидатов она возвращает строгий JSON-план формулировки, код проверяет ID и факты, а при ошибке или таймауте 6000 мс включается fallback. При пустой выдаче предлагаются альтернативы: ближайшая дата, достаточный бюджет.
Ключевые решения
- deterministic filtering and scoring
- stable sort
- strict JSON explanation plan
- fact validation
- fallback explanations
- alternative suggestions search
Особенности
LLM полностью отделена от ранжирования и получает только структурированные факты без имён и полных профилей, поэтому выдача воспроизводима. Если подходящих подрядчиков нет, интерфейс предлагает проверенные альтернативы, где каждая кнопка меняет одно поле запроса. Есть сборка сметы из подрядчиков разных категорий.
Стек
Next.js · React · TypeScript · node:test · Vercel
Модели
gpt-4o-mini
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-181c6071-qamqor.
README
README.mdQamqor Match
Решение кейса #79-lite: подбор event-подрядчиков по параметрам мероприятия с конкретными объяснениями выбора. Сервис принимает город, дату, формат, категорию и бюджет в тенге; дополнительно можно указать язык и длительность. Возвращает до трёх доступных вариантов в стабильном порядке.
Исходный код для проверки: репозиторий организаторов, ветка main.
Демонстрационный сайт: https://qamqor-hackathon.vercel.app. Для запуска и проверки решения сайт команды не требуется: исходный код и датасет находятся в репозитории. Инструкция самостоятельного развёртывания приведена ниже.
Требования кейса: docs/CASE.md. Этот README описывает версию для технической проверки.
Что реализовано
- Серверная загрузка официального CSV из 66 профилей: hackathon-dataset-anonymized.csv.
- Стартовый каталог показывает примеры из того же CSV: имя, категорию, город и стартовую цену. Выбор профиля подставляет город и категорию в форму; ниже доступны все 17 направлений с числом анкет в каждом. Доступность проверяется только после ввода даты. Счётчики вычисляются из CSV при сборке страницы.
- Два готовых demo-примера заполняют обязательные поля формы для быстрой проверки; после ответа страница переходит к результатам. Интерфейс адаптирован для мобильного экрана.
- Фильтрация по городу, категории, дате, формату мероприятия и бюджету. Подрядчики, занятые на выбранную дату, исключаются.
- API проверяет типы обязательных полей, существование даты и положительность числовых значений; некорректный ввод возвращает HTTP 400.
- Дополнительная фильтрация по запрошенному языку и длительности. Ограничение по часам применяется, если в профиле указан
max_hours. - До трёх карточек с именем, категорией, городом, ценой «от» и объяснением. В объяснении используются короткие признаки из анкет, если они явно найдены в официальном описании. Отображаются признаки синтетических и восстановленных данных.
- Для каждого варианта показан сметный ориентир: стартовая цена, заданный лимит на подрядчика и остаток до него. Это сравнение с бюджетом, а не окончательная стоимость услуги.
- Из результатов можно собрать смету одного события: по одному выбранному подрядчику на категорию, сумма стартовых цен и необязательный общий бюджет с ориентировочным остатком. При новом городе, дате или формате смета начинается заново. Она живёт только в открытой вкладке, не создаёт заявку или бронирование.
- Детерминированное ранжирование: одинаковый запрос на неизменном CSV даёт одинаковые ID и порядок, в том числе при недоступном OpenAI.
- Три состояния результата и сообщение при количестве вариантов меньше трёх.
- При пустом результате показаны до трёх проверенных альтернатив: ближайшая подходящая дата, достаточный бюджет или другое явно указанное условие. Каждая кнопка меняет только одно поле и повторяет поиск. Даты перебираются вперёд не более чем на 30 дней и не дальше последней даты календаря CSV; доступность реального рынка не обещается.
- После изменения формы прежняя выдача скрывается; ответ уже отправленного запроса не подменит результаты для новых условий.
- Серверные AI-объяснения и детерминированный fallback. В успешной выдаче интерфейс показывает режим объяснения.
outcome | Значение |
|---|---|
MATCHED | Найдены от одного до трёх подходящих подрядчиков |
NO_CATEGORY_IN_CITY | В выбранном городе нет профилей нужной категории |
NO_ELIGIBLE | Профили города и категории есть, но все исключены фильтрами |
Архитектура
Стек: Next.js App Router, React, TypeScript; серверный источник данных — CSV.
Browser UI → POST /api/match → CSV loader → deterministic matcher
↓
OpenAI explanation layer
↙ ↘
проверенный AI-план fallback
↘ ↙
response
| Компонент | Файл / назначение |
|---|---|
| Browser UI | src/app/page.tsx, components/SearchForm.tsx, ContractorCard.tsx: каталог из CSV, форма, состояния и карточки |
| API | src/app/api/match/route.ts: обработка POST /api/match, время ответа |
| CSV loader | src/lib/csv.ts: чтение и разбор официального CSV на сервере |
| Matcher | src/lib/matcher.ts: фильтры, score, стабильная сортировка, facts и базовые объяснения |
| AI-слой | src/lib/ai-explanations.server.ts: запрос к OpenAI, проверка плана, формулировки и fallback |
| Контракт | src/lib/types.ts: типы запроса и ответа |
Ответ содержит outcome, matches, rejectionSummary, message, algorithmVersion, elapsedMs и explanationMode. В каждой записи matches находятся contractor, score, facts, explanation.
Фильтрация и порядок
После выбора города и категории matcher проверяет занятость, бюджет, формат, язык и длительность. rejectionSummary содержит количества исключений по причинам busy, budget, format, language, duration. Для каждого исключённого кандидата учитывается первая сработавшая причина именно в этом порядке.
Score версии v1 — сумма факторов:
| Фактор | Баллы |
|---|---|
| Остаток бюджета | min(30, round(max(0, (budgetKzt − priceFromKzt) / budgetKzt) × 30)) |
| Запрошенный язык, подтверждённый фильтром | 20; если язык не указан — 0 |
| Запас длительности | min(10, maxHours − durationHours + 5), если оба значения заданы; иначе 0 |
Совпавшее слово описания (facts.evidence) | 20 при наличии; иначе 0 |
| Полнота данных | 5, если город или цена восстановлены; иначе 10 |
Сортировка: score по убыванию, при равенстве — стабильный id по возрастанию. Затем берутся первые три результата. LLM в этом расчёте не участвует.
AI и fallback
LLM не выбирает подрядчиков и не меняет их порядок. Модель получает только явно перечисленные параметры запроса и структурированные facts уже найденных кандидатов: ID, доступность на дату, цену «от», остаток бюджета, формат, подтверждённый язык, часы и короткий признак, извлечённый из анкеты по фиксированным правилам. Полные профили, имена, описания и score ей не передаются.
Текущая реализация делает один запрос к OpenAI Responses API с моделью gpt-4o-mini, без повторных попыток, с store: false. Модель выбирает акцент и вариант формулировки через строгий JSON-план. Код проверяет ID, допустимые значения и наличие соответствующих facts, затем собирает два предложения из этих фактов. Произвольные утверждения модели не выводятся.
explanationMode: "ai"— план модели принят; UI: «Объяснение подготовлено AI-агентом».explanationMode: "fallback"— использованы исходные объяснения matcher; UI: «Использовано базовое объяснение». Отсутствующее поле UI также трактует как fallback.
Без ключа или при ошибке OpenAI приложение продолжает работать. Отсутствие ключа, HTTP/сетевая ошибка, отказ модели, неверный или неполный ответ, неподтверждённые факты и таймаут автоматически возвращают детерминированные объяснения. Общий таймаут AI — 6000 мс, включая чтение ответа; размер ответа ограничен 32 КиБ. При пустой выдаче вызова модели нет. AI может менять формулировки между запросами, но состав и порядок карточек сохраняются.
Локальный запуск
Проверки выполнялись на Node.js 25.2.1 и npm. Тесты используют встроенный node:test и нативную загрузку TypeScript; для воспроизведения используйте указанную версию Node.js.
Для первого запуска клонируйте репозиторий (нужен доступ к приватному репозиторию организаторов):
git clone https://github.com/BAITC-Hacks/hack-181c6071-qamqor.git
cd hack-181c6071-qamqor
git switch main
npm ci
npm run dev
Если репозиторий уже скачан, выполните только npm ci и npm run dev из папки с package.json. npm ci устанавливает версии из сохранённого package-lock.json.
Откройте http://localhost:3000. Без настройки окружения подбор работает через fallback.
На стартовом экране можно выбрать пример профиля и затем указать дату, формат и бюджет. Для полного проверочного запроса нажмите «Ведущий · Астана» или «Флорист · Алматы» над формой, затем «Найти подрядчиков». Первые карточки каталога — только примеры записей, их доступность заранее не обещается. Сметный ориентир в выдаче относится к одному подрядчику и сравнивает стартовую цену с введённым лимитом; он не заменяет финальное коммерческое предложение.
Чтобы проверить общую смету, выполните поиск, добавьте карточку кнопкой «Добавить в смету», затем ищите другую категорию с теми же городом, датой и форматом. В разделе «Смета события» видны выбранные подрядчики и сумма. Укажите общий бюджет, чтобы увидеть ориентировочный остаток или превышение. Другой город, дата или формат начинают новую смету; кнопка заранее показывает «Начать новую смету».
Переменные окружения
Для AI-объяснений задайте OPENAI_API_KEY на сервере: локально в .env.local, на Vercel — в Environment Variables проекта. После изменения локальной переменной перезапустите dev-сервер; для production примените конфигурацию новым deployment.
OPENAI_API_KEY=
Это пустой пример. Настоящий ключ нельзя добавлять в Git, README, клиентский код или логи. .env.local исключён из Git. AI-слой читает ключ только из process.env.OPENAI_API_KEY.
В новом клоне можно создать файл командой PowerShell Copy-Item .env.example .env.local, затем вписать свой ключ после OPENAI_API_KEY= в редакторе. Если .env.local уже существует, отредактируйте его без копирования поверх. Для проверки без AI этот шаг можно пропустить. Подключать базу данных, Supabase или выполнять миграции не нужно: CSV уже включён в репозиторий.
Самостоятельное развёртывание (деплой)
Локальная production-сборка без облака
Из корня клонированного репозитория, после установки зависимостей:
npm run check
node --test tests/matching.test.mjs tests/ai-explanations.test.mjs
npm run build
npm run start
Откройте http://localhost:3000. Перед start остановите dev-сервер на том же порту через Ctrl+C. Оставьте терминал работающим. API доступен по адресу http://localhost:3000/api/match; четыре проверочных запроса приведены ниже. На собственном Node.js-сервере используется та же сборка и запуск; сохраните папку data/ рядом с package.json, поскольку сервер читает CSV из неё. Для постоянной публикации нужны менеджер процесса и HTTPS на стороне хостинга.
Vercel через CLI
Нужны аккаунт Vercel и локальный клон. Выполняйте команды по очереди из корня проекта:
npm install --global vercel
vercel login
vercel link
В vercel link выберите свой аккаунт/команду и создайте новый проект либо выберите свой существующий. Каталог проекта — ./, framework — Next.js. Для воспроизведения не требуется доступ к Vercel-аккаунту команды Qamqor. Созданная служебная папка .vercel/ исключена из Git.
Если нужны AI-объяснения, добавьте собственный ключ в окружение Production:
vercel env add OPENAI_API_KEY production
Вставьте ключ в интерактивном запросе CLI и выберите Yes для Sensitive. Если переменная уже существует, обновите её в настройках проекта; повторно добавлять её не нужно. Ключ не должен иметь префикс NEXT_PUBLIC_. Без него деплой тоже работает, используя fallback. Для Preview ключ при необходимости добавляется отдельно в окружение Preview.
После настройки выполните:
vercel --prod
Дождитесь успешной сборки и откройте URL, который вернула команда. После добавления или изменения переменной окружения выполните новый vercel --prod, чтобы применить её. Инструкции CLI: деплой, переменные окружения.
Vercel через веб-интерфейс
Вместо CLI можно выбрать Add New → Project → Import Git Repository и импортировать этот репозиторий, если Vercel разрешён доступ к нему. Настройки: Framework Preset — Next.js, Root Directory — корень репозитория, Install Command — npm ci, Build Command — npm run build, Output Directory — значение по умолчанию для Next.js. При необходимости добавьте OPENAI_API_KEY для Production и нажмите Deploy. Если политика GitHub-организации не позволяет подключить интеграцию, используйте CLI из доступного вам локального клона. Документация Vercel по Git.
Проверка после развёртывания
- Откройте главную страницу: каталог и форма должны загрузиться без ключа и без базы данных.
- Выберите «Ведущий · Астана» и нажмите «Найти подрядчиков»: ожидаются три карточки в порядке из сценария
denseниже. - Проверьте остальные три сценария ниже и альтернативы при пустой выдаче. Для API-запроса подставьте URL своего сервера в
$baseUrl. - Без ключа ожидается
explanationMode: "fallback"; с рабочим ключом и успешным ответом OpenAI —"ai". При ошибке провайдера fallback допустим; порядок подрядчиков сохраняется.
Git push и деплой — отдельные действия. git push origin main отправляет коммиты в репозиторий организаторов. vercel --prod публикует приложение и сам по себе не отправляет код в GitHub. Для сдачи исходников используется репозиторий; действующий сайт служит дополнительным демо. При подключённой Git-интеграции Vercel деплой может запускаться автоматически после push.
Production-проверки
Перед технической сдачей выполните из корня:
npm run check
npm run build
node --test tests/matching.test.mjs tests/ai-explanations.test.mjs
git diff --check
git diff --stat
check запускает TypeScript и ESLint; build собирает production-версию. Для локального запуска собранной версии используйте npm run start.
Два тестовых файла содержат 31 тест: официальный CSV, четыре demo-сценария, стабильный порядок, успешный AI-план, неверные ID/факты, отсутствие ключа, ошибки, отказ и таймауты. AI-тесты подменяют HTTP-вызов провайдера; им не нужен реальный ключ, они не выполняют платные запросы. Прохождение тестов не заменяет проверку живого OpenAI и браузера на production.
Четыре demo-сценария
Источник точных запросов и ожиданий: data/fixtures/matching-scenarios.json. Все запросы отправляются в POST /api/match с Content-Type: application/json. В этих сценариях язык и длительность не указаны.
1. Плотная категория — dense
{
"city": "Астана",
"date": "2026-11-14",
"eventFormat": "корпоратив",
"category": "Ведущий",
"budgetKzt": 1500000
}
Ожидается MATCHED, три результата в порядке HK-37181 → HK-80581 → HK-97041. В городе пять профилей категории; исключены один занятый и один неподходящий по формату: busy=1, format=1.
2. Редкая категория — rare
{
"city": "Алматы",
"date": "2026-11-15",
"eventFormat": "корпоратив",
"category": "Флорист",
"budgetKzt": 1500000
}
Ожидается MATCHED, одна карточка HK-39372. Из двух флористов один занят: busy=1. Сообщение результата честно указывает: «Нашлось только 1: остальные кандидаты не прошли условия»; дополнительные карточки не создаются.
3. Кандидаты есть, но все исключены — empty
{
"city": "Алматы",
"date": "2026-11-14",
"eventFormat": "корпоратив",
"category": "Флорист",
"budgetKzt": 1500000
}
Ожидается NO_ELIGIBLE, matches: []. В городе два флориста: один занят, другой не подходит по формату; busy=1, format=1. Это не состояние отсутствующей категории.
4. Тот же запрос на другую дату — otherDate
{
"city": "Астана",
"date": "2026-11-15",
"eventFormat": "корпоратив",
"category": "Ведущий",
"budgetKzt": 1500000
}
Сравните с dense: изменена только дата. Ожидается MATCHED, три результата в порядке HK-58385 → HK-37181 → HK-97041. HK-80581 свободен 14 ноября, но занят 15 ноября; HK-58385 занят 14 ноября, но свободен 15 ноября. Разница подтверждается busy_dates официального CSV. Исключения: busy=1, format=1.
Во всех четырёх сценариях остальные счётчики исключения равны нулю. Повторение каждого запроса должно сохранять ID и порядок независимо от explanationMode.
Запуск demo-запроса в PowerShell
$baseUrl = 'http://localhost:3000'
$fixtures = Get-Content -Raw -Encoding UTF8 data/fixtures/matching-scenarios.json | ConvertFrom-Json
$body = $fixtures.dense.request | ConvertTo-Json -Compress
Invoke-RestMethod -Uri "$baseUrl/api/match" -Method Post -ContentType 'application/json; charset=utf-8' -Body ([System.Text.Encoding]::UTF8.GetBytes($body))
Замените dense на rare, empty или otherDate. Для облачной проверки замените $baseUrl на адрес своего деплоя или https://qamqor-hackathon.vercel.app (без завершающего /). В форме вводятся те же значения. Даты fixtures фиксированы: когда они станут прошедшими, ограничение календаря в UI может мешать ручному демо; автоматические тесты от текущей даты не зависят.
Ограничения текущей версии
- Официальный статический датасет из 66 профилей: доступность и цены берутся из CSV и не обновляются в реальном времени. Цена указана как «от»; часть данных помечена как восстановленная или синтетическая.
- В техническую версию не входят бронирование, заявки, регистрация, личные кабинеты и админ-модерация.
- Смета хранится только в памяти открытой вкладки и исчезает после обновления страницы; финальные цены и дополнительные услуги не рассчитываются.
- Неизвестный
max_hoursне исключает кандидата по длительности; описание даёт только совпадение слов, а не семантическую оценку опыта. - Целевой ориентир кейса — ответ до 10 секунд. Таймаут AI ограничен 6 секундами; общее время также зависит от сети, холодного старта и сервера и не является гарантированным SLA.
- Проверки AI с подменённым HTTP подтверждают обработку контракта и fallback, но не доступность OpenAI для конкретного аккаунта. Свежие назначения и статус production-проверки ведутся в docs/ACTIVE_WORK.md.
Следующий этап после технической проверки
Планируется подключение Supabase, авторизации, анкеты подрядчика и админ-модерации. Это следующий этап развития, а не реализованные функции текущей сдачи.
Авторы коммитов
История коммитов
- 2026-09-23 17:50docs: document reproducible setup and deploymentMuhamedali Kamenov
- 2026-09-23 17:39feat: suggest verified dates and budgets for empty searchesMuhamedali Kamenov
- 2026-09-23 17:22fix: reject malformed match requests with clear validationMuhamedali Kamenov
- 2026-09-23 17:19feat: browse all contractor categories and prevent stale resultsMuhamedali Kamenov
- 2026-09-23 17:08feat: build event estimate from matched contractorsMuhamedali Kamenov
- 2026-09-23 16:59feat: add dataset marketplace and guided matchingMuhamedali Kamenov
- 2026-09-23 16:49feat: personalize matching and show budget breakdownMuhamedali Kamenov
- 2026-09-23 16:17docs: complete final compliance auditMuhamedali Kamenov
Все коммиты (50)
- 2026-09-23 16:07feat: clarify available filters and partial resultsMuhamedali Kamenov
- 2026-09-23 16:01fix: constrain AI focus to verified factsMuhamedali Kamenov
- 2026-09-23 16:01merge: record production acceptance resultsMuhamedali Kamenov
- 2026-09-23 15:59merge: finalize technical submission readmeMuhamedali Kamenov
- 2026-09-23 15:55docs: finalize technical submission readmeDinmukhammed
- 2026-09-23 15:53docs: record production acceptance resultsPavlo
- 2026-09-23 15:45docs: define final QA deliverableMuhamedali Kamenov
- 2026-09-23 15:40docs: move team to final submission checksMuhamedali Kamenov
- 2026-09-23 15:20fix: add safe AI fallback diagnosticsMuhamedali Kamenov
- 2026-09-23 15:18docs: mark AI agent integration verifiedMuhamedali Kamenov
- 2026-09-23 15:17merge: add resilient AI explanation agentMuhamedali Kamenov
- 2026-09-23 15:14fix: expose AI explanation modeDinmukhammed
- 2026-09-23 15:13docs: mark agent UI verifiedMuhamedali Kamenov
- 2026-09-23 15:12merge: show agent explanation modeMuhamedali Kamenov
- 2026-09-23 15:11Merge remote-tracking branch 'origin/main' into feat/ai-dataDinmukhammed
- 2026-09-23 15:09feat: show AI explanation modePavlo
- 2026-09-23 15:05feat: add resilient AI matching explanationsDinmukhammed
- 2026-09-23 15:01docs: assign agent UI task to DaurenMuhamedali Kamenov
- 2026-09-23 14:49docs: prioritize resilient AI explanation layerMuhamedali Kamenov
- 2026-09-23 14:47docs: mark data scenarios verifiedMuhamedali Kamenov
- 2026-09-23 14:46merge: add required matching demo scenariosMuhamedali Kamenov
- 2026-09-23 14:44docs: mark verified UI task completeMuhamedali Kamenov
- 2026-09-23 14:43test: add required matching demo scenariosDinmukhammed
- 2026-09-23 14:43merge: integrate verified UI statesMuhamedali Kamenov
- 2026-09-23 14:30docs: record production deploymentMuhamedali Kamenov
- 2026-09-23 14:21Merge remote-tracking branch 'origin/main' into sync-uiMuhamedali Kamenov
- 2026-09-23 14:17merge: synchronize team ownership rulesMuhamedali Kamenov
- 2026-09-23 14:17docs: enforce task ownership across agentsMuhamedali Kamenov
- 2026-09-23 14:13merge: add single-owner work protocolMuhamedali Kamenov
- 2026-09-23 14:13docs: assign single owner for active workMuhamedali Kamenov
- 2026-09-23 14:11merge: integrate search UI with matching coreMuhamedali Kamenov
- 2026-09-23 14:11feat: integrate contractor search interfaceMuhamedali Kamenov
- 2026-09-23 14:10feat: integrate contractor search UIPavlo
- 2026-09-23 14:02Merge remote-tracking branch 'origin/feat/ui' into feat/coreMuhamedali Kamenov
- 2026-09-23 13:58Merge remote-tracking branch 'origin/main' into feat/uiPavlo
- 2026-09-23 13:57merge: integrate deterministic matching coreMuhamedali Kamenov
- 2026-09-23 13:56Merge remote-tracking branch 'origin/feat/core' into feat/coreMuhamedali Kamenov
- 2026-09-23 13:55feat: add deterministic contractor matching coreMuhamedali Kamenov
- 2026-09-23 13:41feat: add contractor search formPavlo
- 2026-09-23 13:32docs: define isolated branch workflowMuhamedali Kamenov
- 2026-09-23 13:22docs: define matching pipeline and team startupMuhamedali Kamenov
- 2026-09-23 13:19chore: add case 79 brief dataset and team planMuhamedali Kamenov
Структура репозитория (48 файлов)
.env.example .gitignore AGENTS.md README.md data/fixtures/matching-scenarios.json data/hackathon-dataset-anonymized.csv data/hackathon-dataset-preview.html docs/ACTIVE_WORK.md docs/CASE.md docs/COORDINATION.md docs/GIT_WORKFLOW.md docs/HOURLY_PROGRESS.md docs/IMPLEMENTATION.md docs/OFFICIAL_INSTRUCTIONS.md docs/PRODUCTION_QA.md docs/TASKS.md docs/TEAM_START.md eslint.config.mjs next-env.d.ts next.config.ts package-lock.json package.json postcss.config.mjs prompts/ALI.md prompts/DAUREN.md prompts/DIMASH.md public/file.svg public/globe.svg public/next.svg public/vercel.svg public/window.svg src/app/api/match/route.ts src/app/components/ContractorCard.tsx src/app/components/SearchForm.module.css src/app/components/SearchForm.tsx src/app/favicon.ico src/app/globals.css src/app/layout.tsx src/app/page.tsx src/lib/ai-explanations.server.ts src/lib/csv.ts src/lib/matcher.ts src/lib/types.ts tests/AI-1.md tests/DATA-1.md tests/ai-explanations.test.mjs tests/matching.test.mjs tsconfig.json
Ветки (4)
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.