factcircuit
FactCircuit: an auditable claim-and-evidence verification loop with immutable provenance, staged checks, bounded retrieval, and reproducible evaluation.
CampusTrace собирает визуальный профиль университета с источником, лицензией и уровнем уверенности для каждого кадра.
Translation pending
DataPython · CSSGitHub @sNibaAdekaOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
CampusTrace — verified visual profile of any university in ~30 s (LOCUS Hackathon 2026, Case 01)
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Проверяемый визуальный профиль университета. Вводите название вуза — получаете фотографии кампуса, общежитий, аудиторий, библиотек, спортивных объектов, лабораторий и студенческой жизни, где у каждого кадра есть кликабельный первоисточник, лицензия, автор, даты и явно названное основание, почему он связан именно с этим вузом. Там, где материалов нет, сервис так и говорит — и отдельно различает «проверено, ничего не нашлось» и «источник не ответил, проверить не успели». Кейс: LOCUS Startup Hackathon 2026, Кейс 01 «Визуальный профиль университета».Read the full README ↗
Проверяемый визуальный профиль университета. Вводите название вуза — получаете фотографии кампуса, общежитий, аудиторий, библиотек, спортивных объектов, лабораторий и студенческой жизни, где у каждого кадра есть кликабельный первоисточник, лицензия, автор, даты и явно названное основание, почему он связан именно с этим вузом. Там, где материалов нет, сервис так и говорит — и отдельно различает «проверено, ничего не нашлось» и «источник не ответил, проверить не успели».
Кейс: LOCUS Startup Hackathon 2026, Кейс 01 «Визуальный профиль университета».
Проблема. Абитуриент выбирает вуз по картинке, а картинки в интернете — чужие, стоковые, устаревшие или вовсе с другого кампуса. Поиск по картинкам не говорит, кто автор, можно ли это показывать и снято ли это вообще здесь.
Наше решение — не «ещё одна галерея», а проверяемый паспорт. Ключевая идея: разделять находку и доказательство. CampusTrace никогда не утверждает больше, чем может показать:
| Что мы говорим | Что за этим стоит |
|---|---|
| «Вероятно» | Файл найден в тематическом источнике вуза, лицензия есть, но конкретный корпус не доказан |
| «Городской контекст» | Это кадр города, и мы прямо говорим, что это не кампус |
| «Требует проверки» | Источник и лицензия есть, но что изображено — не удалось определить ни по тексту, ни по изображению |
| «Не проверено: источник был недоступен» | Раздел пуст не потому, что материалов нет, а потому что источник упал на середине |
Слова «подтверждено» в значении «доказано место съёмки» в интерфейсе нет вообще — потому что доказать это по открытым данным мы не можем, а притворяться нельзя.
Три вещи, которых обычно нет у аналогов:
qwen/qwen3.8-27b или xAI Grok) смотрит на пиксели и называет тип места. При расхождении слоёв статус понижается.Требуется Python 3.11+ и интернет.
cd campustrace-mvp
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# ОБЯЗАТЕЛЬНО: в .env замените [email protected] на реальный контакт.
# Nominatim и Wikimedia требуют, чтобы клиент себя идентифицировал.
set -a; source .env; set +a
uvicorn app.main:app --host 127.0.0.1 --port 8765
Откройте http://127.0.0.1:8765. API-документация: /docs. Health-check: /api/health.
Все ключи опциональны. Без единого ключа работает: поиск вуза, сбор фотографий, лицензии, дедупликация, категории по тексту, карта, сравнение. Ключи только добавляют слои:
| Переменная | Что включает | Что будет без неё |
|---|---|---|
GROQ_API_KEY | Визуальную проверку фото (qwen/qwen3.8-27b, мозаика 2×2) и сводку по отзывам студентов | Категории только по структурированным данным и тексту; профиль прямо сообщает, что визуальная проверка не выполнена |
GROK_API_KEY | Альтернативный vision-провайдер (xAI); если задан, используется вместо Groq | — |
CEREBRAS_API_KEY | Запасной текстовый ИИ (бесплатно ~1 млн токенов/сутки): включается, когда у Groq кончился минутный или суточный лимит (~200 тыс. токенов на модель) | Когда лимит Groq исчерпан, ИИ-функции честно пишут «дневной лимит исчерпан» |
TAVILY_API_KEY | Быстрый веб-поиск отзывов и статей со сниппетами (бесплатно, без карты) | Поиск идёт через browser_search Groq — ~35 тыс. токенов за сессию |
MAPBOX_TOKEN | Второй геокодер + альтернативные изохроны | Карта работает (MapLibre + OpenFreeMap, без ключа); координата помечается «только один источник» |
BRAVE_API_KEY | Поиск обсуждений на форумах и офиц. страниц | Работает только Reddit через PullPush |
FLICKR_API_KEY | Дополнительные фото под CC-лицензиями | Только Wikimedia Commons |
REDDIT_CLIENT_ID + REDDIT_CLIENT_SECRET | Официальный Reddit API (бесплатное приложение типа script) для блока «Голоса студентов»; без него используется архив PullPush, который часто отвечает 429 | Меньше обсуждений; блок честно пишет, что найдено недостаточно |
OPENROUTESERVICE_API_KEY | Изохроны пешей доступности | Зона не рисуется (и не подменяется кругом) |
YOUTUBE_API_KEY | Видео с проверенного офиц. канала | Блок видео пуст |
Базовая карта — MapLibre GL + OpenFreeMap, без API-ключа. Это сознательный выбор: демонстрация не должна падать из-за чужой квоты во время защиты.
Сценарий рассчитан на то, что вы введёте вуз, которого нет в нашем демо-видео.
Шаг 1. Любой незнакомый вуз. Введите на главной, например, Universität Heidelberg, Kyoto University или МГУ. Поиск понимает кириллицу, короткие аббревиатуры (MIT, NYU) и локальные написания. Вуз выбирается вручную — первый результат не подставляется автоматически, потому что одноимённых организаций много.
Шаг 2. Галерея — первое, что вы увидите. Фильтры слева переключают 8 разделов кейса + «Требует проверки». Под каждым фото сразу видны автор, лицензия (ссылкой на её условия) и ссылка на источник.
Шаг 3. Нажмите «Проверить источник» на любом кадре. Откроется паспорт: категория, статус, откуда найден файл, автор, лицензия, дата загрузки и дата съёмки (это разные даты), SHA-1, перцептивный хеш, вердикт визуального классификатора и — главное — согласуются ли две независимые проверки.
Шаг 4. Проверьте честность пустот. Прокрутите до «Полнота источников». Ноль в разделе подписан либо «Проверено — материалов не найдено», либо «Не проверено: источник был недоступен». Это разные утверждения, и мы их не смешиваем.
Шаг 5. Проверьте, что мы не выдаём город за кампус. Откройте фильтр «Город» — эти кадры имеют статус «Городской контекст» и явную пометку, что связь с кампусом не утверждается.
Шаг 6. Два вуза одного города. Соберите Nazarbayev University, затем в блоке «Сравнить покрытие» найдите Astana IT University. Общая городская фотография корректно принадлежит обоим профилям (составной ключ (ror_id, asset_id) в БД). В таблице сравниваются все 8 разделов, и ноль из-за упавшего источника подписан отдельно.
Шаг 7. Отказ источника. Нажмите «Обновить данные» несколько раз подряд. Wikimedia ответит 429; профиль станет partial, предыдущая сохранённая версия останется доступной, и сервис скажет, что именно не успел проверить.
Backend: Python 3.11, FastAPI, httpx (async), SQLite, Pillow (перцептивные хеши). Frontend: ванильный JS без фреймворка и сборщика, MapLibre GL JS, COBE (глобус). Никакого npm-бандла — страница отдаётся статикой. Хранилище: SQLite (профили, материалы, кэш источников, журнал событий).
Поиск по названию
└─ ROR v2 + локальные синонимы/транслитерация ──> institution_id (ROR ID)
│ (идентичность: город, страна, алиасы, домен, Wikidata QID)
▼
Сбор кандидатов (параллельно, у каждого адаптера свой таймаут и учёт отказа)
├─ Wikidata search (haswbstatement): здания вуза (P361/P137/P127) с фото и типом P31 ← категория по типу; SPARQL — запасной путь
├─ Commons depicts: haswbstatement:P180=<вуз|здания> ← «на фото изображено»
├─ Commons geosearch у точки кампуса (P625) — только вместе с названием вуза/здания
├─ Wikimedia Commons: категория вуза + подкатегории (без коллекций/карт/сканов, ≤6 файлов из одной)
├─ Commons search: визуальные запросы по названию
├─ Wikipedia (en/ru): изображения статьи, если Commons беден
├─ Wikidata P18: официальное изображение
├─ Flickr: поиск по CC-лицензиям (опционально)
└─ Отдельно: поиск по городу — это ДРУГОЕ утверждение
▼
Фильтр лицензии ──> только открытые лицензии (CC0 / CC BY / CC BY-SA / PD / GFDL); остальное отбрасывается и считается
▼
Слой 1: текстовая классификация
(название + описание + провенанс подкатегории; стоп-слова событий;
по умолчанию НЕ «кампус», а «unknown»)
▼
Дедупликация: SHA-1 (точные копии) + dhash/Hamming≤5 (визуально похожие)
▼
Слой 2: визуальная классификация (Groq qwen3.8 или xAI Grok; миниатюры 330 px
уходят base64 мозаикой 2×2 — 4 фото за запрос; при 429 проверка
останавливается и профиль сообщает, сколько кадров не проверено)
├─ «не место» ──────────> кандидат снимается с публикации
├─ «город, не кампус» ──> переносится в раздел «Город»
├─ совпало с текстом ───> основание добавляется, статус НЕ повышается
└─ РАСХОЖДЕНИЕ ─────────> статус понижается до «не подтверждено»
▼
evidence[] + evidence_level (число независимых подтверждений) у каждого кадра
▼
Снимок профиля: complete | partial + состояние каждой из 8 категорий
(параллельно /preview отдаёт первые лицензированные кадры из Wikidata за ~3 с)
▼
SQLite: profiles + assets(ror_id, id) + source_events + research_cache
▼
Одностраничный UI: галерея → описание по источникам → полнота → карта → сравнение
Файлы:
| Файл | Ответственность |
|---|---|
app/main.py | HTTP API, бюджет времени, объединение одинаковых запросов, кэш |
app/pipeline.py | Сбор кандидатов, текстовая классификация, дедупликация, описание |
app/vision.py | Независимая визуальная классификация и правила согласования слоёв |
app/integrations.py | Адаптеры внешних API: таймаут, кэш, rate limit, учёт отказов |
app/atlas.py | География: перекрёстная проверка координат, границы OSM, изохроны |
app/discovery.py | Поиск и ранжирование организаций, транслитерация, пагинация |
app/db.py | Схема SQLite и миграция |
app/voices.py | Поиск публичных обсуждений + сводка со ссылками |
scripts/benchmark.py | Замер реального времени ожидания на выборке вузов |
Ключевые архитектурные решения:
campus был категорией по умолчанию, поэтому наградные знаки, концерт, заседание сената и выпускной попадали в «кампус». Теперь по умолчанию — unknown, а список стоп-слов событий (en/ru/et/kk) запрещает общую догадку «кампус» для кадра, в названии которого описано событие, а не место.thumb.wikimedia.org, а проверка хоста этого не знала), и 0 дублей означало «не проверяли». Теперь пользователю показывается, сколько кандидатов реально захешировано.asset_id.| Источник | Назначение | Лицензия/условия |
|---|---|---|
| ROR API v2 | Идентичность организации, алиасы, город, домен | CC0, без ключа |
| Wikidata | Commons-категория P373, координата P625, студенты P2196, канал P2397, изображения P18/P8517/P3451/P5775 | CC0, без ключа |
Wikidata Search API (haswbstatement) + Query Service как запасной путь | Здания и объекты вуза с фото и типом P31; факты вуза (тип, объединения, руководитель, девиз, адрес, сотрудники, эндаумент) | CC0, без ключа |
| Wikimedia Commons | Файлы, метаданные прав, структурированные данные depicts (P180), геопоиск | Лицензия у каждого файла своя; требуется корректный User-Agent |
| Openverse API | Открытые фото (в основном Flickr) с автором и ссылкой на первоисточник; берутся только лицензии CC BY, CC BY-SA, CC0, PDM; название вуза обязано быть в названии или тегах, категория — только из названия | Без ключа: 20 запросов/мин и 200/сутки; ответы кэшируются на 24 ч |
| Wikipedia REST summary | Блок «Об университете»: краткое описание (ru, иначе en) с атрибуцией CC BY-SA | Без ключа |
| GDELT DOC 2.0 | «В новостях»: заголовки за 12 месяцев, где вуз назван в заголовке; только ссылки | Без ключа; не чаще 1 запроса в 5 с, кэш 6 ч; часто отвечает 429 — тогда блок скрыт |
| Официальные виджеты YouTube / Instagram / TikTok |
| Модель / сервис | Где используется | Что именно делает | Границы |
|---|---|---|---|
Groq vision, qwen/qwen3.8-27b (по умолчанию) или Grok vision (xAI) при наличии GROK_API_KEY | app/vision.py | Смотрит на изображение и называет тип места из фиксированного словаря (кампус снаружи, общежитие, аудитория, библиотека, лаборатория, спорт, студенческая жизнь, город-не-кампус, не относится) | Никогда не определяет, какой это вуз — только вид места. Ответ вне словаря отбрасывается. Низкая уверенность не даёт права ни публиковать, ни удалять |
Groq, openai/gpt-oss-20b (ИИ-разбор подписей) | app/triage.py | Один пакетный запрос на профиль: читает только заголовок и описание файла и отвечает на два закрытых вопроса — о чём текст (этот вуз / другая организация / человек или событие / город / неясно) и какой тип места назван. Работает на любом языке (эстонский «ühiselamu» → общежитие, японский 図書館 → библиотека) | Не считается независимым подтверждением — модель читает те же слова, что и человек, только лучше. Может заполнить unknown и понизить подпись о другой организации или человеке; не повышает достоверность; структурированное подтверждение (Wikidata, depicts) она не отменяет. Словарь ответов закрыт, ответ вне словаря отбрасывается, вердикты кэшируются |
Groq, openai/gpt-oss-20b | app/voices.py | Нейтральная сводка только по переданным фрагментам публичных постов | Каждый вывод обязан ссылаться на номера источников; вывод без ссылки отбрасывается |
| Groq compound-mini (web search) | app/voices.py | Поиск обсуждений | Принимаются только URL из результатов инструмента; ссылки, придуманные моделью в тексте, игнорируются (есть регрессионный тест) |
Лимит vision честно ограничен. Бесплатный Groq даёт ~7000 входных токенов в минуту, а одно изображение стоит ~1600–1800 токенов независимо от размера. Поэтому 4 миниатюры склеиваются в одну мозаику 2×2 (≈1600 токенов на четыре кадра) и получают четыре независимых вердикта. Вердикты кэшируются на 30 дней по asset_id; при исчерпании квоты профиль пишет «проверено N из M».
Ни одна модель не генерирует изображения, не описывает кампус «своими словами» и не выставляет оценок вузу.
| Источник | Назначение |
|---|---|
| Mapbox | Второй геокодер для перекрёстной проверки координаты; альтернативные изохроны |
| openrouteservice | Изохроны пешей/вело доступности |
| Flickr | Фото с CC-лицензиями (только id 4,5,9,11,12) |
| Brave Search | Ссылки на официальные страницы и форумы |
| YouTube Data API | Видео проверенного официального канала |
Ключи хранятся только в переменных окружения. .env в .gitignore. В репозитории нет ни одного секрета — проверьте git log -p | grep -i "api_key".
| Компонент | Версия | Происхождение | Лицензия | Как используется |
|---|---|---|---|---|
| FastAPI | 0.115.x | Sebastián Ramírez / open source | MIT | HTTP-слой, автодокументация |
| uvicorn | 0.30.x | encode | BSD-3 | ASGI-сервер |
| httpx | 0.27.x | encode | BSD-3 | Асинхронные HTTP-запросы к источникам |
| Pillow | 10.4.x | Python Imaging Library fork | HPND/MIT-подобная | Перцептивный хеш (dhash) миниатюр |
| MapLibre GL JS | 5.8.0 | MapLibre (форк Mapbox GL JS v1) | BSD-3 | Интерактивная карта; поставляется локально в static/vendor/ |
| COBE | bundled | shuding/cobe | MIT | WebGL-глобус на главной; static/vendor/cobe.bundle.mjs |
| box-loader | — | CSS-анимация загрузки, написана для проекта | — | static/box-loader.css |
| SQLite | встроен в Python | D. R. Hipp | Public domain | Хранилище |
Что написано нами: весь код в app/ и scripts/, весь static/app.js, static/atlas.js, static/globe.js, static/style.css, схема БД, правила классификации и согласования слоёв, промпты.
Использование AI при разработке: код писался совместно с ассистентами (Claude, GPT). Дизайн-направление главной страницы проработано с помощью design-taste-frontend, промпт сохранён рядом с проектом в CAMPUS_TRACE_ASTRA_PROMPT.md. Все архитектурные решения, правила достоверности и формулировки статусов проверены и приняты командой вручную; каждое изменение сопровождается тестом.
Данные OSM-слоёв (data/atlas_nu.json, data/atlas_upenn.json) — снимок открытых данных OpenStreetMap от 16 сентября 2026, каждый объект содержит ссылку на исходный OSM-объект, атрибуция ODbL. Это подтверждает наличие объекта в OSM, а не его официальный статус.
Жюри оценивает полное время: от ввода названия до полезного результата, лимит 30 секунд. Поэтому:
PROFILE_BUDGET_SECONDS (по умолчанию 24 с) плюс 3 с страховочного таймаута; остальное оставлено на поиск, передачу и отрисовку./api/profiles/{id}/preview (изображения Wikidata вуза и его зданий, один запрос к Commons) и показываются прямо на экране загрузки.timings (по стадиям), elapsed_ms и time_to_first_asset_ms.# 1) сервер в отдельном терминале
uvicorn app.main:app --port 8765
# 2) холодный прогон (единственный честный)
python3 scripts/benchmark.py --base http://127.0.0.1:8765 --refresh \
--out docs/benchmark.json --markdown docs/benchmark.md
Выборка по умолчанию — 15 вузов: Казахстан, Европа, Северная Америка, Азия, Африка, Южная Америка; большие и малые; латиница и кириллица; короткая аббревиатура (MIT); два вуза одного города. Скрипт считает p50/p95 для submit → первое фото и submit → полный профиль, число неполных профилей и число превышений 30 секунд.
Стенд: https://campustraceapp.vercel.app (Vercel, функция Python), запросы из Казахстана, --refresh, вузы по очереди, версия пайплайна 0.8.0, бюджет сервера 21 с. Ключей ИИ на проде в момент замера не было, поэтому столбец Vision = 0. 14 из 15 сборок прошли без единого попадания в кэш источников.
| Запрос | Определён как | Материалов | Разделов | submit→первое фото | submit→полный профиль | Из кэша источников | Vision | Статус |
|---|---|---|---|---|---|---|---|---|
| Nazarbayev University | Nazarbayev University | 20 | 4 | 8.9 с | 8.9 с | 0/16 | 0 | complete |
| Astana IT University | Astana IT University | 14 | 5 | 6.9 с | 6.9 с | 0/8 | 0 | complete |
| Al-Farabi Kazakh National University | Al-Farabi Kazakh National University | 12 | 2 | 1.5 с | 9.7 с | 0/16 | 0 | complete |
| University of Tartu | University of Tartu | 40 | 3 | 1.3 с | 15.8 с | 0/20 | 0 | partial |
| МГУ | Lomonosov Moscow State University | 83 | 5 | 2.4 с | 16.7 с | 1/19 | 0 | complete |
| MIT | Massachusetts Institute of Technology | 63 | 7 | 2.9 с | 13.0 с | 0/17 | 0 | complete |
| University of Pennsylvania | University of Pennsylvania | 60 | 6 | 2.3 с | 13.7 с | 0/18 | 0 | complete |
| Universität Heidelberg | Heidelberg University |
Замер: 2026-09-19 10:10:44+0500. Вузов: 15, с материалами: 15, пустых: 0, не найдено в реестре: 0, ошибок: 0, недоступен реестр: 0, неполных профилей: 4. Сборок без единого попадания в кэш источников: 14. Стенд: https://campustraceapp.vercel.app.
submit → первое фото: p50 2180 мс, p95 8936 мс. submit → полный профиль: p50 12979 мс, p95 16693 мс, максимум 16935 мс, превысили 30 с: 0.
Итог: у всех 15 вузов есть фото с открытой лицензией (в среднем 42,8), ошибок нет, ни один вуз не превысил 30 секунд; первое фото — p50 2,2 с; полный профиль — p50 13,0 с, максимум 16,9 с. Сырые строки — docs/benchmark-prod.json.
Стенд: MacBook, домашняя сеть в Казахстане, локальный сервер с пустой SQLite (кэш источников холодный), --refresh, вузы по очереди. Код — коммит 36790f9, запущенный с REQUEST_TIMEOUT_SECONDS=6 (в самом коммите значение по умолчанию уже 8 с — почему, см. ниже; воспроизвести: REQUEST_TIMEOUT_SECONDS=6 DATABASE_PATH=/tmp/cold.sqlite3 uvicorn app.main:app --port 8766). «Из кэша источников» — сколько ответов внешних API пришло из кэша: это поиск и предпросмотр того же вуза, запущенные браузером одновременно со сборкой. «submit→первое фото» — момент, когда ответил /preview с кадрами (так же, как это видит пользователь); если предпросмотр пуст, первое фото приходит вместе с полным профилем. Vision — сколько кадров проверено по изображению за эту сборку (бесплатный лимит Groq — примерно 4 мозаики в минуту).
| Запрос | Определён как | Материалов | Разделов | submit→первое фото | submit→полный профиль | Из кэша источников | Vision | Статус |
|---|---|---|---|---|---|---|---|---|
| Nazarbayev University | Nazarbayev University | 21 | 4 | 14.1 с | 14.1 с | 1/9 | 8 | complete |
| Astana IT University | Astana IT University | 14 | 5 | 9.2 с | 9.2 с | 1/7 | 0 | complete |
| Al-Farabi Kazakh National University | Al-Farabi Kazakh National University | 10 | 2 | 23.7 с | 23.7 с | 0/11 | 4 | partial |
| University of Tartu | University of Tartu | 38 | 4 | 5.6 с | 26.5 с | 0/13 | 4 | complete |
| МГУ | Lomonosov Moscow State University | 0 | 0 | 4.3 с | 19.1 с | 2/12 | 0 | partial |
| MIT | Massachusetts Institute of Technology | 51 | 6 | 4.9 с | 24.6 с | 1/12 | 4 | complete |
| University of Pennsylvania | University of Pennsylvania | 36 | 3 | 2.8 с | 22.5 с | 1/12 | 4 | complete |
| Universität Heidelberg | Heidelberg University |
Замер: 2026-09-18 21:14:38+0500. Вузов: 15, с материалами: 14, пустых: 1, не найдено в реестре: 0, ошибок: 0, недоступен реестр: 0, неполных профилей: 3. Сборок без единого попадания в кэш источников: 5. Стенд: http://127.0.0.1:8766.
submit → первое фото: p50 4648 мс, p95 14093 мс. submit → полный профиль: p50 22208 мс, p95 24621 мс, максимум 26497 мс, превысили 30 с: 0.
Что видно из замера — честно:
partial: часть этапов была пропущена по бюджету времени, и профиль прямо это говорит.partial. После этого таймаут запроса вернули к 8 с; при повторной сборке МГУ даёт 60 материалов, но в таблицу это не вписано, потому что это уже не холодный замер.docs/benchmark.json.Соблазн очевиден: в Instagram и Threads больше «живых» фото кампуса, чем в Wikimedia Commons. Мы сознательно не берём оттуда фотографии, и вот почему:
Что мы делаем вместо этого. Публичные обсуждения (Reddit, студенческие форумы, студенческие издания) используются как текстовое свидетельство: заголовок, короткая цитата, ссылка на первоисточник и нейтральная сводка. Фотографии оттуда не встраиваются. Для изображений источники остаются лицензионно чистыми: Wikimedia Commons и Flickr с фильтром по CC-лицензиям.
Это сознательный размен recall на правовую чистоту. Если вуз захочет более богатую галерею — «карта пробелов» в профиле показывает, каких именно материалов не хватает, и вуз может передать их под открытой лицензией.
Заполните перед сабмитом. Кейс требует явного указания ролей; выдумывать их за команду нельзя.
| Участник | Роль | Зона ответственности |
|---|---|---|
<ФИО> | Backend / пайплайн достоверности | app/pipeline.py, app/vision.py, правила согласования слоёв |
<ФИО> | Frontend / UX | static/, дизайн-система, доступность |
<ФИО> | Данные и интеграции | app/integrations.py, лицензии, OSM-слои |
<ФИО> | Продукт, презентация, демо | README, слайды, видео, коммуникация с жюри |
Контакт для жюри: <email>. Промокод сабмита на aistartify.com: LOCUSCASE1.
В репозитории лежат готовые конфигурации; выберите одну.
vercel.json описывает один сервис FastAPI с точкой входа api/index.py (она создаёт схему SQLite при импорте: на Vercel файловая система только для чтения, кроме /tmp). Деплой: vercel link --yes --project campustrace, затем vercel deploy --prod --yes; репозиторий подключён, поэтому каждый push в main выкатывается сам. Переменные окружения проекта: CAMPUS_TRACE_USER_AGENT, DATABASE_PATH=/tmp/campustrace.sqlite3; GROQ_API_KEY добавляется владельцем вручную (vercel env add GROQ_API_KEY production).
Ограничения этого хоста, о которых нужно знать: кэш SQLite живёт в /tmp каждого инстанса и теряется при холодном старте; внутрипроцессный лимитер Commons и объединение одинаковых запросов действуют в пределах одного инстанса; Openverse отвечает 401 анонимным запросам из дата-центров — фото оттуда включаются после регистрации бесплатного клиента (OPENVERSE_CLIENT_ID/SECRET).
render.yaml подхватится автоматически.CAMPUS_TRACE_USER_AGENT с реальным контактом. Остальные ключи — по желанию.https://<app>.onrender.com/api/health.На бесплатном плане инстанс засыпает при простое. Откройте ссылку за 5 минут до защиты, чтобы жюри не попало на холодный старт поверх 30-секундного бюджета.
fly launch --no-deploy # подхватит fly.toml
fly volumes create campustrace_data --size 1
fly secrets set CAMPUS_TRACE_USER_AGENT="CampusTrace/0.6 (contact: [email protected])"
fly secrets set GROK_API_KEY="..." # опционально
fly deploy
New Project → Deploy from GitHub repo. railway.json укажет на Dockerfile. Переменные задаются во вкладке Variables.
docker build -t campustrace .
docker run -p 8000:8000 -v campustrace-data:/data \
-e CAMPUS_TRACE_USER_AGENT="CampusTrace/0.6 (contact: [email protected])" \
campustrace
Чек-лист после деплоя:
/api/health отвечает {"ok": true}/api/integrations показывает, какие ключи реально подключеныpython3 -m unittest discover -s tests -v # 88 тестов
python3 -m compileall -q app
node --check static/app.js
Тесты закрывают именно те свойства, которые легко потерять при доработке:
thumb.wikimedia.org (регрессия на реальный дефект);cmcontinue;MIT) находится, но не срабатывает внутри слова Summit;P2196 (число студентов) может быть без года или без первичного источника; мы показываем год и ссылку и предупреждаем об этом.| Запрещено кейсом | Как это исключено у нас |
|---|---|
| Выдавать стоковые/чужие фото за конкретный кампус | Кадр без лицензии не публикуется; городские кадры имеют отдельный статус и прямую пометку; фото из соцсетей не встраиваются вообще |
| Скрывать ручной подбор под видом автопоиска | Ручных подборок фото нет. Единственные ручные данные — OSM-слои трёх вузов и локальные синонимы поиска; и то и другое описано выше |
| Подделывать источники, скорость, достоверность | Клик по любому фото открывает первоисточник; таблица скорости — живой замер с датой, стендом и долей ответов из кэша; «показатель достоверности» — не процент, а уровень (высокая / средняя / низкая), вычисляемый по числу независимых видов подтверждений; понижается при расхождении текста и изображения, неопределённой категории и городском контексте, никогда не повышается; каждое подтверждение раскрывается ссылкой |
| Нарушать лицензии источников | Жёсткий фильтр лицензии; атрибуция на карточке и в паспорте; соблюдение лимита Nominatim и требований User-Agent; ODbL-атрибуция карт |
| Публиковать секретные ключи | .env в .gitignore; все ключи только через переменные окружения; в коде нет ни одного литерала ключа |
| Требование кейса | Состояние |
|---|---|
| 1. Поиск по названию + разрешение неоднозначности | Есть: ROR + локальные синонимы, транслитерация, пагинация, выбор вручную |
| 2. Кампус, общежитие, аудитории, библиотека, город | Есть; полнота ограничена доступностью открытых материалов, пробелы показаны явно |
| 3. Автоматическая классификация | Есть: два независимых слоя — текстовый и визуальный |
| 4. Удаление одинаковых, похожих и нерелевантных | Есть: SHA-1 + dhash + снятие нерелевантных визуальным классификатором |
| 5. Проверка связи фото с вузом/объектом/городом | Есть на уровне оснований и статусов; геодоказательства не заявляются |
| 6. Кликабельный источник и дата | Есть: источник, автор, лицензия, дата загрузки и дата съёмки раздельно |
| 7. Краткое описание кампуса из источников | Есть: собирается только из найденных объектов, со ссылками |
| 8. Фильтры общежитие/спорт/лаборатории/студенческая жизнь | Есть, плюс отдельный раздел «Требует проверки» |
Ограничение — 3 минуты. Сценарий (показывать экран, говорить коротко):
| Время | Что показать | Что сказать |
|---|---|---|
| 0:00–0:20 | Главная, глобус | «Абитуриент выбирает вуз по картинкам, но не знает, чьи это картинки и тот ли это кампус. CampusTrace показывает только то, что может подтвердить источником» |
| 0:20–0:50 | Ввод незнакомого вуза, сборка профиля | «Поиск понимает кириллицу и аббревиатуры. Организация выбирается вручную — одноимённых много. Полная сборка укладывается в бюджет 30 секунд» |
| 0:50–1:25 | Галерея, фильтры, карточка с автором и лицензией | «Восемь разделов кейса. Автор и лицензия — сразу под кадром, а не спрятаны» |
| 1:25–2:00 | «Проверить источник» → паспорт | «Вот главное: две независимые проверки — текст источника и визуальный классификатор. Если они расходятся, мы понижаем уверенность, а не выбираем удобный вариант» |
| 2:00–2:25 | Полнота источников: пустой раздел и раздел с упавшим источником | «Ноль ноль рознь. "Проверено, не нашли" и "источник не ответил" — разные утверждения, и мы их различаем» |
| 2:25–2:45 | Фильтр «Город» + сравнение двух вузов одного города | «Городской кадр никогда не выдаётся за кампус. Сравниваются все восемь разделов, а ноль из-за отказа источника подписан отдельно» |
| 2:45–3:00 | Финал | «Мы не доказываем место съёмки — по открытым данным это невозможно. Мы показываем, что именно проверено, а что нет. Это и есть продукт» |
Технически: экран 1920×1080, запись без звука окружения, голос поверх, показывать реальный развёрнутый сайт (не localhost), не ускорять таймер сборки. Вуз в видео должен отличаться от того, который вы покажете на защите.
GET /api/health
GET /api/integrations
GET /api/search/suggest?q=НУ&page=1
GET /api/institutions?q=Nazarbayev%20University
GET /api/profiles/{ror_id} # ?refresh=true — принудительная пересборка
GET /api/profiles/{ror_id}/preview # первые лицензированные кадры из Wikidata за ~3 с
GET /api/profiles/{ror_id}/ask?q=... # ответ только по источникам профиля, с номерами ссылок
GET /api/profiles/{ror_id}/news # заголовки GDELT, где назван вуз
GET /api/profiles/{ror_id}/extras # кэшируется на час
GET /api/profiles/{ror_id}/student-voices
GET /api/assets/{asset_id}?ror_id={ror_id}
GET /api/atlas/{ror_id}
GET /api/atlas/{ror_id}/isochrone?mode=walking&minutes=15
GET /api/compare?left={ror_id}&right={ror_id}
Полезные поля ответа профиля: evidence и evidence_level у каждого кадра, rejected_license_count, institution.buildings, profile_status, category_status, unclassified_count, vision, rejected_by_vision, timings, time_to_first_asset_ms, duplicate_count, license_eligible_count, warnings, source_events, from_cache.
| «Соцсети вуза»: аккаунты из Wikidata (P2003, P2397, P7085, P2013, P3185…), плеер канала и публичные посты, найденные поиском |
| Контент остаётся на платформе; в проверку и категории фото не входит |
| Simple Icons | Логотипы платформ в ленте источников и карточках отзывов | CC0; static/brand-icons.js |
| Wikipedia API | Изображения статьи вуза, когда Commons беден | CC BY-SA, без ключа |
| OpenAlex | Ряд публикаций по годам | CC0, без ключа |
| Open-Meteo | Погода города | Без ключа, некоммерческое использование |
| Nominatim / OSM | Кандидатная точка карты | ODbL; максимум 1 запрос/с, обязателен контакт в User-Agent |
| OpenFreeMap | Базовая карта | Данные OSM, ODbL, без ключа |
| PullPush | Индекс публичных постов Reddit | Без ключа, выдача заведомо неполная |
| 49 |
| 3 |
| 2.1 с |
| 13.4 с |
| 0/17 |
| 0 |
| complete |
| Politecnico di Milano | Politecnico di Milano | 41 | 6 | 1.9 с | 10.7 с | 0/15 | 0 | complete |
| Université de Montréal | Université de Montréal | 55 | 4 | 1.9 с | 13.7 с | 0/17 | 0 | complete |
| National University of Singapore | National University of Singapore | 32 | 5 | 11.3 с | 11.3 с | 0/16 | 0 | complete |
| Universidad de Buenos Aires | Universidad de Buenos Aires | 28 | 3 | 1.9 с | 8.4 с | 0/14 | 0 | partial |
| Uppsala universitet | Uppsala University | 40 | 5 | 2.2 с | 15.8 с | 0/20 | 0 | complete |
| University of Cape Town | University of Cape Town | 70 | 6 | 3.0 с | 16.9 с | 0/19 | 0 | partial |
| Kyoto University | Kyoto University | 35 | 3 | 2.2 с | 9.8 с | 0/15 | 0 | partial |
| 38 |
| 3 |
| 5.0 с |
| 24.3 с |
| 1/11 |
| 4 |
| partial |
| Politecnico di Milano | Politecnico di Milano | 30 | 6 | 3.5 с | 16.1 с | 0/10 | 0 | complete |
| Université de Montréal | Université de Montréal | 49 | 4 | 4.6 с | 20.9 с | 1/13 | 4 | complete |
| National University of Singapore | National University of Singapore | 28 | 4 | 3.7 с | 23.9 с | 1/12 | 2 | partial |
| Universidad de Buenos Aires | Universidad de Buenos Aires | 28 | 3 | 4.4 с | 18.7 с | 0/9 | 0 | complete |
| Uppsala universitet | Uppsala University | 39 | 4 | 3.8 с | 23.7 с | 1/14 | 4 | complete |
| University of Cape Town | University of Cape Town | 55 | 6 | 5.5 с | 22.2 с | 0/12 | 0 | complete |
| Kyoto University | Kyoto University | 26 | 4 | 4.1 с | 12.7 с | 1/9 | 0 | complete |