Каким по счёту ты родился
Представь очередь. В ней все, кто когда-либо жил на Земле — от первого человека до ребёнка, родившегося минуту назад. Очередь длиной примерно в сто семнадцать миллиардов. Где-то в ней стоишь ты. Место у тебя есть с рождения, его никто не отнимет и не отдаст другому. Просто до сих пор никто его не называл.

Я наткнулся на это случайно.Нашёл у демографов из Population Reference Bureau: к 2022 году за всю историю родилось примерно 117 миллиардов человек. Живых сейчас восемь с третью. Остальные без малого сто девять — это и есть история человечества.
Раз всех можно посчитать, у каждого есть номер. Порядковый. Его надо просто назвать.
Как считается номер
Номер зависит только от даты рождения. Одна и та же дата всегда даёт одно и то же число — хоть на сервере, хоть на бумаге. Никто номер не выдаёт: его можно посчитать для любой даты, даже для той, которая ещё не наступила. У человека, который родится через год, номер уже есть — просто пока некому его назвать.
Внутри — таблица из пятидесяти точек: к такому-то году родилось столько-то людей суммарно. Прошлое — тридцать восемь точек — из одной публикации демографов 2022 года. Ещё двенадцать уходят в будущее, до 3400 года: это уже не данные, а проекция с предположением о снижающейся рождаемости, и в коде так и написано. Расставлены неровно: на 1950–2022 приходится двадцать девять, на все предыдущие двести тысяч лет — девять. Про недавних людей известно куда больше, чем про остальных.

