html
SOLPDT 1.1 | APPENDIX E
AMORTIZATION REFERENCE | NMA ACCOUNTING

Приложение E: Амортизация нематериальных активов
Amortization Reference

Стандарт: SOLPDT 1.1 — Agentic Trust Layer Extension

E.1. Назначение

Настоящее приложение содержит референсную имплементацию механизмов амортизации нематериальных активов (НМА) в рамках стандарта SOLPDT 1.1. Документ предназначен для:

  • Разработчиков смарт-контрактов, реализующих автоматический расчёт остаточной стоимости
  • Интеграторов ERP-систем, синхронизирующих данные с блокчейном
  • Аудиторов, проверяющих корректность начисления амортизации
  • Бухгалтеров, формирующих отчётность по ФСБУ 14/2022 и IAS 38

В документе приведены математические формулы, примеры кода на Rust (для смарт-контрактов Solana) и JavaScript (для off-chain вычислений), а также правила интеграции с другими компонентами SOLPDT.

E.2. Методы амортизации

Стандарт SOLPDT 1.1 поддерживает три метода начисления амортизации, определённые в поле amortization_method блока accounting_asset_details (см. Приложение D).

E.2.1. Линейный метод (straight_line)

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

Ежемесячная амортизация = (Первоначальная стоимость - Ликвидационная стоимость) / СПИ (месяцев)

Остаточная стоимость через N месяцев:

Накопленная амортизация = Ежемесячная амортизация × N
Остаточная стоимость = Первоначальная стоимость - Накопленная амортизация

E.2.2. Метод уменьшаемого остатка (declining_balance)

Ускоренная амортизация, при которой в первые периоды списывается большая сумма.

Годовая норма = (1 / СПИ (лет)) × Коэффициент ускорения
Ежегодная амортизация = Остаточная стоимость на начало года × Годовая норма
Примечание: Коэффициент ускорения устанавливается учётной политикой организации. Для цифровых активов в SOLPDT рекомендуется значение 2.0.

E.2.3. Пропорционально объёму (units_of_production)

Амортизация начисляется исходя из фактического использования актива.

Амортизация за период = (Первоначальная стоимость - Ликвидационная стоимость) × (Фактический объём за период / Ожидаемый общий объём)
Примечание: В контексте SOLPDT «объём» может измеряться количеством успешных транзакций, обработанных ИИ-агентом.

E.3. Расчёт амортизации в среде блокчейна

E.3.1. Особенности блокчейн-времени

В сети Solana время измеряется в Unix-секундах, доступных через системный вызов Clock::get()?.unix_timestamp. Использование временных меток (вместо номера слота) гарантирует детерминированный расчёт амортизации независимо от пропусков слотов или форков.

Важные константы:

КонстантаЗначение (секунды)Примечание
SECONDS_PER_MINUTE60
SECONDS_PER_HOUR3 600
SECONDS_PER_DAY86 400
SECONDS_PER_MONTH2 629 74330.44 дня (365.25 / 12)
SECONDS_PER_YEAR31 556 926365.25 дней

E.3.2. Линейная амортизация через Unix-время

Общий срок в секундах = СПИ (месяцев) × SECONDS_PER_MONTH
Прошедшее время = Текущее Unix-время - Время начала амортизации

Если Прошедшее время ≥ Общий срок в секундах:
    Остаточная стоимость = Ликвидационная стоимость
Иначе:
    Накопленная амортизация = (Первоначальная стоимость - Ликвидационная стоимость) × (Прошедшее время / Общий срок в секундах)
    Остаточная стоимость = Первоначальная стоимость - Накопленная амортизация

E.4. Референсная имплементация на Rust (Solana / Anchor)

E.4.1. Константы и структуры данных

use anchor_lang::prelude::*;

// Константы времени
pub const SECONDS_PER_MONTH: i64 = 2_629_743;  // 30.44 дня
pub const SECONDS_PER_YEAR: i64 = 31_556_926;  // 365.25 дней

// Методы амортизации
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq)]
pub enum AmortizationMethod {
    StraightLine,
    DecliningBalance { acceleration_factor: u8 },  // 1-3, типично 2
    UnitsOfProduction,
}

