- PVSM.RU - https://www.pvsm.ru -

Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!)

Представляю вам третью часть моего, немного затянувшегося, рассказа.
Получив положительную оценку первой [1] и второй [2] частей, я не хотел заставлять читателей ждать слишком долго, но жизнь и реальность вносит свои коррективы.

image

За 2 дня до начала хакатона Radio Canada прислали письмо в котором сообщили, что нашей команде выделен ментор — Patrick Lévesque [3]. Для меня это было несколько неожиданно, хотя, наверное, об этом что-то было написано на сайте и было сказано во время предварительной презентации. Видимо, я пропустил это мимо ушей. Так или иначе, это добавляло уверенности, что нам будет у кого попросить помощи в случае возникновения каких-либо вопросов.

Вторник, среда, четверг рабочей недели пролетели стремительно как всегда. Предвкушение приключения нарастало. И к пятнице достигло своего пика. Жена была предупреждена что на выходные, начиная с пятницы, я выпадаю из жизни и исчезну с радаров.

Итак после работы в пятницу я в боевом настроении направился снова в Дом Радио.

День первый. Пятница вечер. Время разбрасывать камни.

Расписание на пятницу

Оригинал вы можете посмотреть тут [4]. Но спрашивается: кому оно надо? Думаю оно и на русском мало кому интересно. Но с другой стороны, кто-то же мою историю читает и плюсует, вдруг именно эти мелочи и есть моя изюминка?

Пятница, 23 марта
17:00 Прибытие и регистрация (Столовая Radio-Canada закрывается в 19:00).
18:00 Приветствие
18:15 Представление менторов, помощников, команд и подсказки
19:00 Мастер-класс по подготовке презентации прототипов с Matthieu Dugal [5] и Chloe Sondervorst [6]
19:30 Перерыв
19:50 На выбор:
1. Мастер-класс
Презентация сервисов Microsoft
Презентация сервисов Radio-Canada
2. Начало работы команд
22:00 Окончание первого дня

Упоминание о столовой было очень уместно. Во-первых, оно напомнило мне о том что я буду после трудового дня с пустым желудком, и что-нибудь перекусить перед началом этого действа очень даже не помешает. Звездочка даже предлагала принести мне поесть, поскольку она пришла на хакатон не из колледжа, как обычно, а из дома, вполне сытая и полная сил. Но я как всегда перехитрил сам себя. Думал что у нас будет время, желание и возможность заказать пиццу. Но в реальности оказалось, что пришел на голодный желудок в нашей команде только я один. Поэтому я таки сходил в столовую и взял себе какой-то гамбургер, сразу после регистрации.

Я прибыл на место примерно в 17:45.

Перед входом в зал где проводилось мероприятие, необходимо было зарегистрироваться и получить футболку участника хакатона и небольшой сувенир в виде бутылки для воды. Участникам выдавали белые футболки, а организаторам и менторам черные. Моя попытка разжиться черной маечкой не увенчалась успехом. Ну да ладно, белая тоже ничего.
Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 2

Когда я зашел в зал, Звездочка и Платон уже облюбовали и заняли один из столов предназначенных для команд. Занимались подключением ноутов к питанию и интернету.

Я тоже приготовил свой лэптоп, а потом использовал пару минут оставшихся до начала презентации, чтобы сходить в столовую и перекусить.

К тому времени как я вернулся в зал, Меркурий уже тоже присоединился к команде. Фаэтона не было и когда он появится было неизвестно.

С небольшим отставанием от расписания, примерно в 18:10, мы прослушали приветственное слово от Maxime St-Pierre [7]. Он снова повторил что Radio Canada возлагает большие надежды на это мероприятие, поскольку прошлогодний опыт принес хорошие практические плоды. Так же он упомянул, что тема хакатона: ИИ, очень актуальна в наши дни. И подтверждение этому, например, случившийся буквально на этой неделе скандал с Cambridge Analytica [8].

Вы можете просмотреть запись трансляции этого вечера полностью:

Максим не стал затягивать свое выступление и передал эстафету ведущему вечера Matthieu Dugal [5].

Мэтью, насколько я понял, очень известный телевизионный ведущий на канадском телевидении. Он ведет несколько программ посвященных технологиям. Это сыграло положительную роль в организации как этого вечера, так и всего хакатона. С одной стороны его опыт работы в телевизионном формате очень помог в плане тайм менеджмента. Все пункты программы, несмотря на задержку в начале, в конечном счете полностью вписались в заявленную заранее программу. С другой стороны, у него не вызывало никаких затруднений произношение технических терминов, что я думаю имело бы место, если бы ведущий не был знаком с технической тематикой.

С годами, я все больше уважаю людей и организации, которые умеют «отвечать» за свои слова. Несмотря на наличие все большего инструментария доступного всем и повсеместно заявляемый «профессионализм», пока еще, я очень часто встречаю людей и ситуации когда организаторы не умеют контролировать свои же мероприятия. В этом плане, Radio Canada оказались на высоте.

Сначала, Мэтью еще раз проговорил все требования хакатона и критерии оценки прототипов. Я не буду перечислять их еще раз. Вы можете перечитать их в первой [1] и второй [2] частях.

Критерии оценки прототипов

Похоже я не описывал критерии оценки прототипов в первых двух частях. Странно, я был уверен, что упоминал о них.

На французском

Les critères d’évaluation sont les suivants:

Le prototype tient compte des critères obligatoires du règlement (API et Azure)/20 points
Originalité/35 points
Excellence de la technique/20 points
Impact/potentiel/durabilité/20 points
Présentation/5 points

  • Прототип должен соответствовать объявленным требованиям, перечисленным ранее — 20 очков
  • Оригинальность идеи — 35 очков
  • Техническое исполнение — 20 очков
  • Возможное воздействие на бизнес/коммерческий потенциал — 20 очков
  • Презентация прототипа — 5 очков

Так же он еще раз озвучил список призов [9] для победителей.

Список призов

1-ое место — XBox One + $1000
2-ое место — Google Home Max + $250
3-ье место — $500
И два приза (Xbox One S1), которые будут разыграны среди оставшихся участников путем розыгрыша.

(насколько мы поняли призы получили КАЖДЫЙ участник команды)

После этого Мэтью поименно представил всех менторов и помощников [10]
и каждую команду участницу [11].

Несмотря на то, что на экране была представлена 31 команда. Я и наша команда все еще питали иллюзии, что половина этих команд, либо не будет участвовать вообще, либо не справится с прототипированием, либо они окажутся значительно слабее нашей команды. На тот момент, я даже не посчитал количество команд. Я был слишком занят настройкой доступа к Azure и вообще смотрел и слушал все только краем уха. Хотя вид людей в зале меня немного и настораживал. В том плане, что я предполагал что большая часть участников будут студенты вузов. А мои глаза мне говорили, что люди с бородой и в очках навряд ли являются студентами. То есть визуально состав участников был вполне зрелым.

Немного скорректировав расписание, Мэтью объявил перерыв на 20 минут. Это было весьма кстати, всем надо было обменяться мнениями, пройтись, размяться, освежиться.

Во время перерыва, Звездочка попросила еще раз объяснить Меркурию архитектуру нашего проекта и роль каждого члена команды. Что я и сделал на бумаге.

Немного красивее и структурнее, чем тогда на бумаге

Примерно получалось что-то вроде:
Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 3

1. WEB page / Widget — HTML + JS

функциональность: видимая часть приложения организованная в виде HTML и поимкового поля ввода обрабатываемого JS.
исполнители: Звездочка — HTML, Меркурий — JS
заметки на полях: я предложил 3 варианта реализации для этой части: отдельная страница (самое простое лобовое решение), поле встраиваемое в страницу Radio Canada [12] (я думал, что ребята смогут переиспользовать структуру страницы и классы CSS для прорисовки результатов поиска в виде блоков/виджетов, чтобы не тратить время на создание своих) и третий вариант, это такой же как второй, только поле строки поиска я предложил сделать в виде Chrome Extension, поскольку знал, что сделать простейшее расширение [13] для браузера вроде несложно. А установка такого плагина позволяет получить доступ к любой странице, в том числе, необходимой нам странице с новостной лентой.

Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 4

2. PHP, MySQL — Search API backend

функциональность: простейший поисковый api, по нашему замыслу он должен принимать запрос от поисковой строки на странице, производить поиск по БД и возвращать JSON отформатированные результаты поиска.
исполнитель: Ваш покорный слуга
заметки на полях: посольку последний проект в котором я принимал участие на своей основной работе был сделан на Laravel, а также поскольку этот фреймворк является на сегодня законодателем мод, я решил что буду использовать Laravel [14]. Это не должно было принести особых сюрпризов, а я со своей стороны подучу какие-то особенности Laravel при создании нового проекта на нем, тем более не в режиме, так сказать, сайта, а в режиме API сервиса.

3. MySQL

функциональность: база данных, хранилище для контента и результатов анализа
заметки на полях: Поскольку строгих требований к тому какую именно использовать базу данных не было, Платон спросил не лучше ли будет использовать MongoDB. Для MongoDB мол JSON формат практически родной и типа должно быть проще. Честно сказать я не могу ответить и сейчас насколько правильнее делать подобные решения на не реляционных базах данных. Из того что я читал по этой теме, я делаю вывод, что если требуется довольно простая выборка по заранее известным полям, и, в нашем случае, по сути, по одной единственной таблице, то реляционные базы данных все еще являются лучшим решением. (Поправьте меня если я не прав). Но мой выбор MySQL был обусловлен не архитектурным предпочтением, а прагматическими соображениями: я просто знаю как этим пользоваться. Для Платона же обе базы были чем-то новым и, в любом случае, экспериментом, поэтому он легко со мной согласился.