Между соседними точками считаем по прямой:
function interpolate(year: number): number {
for (let i = 0; i < ANCHORS.length - 1; i++) {
const a = ANCHORS[i]!, b = ANCHORS[i + 1]!;
if (year >= a.year && year <= b.year) {
const t = (year - a.year) / (b.year - a.year);
return a.cumulative + t * (b.cumulative - a.cumulative);
}
}
return 0;
}
Можно было вести плавную кривую. Не стал: между густыми современными точками разницы почти нет, а между редкими древними кривая нарисует изгибы, которых никто не измерял. Прямая честно отдаёт то, что написано в таблице.
Внутри года место определяет доля прошедших дней:
const fraction = dayOfYear / daysInYear(year);
const number = Math.round(yearStart + fraction * (yearEnd - yearStart));
Чем дальше дата от двадцатого века, тем хуже мы знаем ответ, и сайт пишет об этом рядом с номером:
после 1900 → разброс в тысячу
раньше 1900 → разброс в 50 миллионов
раньше 1500 → разброс в 500 миллионов
до нашей эры → разброс в 2 миллиарда
Две таблицы, склеенные в одну. Сначала древние века я взял из старой публикации демографов, а итог — из новой. Числа же про одно и то же. Но десять миллиардов древних рождений, которых в старой не было, никуда не делись — они распределились по ближайшим точкам, то есть по годам с 1990 по 2026. Получалось, что сейчас рождается до 550 миллионов человек в год при настоящих ста тридцати двух.
Пришлось собрать таблицу заново, целиком по одной публикации. Счётчик при этом один раз уменьшился на четверть миллиарда.
Год «50» оказался 1950-м. В JavaScript есть стандартный способ собрать дату из года, месяца и дня. Если дать ему год 50, он поймёт это как 1950-й, а не как пятидесятый год нашей эры: так решили в девяностых, и с тех пор это живёт в языке.
// НЕ Date.UTC(year, 0, 1): для годов 0–99 Date.UTC маппит их в 1900–1999
const yearStart0 = new Date(0);
yearStart0.setUTCFullYear(year, 0, 1);
Анонимность
Номер сам по себе — красивая цифра. Чтобы она что-то значила, вокруг неё нужны люди, занятые чем-то вместе. Любой участник задаёт вопрос про то, что решится на днях, реестр отвечает, а когда жизнь даёт свой ответ — видно, как думали люди.
Чтобы это работало, ответы должны быть анонимными не на словах. «Мы не передаём ваши данные» — это политика, а политику отменяют одним решением. Мне хотелось, чтобы анонимность держала сама база.
CREATE TABLE votes ( -- ответы
poll_id UUID NOT NULL, -- на какой вопрос
option_id UUID NOT NULL, -- какой вариант выбран
country_code CHAR(2) NOT NULL,
voted_at TIMESTAMPTZ NOT NULL
);
CREATE TABLE poll_participations ( -- кто уже отвечал
user_id UUID NOT NULL,
poll_id UUID NOT NULL,
PRIMARY KEY (user_id, poll_id)
);
В таблице ответов нет колонки с человеком. Не пустая — её там нет. Во второй человек есть, но нет выбранного варианта: она нужна, только чтобы нельзя было ответить дважды. Она знает, что ты участвовал, но не знает, что ты выбрал. Внутри этих двух таблиц связать одно с другим нечем — кроме одной нити, о которой ниже. В настоящей таблице ответов есть ещё код континента; ничего, что указывает на человека, там нет.
Ещё время ответа при записи сдвигается на случайные секунды в пределах полуминуты. Иначе два ответа, пришедшие в одну секунду, совпали бы по времени с двумя записями об участии, и анонимность развалилась бы через часы. Со стороны участия то же: дата участия хранится с точностью до дня.
Сами таблицы закрыты от обычных ролей базы. Наружу торчат функции и одно представление, которые умеют посчитать и вернуть итог, но не умеют отдать строки.
Порог, который выглядел защитой. Разрез по странам открывается, только если в стране ответили хотя бы пятьдесят человек — иначе получается: «в маленькой стране ответили трое, и один из них ты».
Дыра видна, если написать её числами: сорок девять против одного. Порог пройден, а единственный человек выделен полностью. Пришлось добавить второе условие — хотя бы пять ответов в каждом варианте.
WHERE ct.total >= 50 AND ct.min_bucket >= 5;
Порог на общей сумме не защищает меньшинство внутри суммы. Очевидно, когда скажешь вслух, и незаметно, пока написано одним неравенством.
Граница, которую называю сам
Я не говорю, что связь человека с ответом «невосстановима». Это слишком громкое слово для любого сайта, у которого есть сервер.
Внутри базы одна нить всё же есть, и я назову её сам. Запись об участии и запись ответа рождаются в одной транзакции, а Postgres помечает обе строки её номером. Пока этот номер не перезаписан обслуживанием таблиц, человек с правами суперпользователя может сложить их вместе. Снаружи остаются логи платформы, на которой крутится сайт: обычные записи веб-сервера о запросах. Я ими для такого сопоставления не пользуюсь, но сказать «невозможно» не могу. Вот и вся граница.
Так честнее, чем написать «полная анонимность» и надеяться, что не спросят.
Ставки без денег
На свой ответ можно поставить баллы (на спортивных — и без ответа). На обычных вопросах коэффициент начинается с равных шансов, а когда ответов набирается пятьдесят, складывается из того, как ответили остальные. На спортивных беру его с открытого рынка — пока руками, автомат для этого написан, но ещё не включён — и убираю заработок букмекера: у любой конторы сумма шансов чуть больше ста процентов, эта добавка и есть её доход. Я возвращаю сумму к ста, чтобы число измеряло мнение, а не чужую прибыль. Денег в этом нет: баллы не переводятся между людьми и не превращаются в приз.
Ещё формулировка вопроса и условие исхода замерзают после первого ответа. Человек отвечал на конкретный вопрос с конкретным правилом — менять их потом значит переписывать договор с ним. Условие исхода стережёт триггер в самой базе; формулировку — единственная функция, через которую её можно править. Интерфейс тут ни при чём.
Письма тем, кто ещё не родился
Написать письмо в будущее можно много где. А конкретному человеку, который ещё не родился, было нельзя: непонятно, кому. Теперь у будущего человека есть адрес — его номер.
Письмо адресуется номеру, а не почте или имени, и ждёт своей даты. Если номер за год после неё так и не заняли, письмо истекает — вечных писем я не обещаю. Это единственная часть сайта, где номер нужен не тебе, а кому-то другому.

Про номера у людей
Мне уже говорили: номера у людей — так себе идея. Я так не думаю. Номер ничего не заменяет и не отнимает, ни имени, ни лица. Он называет место, которое у тебя есть с первого дня. Мы считаем года. Считаем историю. Считаем население. Странно было бы не посчитать людей.
Свой номер можно посмотреть по дате рождения: papanumbers.com. Из России сайт открывается через VPN — сервер за блокировкой, зеркала пока нет.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.