// Структура учёта амортизации
#[account]
#[derive(Default)]
pub struct AmortizationAccount {
    pub initial_cost: u64,              // Первоначальная стоимость (в минимальных единицах)
    pub residual_value: u64,            // Ликвидационная стоимость
    pub useful_life_months: u64,        // СПИ в месяцах
    pub method: AmortizationMethod,     // Метод амортизации
    pub start_timestamp: i64,           // Unix-время начала амортизации
    pub last_update_timestamp: i64,     // Время последнего обновления
    pub accumulated_amortization: u64,  // Накопленная амортизация
    pub current_nbv: u64,               // Текущая остаточная стоимость
    pub total_units_expected: u64,      // Ожидаемый объём (для units_of_production)
    pub total_units_processed: u64,     // Обработанный объём
}

E.4.2. Функция расчёта линейной амортизации

/// Рассчитывает текущую остаточную стоимость по линейному методу.
/// 
/// # Аргументы
/// * `initial_cost` - первоначальная стоимость
/// * `residual_value` - ликвидационная стоимость
/// * `useful_life_months` - срок полезного использования в месяцах
/// * `start_timestamp` - Unix-время начала амортизации
/// * `current_timestamp` - текущее Unix-время
///
/// # Возвращает
/// Текущую остаточную стоимость (Net Book Value)
pub fn calculate_straight_line_nbv(
    initial_cost: u64,
    residual_value: u64,
    useful_life_months: u64,
    start_timestamp: i64,
    current_timestamp: i64,
) -> u64 {
    // Проверка входных данных
    if current_timestamp <= start_timestamp {
        return initial_cost;
    }
    
    let total_life_seconds: u64 = useful_life_months
        .checked_mul(SECONDS_PER_MONTH as u64)
        .unwrap_or(u64::MAX);
    
    let elapsed_seconds = (current_timestamp - start_timestamp) as u64;
    
    // Если срок истёк — возвращаем ликвидационную стоимость
    if elapsed_seconds >= total_life_seconds {
        return residual_value;
    }
    
    // Расчёт накопленной амортизации
    let depreciable_amount = initial_cost
        .checked_sub(residual_value)
        .unwrap_or(0);
    
    // Используем u128 для предотвращения переполнения при умножении
    let amortization = (depreciable_amount as u128 * elapsed_seconds as u128)
        .checked_div(total_life_seconds as u128)
        .unwrap_or(0) as u64;
    
    initial_cost.checked_sub(amortization).unwrap_or(residual_value)
}

E.4.3. Функция расчёта ускоренной амортизации

/// Рассчитывает текущую остаточную стоимость по методу уменьшаемого остатка.
/// 
/// ВНИМАНИЕ: Данный метод требует хранения истории начислений.
/// Функция возвращает амортизацию за ОДИН ПЕРИОД (год).
pub fn calculate_declining_balance_annual_amortization(
    current_nbv: u64,
    residual_value: u64,
    useful_life_years: u64,
    acceleration_factor: u8,
) -> u64 {
    if current_nbv <= residual_value {
        return 0;
    }
    
    // Годовая норма амортизации
    let base_rate: u128 = 1_000_000_000; // 100% в формате с 9 знаками после запятой
    
    let annual_rate = base_rate
        .checked_div(useful_life_years as u128)
        .unwrap_or(0)
        .checked_mul(acceleration_factor as u128)
        .unwrap_or(0);
    
    // Годовая сумма амортизации
    let depreciable_base = current_nbv.checked_sub(residual_value).unwrap_or(0);
    
    ((depreciable_base as u128 * annual_rate) / base_rate) as u64
}

E.4.4. Функция обновления состояния в смарт-контракте

/// Обновляет учётные данные амортизации.
/// Вызывается периодически или при каждом обращении к активу.
pub fn update_amortization(ctx: Context<UpdateAmortization>) -> Result<()> {
    let account = &mut ctx.accounts.amortization_account;
    let clock = Clock::get()?;
    let current_timestamp = clock.unix_timestamp;
    
    // Если время не изменилось — выходим
    if current_timestamp <= account.last_update_timestamp {
        return Ok(());
    }
    
    // Рассчитываем новую остаточную стоимость в зависимости от метода
    let new_nbv = match account.method {
        AmortizationMethod::StraightLine => {
            calculate_straight_line_nbv(
                account.initial_cost,
                account.residual_value,
                account.useful_life_months,
                account.start_timestamp,
                current_timestamp,
            )
        }
        AmortizationMethod::DecliningBalance { acceleration_factor } => {
            // Для упрощения примера — используем упрощённую логику
            let years_elapsed = ((current_timestamp - account.start_timestamp) as u64)
                .checked_div(SECONDS_PER_YEAR as u64)
                .unwrap_or(0);
            
            let mut nbv = account.initial_cost;
            for _ in 0..years_elapsed {
                let amort = calculate_declining_balance_annual_amortization(
                    nbv,
                    account.residual_value,
                    account.useful_life_months / 12,
                    acceleration_factor,
                );
                nbv = nbv.checked_sub(amort).unwrap_or(account.residual_value);
                if nbv <= account.residual_value {
                    nbv = account.residual_value;
                    break;
                }
            }
            nbv
        }
        AmortizationMethod::UnitsOfProduction => {
            account.current_nbv
        }
    };
    
    // Обновляем накопленную амортизацию
    account.accumulated_amortization = account.initial_cost
        .checked_sub(new_nbv)
        .unwrap_or(0);
    
    account.current_nbv = new_nbv;
    account.last_update_timestamp = current_timestamp;
    
    Ok(())
}

E.5. Референсная имплементация на JavaScript / TypeScript

E.5.1. Константы и вспомогательные функции

// Константы времени (секунды)
export const SECONDS_PER_MONTH = 2629743;  // 30.44 дня
export const SECONDS_PER_YEAR = 31556926;  // 365.25 дней

// Методы амортизации
export enum AmortizationMethod {
  STRAIGHT_LINE = 'straight_line',
  DECLINING_BALANCE = 'declining_balance',
  UNITS_OF_PRODUCTION = 'units_of_production',
}

// Интерфейс данных амортизации
export interface AmortizationData {
  initialCost: number;
  residualValue: number;
  usefulLifeMonths: number;
  method: AmortizationMethod;
  startDate: Date;
  accelerationFactor?: number;      // для declining_balance
  totalUnitsExpected?: number;      // для units_of_production
  unitsProcessed?: number;          // для units_of_production
}

E.5.2. Линейная амортизация

/**
 * Рассчитывает текущую остаточную стоимость по линейному методу.
 */
export function calculateStraightLineNBV(
  data: AmortizationData,
  currentDate: Date = new Date()
): number {
  const { initialCost, residualValue, usefulLifeMonths, startDate } = data;
  
  const totalLifeSeconds = usefulLifeMonths * SECONDS_PER_MONTH;
  const elapsedSeconds = Math.floor((currentDate.getTime() - startDate.getTime()) / 1000);
  
  // Срок истёк
  if (elapsedSeconds >= totalLifeSeconds) {
    return residualValue;
  }
  
  // Срок ещё не начался
  if (elapsedSeconds <= 0) {
    return initialCost;
  }
  
  const depreciableAmount = initialCost - residualValue;
  const amortization = (depreciableAmount * elapsedSeconds) / totalLifeSeconds;
  
  return initialCost - amortization;
}

E.5.3. Метод уменьшаемого остатка

/**
 * Рассчитывает амортизацию за один год по методу уменьшаемого остатка.
 */
export function calculateDecliningBalanceAnnualAmortization(
  currentNBV: number,
  residualValue: number,
  usefulLifeYears: number,
  accelerationFactor: number = 2.0
): number {
  if (currentNBV <= residualValue) {
    return 0;
  }
  
  const annualRate = (1 / usefulLifeYears) * accelerationFactor;
  const depreciableBase = currentNBV - residualValue;
  
  return Math.min(depreciableBase, depreciableBase * annualRate);
}

/**
 * Рассчитывает остаточную стоимость после N лет по методу уменьшаемого остатка.
 */
export function calculateDecliningBalanceNBV(
  data: AmortizationData,
  currentDate: Date = new Date()
): number {
  const { initialCost, residualValue, usefulLifeMonths, startDate, accelerationFactor = 2.0 } = data;
  
  const usefulLifeYears = usefulLifeMonths / 12;
  const elapsedYears = (currentDate.getTime() - startDate.getTime()) / 
                       (SECONDS_PER_YEAR * 1000);
  
  let nbv = initialCost;
  const fullYears = Math.floor(elapsedYears);
  
  for (let i = 0; i < fullYears; i++) {
    const amort = calculateDecliningBalanceAnnualAmortization(
      nbv, residualValue, usefulLifeYears, accelerationFactor
    );
    nbv = Math.max(residualValue, nbv - amort);
  }
  
  return nbv;
}

E.5.4. Пропорционально объёму

/**
 * Рассчитывает остаточную стоимость по методу пропорционально объёму.
 */
export function calculateUnitsOfProductionNBV(
  data: AmortizationData
): number {
  const { initialCost, residualValue, totalUnitsExpected = 0, unitsProcessed = 0 } = data;
  
  if (totalUnitsExpected === 0) {
    return initialCost;
  }
  
  const depreciableAmount = initialCost - residualValue;
  const amortization = (depreciableAmount * unitsProcessed) / totalUnitsExpected;
  
  return Math.max(residualValue, initialCost - amortization);
}

E.5.5. Универсальная функция расчёта

/**
 * Универсальная функция расчёта остаточной стоимости.
 */
export function calculateCurrentNBV(
  data: AmortizationData,
  currentDate: Date = new Date()
): number {
  switch (data.method) {
    case AmortizationMethod.STRAIGHT_LINE:
      return calculateStraightLineNBV(data, currentDate);
      
    case AmortizationMethod.DECLINING_BALANCE:
      return calculateDecliningBalanceNBV(data, currentDate);
      
    case AmortizationMethod.UNITS_OF_PRODUCTION:
      return calculateUnitsOfProductionNBV(data);
      
    default:
      throw new Error(`Unsupported amortization method: ${data.method}`);
  }
}

/**
 * Рассчитывает ежемесячную сумму амортизации (для линейного метода).
 */
export function getMonthlyAmortization(data: AmortizationData): number {
  if (data.method !== AmortizationMethod.STRAIGHT_LINE) {
    throw new Error('Monthly amortization is only defined for straight-line method');
  }
  
  return (data.initialCost - data.residualValue) / data.usefulLifeMonths;
}

E.5.6. Пример использования

// Пример: ИИ-агент с первоначальной стоимостью 150 000 RUB
const agentAsset: AmortizationData = {
  initialCost: 150000,
  residualValue: 0,
  usefulLifeMonths: 24,
  method: AmortizationMethod.STRAIGHT_LINE,
  startDate: new Date('2026-04-09T10:30:00Z'),
};

// Расчёт через 8 месяцев
const eightMonthsLater = new Date('2026-12-09T10:30:00Z');
const nbv = calculateCurrentNBV(agentAsset, eightMonthsLater);

console.log(`Остаточная стоимость: ${nbv.toFixed(2)} RUB`);           // 100000.00 RUB
console.log(`Ежемесячная амортизация: ${getMonthlyAmortization(agentAsset).toFixed(2)} RUB`); // 6250.00 RUB

// Накопленная амортизация
const accumulated = agentAsset.initialCost - nbv;
console.log(`Накопленная амортизация: ${accumulated.toFixed(2)} RUB`); // 50000.00 RUB

E.6. Таблицы амортизации (справочные)

E.6.1. Линейная амортизация для СПИ = 24 месяца

МесяцОстаточная стоимостьАмортизация за месяцНакопленная амортизация
0150 000,00 ₽0,00 ₽
1143 750,00 ₽6 250,00 ₽6 250,00 ₽
6112 500,00 ₽6 250,00 ₽37 500,00 ₽
1275 000,00 ₽6 250,00 ₽75 000,00 ₽
1837 500,00 ₽6 250,00 ₽112 500,00 ₽
240,00 ₽6 250,00 ₽150 000,00 ₽

E.6.2. Ускоренная амортизация (коэффициент 2.0) для СПИ = 2 года

ГодОстаточная стоимость на началоАмортизация за годОстаточная стоимость на конец
0150 000,00 ₽150 000,00 ₽
1150 000,00 ₽150 000,00 ₽0,00 ₽
Примечание: При коэффициенте 2.0 и СПИ = 2 года актив полностью самортизируется за первый год.

E.7. Интеграция с компонентами SOLPDT

E.7.1. Связь с accounting_asset_details

Поле amortization_policy в блоке accounting_asset_details (Приложение D) содержит все параметры, необходимые для расчёта:

"amortization_policy": {
  "useful_life_months": 24,
  "amortization_method": "straight_line",
  "residual_value": 0.00,
  "start_date_trigger": "first_settlement_timestamp",
  "start_date": "2026-04-09T10:30:00Z"
}

Функция calculateCurrentNBV может напрямую использовать эти данные.

E.7.2. Связь с TRL (реестр отозванного доверия)

При внесении агента в TRL:

  • Амортизация прекращается
  • Остаточная стоимость принудительно обнуляется
  • В impairment_test.last_test_result записывается "written_off"
Примечание: Подробнее — в Приложении F: Протокол списания (Write-off Protocol).

E.7.3. Связь с PDT-Score

Стандарт SOLPDT 1.1 предусматривает автоматический тест на обесценение при падении PDT-Score ниже 0.5 в течение 30 дней. В этом случае:

  • Проводится внеочередной расчёт амортизации
  • Может быть применён повышающий коэффициент износа
  • Актив может быть частично обесценен до справедливой стоимости

E.8. Проверка корректности расчётов (тестовые векторы)

Тестовый вектор 1: Линейная амортизация

ПараметрЗначение
initialCost100 000
residualValue0
usefulLifeMonths12
startDate2026-01-01T00:00:00Z
currentDate2026-07-01T00:00:00Z (ровно 6 месяцев)
Ожидаемый результат: NBV = 50 000

Тестовый вектор 2: Частичный период

ПараметрЗначение
initialCost120 000
residualValue0
usefulLifeMonths12
startDate2026-01-01T00:00:00Z
currentDate2026-01-16T00:00:00Z (15 дней = ~0.5 месяца)
Ожидаемый результат: NBV ≈ 115 000 (погрешность в пределах 1%)

Тестовый вектор 3: Истечение срока

ПараметрЗначение
initialCost50 000
residualValue5 000
usefulLifeMonths24
startDate2024-01-01T00:00:00Z
currentDate2026-01-01T00:00:00Z (24+ месяцев)
Ожидаемый результат: NBV = 5 000 (ликвидационная стоимость)

E.9. Рекомендации для разработчиков

E.9.1. Выбор точности вычислений

  • В смарт-контрактах Solana используйте u64 для сумм в минимальных единицах (лампортах, сатоши)
  • Для фиатных валют с десятичными знаками умножайте на 10^6 или 10^9 перед записью в блокчейн
  • При делении всегда проверяйте делитель на ноль

E.9.2. Оптимизация газа (compute units)

  • Функция update_amortization должна вызываться не чаще одного раза в сутки
  • Кэшируйте результат current_nbv в аккаунте и обновляйте только при изменении временного периода
  • Используйте checked_* операции для безопасной арифметики

E.9.3. Тестирование

  • Используйте мок Clock в тестах Anchor для симуляции течения времени
  • Проверяйте граничные условия: elapsed_seconds == 0, elapsed_seconds >= total_life_seconds
  • Валидируйте результаты относительно Excel / Google Sheets

КОРНЕВОЙ УЗЕЛ (СОЗДАТЕЛЬ СТАНДАРТА): Юрий Соколов (SOL Trust Network)
КОНТАКТЫ: standards@solpdt.com