4. Python App

функциональность: (Radio Canada -> Python App -> Azure Cognitive Services -> Python App -> MySQL) Эта часть по сути самая важная с точки зрения требований хакатона. Необходимо получить контент через/от API Radio Canada, возможно обработать его, затем отправить контент на анализ в какой-то из ИИ сервисов, получить ответ и положить контент вместе с результататми анализа в базу данных.
исполнитель: Платон
заметки на полях: Весь груз ответственности лег таким образом на Платона. Он немного переживал по этому поводу. Но как я уже отмачал, он провел свою домашнюю работу и чувствовал что обладает всеми наобходимыми для проекта знаниями. Платон как и вся команда не имел никаких сомнений что мы справимся с поставленной задачей.

5. Azure Cognitive Services

функциональность: на момент начала хакатона я не знал какой именно из сервисов будет задействован

6. Radio Canada API

функциональность: на момент начала хакатона я не знал какой именно из 4 сервисов будет задействован Платоном (описание 4 API Radio Canada читайте во 2-ой части). Как и в случае с ИИ сервисами решение должен был принять Платон, исходя из того что ему проще и удобнее реализовать.

После перерыва Matthieu Dugal [5] и Chloe Sondervorst [6] провели мастер-класс по подготовке презентации прототипов. Они обратили внимание аудитории на то, что, во-первых, надо понимать что ваш продукт-прототип «встречают по одежке», а следовательно от подачи материала будет очень сильно зависеть успех и шансы на выигрыш. Второе, на что ведущие обратили наше внимание это ограниченность во времени. На выступление каждой команды в финальный день отводится 3 минуты на само выступление и еще 2 минуты на ответы на вопросы жюри, если таковые возникнут. 3 минуты — это реально мало, поэтому надо сфокусировать свое выступление на самых выжных моментах и правильно расставить акцент в выступлении.

Если мы произведем простой математический расчет 31 команда х (3 мин + 2 мин) = 155 мин = 2 ч 35 мин. А затем добавим пару минут на всякие заминки, смену команд на сцене и прочее, то становится очевидным, что только на прослушивание команд уйдет в лучшем случае 3 часа. Но я повторюсь, на тот момент я еще не понял что в забеге состязании участвует 31 полноценная команда. Если вы помните по нашему первоначальному плану, когда мы регистрировали команду, мы предполагали что реально команд будет не более 10, из которых возможно пару отвалятся сами собой.

Хлоя искренне пыталась за это короткое время дать краткий курс публичных выступлений и ораторского искусства. Она рассказывала каковы основные критерии по которым можно оценить свою презентацию: первое впечатление, история, правдоподобность/реалистичность, простота, время (тайм-менеджмент), эмоциональность, видение, страсть/эмоциональная увлеченность.

Завершая свою часть выступления ведущие еще раз обратили внимание [15] на заявленные критерии оценки прототипов.

После завершения мастер-класса у участников была возможность задать еще несколько вопросов. Часть этих вопросов касалась того насколько функциональным должен быть прототип, кто и как будет это проверять и следует ли включать реальную демонстрацию в презентацию или можно обойтись какими-то статичными слайдами со скриншотами или чем-то наподобие.
Отвечая на один из таких вопросов Maxime St-Pierre четко заявил что прототип однозначно должен быть функциональным и лучше всего включить его демонстрацию в свою финальную презентацию.

Хочу обратить ваше внимание на этот маленький штрих. Потому что мы базировали свои ожидания именно из расчета на то что прототип должен быть готов и он должен соответствовать
объявленным критериям.

После ответов на вопросы нам еще раз пожелали удачи. И работа закипела! (примерно 20:00)

Много текста для любителей читать

Вернее, как работа, некая деятельность. С моей стороны, я сразу объяснил всем членам нашей команды, что мы должны по-возможности вести разработку своих частей максимально независимо. В то же время мы должны постараться избегать взаимной блокировки друг друга. Согласно нашей архитектуре получалось что точки возможной взаимной блокировки являются переходы: JS <-> PHP, PHP <-> MySQL <-> Python. То есть мою часть определенным образом может задержать/заблокировать неготовность или проблемы с БД. А я в свою очередь могу оказаться блокирующим звеном для нашего фронт енда.

Поэтому первым делом нужно было поднять базу данных и проверить доступ к ней как со стороны PHP так и Python.

Платон тем временем разбирался с обоими API определяясь в какую сторону мы будем двигаться.

Звездочка и Меркурий занимались своими делами. Кстати, они меня приятно удивили, тем что сразу подняли общий репозиторий на Github и реально занимались совместной разработкой с первых минут, динамично обмениваясь обновлениями. К слову сказать, я до сих пор не залил свой код никуда, хотя прошло уже несколько недель. (мне стыдно, честно)

Я попросил Звездочку исходить из того что она будет получать из PHP сервиса некий JSON массив с элементами, у каждого элемента будет следующий минимальный набор полей: video_id, title (заголовок), category (некая категория к которой относится контент, может это будет: спорт, политика, эекономика. А может музыкальный стиль или исполнитель, а может просто регион Квебека), body/description (тело статьи), image/video (url к видео или картинке). И соответственно она должна создать HTML макет для вывода этих элементов. Я попросил ее чтобы макет был разработан под 6 элементов в 2-х категориях.

Первая Альфа, внешний вид, фронт енд

Вот что примерно было готово у нашей фронт енд команды на конец дня.
Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 5

Сгенерированный HTML

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <link href="css/bootstrap.min.css" rel="stylesheet">
  <link href="css/main.css" rel="stylesheet">

</head>

<body>
<div class="card-deck">
  <div class="card" id="card">
    <img class="card-img-top" src="images/placeholder-images.jpg" alt="Card image cap">
    <div class="card-body">
      <h5 class="card-title">Will need info from api</h5>
      <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
      <a href="#" class="btn btn-primary">Read more ...</a>
    </div>
  </div>
  <div class="card" id="card">
    <img class="card-img-top" src="images/placeholder-images.jpg" alt="Card image cap">
    <div class="card-body">
      <h5 class="card-title">Will need info from api</h5>
      <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
      <a href="#" class="btn btn-primary">Read more ...</a>
    </div>
  </div>
  <div class="card" id="card">
    <img class="card-img-top" src="images/placeholder-images.jpg" alt="Card image cap">
    <div class="card-body">
      <h5 class="card-title">Will need info from api</h5>
      <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
      <a href="#" class="btn btn-primary">Read more ...</a>
    </div>
  </div>
</div>

<div class="card-deck">
  <div class="card" id="card">
    <img class="card-img-top" src="images/placeholder-images.jpg" alt="Card image cap">
    <div class="card-body">
      <h5 class="card-title">Will need info from api</h5>
      <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
      <a href="#" class="btn btn-primary">Read more ...</a>
    </div>
  </div>
  <div class="card" id="card">
    <img class="card-img-top" src="images/placeholder-images.jpg" alt="Card image cap">
    <div class="card-body">
      <h5 class="card-title">Will need info from api</h5>
      <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
      <a href="#" class="btn btn-primary">Read more ...</a>
    </div>
  </div>
  <div class="card" id="card">
    <img class="card-img-top" src="images/placeholder-images.jpg" alt="Card image cap">
    <div class="card-body">
      <h5 class="card-title">Will need info from api</h5>
      <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
      <a href="#" class="btn btn-primary">Read more ...</a>
    </div>
  </div>
</div>
</body>
</html>

Я запустил несколько VM одну с Debian Jessie, другую с Ubuntu 16 и пытался поставить на них MySQL — не получалось. То ли я слишком спешил, то ли слишком устал, я не знаю почему. Даже сами VM поднялись у меня не с первой попытки. Поскольку я не хотел никому мешать пока все не заработает я пытался запустить каждую VM в отдельной секьюрити груп. Azure плевался и говорил что мне это не положено (restricted). Я так и не понял почему именно не положено. Подсказал мне Платон, когда я в отчаянии пожаловался ему на упрямство Azure. Дальше пошло немного легче. Все машины и сервисы которые я использовал после этого, я создавал только в одной изначально имевшейся на нашем аккаунте секьюрити группе и подсетке, и естесственно в одном и том же регионе.

С другой стороны, что мне понравилось в Azure VM, что в отличие от AWS у меня есть возможность выбора: я могу иметь доступ SSH к машине с использованием приватного ключа, либо с использованием традиционной пары «имя пользователя»-«пароль». Я отдаю себе отчет в том что наверное подход AWS более безопасный. Но в некоторых ситуациях (как например эта), когда вопросы безопасности временно не имеют значения, аккаунт сам по себе временный, члены команды не имеют достаточной квалификации, а времени на их обучение просто нет, то в таких случаях мне кажется обмениваться простыми парами:«имя пользователя»-«пароль» более эффективно и достаточно безопасно.

Итак я пытался поднять MySQL на двух VM и открыть порт 3306 наружу, для того чтобы проще было тестировать и подключаться к серверу. Одновременно я поднял 2 или даже 3 MySQL базы как сервис Azure, надеясь на то что сервисом должно быть проще и удобнее пользоваться.

Тем временем к Звездочке подошел Olivier Fortin, как вы помните это специалист по Web Accesibility. Он поинтересовался насколько наш код HTML соответствует стандартам. Звездочка живо отреагировала на его вопрос и показала все что у нее было на тот момент. Естесственно код был далек от того чтобы считаться валидным для WCAG 2.0. Но для нас этот стандарт был чем-то совершенно отвлеченным и не основным приоритетом. Оливье подсказал на что именно стоит обратить внимание и порекомандовал установить утилиты которыми пользуются люди с ограниченными способностями, чтобы проверять насколько удобно пользоваться нашим продуктом используя эти утилиты.

К 22:00 когда нас попросили закругляться я был посередине ничего. MySQL не отвечал мне нигде и никак. Настроение было не очень. И я боялся что завтра я реально стану блоком для всех. Но, слава богу, я не мальчик. Я спокойно рассудил что пятница вечер, конец рабочей недели, куча информации, немного не сытый желудок, все это вместе может быть причиной моих неудач. И следуя русской пословице: утро вечера мудренее, стоит пойти домой и спокойно выспаться, а завтра вернуться со свежими силами и свежей головой.

О кстати, чуть на забыл, кажется Фаэтон появился в 20:30 или 21:00, я уже не вспомню, потому что к этому времени был полностью погружен в танцы с бубном эксперименты с Azure.

День второй. Суббота. Муки творчества.

Расписание на субботу

Суббота, 24 марта

9:00 Прибытие, завтрак, продолжение работы в командах
12:00 Обед, продолжение работы в командах
18:00 Ужин, продолжение работы в командах
22:00 Конец второго дня соревнований

Как вы видите второй день с точки зрения расписания был самым лаконичным.

Работать, работать и еще раз работать.

Несмотря на всё моё нетерпение в субботу утром, я пришел позже всех. Ну не всех, но четвертым из нашей команды к 9:30. Мне нужно было завезти младшего сына в русскую школу.

Зачем мы водим детей в русскую школу

Так как мы хотим чтобы ребенок не потерял совсем культурную связь и с нами и со своими бабушками и дедушками, тетями и дядями, сестрами и братьями живущими от нас далеко географичкски, но всегда близкими нам сердцем, мы водим ребенка каждую субботу в русскую школу. Кстати, она является самой большой из русских школ находящихся за пределами России.

Мы знаем много примеров, когда дети приезжая в Канаду в довольно юном возрасте и/или родившиеся здесь, через насколько лет забывают родной язык. Дело в том, что многие родители приехав в Канаду, на мой взгляд, слишком сильно фокусируются на интеграции. Я могу их понять. Действительно, зачем было переезжать, если не интегрироваться в местное общество. И стараясь для своих детей все возможное они начиниают и в кругу семьи говорить на английском или французском. А дети встроены так, что они очень быстро осваивают новый язык. Чем меньше ребенок, тем быстрее он переключается. А затем садик и школа делают свое дело.

К тому же, дополнительные занятия по субботам или воскресеньям требуют дополнительных усилий, денег, времени, внимания. У кого-то не хватает денег, у кого-то времени, кто-то просто устает рассказывать и доказывать ребенку зачем ему это нужно. А дети тут растут, давайте назовем это, «свободолюбивыми». И поверьте, каждую субботу отвозить утром и забирать вечером ребенка за 20 км, это дополнительная ноша, которая напрягает и ломает многие планы на редкие выходные дни.

Помимо занятий русским языком, литературой и географией, считается что эта школа (как и несколько других русских школ в регионе) дают хорошую дополнительную базу по математике и физике, что позволяет ученикам лучше усваивать оригинальную канадскую программу обучения по основному месту учебы в публичных школах.

Звездочка, кстати, тоже посещала эту школу вплоть до поступления в колледж. И она сохранила очень дружеские отношения со многими своими одноклассниками оттуда. Они до сих пор регулярно, пусть и не очень часто, в силу занятости, встрачаются и проводят время вместе.

Я поприветствовал своих коллег и подготовил свое рабочее место к работе. Затем Звездочка проводила меня в импровизированный буфет, где можно было взять кофе, сок и кексик на зватрак.

По пути она поделилась своими планами на день. Сказала что у них с Меркурием работа кипит и я могу особо не беспокоиться, если у них возникнут вопросы или проблемы — они сообщат. Мы вернулись за наш стол.

Платон сообщил мне, что он определился с направлением движения и весь погружен в работу. Также мимоходом он сказал что MySQL работает. Я удивился, но не стал вникать, что именно и как работает. Было немного стыдно, что заработало у Платона, то что должен был подготовить я.

Так же следует упомянуть, что наш ментор Patrick Lévesque подходил к нам и накануне вечером два раза и в субботу, чтобы удостовариться что у нас все движется нормально и нам не нужна его помощь. Это было приятно и укрепляло нас в нашей уверенности в своих силах.

Тем временем я попросил Звездочку и Меркурия показать куда движутся они, чтобы понимать насколько далеки они от возможной точки блокировки, когда нам надо будет соединять все наши части вместе. Звездочка сказала что они работают таки над Chrome Extension, который им без труда удалось запустить в простейшем варианте по туториалу и сейчас она разбирается как это расширение для браузера может взаимодействовать со страницей Radio Canada.

Меркурий сказал что он работает над динамическим полем ввода, чтобы при нажатии очередной клавиши отправлялся запрос в бэкенд. Таким образом получалось, что уже вот-вот Меркурию понадобится мой бэкенд сервис, чтобы двигаться дальше не вслепую.

Оставив их продолжать работать в верном и уже немного более определенном направлении, я подошел пообщаться с Платоном.

Платон сообщил мне довольно большую и важную порцию информации.

Куда идем мы с Пятачком?

От Radio Canada мы будем использовать публичный новостной API Lineup [16].

В качестве ИИ будет задействован сервис Text Analytics [17]. Соответственно, после анализа помимо контента Radio Canada у нас будет два дополнительных параматра/поля БД: SENTIMENT (эмоциональная оценка текста: от 0 до 1, где 0 негативный текст, а 1 поззитивный), KEY PHRASES (ключевые слова и фразы). Я просто принял эту информацию к сведению и пытался на ходу проанализировать, что и как это меняет в нашем проекте и как можно использовать эти дополнительные поля.

На мой вопрос как Платону удалось поднять БД, он сказал: так ты же вчера поднял. Оказалось, что вчера во время своих экспериментов я регулярно скидывал название VM и сервисов которые я пытался запустить, вместе с иманами пользователей и паролями Платону в Skype. Я делал это так сказать «про запас», чтобы долго не искать потом, когда я определюсь какой именно сервис мы будем использовать. Платон же со своей стороны протестировал один из них и сервис ему благополучно ответил, т.е. БД отвечала как живая. Я уточнил у Платона к какому именно сервису или VM он обращался и какой утилитой. Оказалось, поскольку мир БД для Платона почти в новинку, он просто постучался на указанный сервис из Python следуя какому-то туториалу, найденному на просторах Интернета.

С одной стороны, меня это порадовало. Получалось, что сервис все-таки поднял я. С другой, было непонятно почему у меня не получалось подключиться к нему вчера из моего любимого PHPStorm. Объяснение было простым, Платон подключался к сервису так сказать изнутри, находясь внутри сети Azure и в той же секьюрити груп на уже работающей VM с его Python. А я вчера вечером пытался настроить и протестировать публичный доступ для всех. То есть ту цель, насчет публичного доступа я так и не достиг, но БД была доступна для работы, а это главное. Итак, вариант который заработал для нас это был MySQL как сервис от Azure. Остальные VM, поднятые накануне, я просто не задействовал и не гасил до окончания хакатона, просто потому что не было времени.

Тик-так, мои партнеры все в работе, на часах уже 10 с хвостиком, а у меня еще даже нет dev сервера с PHP.

Как поднялся сервер с PHP

В этот раз у меня почему-то все получилось с первого раза.

Поднимаю новую VM Debian Jessie. Это занимает пару минут (примерно столько же это занимает и не AWS). Успешно подключаюсь по SSH моим стареньким другом Putty, это радует. Устанавливаю PHP 7.2, вроде встал. Устанавливаю Composer, полет нормальный. Устанавливаю composer global require "laravel/installer", вроде ошибок нет. Следуя далее официальному гиду [18] по установке создаю проект Laravel laravel new api. Все прошло гладко. Еще пару шагов: устанавливаю Nginx, меняю дефолтный конфиг, чтобы указывал на папку /public в папке с моим Laravel проектом и вызывал php-fpm для php файлов. В Azure открываю доступ к порту 8080 для моей VM c PHP.

Для гарантии я подправил дефолтный welocome.blade.php чтобы быть уверенным что я вижу именно свою страницу и свой проект.

<div class="title m-b-md">
        Hackathon 2018 Radio Canada
</div>

Проверяю в браузере по публичному IP моей виртуалки и вуаля!
Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 6

Первая цель: не блокировать работу ребят на фронт енде. Надо создать простой контроллер и выдавать им псевдо ответ, пусть даже статический. Но как минимум они смогут уже отрабатывать взаимодействие.

Первая итерация PHP

Создаю новый route в routes/web.php

Route::get('/search', 'SearchController@index');

и новый контроллер app/Http/Controllers/SearchController.php с единственным пока методом/экшеном index

    public function index()
    {
        return response()->json([
        ['video_id'=>'1', 'title'=>'title 1'],
            ['video_id'=>'2', 'title'=>'title 2'],
            ['video_id'=>'3', 'title'=>'title 3'],

    ]);

Проверяю у себя в браузере. Вроде работает.

[{"video_id":"1","title":"title 1"},{"video_id":"2","title":"title 2"},{"video_id":"3","title":"title 3"}]

Это уже был какой-то прогресс, и я чувствовал, что как и другие члены команды, что я влился в работу. Тревога за то, что я окажусь слабым звеном отступила.

Звездочка к этому времени приступила к адаптации кода под Web Accesibility. Для начала она еще с прошлого вечера установила NVDA [19] чтобы иметь возможность тестировать свои изменения. За этот день она реально стала, если не экспертом, то хорошим специалистом по Web Accesibility и думаю, что она, как впрочем и я сам, поняла что значит термин «семантика сайта». Когда семантика становится не просто чем-то теоретическим, но имеет очень важное практическое значение.

Для сравнения вот итоговый HTML который был представлен в финальной версии:

HTML - финальная версия

<body>
<div id="searchDisplay">
  <section><h3>Région - Saguenay - Lac-Saint-Jean - Sports</h3>
    <section id="deckRégion - Saguenay - Lac-Saint-Jean - Sports"
         title="Categorie : Région - Saguenay - Lac-Saint-Jean - Sports">
      <article id="card1090009"><img class="card-image-top"
           src="https://images.radio-canada.ca/v1/ici-info/sports/4x3/charles-hamelin-champion-monde-patinage-vitesse-courte-piste.jpg"
           alt="Charles Hamelin champion du monde, Marianne St-Gelais fait ses adieux">
        <h5 class="card-title">Charles Hamelin champion du monde, Marianne St-Gelais fait ses adieux</h5>
        <p class="card-text">Le Québécois Charles Hamelin a remporté le titre cumulatif pour une première fois
          dans sa carrière, dimanche, aux Championnats du monde de patinage de vitesse sur courte piste à
          Montréal.</p>
        <div id="tags"><a title="Lien pour twiter" target="_blank"
            href="https://twitter.com/hashtag/TitreCumulatif">#TitreCumulatif</a><a
            title="Lien pour twiter" target="_blank"
            href="https://twitter.com/hashtag/QuébécoisCharlesHamelin">#QuébécoisCharlesHamelin</a><a
            title="Lien pour twiter" target="_blank" href="https://twitter.com/hashtag/PatinageDeVitesse">#PatinageDeVitesse</a><a
            title="Lien pour twiter" target="_blank" href="https://twitter.com/hashtag/PremièreFois">#PremièreFois</a>
        </div>
        <h6 id="emotionBar"><h5 style="color: #ff0000">◙</h5><h5 style="color: #ff4000">◙</h5><h5
            style="color: #ff8000">◙</h5><h5 style="color: #ffbf00">◙</h5><h5 style="color: #ffff00">◙</h5>
          <h5 style="color: #bfff00">◙</h5><h5 style="color: #80ff00">◙</h5><h5 style="color: #d1e1f9; ">
            ◙</h5><h5 style="color: #d1e1f9; ">◙</h5><h5 style="color: #d1e1f9; ">◙</h5></h6>
        <a href="http://ici.radio-canada.ca/sports/1090009/championnat-monde-patinage-vitesse-courte-piste-carriere-individuelle-marianne-st-gelais-kim-boutin-charles-hamelin-samuel-girard"
           class="btn btn-primary" id="readmore" target="_blank" title="Lire plus par rapport a l'article">Lire
          plus...</a></article>
      <article id="card1089683">
          <!-- УДАЛЕНО ДЛЯ УДОБСТВА ПРОСМОТРА -->
      </article>
      <article id="card64200">
          <!-- УДАЛЕНО ДЛЯ УДОБСТВА ПРОСМОТРА -->
      </article>
      <article id="card1088454">
          <!-- УДАЛЕНО ДЛЯ УДОБСТВА ПРОСМОТРА -->
      </article>
    </section>
  </section>
  <section><h3>Région - Estrie - Sports</h3>
    <section id="deckRégion - Estrie - Sports" title="Categorie : Région - Estrie - Sports">
        <!-- УДАЛЕНО ДЛЯ УДОБСТВА ПРОСМОТРА -->
    </section>
  </section>
  <section><h3>Région - Bas-Saint-Laurent - À ne pas manquer</h3>
    <section id="deckRégion - Bas-Saint-Laurent - À ne pas manquer"
         title="Categorie : Région - Bas-Saint-Laurent - À ne pas manquer">
        <!-- УДАЛЕНО ДЛЯ УДОБСТВА ПРОСМОТРА -->
    </section>
  </section>
</div>
</body>
</html>

Если вы сравните с первоначальной версией, то вы увидите что некоторые тэги

<div>

были заменены на более семантически понятные

<section> и <article>

а для тэгов

<img> и <a>

соответственно добавлены alt и title аттрибуты. До этого хакатона, я относил подобные модификации по большей части к SEO и считал это излишествами, с несколько непонятным прикладным назначением. Теперь думаю прикладное предназначение подобных практик надолго останется в моем восприятии HTML кода: эти изменения реально помогают программам типа NVDA [19] «читать» экран или вебстраницу на более понятном, скажем прямо, человеческом языке.

Следующим шагом в моем плане было создать сервис для Звездочки, который заменит ее статические данные, моими статическими данными, которые я впоследствии заменю динамическими данными из БД. Я добавил еще один путь в конфиг Laravel

Route::get('/feed', 'SearchController@feed');

И соответственно еще один метод в мой SearchController

    public function feed()
    {
        return response()->json([
            ['video_id'=>'1', 'title'=>'title 1', 'description'=>'Description 1'],
            ['video_id'=>'2', 'title'=>'title 2', 'description'=>'Description 2'],
            ['video_id'=>'3', 'title'=>'title 3', 'description'=>'Description 3']
        ]);
    }
Первая склейка фронт энд - бэк энд

Было уже начало 12-го. Я попросил Звездочку попытаться заменить свой шаблон данных вызовом моего шаблона. Она в это время была в основном занята модификацией HTML и доработкой CSS. Хочу заметить, что после разговора с Оливье, она отказалась от использования bootstrap.css. Я был немного не согласен с таким решением, но нехватка времени не позволяла углубляться в дискуссии. Я решил каждый делает свой кусок тем способом, который он понимает. Итак звездочка мне сообщила что вызов моего сервиса не работает! Я перепроверил несколько раз у себя в браузере — мой сервис прекрасно выдавал json.

Сначала я полез в код JS, не очень доверяя Звездочке. Тем более что она не претендовала на 100% знание JS.

Я был немного обескуражен обнаружив вот примерно такой вот код:

function parseJson()
{
	xml = new XMLHttpRequest();
	xml.onreadystatechange = getData;
	xml.open("GET","http://ХХХ.ХХ.ХХХ.ХХ/feed/");
	xml.send();
}

function getData()
{
	if(xml.readyState == XMLHttpRequest.DONE && xml.status == 200)
	{
		console.log(xml.responseText);
		jsonArray = JSON.parse(xml.responseText);
		fillInCards();
	} else {
		console.log("There was a problem with connection");
	}
}

Я не спец в JS. Но то с чем я привык работать, если мы говорим про AJAX, то это в обязательном порядке jQuery или его аналоги. Не помню почему у меня сформировалось такое мнение, но я как-то внутренне против использования сырого JS для вызовов AJAX. Наверное, в силу своих ограниченных знаний я полагаюсь на библиотеки, которые по умолчанию сделают за меня какие-то действия о которых я могу забыть или не знать: открыть/закрыть соединение, обработать нестандартное событие, вызвать какой-то хэндлер, проверить/валидировать данные перед отправкой и/или при получении.

Для меня более привычным был бы код вида:

$.ajax({
  url: "http://ХХХ.ХХ.ХХХ.ХХ/feed/",
  cache: false,
  success: function(data) {
      getData(data); 
    }
});

Но что поделать, Звездочка настаивала, что ее код проверен во многих лабораторных работах и проектах, которые они недвано делали в колледже и должен работать. Мне пришлось ей поверить. Тем более, что после небольшого расследования я обнаружил:
Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 7
что казалось мне не совсем логичным. То есть запрос ajax получал от моего сервиса ответ со статусом 200, но не получал тело ответа и выдавал в консоль JS ошибку: No 'Access-Control-Allow-Origin' header is present on the requested resource. Минут 5 я наверное тупил пытаясь осознать что происходит. Затем до меня дошло, что это видимо дефолтные установки безопасности Laravel или скорее даже установки самого Chrome, которые по умолчанию разрешают AJAX запросы только для своего домена/хоста.

Немного погуглив я решил, что я могу просто вернуть header 'Access-Control-Allow-Origin' вместе с данными, что должно решить проблему. Немного поэкспериментировав мой метод приобрел вид:

public function feed()
{
  return response()->json([
    ['video_id'=>'1', 'title'=>'title 1', 'description'=>'Description 1'],
    ['video_id'=>'2', 'title'=>'title 2', 'description'=>'Description 2'],
    ['video_id'=>'3', 'title'=>'title 3', 'description'=>'Description 3']
    ],200)->header('Access-Control-Allow-Origin',"*");;
}

После этого JS вроде перестал ругаться. Похоже было на то что связка фронт энд — бэк энд приобретает очертания.

Подошло время второго завтрака или первого обеда, как вам будет угодно, по-местному просто ланч. Первыми пошли перекусить Звездочка с Меркурием. А мы с Платоном, ненадолго оторвавшись от кода, обсудили текущий прогресс и структуру таблицы базы данных. Платон еще раз подтвердил что код получения новостей и отправки их на анализ в ИИ готов практически на 90%.

К сожалению Платон создал репозиторий уже после хакатона, так что проследить за развитием его скрипта во времени не представляется возможным. Я вставлю скрипт целиком ближе к концу этой части. А пока просто скажу, что на момент перед ланчем Платон сказал что я могу ожидать появления примерно 100-120 записей в БД вскоре после обеда. Меня это пока вполне устраивало, чувствовалось что разработка точно двигается по всем фронтам как запланировано.
И мы со спокойной душой тоже пошли перекусить.

Чем кормят работников умственного труда

Еда была сытной, но странноватой. Была какая-то каша типа киноа [20], но она была не теплой, а холодной, плюс к тому же она выглядела скорее как салат, в котором как оказалось участвовали яблоки и кажется даже виноград. Наверное любители здорового питания были в восторге. В дополнение была небольшая порция вполне обычного салата: кусочки болгарского перца, листочки салата, какая-то зелень (если честно состав я уже не помню, надо было наверное все фотографировать. Век живи век учись! :-) ). И был двойной бутерброд типа сэндвич, если я ничего не путаю то можно было выбрать либо с куриным мясом либо с копченым лососем. Я выбрал с лососем. В состав бутерброда также вошел какой-то творожный сыр похожий на Филадельфию и опять же странным образом тонкие ломтики яблок. В добавок там был еще какой-то салатик и мини десерт. В общем, скромно и со вкусом. Мы однозначно утолили голод и даже получили какое-то эстетическое удовольствие.

Если по поводу еды у меня в целом претензий не было, несмотря на немного необычный, на мой взгляд, подбор блюд. То по поводу кофе, а вернее его постоянного отсутствия у меня претензии были, есть и будут. Я не знаю как у вас с этим делом, но для меня кофе это топливо. Я не могу интенсивно работать без кофе. Любой проект, любая нагрузка — мне нужен кофе. И насколько я знаю таких как я в Канаде много. Кофе есть в каждом офисе где я работал, а я уже успел поработать примерно в десятке компаний, больших и малых, богатых и не очень. Кофе есть везде, где-то получше, где-то похуже, где-то нерегулярно, но кофе машина обязательный атрибут любого, уважающего себя офиса. Так вот, с поставками кофе на хакатон были большие проблемы. Кофе закончился уже к 12. С утра было 2 термоса наверное литра на 3 каждый. И я так и не понял обновляли ли запасы кофе после ланча или нет, но у меня реально была небольшая «ломка».

После ланча, в ожидании реальных данных в БД я сфокусировался на взаимодействии с фронт эндом. Я попросил Звездочку показать что у них получается с интеграцией со страницей Radio Canada [12]. Она показала, что все чего им удалось добиться на данный момент это очистить полностью оригинальный HTML, либо основной блок с контентом.

Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 8

Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 9

И мои мечты и фантазии о попытке переиспользования оригинального CSS очень маловероятны. При таком качестве дизайна, я уже не видел смысла в попытке интегрироваться в оригинальную страницу Radio Canada. Получалось что при такой корявой интеграции, страница становится некрасивее и симпатичнее, как задумывалось, а скорее наоборот, мы просто уродуем оригинальный дизайн своими вставками. При том, что еще неизвестно как отреагирует оригинальный JS и CSS.

После непродолжительной дискуссии все согласились немного изменить концепцию нашего прототипа. Я предложил отбросить идеб интеграции, и раз уж у нас в любом случае есть некий браузер плагин, то мы можем делать вывод результатов прямо в нем. То есть прямо под полем поиковой строки выводить панель в правой части окна браузера.
Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 10

Время шло. Была заминка, когда Звездочке пришлось переписывать свои циклы, чтобы сделать количество категорий на равным 2 а динамически создавать секции для категории, если появлялась новая у следующего элемента и добавлять контент к уже имеющейся секции, если категория уже была получена ранее. Плюс дизайн для правой панели подразумевал вывод уже не в 3 колонки, а, просто как ленту, в одну.

Платон ненадолго застрял с выводом данных в БД. Как я уже отмечал ранее, это был для него первый пример реального использования питона с БД. Заминка произошла с prepared statement. Когда Платон устал бороться с непонятными для него сообщенями питона, он попросил меня глянуть свежим взглядом в чем может быть проблема. Вот тут я смог помочь, поскольку БД это так сказать моя тема. Платон пытался модифицировать пример из интернета для prepared statement. Но использовал прямую конкатенацию значений и естесственно питон ругался. Как только мы отделили запрос от данных все заработало.

Как только появились первые данные в БД, я заменил свои заглушки с данными на реальные данные из базы данных. В связи с чем у ребят на фронтенде сразу поплыли стили и элементы. Заголовки категорий, во-первых, оказались слишком большими, и во-вторых, оказалось что данные, с какого-то перепугу, включают в себя рэндомно HTML разметку.

Примеры данных

title:

Protection de l’eau potable : des municipalités québécoises prêtes à aller devant les tribunaux

summary:

<p>Des représentants de plus de 300 municipalités du Québec ont voté à l'unanimité une résolution qui leur permettrait d'aller devant les tribunaux si Québec n'impose pas des normes plus sévères afin de protéger leurs sources d'eau potable. Même s'ils concèdent voir des signes positifs au gouvernement provincial, ils menacent d'entamer des démarches judiciaires si les lois n'évoluent pas rapidement.</p>

body:

<p><small class="item-without-url">Ma région</small><span><a href="/grandmontreal">Grand Montréal</a></span></p> <p>Des représentants de plus de 300 municipalités du Québec ont voté à l'unanimité une résolution qui leur permettrait d'aller devant les tribunaux si Québec n'impose pas des normes plus sévères afin de protéger leurs sources d'eau potable. Même s'ils concèdent voir des signes positifs au gouvernement provincial, ils menacent d'entamer des démarches judiciaires si les lois n'évoluent pas rapidement.</p> <p>Avec ce vote unanime, les municipalités rassemblées ont envoyé un signe clair à Québec. Les 338 représentants demandent une dérogation, au ministre de l’Environnement, pour obtenir le pouvoir d'imposer des distances d'un minimum de deux kilomètrres entre les sources d'eau potable et les éventuelles installations gazières et pétrolières, plutôt que les 500 mètres prescrits par le gouvernement.<br/><br/>« Ça m’a ému de voir l’unanimité et la solidarité de mes collègues », raconte Gérard Jean, maire de Lanoraie, une municipalité de Lanaudière.<br/><br/><div class="center image-from-url-with-caption-v2" data-component-name="ImageFromUrlWithCaptionV2"><div class="wrapper-media"><div class="container-image image-soft-crop-xl image-soft-crop-lg"><picture class="picture bunker-component" data-component-name="Picture"><source media="(min-width: 1599.01px)" srcset="https://images.radio-canada.ca/q_auto,w_635/v1/ici-info/16x9/lisette-maillet-austin.jpg"/><source media="(min-width: 1239.01px)" srcset="https://images.radio-canada.ca/q_auto,w_635/v1/ici-info/16x9/lisette-maillet-austin.jpg"/><source media="(min-width: 1023.01px)" srcset="https://images.radio-canada.ca/q_auto,w_1250/v1/ici-info/16x9/lisette-maillet-austin.jpg"/><source media="(min-width: 640.01px)" srcset="https://images.radio-canada.ca/q_auto,w_960/v1/ici-info/16x9/lisette-maillet-austin.jpg"/><source media="(min-width: 522.01px)" srcset="https://images.radio-canada.ca/q_auto,w_635/v1/ici-info/16x9/lisette-maillet-austin.jpg"/><source media="(min-width: 0px)" srcset="https://images.radio-canada.ca/q_auto,w_480/v1/ici-info/16x9/lisette-maillet-austin.jpg"/><img alt="La mairesse Lisette Maillé, d'Austin, dans une salle de réunion" src="https://images.radio-canada.ca/q_auto,w_635/v1/ici-info/16x9/lisette-maillet-austin.jpg" title="La mairesse Lisette Maillé, d'Austin, dans une salle de réunion"/> </picture></div></div><span aria-hidden="true" class="photo-caption"> Lisette Maillé, mairesse d'Austin, en Estrie, milite en faveur d'une meilleure protection des sources d'eau potable. Photo : Radio-Canada/Radio-Canada </span></div><blockquote><p class="quote">C'est deux kilomètres ou rien!<footer>Lisette Maillé, mairesse d'Austin, en Estrie</footer></p></blockquote><p>Actuellement, des pourparlers sont en cours avec la ministre du Développement durable, de l'Environnement et de la Lutte contre les changements climatiques, Isabelle Melançon.<br/><br/>Les membres du comité de pilotage du dossier se disent confiants que la ministre Melançon approuve leur demande. À la suite d'une rencontre avec le comité, le 20 mars, elle s'est engagée à rendre une réponse dans la semaine du 9 avril.<p>En entrevue avec Radio-Canada, Gérard Jean s’est montré optimiste, disant avoir senti « une grande ouverture » de la part de Mme Melançon.<p>M. Jean rappelle que les municipalités attendent une réponse positive depuis près de quatre ans. Dans la négative, les municipalités pourraient envisager des mesures judiciaires.<p><strong>Un précédent</strong><p>Le 18 février, la municipalité de <a href="/nouvelle/1086340/ristigouche-sud-est-gastem-deboutee">Ristigouche Sud-Est a gagné son procès face à la pétrolière Gastem</a>. Le droit de protéger les sources d'eau potable de ses citoyens lui a été reconnu par la Cour supérieure. Selon le maire de Ristigouche Sud-Est, François Boulay, ce jugement permet d'« appuyer les municipalités » face à un règlement qu'il juge actuellement « inadéquat ».<blockquote><p class="quote">Comme nous l’avons vu dans le jugement de Ristigouche, le principe de précaution appartient aux municipalités. C’est nous qui sommes redevables aux citoyens.<footer>Gérard Jean, maire de Lanoraie</footer></p></blockquote><p>Selon M. Jean, ce jugement « renforce » la position des municipalités dans ce dossier. « Je pense que le gouvernement a pris bonne note de ça », a-t-il ajouté.<p>Vendredi, Mme Melançon s’est prononcée <a href="/nouvelle/1091246/environnement-energies-fossiles-ministre-isabelle-melancon-exploitation-environnement">contre les projets d'exploitation de pétrole et de gaz naturel</a> sur le territoire de la province.<br/><br/><div class="framed"><p><strong>À lire aussi :</strong><ul><li><a href="../" title="http://ici.radio-canada.ca/nouvelle/1059037/exploitation-petrole-philippe-couillard-rassurant">Exploitation pétrolière : Philippe Couillard se veut rassurant</a><li><a href="/nouvelle/1089162/climat-changements-climatiques-villes-solutions-idees">Changements climatiques : trois idées innovantes pour les villes</a></li></li></ul></p></div></p></p></p></p></p></p></p></p> <p class="quote">C'est deux kilomètres ou rien!<footer>Lisette Maillé, mairesse d'Austin, en Estrie</footer></p> <p>Actuellement, des pourparlers sont en cours avec la ministre du Développement durable, de l'Environnement et de la Lutte contre les changements climatiques, Isabelle Melançon.<br/><br/>Les membres du comité de pilotage du dossier se disent confiants que la ministre Melançon approuve leur demande. À la suite d'une rencontre avec le comité, le 20 mars, elle s'est engagée à rendre une réponse dans la semaine du 9 avril.<p>En entrevue avec Radio-Canada, Gérard Jean s’est montré optimiste, disant avoir senti « une grande ouverture » de la part de Mme Melançon.<p>M. Jean rappelle que les municipalités attendent une réponse positive depuis près de quatre ans. Dans la négative, les municipalités pourraient envisager des mesures judiciaires.<p><strong>Un précédent</strong><p>Le 18 février, la municipalité de <a href="/nouvelle/1086340/ristigouche-sud-est-gastem-deboutee">Ristigouche Sud-Est a gagné son procès face à la pétrolière Gastem</a>. Le droit de protéger les sources d'eau potable de ses citoyens lui a été reconnu par la Cour supérieure. Selon le maire de Ristigouche Sud-Est, François Boulay, ce jugement permet d'« appuyer les municipalités » face à un règlement qu'il juge actuellement « inadéquat ».<blockquote><p class="quote">Comme nous l’avons vu dans le jugement de Ristigouche, le principe de précaution appartient aux municipalités. C’est nous qui sommes redevables aux citoyens.<footer>Gérard Jean, maire de Lanoraie</footer></p></blockquote><p>Selon M. Jean, ce jugement « renforce » la position des municipalités dans ce dossier. « Je pense que le gouvernement a pris bonne note de ça », a-t-il ajouté.<p>Vendredi, Mme Melançon s’est prononcée <a href="/nouvelle/1091246/environnement-energies-fossiles-ministre-isabelle-melancon-exploitation-environnement">contre les projets d'exploitation de pétrole et de gaz naturel</a> sur le territoire de la province.<br/><br/><div class="framed"><p><strong>À lire aussi :</strong><ul><li><a href="../" title="http://ici.radio-canada.ca/nouvelle/1059037/exploitation-petrole-philippe-couillard-rassurant">Exploitation pétrolière : Philippe Couillard se veut rassurant</a><li><a href="/nouvelle/1089162/climat-changements-climatiques-villes-solutions-idees">Changements climatiques : trois idées innovantes pour les villes</a></li></li></ul></p></div></p></p></p></p></p></p></p> <p>En entrevue avec Radio-Canada, Gérard Jean s’est montré optimiste, disant avoir senti « une grande ouverture » de la part de Mme Melançon.<p>M. Jean rappelle que les municipalités attendent une réponse positive depuis près de quatre ans. Dans la négative, les municipalités pourraient envisager des mesures judiciaires.<p><strong>Un précédent</strong><p>Le 18 février, la municipalité de <a href="/nouvelle/1086340/ristigouche-sud-est-gastem-deboutee">Ristigouche Sud-Est a gagné son procès face à la pétrolière Gastem</a>. Le droit de protéger les sources d'eau potable de ses citoyens lui a été reconnu par la Cour supérieure. Selon le maire de Ristigouche Sud-Est, François Boulay, ce jugement permet d'« appuyer les municipalités » face à un règlement qu'il juge actuellement « inadéquat ».<blockquote><p class="quote">Comme nous l’avons vu dans le jugement de Ristigouche, le principe de précaution appartient aux municipalités. C’est nous qui sommes redevables aux citoyens.<footer>Gérard Jean, maire de Lanoraie</footer></p></blockquote><p>Selon M. Jean, ce jugement « renforce » la position des municipalités dans ce dossier. « Je pense que le gouvernement a pris bonne note de ça », a-t-il ajouté.<p>Vendredi, Mme Melançon s’est prononcée <a href="/nouvelle/1091246/environnement-energies-fossiles-ministre-isabelle-melancon-exploitation-environnement">contre les projets d'exploitation de pétrole et de gaz naturel</a> sur le territoire de la province.<br/><br/><div class="framed"><p><strong>À lire aussi :</strong><ul><li><a href="../" title="http://ici.radio-canada.ca/nouvelle/1059037/exploitation-petrole-philippe-couillard-rassurant">Exploitation pétrolière : Philippe Couillard se veut rassurant</a><li><a href="/nouvelle/1089162/climat-changements-climatiques-villes-solutions-idees">Changements climatiques : trois idées innovantes pour les villes</a></li></li></ul></p></div></p></p></p></p></p></p> <p>M. Jean rappelle que les municipalités attendent une réponse positive depuis près de quatre ans. Dans la négative, les municipalités pourraient envisager des mesures judiciaires.<p><strong>Un précédent</strong><p>Le 18 février, la municipalité de <a href="/nouvelle/1086340/ristigouche-sud-est-gastem-deboutee">Ristigouche Sud-Est a gagné son procès face à la pétrolière Gastem</a>. Le droit de protéger les sources d'eau potable de ses citoyens lui a été reconnu par la Cour supérieure. Selon le maire de Ristigouche Sud-Est, François Boulay, ce jugement permet d'« appuyer les municipalités » face à un règlement qu'il juge actuellement « inadéquat ».<blockquote><p class="quote">Comme nous l’avons vu dans le jugement de Ristigouche, le principe de précaution appartient aux municipalités. C’est nous qui sommes redevables aux citoyens.<footer>Gérard Jean, maire de Lanoraie</footer></p></blockquote><p>Selon M. Jean, ce jugement « renforce » la position des municipalités dans ce dossier. « Je pense que le gouvernement a pris bonne note de ça », a-t-il ajouté.<p>Vendredi, Mme Melançon s’est prononcée <a href="/nouvelle/1091246/environnement-energies-fossiles-ministre-isabelle-melancon-exploitation-environnement">contre les projets d'exploitation de pétrole et de gaz naturel</a> sur le territoire de la province.<br/><br/><div class="framed"><p><strong>À lire aussi :</strong><ul><li><a href="../" title="http://ici.radio-canada.ca/nouvelle/1059037/exploitation-petrole-philippe-couillard-rassurant">Exploitation pétrolière : Philippe Couillard se veut rassurant</a><li><a href="/nouvelle/1089162/climat-changements-climatiques-villes-solutions-idees">Changements climatiques : trois idées innovantes pour les villes</a></li></li></ul></p></div></p></p></p></p></p> <p><strong>Un précédent</strong><p>Le 18 février, la municipalité de <a href="/nouvelle/1086340/ristigouche-sud-est-gastem-deboutee">Ristigouche Sud-Est a gagné son procès face à la pétrolière Gastem</a>. Le droit de protéger les sources d'eau potable de ses citoyens lui a été reconnu par la Cour supérieure. Selon le maire de Ristigouche Sud-Est, François Boulay, ce jugement permet d'« appuyer les municipalités » face à un règlement qu'il juge actuellement « inadéquat ».<blockquote><p class="quote">Comme nous l’avons vu dans le jugement de Ristigouche, le principe de précaution appartient aux municipalités. C’est nous qui sommes redevables aux citoyens.<footer>Gérard Jean, maire de Lanoraie</footer></p></blockquote><p>Selon M. Jean, ce jugement « renforce » la position des municipalités dans ce dossier. « Je pense que le gouvernement a pris bonne note de ça », a-t-il ajouté.<p>Vendredi, Mme Melançon s’est prononcée <a href="/nouvelle/1091246/environnement-energies-fossiles-ministre-isabelle-melancon-exploitation-environnement">contre les projets d'exploitation de pétrole et de gaz naturel</a> sur le territoire de la province.<br/><br/><div class="framed"><p><strong>À lire aussi :</strong><ul><li><a href="../" title="http://ici.radio-canada.ca/nouvelle/1059037/exploitation-petrole-philippe-couillard-rassurant">Exploitation pétrolière : Philippe Couillard se veut rassurant</a><li><a href="/nouvelle/1089162/climat-changements-climatiques-villes-solutions-idees">Changements climatiques : trois idées innovantes pour les villes</a></li></li></ul></p></div></p></p></p></p> <p>Le 18 février, la municipalité de <a href="/nouvelle/1086340/ristigouche-sud-est-gastem-deboutee">Ristigouche Sud-Est a gagné son procès face à la pétrolière Gastem</a>. Le droit de protéger les sources d'eau potable de ses citoyens lui a été reconnu par la Cour supérieure. Selon le maire de Ristigouche Sud-Est, François Boulay, ce jugement permet d'« appuyer les municipalités » face à un règlement qu'il juge actuellement « inadéquat ».<blockquote><p class="quote">Comme nous l’avons vu dans le jugement de Ristigouche, le principe de précaution appartient aux municipalités. C’est nous qui sommes redevables aux citoyens.<footer>Gérard Jean, maire de Lanoraie</footer></p></blockquote><p>Selon M. Jean, ce jugement « renforce » la position des municipalités dans ce dossier. « Je pense que le gouvernement a pris bonne note de ça », a-t-il ajouté.<p>Vendredi, Mme Melançon s’est prononcée <a href="/nouvelle/1091246/environnement-energies-fossiles-ministre-isabelle-melancon-exploitation-environnement">contre les projets d'exploitation de pétrole et de gaz naturel</a> sur le territoire de la province.<br/><br/><div class="framed"><p><strong>À lire aussi :</strong><ul><li><a href="../" title="http://ici.radio-canada.ca/nouvelle/1059037/exploitation-petrole-philippe-couillard-rassurant">Exploitation pétrolière : Philippe Couillard se veut rassurant</a><li><a href="/nouvelle/1089162/climat-changements-climatiques-villes-solutions-idees">Changements climatiques : trois idées innovantes pour les villes</a></li></li></ul></p></div></p></p></p> <p class="quote">Comme nous l’avons vu dans le jugement de Ristigouche, le principe de précaution appartient aux municipalités. C’est nous qui sommes redevables aux citoyens.<footer>Gérard Jean, maire de Lanoraie</footer></p> <p>Selon M. Jean, ce jugement « renforce » la position des municipalités dans ce dossier. « Je pense que le gouvernement a pris bonne note de ça », a-t-il ajouté.<p>Vendredi, Mme Melançon s’est prononcée <a href="/nouvelle/1091246/environnement-energies-fossiles-ministre-isabelle-melancon-exploitation-environnement">contre les projets d'exploitation de pétrole et de gaz naturel</a> sur le territoire de la province.<br/><br/><div class="framed"><p><strong>À lire aussi :</strong><ul><li><a href="../" title="http://ici.radio-canada.ca/nouvelle/1059037/exploitation-petrole-philippe-couillard-rassurant">Exploitation pétrolière : Philippe Couillard se veut rassurant</a><li><a href="/nouvelle/1089162/climat-changements-climatiques-villes-solutions-idees">Changements climatiques : trois idées innovantes pour les villes</a></li></li></ul></p></div></p></p> <p>Vendredi, Mme Melançon s’est prononcée <a href="/nouvelle/1091246/environnement-energies-fossiles-ministre-isabelle-melancon-exploitation-environnement">contre les projets d'exploitation de pétrole et de gaz naturel</a> sur le territoire de la province.<br/><br/><div class="framed"><p><strong>À lire aussi :</strong><ul><li><a href="../" title="http://ici.radio-canada.ca/nouvelle/1059037/exploitation-petrole-philippe-couillard-rassurant">Exploitation pétrolière : Philippe Couillard se veut rassurant</a><li><a href="/nouvelle/1089162/climat-changements-climatiques-villes-solutions-idees">Changements climatiques : trois idées innovantes pour les villes</a></li></li></ul></p></div></p> <p><strong>À lire aussi :</strong><ul><li><a href="../" title="http://ici.radio-canada.ca/nouvelle/1059037/exploitation-petrole-philippe-couillard-rassurant">Exploitation pétrolière : Philippe Couillard se veut rassurant</a><li><a href="/nouvelle/1089162/climat-changements-climatiques-villes-solutions-idees">Changements climatiques : trois idées innovantes pour les villes</a></li></li></ul></p> <p aria-hidden="true" class="ticker">Armes à feu<h3>La jeunesse américaine se mobilise en masse contre les armes à feu</h3></p> <p aria-hidden="true" class="ticker">Criminalité<h3>Attentat en France : hommage national en l’honneur du gendarme Beltrame</h3></p> <p aria-hidden="true" class="ticker">Hockey<h3>Alexis Lafrenière : l’éclatante entrée en scène d’un surdoué</h3></p> <p aria-hidden="true" class="ticker">Catalogne : vers une rupture avec l’Espagne?<h3>Carles Puigdemont évite l'arrestation en Finlande</h3></p> <p aria-hidden="true" class="ticker">Emploi<h3>Immigration : le silence de la CAQ</h3></p> <p class="text">Veuillez noter que Radio-Canada ne cautionne pas les opinions exprimées. Vos commentaires seront modérés, et publiés s'ils respectent <a href="http://ici.radio-canada.ca/apropos/conditionsutilisation/netiquette/">la nétiquette</a>. Bonne discussion!<div class="component viafoura-commentaire bunker-component" data-articleid="commments--1641051734" data-bind="appearsOnScreenOnce: appearsOnScreenOnceHandler,css:(showOnMobile() == 'True' && forceMobile())?'':'hidden-mobile hidden-mobile-xs hidden-tablet-lg hidden-tablet-md'" data-component-name="ViaFourra" data-lazyload="False" data-loadonclick="True" data-path="http://v4.radio-canada.ca/nouvelle/1091279/protection-eau-potable-municipalites-quebec-tribunal-melancon-ristigouche-lanoraie" data-showonmobile="True"><div class="button-show-more-comments" data-bind="visible:forceMobile" style="display:none"><button data-bind="click:clickHandlerShowComment, text: textButton()">Afficher les commentaires</button></div><div class="button-show-more-comments" data-bind="visible:!forceMobile()" style="display:none"><button data-bind="click:clickHandlerShowComment, text: textButton()">Afficher les commentaires</button></div><div id="pluckComments-0"><a id="commenter" name="commenter"></a></div></div></p> <p class="kicker"><span class="info-feed-content-tag" data-component-name="InfoFeedContentTag">Industrie pétrolière</span>....

Обнаружив это Зваздочка начала переделывать вывод контента у себя в JS, а я естесственно начал менять у себя. Поскольку я считал, что я являюсь поставщиком контента для фронтенда, поэтому даже если я полцчаю из БД «грязный» текст, я должен его очистить и выдавать наружу уже очищенный от HTML мусора текст.

Хорошо что нам в данном случае не нужно было тело статей, как вы видите формат хранения весьма неожиданно включает мало того что HTML разметку и стили CSS инлайн, но и вызовы JS функций. Я бы не решился называть это API, а уж тем более публичным API. Но имеем что имеем и для нашего прототипа нам достаточно побороть title и summary, для этого по быстрому была создана функция подчищающая HTML мусор и в дальнейшем производящая необходимое форматирование других данных.

Форматируем данные из БД перед тем как отдать на фронтенд

private function decorate($article) {
        return [
            'video_id'=>$article['article_id'],
            'category'=>$article['region'],
            'title'=> html_entity_decode($article['title']),
            'description'=>strip_tags(html_entity_decode($article['summary'])),
            'image'=>$article['images'],
            'rating'=>round(10*floatval ($article['sentiment']),0),
            'link'=>$article['link'],
            'tags'=>$this->decorateTags($article['keyphrases'])
        ];
    }

Время тикает. Все в работе и отладке. Все части прототипа выглядят функционирующими. Команда чувствует эмоциональный подъем.

Но, как и было известно с самого начала к нашей поисковой строке надо как-то прикрутить ИИ!

Итак у нас есть «эмоциональная оценка» и «ключвые слова» которые вернул нам всемогущий ИИ. Но как нам показать важностьи нужность этих опций при поиске? Должны ли мы сортировать результаты поиска по рейтингу позитивности? Что делать с ключевыми словами? Тем более что из-за восхитительного «грязного» контента, ключевые слова выглядят, например вот так:

municipalités du Québec,signes positifs,gouvernement provincial,nbsp,sources d'eau potable,démarches judiciaires,lois,unanimité,représentants,normes,résolution,tribunaux

Обратите внимание на ключевое слово «nbsp» ;-) Хотя, надо отдать должное ИИ, HTML тэги он в качестве ключевых слов вроде не возвращал.

На ходу было принято решение, что, независимо от того как мы будем использовать эти дополнительные данные анализа, мне точно надо отдавать их вместе с каждой записью, как вы уже видели это в функции чуть выше.

Для визуализации я предложил использовать псевдографический прогресс бар из каких-нибудь символов. Просто потому что не хотел чтобы ребята тратили время на поиск каких-то плагинов, тем более что как я уже сказал они работали с голым JS даже без jQuery, так что я сомневался что они смогут что-то быстро найти и прикрутить.

По поводу ключевых слов придумали превратить их в ссылки хэштэги к Твитеру. Сначала казалось что у Твиттера не будет контента на любое произвольное слово или словосочетание, но, после нескольких тестов, оказалось что идея вполне работоспособная.

Таким образом на этом этапе решили что мы просто расширим и обогатим результаты поиска оценкой позитивности и линками-хэштэгами.

Как-то стремительно и незаметно подошло время ужина. Несмотря на то что все были очень сильно погружены в работу, мы все с удовольствием оторвались от кода и пошли в комнату где для участников принесли пайки :-) Точно меню уже не вспомню, но ужин снова был холодным, что меня немного огорчило. И как я уже говорил с кофе тоже была напряженка.

Во время ужина мы обсудили планы на окончание дня, нам все еще предстояло многое отладить. Я предложил если у нас будет возможность добавить в наш плагин возможность фильтрации по уровню позитивности. Типа если пользователь не хочет сегодня читать «плохие» новости, то он может поднять минимальный уровень позитивности при поиске и мы будем выдавать ему отфильтрованные результаты. Ребята согласились попробовать, поскольку функции у них были разделены. Звездочка в основном занималась дизайном и версткой, а Меркурий взаимодействием с моим PHP бэкендом. И эта часть была как бы закончена и работала и мы с ним могли вполне успеть добавить один два новых запроса.

С Платоном обсудили необходимость обработки большего количества данных. Он объяснил, что 100-120 записей, которые мы уже получили — это просто она категория. И что у него есть возможность вытащить еще несколько категорий и соответственно прогнать все через ИИ и выгрузить в БД. На том и порешили.

Итак после ужина мы все вернулись к работе. Но я попросил Звездочку купить кофе.

Не сочтите за рекламу, но скорее национальный колорит

У нас в Канаде есть сеть кофейн TimHortons [21]. Это канадская франшиза.
Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 11

Пока мы сюда не приехали я о них никогда не слышал. Но тут это практически один из символов Канады. В целом, в общественном мнении, все согласны, что самый качественный кофе в Starbucks, но Timhortons — это не только качественный кофе, но и поддержка своего отечественного канадского производителя. Плюс пончики (donuts) это тоже визитная карточка этой франшизы.

Очень много народа начинает свой день именно в TimHortons. Люди заезжают туда по дороге на работу за кофе или кофе с пончиками или даже за полноценным завтраком, какой-нибудь омлет с беконом.
Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 12

Почему я решил рассказать о TimHortons? Потому что у них есть большая коробка кофе, я никак не могу запомнить название в меню. Но суть в том что вам выдается картонная коробка с горячим кофе на 12 человек, плюс стаканчики с крышечками, плюс сахар и сливки, плюс палочки для размешивания. Этот формат очень удобен для всяких посиделок на природе или просто в компании. Картонный термос вполне прилично держит температуру.
Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 13

И когда Звездочка вернулась с волшебным горячим напиком у нас открылось второе дыхание.

В течении последующих 3-4 часов мы интенсивно работали стараясь создать что-то симпатичное. Для динамичного поиска я сделал два различных запроса в зависимости от количества символов поиска приходящих ко мне на бэкенд в запросе. Поскольку я знал, что полнотекстовый поиск плохо работает с короткими словами, а многие буквосочетания попросту игнорирует, то я решил что для поиска до 3х символов я буду ипользовать обычный поиск по БД LIKE, а когда строка поиска станет длиннее, то буду выполнять другой запрос с MATCH. Это позволит нам избежать пустых ответов с бэкенда, для более красивой презентации.

Не судите строго

public function searchRange( $min, $max, $term) {
        $pdo = DB::connection()->getPdo();
        if(strlen($term)>3){
            $query = "SELECT * from articles WHERE 
                        ROUND(sentiment,1) >= :min AND ROUND(sentiment,1) <= :max
                        AND images != '0'
                        AND MATCH (title, summary, body, keyphrases) AGAINST (:term  IN NATURAL LANGUAGE MODE) LIMIT 50;";
            $stmt = $pdo->prepare($query);
            $stmt->bindValue('min',round(intval($min)/10,2));
            $stmt->bindValue('max',round(intval($max)/10,2));
            $stmt->bindParam('term',$term);
        } else {
            $query = "SELECT * from articles WHERE 
                        ROUND(sentiment,1) >= :min AND ROUND(sentiment,1) <= :max
                        AND images != '0'
                         AND (title LIKE :term
                         OR summary LIKE :term1)
                      LIMIT 50;";
            $stmt = $pdo->prepare($query);
            $stmt->bindValue('min',round(intval($min)/10,2));
            $stmt->bindValue('max',round(intval($max)/10,2));
            $stmt->bindValue('term',"%$term%");
            $stmt->bindValue('term1',"%$term%");
        }
        $stmt->execute();
        $res = $stmt->fetchAll();
        $ret = [];
        foreach($res as $article) {
            $ret[] = $this->decorate($article);
        }
        return response()->json($ret,200)->header('Access-Control-Allow-Origin',"*");
}

Но мы все уже испытывали радость и, наверное, даже гордость от того что решение вообще работало. У нас был полноценный прототип, который работал согласно нашей задумке. Он соответствовал всем критериям объявленным организаторами. Нам всем этого было более чем достаточно.

Под самый конец дня, Платон предоставил примерно 1200 записей в таблицу по 80 категориям/регионам. Для беглого тестирования и демонстрации результат выглядел, как минимум, рабочим.

Есть ли порох в пороховницах? Hackathon Radio Canada 2018 (Часть третья — На Старт! Внимание! Марш!) - 14

Завершая очередную часть своего повествования я хочу извиниться, во-первых, за задержку (прошел почти месяц с даты публикации второй части [2]), поверьте я искренне пытался писать быстрее. За это время я успел, потерять работу, найти новую и изучить, хоть и бегло ELK stack (Elasticsearch, Logstash, Kibana, Filebeat). Думаю следующая статья будет именно об Elastic.

Когда я начинал писать эту 3-ю часть я был уверен, что она станет заключительной. Я переоценил свои силы и недооценил количество материала. Наверное с опытом я научусь излагать как-то более кратко. Прошу принять во внимание что в данном случае трудно определить что именно заинтересует читателя. Это все же не совсем техническая статья, а обо всем понемногу. Но в тоже время хочется показать и техническую сторону происходящего.

О чуть не забыл, вы можете скачать и установить наш Chrome plugin [22] правда только в режиме разработчика. Вы можете просмотреть код перед установкой, слава богу, там практически нечего и читать-то.

Я очень постараюсь не задерживаться с заключительной частью с описанием финального дня, проектов финалистов и общими выводами которые я для себя сделал.

Автор: dzsysop

Источник [23]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/azure/279372

Ссылки в тексте:

[1] первой: https://habrahabr.ru/post/352774/

[2] второй: https://habrahabr.ru/post/352874/

[3] Patrick Lévesque: https://www.linkedin.com/in/patrick-l%C3%A9vesque-5036933/

[4] тут: https://www.agorize.com/fr/challenges/radio-canada-2018/pages/les-horaires?lang=fr

[5] Matthieu Dugal: https://www.linkedin.com/in/matthieu-dugal-38b61260/

[6] Chloe Sondervorst: https://www.linkedin.com/in/sondervorst/

[7] Maxime St-Pierre: https://www.linkedin.com/in/maximestpierre/

[8] Cambridge Analytica: https://www.cnbc.com/2018/03/21/facebook-cambridge-analytica-scandal-everything-you-need-to-know.html

[9] список призов: https://www.agorize.com/fr/challenges/radio-canada-2018?lang=fr

[10] менторов и помощников: https://youtu.be/lpBz7seSCM0?t=18m35s

[11] каждую команду участницу: https://youtu.be/lpBz7seSCM0?t=23m20s

[12] страницу Radio Canada: https://ici.radio-canada.ca/

[13] сделать простейшее расширение: https://developer.chrome.com/extensions/getstarted

[14] Laravel: https://laravel.com/

[15] обратили внимание: https://youtu.be/lpBz7seSCM0?t=1h16m23s

[16] API Lineup: https://services.radio-canada.ca/neuro/help

[17] ext Analytics: https://azure.microsoft.com/en-us/services/cognitive-services/text-analytics/

[18] официальному гиду: https://laravel.com/docs/5.6/installation

[19] NVDA: https://www.nvaccess.org/

[20] киноа: https://ru.wikipedia.org/wiki/%D0%9A%D0%B8%D0%BD%D0%BE%D0%B0

[21] TimHortons: http://www.timhortons.com/ca/en/index.php

[22] Chrome plugin: https://github.com/mikarini/RCextension/tree/master/Extension

[23] Источник: https://habr.com/post/353058/?utm_source=habrahabr&utm_medium=rss&utm_campaign=353058