Skip to content

Repository files navigation

Polza Agency — тестовое задание «Технический специалист»

Пайплайн JSON + CSV → PostgreSQL с дедупликацией и валидацией, три аналитических SQL-запроса и страница /companies на Next.js (App Router) поверх этой базы.

Задача Где смотреть
1. Выгрузка → Postgres db/schema.sql, db/queries.sql, scripts/import-json.ts
2. Мини-фича + доказательство src/app/companies/page.tsx, docs/PROOF.md, docs/screenshots/
3. Данные с сюрпризом ANOMALIES.md, scripts/import-csv.ts
4. Вайбкод/LLM-стек Отправляется отдельным текстовым файлом — по формату сдачи из задания
Как делалось WORKLOG.md

Требования

  • Node.js 20+ (проверено на 24.14.0)
  • Docker с docker compose (для PostgreSQL 16)

Всё остальное ставится через npm ci.


Быстрый старт

npm ci
cp .env.example .env
npm run setup

npm run setup последовательно выполняет: поднять контейнер Postgres → применить схему → загрузить JSON → загрузить CSV. Занимает около 20 секунд.

Дальше — приложение:

npm run build
npm run start

Открыть http://localhost:3000/companies.

Для разработки вместо build/start можно npm run dev.

Что должно получиться

Import finished  (source: json_pack, run #1)
  records read      : 1000
  inserted          : 994
  duplicates skipped: 6

Import finished  (source: review_csv, run #2)
  records read      : 207
  inserted          : 190
  updated           : 6
  duplicates skipped: 9
  rejected rows     : 2

Итого в базе 1184 компании. Повторный запуск npm run import:json и npm run import:csv это число не меняет — импорт идемпотентен.


Команды

Команда Что делает
npm run setup Полная установка: контейнер + схема + оба импорта
npm run db:up / npm run db:down Поднять / остановить PostgreSQL
npm run db:reset Снести том с данными и создать базу заново
npm run db:schema Применить db/schema.sql (идемпотентно)
npm run db:psql psql внутри контейнера
npm run import:json Загрузить data/page_*.json
npm run import:csv Загрузить data/review.csv
npm run queries Выполнить три запроса из db/queries.sql и напечатать таблицы
npm run queries -- --markdown То же в Markdown
npm run dev / build / start Next.js
npm test Все тесты (нужен поднятый Postgres)
npm run test:unit Только юнит-тесты (Postgres не нужен)
npm run typecheck tsc --noEmit
npm run screenshots Перегенерировать скриншоты (нужен запущенный npm run start и один раз npx playwright install chromium)

Конфигурация

Секретов в репозитории нет. Все настройки — через переменные окружения, образец в .env.example; .env в .gitignore.

Переменная По умолчанию Назначение
DATABASE_URL postgresql://polza:polza@localhost:5433/polza Строка подключения для скриптов и приложения
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB polza Учётные данные контейнера
POSTGRES_PORT 5433 Порт на хосте (5433, чтобы не конфликтовать с локальным Postgres на 5432)
PGSSLMODE disable require — если DATABASE_URL смотрит на Supabase или другой managed-хост

Подключение к базе живёт только на сервере: страница /companies — Server Component, в браузер уходит готовый HTML.

Supabase вместо Docker

Задание допускает оба варианта. Для Supabase достаточно подменить окружение, код менять не нужно:

export DATABASE_URL='postgresql://postgres:...@db.<ref>.supabase.co:5432/postgres'
export PGSSLMODE=require
npm run db:schema && npm run import:json && npm run import:csv

Схема данных

Три таблицы (полностью — в db/schema.sql):

  • companies — чистый слой, одна строка на реальную компанию. Типы настоящие (numeric, integer), диапазоны закрыты CHECK-ограничениями. Кроме отображаемых значений хранятся нормализованные (name_norm, city_norm, address_norm) — по ним работают дедупликация и поиск.
  • import_issues — журнал: каждое исправленное, отклонённое или помеченное значение с исходным текстом и тем, что с ним сделали. ANOMALIES.md собран из этой таблицы, а не написан вручную.
  • import_runs — история запусков, по ней проверяется идемпотентность.

Индексы: уникальный source_id (ключ upsert), уникальный бизнес-ключ (name_norm, city_norm, address_norm), индексы под каждый из трёх аналитических запросов и GIN-индекс pg_trgm под поиск по подстроке.

Дедупликация — три уровня

  1. По source_id внутри пачки — ловит повторы страниц JSON (6 записей).
  2. По бизнес-ключу внутри пачки — одна компания под двумя id в одном файле.
  3. По бизнес-ключу против БД — ловит повторный экспорт с перенумерованными id: review.csv содержит 6 таких записей, и дедупликация только по id их не заметила бы. Подробности — в ANOMALIES.md.

SQL-запросы

Три запроса из задания — в db/queries.sql, с комментариями о том, почему выбраны именно такие фильтры:

  1. топ-5 категорий по числу компаний;
  2. средний рейтинг по городам среди компаний с 10+ отзывами;
  3. доля компаний с сайтом по категориям.
npm run queries

Результаты прогона — в docs/evidence/verification-run.txt, раздел 11.


Тесты

npm run test:unit   # 46 тестов, без базы
npm test            # 59 тестов, нужен поднятый Postgres
  • Юнит-тесты — правила очистки на реальных значениях из выгрузки (-3, 7.2, N/A, 4,5, много, 8 (925) abc-12-34, нет сайта, htp://, mojibake, сдвиг колонок). Это регрессионные тесты против конкретного датасета, а не выдуманные примеры.
  • Интеграционные тесты поднимают отдельную схему test_import в той же базе, чтобы не трогать рабочие данные, и проверяют числа импорта, идемпотентность, отлов перенумерованного экспорта и инварианты качества (нет рейтингов вне 0..5, нет mojibake, нет дублей по бизнес-ключу).

Доказательства работы

  • docs/PROOF.md — что проверялось руками, что при этом ломалось и как чинилось.
  • docs/screenshots/ — 7 скриншотов: список, поиск, фильтр по городу, комбинация, пустой результат, пагинация, недоступная база.
  • docs/evidence/verification-run.txt — полный протокол прогона с нуля: удаление тома, импорт, повторный импорт, проверки качества, запросы, тесты.
  • docs/evidence/clean-clone-run.txt — проверка воспроизводимости: репозиторий склонирован в пустой каталог, и все команды из этого README выполнены ровно так, как они здесь записаны.

Скриншоты снимаются скриптом (scripts/screenshots.ts), а не вручную, — чтобы доказательства можно было воспроизвести, а не принимать на веру. Для этого нужен один раз выполненный npx playwright install chromium.


Известные ограничения

  • npm audit показывает предупреждение в sharp (транзитивная зависимость оптимизатора картинок Next.js). Приложение картинок не отдаёт, этот код не выполняется, а npm audit fix --force откатывает Next.js до 9.x — что заведомо хуже. Оставлено осознанно.
  • Список канонических городов в normalize.ts покрывает те 20 городов, что есть в выгрузке. Незнакомый город не отбрасывается, а сохраняется с пометкой city_unknown — молча терять данные хуже.
  • Строка со сдвигом колонок (c_001015) не восстанавливается автоматически: где потерялась настоящая категория — неизвестно. Поля обнулены, запись помечена is_suspect.
  • Поля email в исходных данных нет — см. пункт 12 в ANOMALIES.md.

About

JSON + CSV to PostgreSQL pipeline with three-level deduplication, validation and idempotent re-runs, three analytical queries, and a /companies page on Next.js App Router

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages