Shanraq.org Shanraq.org
Модель на наших данных: число проходит сверку
IT

Python: от данных до своей сводки Урок 51 из 56

Модель на наших данных: число проходит сверку

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

Зачем это нужно

В уроке 49 мы проверили форму JSON. Ответ {"value": 17.9} может идеально соответствовать схеме и всё равно быть выдумкой. Схема знает тип числа, но не знает нашу таблицу.

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

Ещё надёжнее не разрешать модели вписывать цифры в готовую фразу. Она возвращает только словесную подпись, а проверенные числа форматирует программа.

Сразу целиком

Файл grounded.py продолжает договор прошлого урока.

"""Урок 50: сверить ответ модели с исходными данными."""

import json
from decimal import Decimal

from jsonschema import Draft202012Validator

SOURCES = {
    "inflation-kz-2025": {
        "year": 2025,
        "value": Decimal("11.4"),
        "unit": "percent",
    }
}

CLAIM_SCHEMA = {
    "type": "object",
    "properties": {
        "source_id": {"type": "string"},
        "year": {"type": "integer"},
        "value": {"type": "number"},
        "unit": {"type": "string"},
        "label": {"type": "string", "minLength": 1},
    },
    "required": ["source_id", "year", "value", "unit", "label"],
    "additionalProperties": False,
}
VALIDATOR = Draft202012Validator(CLAIM_SCHEMA)


def verify_claim(raw):
    """Проверить форму, ссылку на источник и каждое число."""
    claim = json.loads(raw, parse_float=Decimal)
    VALIDATOR.validate(claim)
    source = SOURCES.get(claim["source_id"])
    if source is None:
        raise ValueError("неизвестный источник")
    for field in ("year", "value", "unit"):
        if claim[field] != source[field]:
            raise ValueError(f"поле {field} не совпало с источником")
    if any(char.isdigit() for char in claim["label"]):
        raise ValueError("модель добавила число в подпись")
    return claim, source


raw = json.dumps({
    "source_id": "inflation-kz-2025",
    "year": 2025,
    "value": 11.4,
    "unit": "percent",
    "label": "Инфляция в Казахстане",
})
claim, source = verify_claim(raw)
text = f'{claim["label"]}: {source["value"]} % ({source["year"]}).'

print("источник:", claim["source_id"])
print("число сверено:", claim["value"] == source["value"])
print("единица сверена:", claim["unit"] == source["unit"])
print("итог:", text)

Вывод:

источник: inflation-kz-2025
число сверено: True
единица сверена: True
итог: Инфляция в Казахстане: 11.4 % (2025).

Данные получают адрес

source_id — не ссылка на весь сайт и не номер строки, который изменится после сортировки. Это устойчивый идентификатор конкретной записи. Он должен вести к версии набора данных, дате наблюдения и единице измерения.

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

Почему Decimal

JSON обычно превращает дробь в float. Для денег и точной сверки двоичная дробь неудобна. json.loads(..., parse_float=Decimal) читает 11.4 как десятичное значение, которое можно сравнить с Decimal("11.4") без допуска «примерно равно».

Это не делает число истинным — лишь исключает случайную ошибку представления. Истинность даёт совпадение с выбранным источником.

Словесная часть и числовая часть

Если модель вернула label="Инфляция выросла на 15 процентов", она уже спрятала новое число в тексте. Простое правило isdigit() отклоняет такую подпись. Оно намеренно строгое: модель формулирует название показателя, код печатает значения.

Для настоящего редакционного текста одной проверки цифр мало: модель может написать неверное «снизилась», не добавляя числа. Поэтому сейчас разрешена нейтральная подпись, а вывод о росте программа позже сможет вычислить сама из двух проверенных значений.

Отказ вместо исправления

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

Полезные причины отказа:

  • неизвестный source_id;
  • значение, год или единица не совпали;
  • цифра появилась внутри словесной подписи;
  • JSON не прошёл схему.

Карта урока

Карта урока: источник, утверждение, сверка и безопасный текст

Восстановите опорный сигнал: источник → source_id → схема → сверка полей → код подставляет число.

Скажите своими словами

  1. Почему правильной JSON Schema недостаточно для проверки факта?
  2. Зачем нужен устойчивый source_id, если значение уже есть в ответе?
  3. Почему итоговое число печатает код, а не модель?
  4. Что нужно сделать с ответом, в котором значение не совпало?

Разминка

1. Предскажите. Равны ли значения после обычного сложения float?

print(0.1 + 0.2 == 0.3)

2. Заполните пропуск. Прочитайте дроби JSON как Decimal.

claim = json.loads(raw, ...=Decimal)

3. Почините. Код доверяет числу модели, не сверяя источник.

claim = json.loads(raw)
text = f'Инфляция: {claim["value"]} %'

Задание

Обязательное. Напишите accept_many(raw_items, sources). Примите только утверждения, у которых существуют source_id и полностью совпадают year, value, unit; подпись не должна содержать цифр. Верните принятые идентификаторы и количество отказов по причинам.

принято: ['inflation-kz-2025']
неизвестный источник: 1
не совпало значение: 1
число в подписи: 1

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

По желанию. Добавьте к источнику dataset_version и SHA-256 файла. Отклоняйте ответ, если он ссылается на другую версию.

Куда это встанет в проекте

Теперь модель может предложить подпись к числу, но не может незаметно заменить само число. В уроке 51 проверим более трудную границу: правдоподобные объяснения причин, которых в данных вообще нет.

Ответы

Показать ответы
  1. Схема проверяет форму и тип, но не сравнивает ответ с нашей таблицей.
  2. Он однозначно показывает, с какой записью выполнять сверку и какую версию хранить.
  3. Тогда в публикацию попадает значение из доверенного источника, а не копия, набранная моделью.
  4. Отклонить, записать конкретную причину и не передавать дальше.
False
  1. Аргумент — parse_float.
import json
from decimal import Decimal

raw = '{"value": 11.4}'
claim = json.loads(raw, parse_float=Decimal)
print(claim["value"] == Decimal("11.4"))
True
  1. Найдите источник, сравните поля и печатайте значение источника.
claim = {"source_id": "cpi-2025", "value": 11.4}
sources = {"cpi-2025": {"value": 11.4}}
source = sources[claim["source_id"]]
assert claim["value"] == source["value"]
print(f'Инфляция: {source["value"]} %')
Инфляция: 11.4 %

Источники

Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом

Проверить задание

Сначала решите и запустите в VS Code — редактор покажет ошибку на месте. Готовое решение вставьте сюда. Проверяет модель: она укажет на ошибку, но не даст готовый ответ.

Чтобы проверить, нужно войти. Войти

Комментарии (0)

Пока нет комментариев. Будьте первым.