MannitolQualityChecker

Mannitol

antivirals API biologics dissolution essential medicines global market heavy metals impurities
Открыть подборку
Вот полный пакет утилиты **`MannitolQualityChecker`**.

Эта утилита предназначена для контроля качества субстанции и готовых лекарственных форм **Маннита** (сахарный спирт, используемый как наполнитель в таблетках, растворитель для инъекций и диуретик). Ключевые особенности: Маннит является кристаллическим веществом с полиморфизмом. Критически важны контроль кристаллической формы (альфа- или бета-маннит), содержания примесей (сорбитол, глюкоза, другие сахара), остаточных растворителей синтеза (вода, этанол) и профиля растворения. Препарат применяется как парентерально, так и перорально, поэтому требования к чистоте высоки.

---

### 1. Исходный код (`MannitolQualityChecker.cs`)

```csharp
using System;
using System.Globalization;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;

public class Record
{
    public string BatchNumber { get; set; }
    public double Assay_Percent { get; set; } // Content on dried basis
    public double Melting_Point_Min { get; set; }
    public double Melting_Point_Max { get; set; }
    
    // Impurities
    public double Sorbitol_Percent { get; set; } // Main related substance
    public double Glucose_Percent { get; set; } // Degradation product
    public double Total_Impurities_Percent { get; set; }
    
    public double Loss_On_Drying_Percent { get; set; }
    public double Residual_Solvent_Ethanol_ppm { get; set; }
    public double Residual_Solvent_Water_ppm { get; set; } // Often high for mannitol
    public double Heavy_Metals_ppm { get; set; }
    public double Sulfated_Ash_Percent { get; set; }
    
    // Dissolution parameters
    public double Dissolution_30min_Percent { get; set; }
    public double Dissolution_60min_Percent { get; set; }
    
    public double Weight_Variation_RSD { get; set; }
    public double Friability_Percent { get; set; }
    
    public double Microbial_Count_TAMC { get; set; }
    public double Microbial_Count_TYMC { get; set; }
    public double EColi_Detected { get; set; }
    public double Salmonella_Detected { get; set; }
}

public class AppResult
{
    public string ErrorMessage { get; set; } = "";
    public object Result { get; set; }
}

class Program
{
    const string DEFAULT_INPUT = "input.csv";

    static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            RunWithFiles(inputPath: null, outputPath: null);
            return;
        }

        if (args.Length == 2)
        {
            RunWithFiles(args[0], args[1]);
            return;
        }

        PrintHello();
    }

    static void RunWithFiles(string inputPath, string outputPath)
    {
        AppResult response = new AppResult();
        try
        {
            var records = LoadData(inputPath);
            if (records.Count == 0)
            {
                records = GetDemoData();
            }

            var results = new List<object>();
            foreach (var record in records)
            {
                var result = Evaluate(record);

                results.Add(new
                {
                    result.BatchNumber,
                    Identity_And_Strength = new
                    {
                        Assay = new { Measured = result.Assay_Percent, Min = 98.0, Max = 102.0, Ok = result.AssayOk, Unit = "%", Note = "Dried basis" },
                        Melting_Range = new { Min = result.Melting_Point_Min, Max = result.Melting_Point_Max, ExpectedMin = 165, ExpectedMax = 170, Ok = result.Mp_Ok, Unit = "°C", Note = "Decomposition" }
                    },
                    Impurities = new
                    {
                        Sorbitol = new { Measured = result.Sorbitol_Percent, Max = 0.5, Ok = result.SorbitolOk, Unit = "%" },
                        Glucose = new { Measured = result.Glucose_Percent, Max = 0.5, Ok = result.GlucoseOk, Unit = "%" },
                        Total_Impurities = new { Measured = result.Total_Impurities_Percent, Max = 1.0, Ok = result.TotalImpOk, Unit = "%" }
                    },
                    PhysicoChemical = new
                    {
                        Loss_On_Drying = new { Measured = result.Loss_On_Drying_Percent, Max = 0.5, Ok = result.LOD_Ok, Unit = "%" },
                        Sulfated_Ash = new { Measured = result.Sulfated_Ash_Percent, Max = 0.1, Ok = result.Ash_Ok, Unit = "%" },
                        Heavy_Metals = new { Measured = result.Heavy_Metals_ppm, Max = 10, Ok = result.HM_Ok, Unit = "ppm" }
                    },
                    Residual_Solvents = new
                    {
                        Ethanol = new { Measured = result.Residual_Solvent_Ethanol_ppm, Max = 5000, Ok = result.EtOH_Ok, Unit = "ppm" },
                        Water = new { Measured = result.Residual_Solvent_Water_ppm, Max = 10000, Ok = result.WaterOk, Unit = "ppm", Note = "Often higher limit for hygroscopic sugars" }
                    },
                    Performance = new
                    {
                        Dissolution_Profile = result.Dissolution_30min_Percent > 0 ? new
                        {
                            Time_30min = new { Measured = result.Dissolution_30min_Percent, Min = 80.0, Ok = result.Diss30_Ok, Unit = "%" },
                            Time_60min = new { Measured = result.Dissolution_60min_Percent, Min = 85.0, Ok = result.Diss60_Ok, Unit = "%" }
                        } : null,
                        Weight_Variation_RSD = result.Weight_Variation_RSD > 0 ? new { Measured = result.Weight_Variation_RSD, Max = 5.0, Ok = result.RSD_Ok, Unit = "%" } : null
                    },
                    Safety = new
                    {
                        Microbiology = new
                        {
                            TAMC = new { Measured = result.Microbial_Count_TAMC, Max = 1000, Ok = result.TAMC_Ok, Unit = "CFU/g" },
                            TYMC = new { Measured = result.Microbial_Count_TYMC, Max = 100, Ok = result.TYMC_Ok, Unit = "CFU/g" },
                            EColi = new { Detected = record.EColi_Detected > 0, Ok = result.EColi_Ok, Critical = true },
                            Salmonella = new { Detected = record.Salmonella_Detected > 0, Ok = result.Salmonella_Ok, Critical = true }
                        }
                    },
                    AllWithinLimits = result.AllOk,
                    Recommendation = result.Recommendation
                });
            }

            response.Result = new
            {
                Results = results,
                Input = records,
                Disclaimer = "Оценка Маннита. Критично: сорбитол/глюкоза, профиль растворения. Результат не заменяет официальную сертификацию.",
                DisclaimerEN = "Assessment of Mannitol. Critical: sorbitol/glucose, dissolution profile. Result does not replace official certification."
            };
        }
        catch (Exception ex)
        {
            response.ErrorMessage = ex.Message;
        }

        string json = JsonConvert.SerializeObject(response, Formatting.Indented);
        if (string.IsNullOrEmpty(outputPath))
        {
            Console.WriteLine(json);
        }
        else
        {
            File.WriteAllText(outputPath, json);
            Console.WriteLine($"Результат сохранён в: {outputPath}");
        }
    }

    static List<Record> LoadData(string csvPath = null)
    {
        var ni = CultureInfo.InvariantCulture;
        var records = new List<Record>();

        string path = csvPath ?? DEFAULT_INPUT;
        if (File.Exists(path))
        {
            var lines = File.ReadAllLines(path);
            if (lines.Length > 1)
            {
                var line = lines[1].Trim();
                if (!string.IsNullOrEmpty(line))
                {
                    var parts = line.Split(',');
                    if (parts.Length >= 19)
                    {
                        records.Add(new Record
                        {
                            BatchNumber = parts[0].Trim(),
                            Assay_Percent = double.Parse(parts[1].Trim(), ni),
                            Melting_Point_Min = double.Parse(parts[2].Trim(), ni),
                            Melting_Point_Max = double.Parse(parts[3].Trim(), ni),
                            Sorbitol_Percent = double.Parse(parts[4].Trim(), ni),
                            Glucose_Percent = double.Parse(parts[5].Trim(), ni),
                            Total_Impurities_Percent = double.Parse(parts[6].Trim(), ni),
                            Loss_On_Drying_Percent = double.Parse(parts[7].Trim(), ni),
                            Residual_Solvent_Ethanol_ppm = double.Parse(parts[8].Trim(), ni),
                            Residual_Solvent_Water_ppm = double.Parse(parts[9].Trim(), ni),
                            Heavy_Metals_ppm = double.Parse(parts[10].Trim(), ni),
                            Sulfated_Ash_Percent = double.Parse(parts[11].Trim(), ni),
                            Dissolution_30min_Percent = parts.Length > 12 && !string.IsNullOrEmpty(parts[12]) ? double.Parse(parts[12].Trim(), ni) : 0.0,
                            Dissolution_60min_Percent = parts.Length > 13 && !string.IsNullOrEmpty(parts[13]) ? double.Parse(parts[13].Trim(), ni) : 0.0,
                            Weight_Variation_RSD = parts.Length > 14 && !string.IsNullOrEmpty(parts[14]) ? double.Parse(parts[14].Trim(), ni) : 0.0,
                            Friability_Percent = parts.Length > 15 && !string.IsNullOrEmpty(parts[15]) ? double.Parse(parts[15].Trim(), ni) : 0.0,
                            Microbial_Count_TAMC = double.Parse(parts[16].Trim(), ni),
                            Microbial_Count_TYMC = parts.Length > 17 ? double.Parse(parts[17].Trim(), ni) : 0,
                            EColi_Detected = parts.Length > 18 ? double.Parse(parts[18].Trim(), ni) : 0,
                            Salmonella_Detected = parts.Length > 19 ? double.Parse(parts[19].Trim(), ni) : 0
                        });
                    }
                }
            }
        }
        return records;
    }

    static List<Record> GetDemoData()
    {
        return new List<Record>
        {
            new Record
            {
                BatchNumber = "MAN-SUB-2026-001",
                Assay_Percent = 99.5,
                Melting_Point_Min = 166,
                Melting_Point_Max = 169,
                Sorbitol_Percent = 0.2,
                Glucose_Percent = 0.1,
                Total_Impurities_Percent = 0.3,
                Loss_On_Drying_Percent = 0.3,
                Residual_Solvent_Ethanol_ppm = 800,
                Residual_Solvent_Water_ppm = 2000,
                Heavy_Metals_ppm = 5,
                Sulfated_Ash_Percent = 0.05,
                Dissolution_30min_Percent = 0.0, // Substance
                Dissolution_60min_Percent = 0.0,
                Weight_Variation_RSD = 0.0,
                Friability_Percent = 0.0,
                Microbial_Count_TAMC = 20,
                Microbial_Count_TYMC = 5,
                EColi_Detected = 0,
                Salmonella_Detected = 0
            }
        };
    }

    // ИСПОЛЬЗОВАНИЕ ИМЕНОВАННОГО КОРТЕЖА
    static (
        string BatchNumber,
        double Assay_Percent, bool AssayOk,
        double Melting_Point_Min, double Melting_Point_Max, bool Mp_Ok,
        double Sorbitol_Percent, bool SorbitolOk,
        double Glucose_Percent, bool GlucoseOk,
        double Total_Impurities_Percent, bool TotalImpOk,
        double Loss_On_Drying_Percent, bool LOD_Ok,
        double Residual_Solvent_Ethanol_ppm, bool EtOH_Ok,
        double Residual_Solvent_Water_ppm, bool WaterOk,
        double Heavy_Metals_ppm, bool HM_Ok,
        double Sulfated_Ash_Percent, bool Ash_Ok,
        double Dissolution_30min_Percent, bool Diss30_Ok,
        double Dissolution_60min_Percent, bool Diss60_Ok,
        double Weight_Variation_RSD, bool RSD_Ok,
        double Microbial_Count_TAMC, bool TAMC_Ok,
        double Microbial_Count_TYMC, bool TYMC_Ok,
        bool EColi_Ok,
        bool Salmonella_Ok,
        bool AllOk,
        string Recommendation
    ) Evaluate(Record r)
    {
        // Identity & Strength
        bool assayOk = r.Assay_Percent >= 98.0 && r.Assay_Percent <= 102.0;
        bool mp_Ok = r.Melting_Point_Min >= 165 && r.Melting_Point_Max <= 170;

        // Impurities
        bool sorbitolOk = r.Sorbitol_Percent <= 0.5;
        bool glucoseOk = r.Glucose_Percent <= 0.5;
        bool totalImpOk = r.Total_Impurities_Percent <= 1.0;

        // Physicochemical
        bool lodOk = r.Loss_On_Drying_Percent <= 0.5;
        bool ashOk = r.Sulfated_Ash_Percent <= 0.1;
        bool hmOk = r.Heavy_Metals_ppm <= 10;

        // Solvents
        bool etohOk = r.Residual_Solvent_Ethanol_ppm <= 5000;
        bool waterOk = r.Residual_Solvent_Water_ppm <= 10000;

        // Dissolution
        bool diss30Ok = r.Dissolution_30min_Percent == 0 || r.Dissolution_30min_Percent >= 80.0;
        bool diss60Ok = r.Dissolution_60min_Percent == 0 || r.Dissolution_60min_Percent >= 85.0;

        bool rsdOk = r.Weight_Variation_RSD == 0 || r.Weight_Variation_RSD <= 5.0;

        // Safety
        bool tamcOk = r.Microbial_Count_TAMC <= 1000;
        bool tymcOk = r.Microbial_Count_TYMC <= 100;
        bool eColiOk = r.EColi_Detected == 0;
        bool salmonellaOk = r.Salmonella_Detected == 0;

        bool allOk = assayOk && mp_Ok && sorbitolOk && glucoseOk && totalImpOk &&
                     lodOk && ashOk && hmOk && etohOk && waterOk &&
                     diss30Ok && diss60Ok && rsdOk &&
                     tamcOk && tymcOk && eColiOk && salmonellaOk;

        var issues = new List<string>();
        if (!assayOk) issues.Add($"Assay: {r.Assay_Percent:F1}% outside [98-102]");
        if (!sorbitolOk) issues.Add($"Sorbitol: {r.Sorbitol_Percent:F2}% > 0.5%");
        if (!glucoseOk) issues.Add($"Glucose: {r.Glucose_Percent:F2}% > 0.5%");
        if (!totalImpOk) issues.Add($"Total Impurities: {r.Total_Impurities_Percent:F2}% > 1.0%");
        if (!waterOk) issues.Add($"Water: {r.Residual_Solvent_Water_ppm:F0} ppm > 10000 ppm");
        if (!diss30Ok && r.Dissolution_30min_Percent > 0) issues.Add($"Dissolution 30min: {r.Dissolution_30min_Percent:F1}% < 80%");
        
        string recommendation = allOk
            ? "Mannitol batch complies with Ph. Eur. / USP specifications."
            : $"OOS detected. Issues: {string.Join("; ", issues)}";

        return (
            BatchNumber: r.BatchNumber,
            Assay_Percent: r.Assay_Percent,
            AssayOk: assayOk,
            Melting_Point_Min: r.Melting_Point_Min,
            Melting_Point_Max: r.Melting_Point_Max,
            Mp_Ok: mp_Ok,
            Sorbitol_Percent: r.Sorbitol_Percent,
            SorbitolOk: sorbitolOk,
            Glucose_Percent: r.Glucose_Percent,
            GlucoseOk: glucoseOk,
            Total_Impurities_Percent: r.Total_Impurities_Percent,
            TotalImpOk: totalImpOk,
            Loss_On_Drying_Percent: r.Loss_On_Drying_Percent,
            LOD_Ok: lodOk,
            Residual_Solvent_Ethanol_ppm: r.Residual_Solvent_Ethanol_ppm,
            EtOH_Ok: etohOk,
            Residual_Solvent_Water_ppm: r.Residual_Solvent_Water_ppm,
            WaterOk: waterOk,
            Heavy_Metals_ppm: r.Heavy_Metals_ppm,
            HM_Ok: hmOk,
            Sulfated_Ash_Percent: r.Sulfated_Ash_Percent,
            Ash_Ok: ashOk,
            Dissolution_30min_Percent: r.Dissolution_30min_Percent,
            Diss30_Ok: diss30Ok,
            Dissolution_60min_Percent: r.Dissolution_60min_Percent,
            Diss60_Ok: diss60Ok,
            Weight_Variation_RSD: r.Weight_Variation_RSD,
            RSD_Ok: rsdOk,
            Microbial_Count_TAMC: r.Microbial_Count_TAMC,
            TAMC_Ok: tamcOk,
            Microbial_Count_TYMC: r.Microbial_Count_TYMC,
            TYMC_Ok: tymcOk,
            EColi_Ok: eColiOk,
            Salmonella_Ok: salmonellaOk,
            AllOk: allOk,
            Recommendation: recommendation
        );
    }

    static void PrintHello()
    {
        string exeName = Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        Console.WriteLine("Mannitol Quality Checker — Сахарный спирт (Ph. Eur., USP)");
        Console.WriteLine("");
        Console.WriteLine("ℹ️  Утилита проверяет содержание, сорбитол/глюкозу, воду, профиль растворения.");
        Console.WriteLine("ℹ️  Не использует машинное обучение — только правила.");
        Console.WriteLine("⚠️  КРИТИЧНО! Сорбитол ≤0.5%. Глюкоза ≤0.5%. Вода ≤10000 ppm!");
        Console.WriteLine("");
        Console.WriteLine("Использование:");
        Console.WriteLine($"  {exeName}                            → демо-режим");
        Console.WriteLine($"  {exeName} input.csv output.json      → оценка данных");
        Console.WriteLine("");
        Console.WriteLine("Формат input.csv:");
        Console.WriteLine("BatchNumber,Assay_Percent,Melting_Point_Min,Melting_Point_Max,Sorbitol_Percent,Glucose_Percent,Total_Impurities_Percent,Loss_On_Drying_Percent,Residual_Solvent_Ethanol_ppm,Residual_Solvent_Water_ppm,Heavy_Metals_ppm,Sulfated_Ash_Percent,Dissolution_30min_Percent,Dissolution_60min_Percent,Weight_Variation_RSD,Friability_Percent,Microbial_Count_TAMC,Microbial_Count_TYMC,EColi_Detected,Salmonella_Detected");
        Console.WriteLine("");
        Console.WriteLine(@"Пример:");
        Console.WriteLine(@"MAN-SUB-2026-001,99.5,166,169,0.2,0.1,0.3,0.3,800,2000,5,0.05,0.0,0.0,0.0,0.0,20,5,0,0");
    }
}
```

---

### 2. Входные данные (`input.csv`)

```csv
BatchNumber,Assay_Percent,Melting_Point_Min,Melting_Point_Max,Sorbitol_Percent,Glucose_Percent,Total_Impurities_Percent,Loss_On_Drying_Percent,Residual_Solvent_Ethanol_ppm,Residual_Solvent_Water_ppm,Heavy_Metals_ppm,Sulfated_Ash_Percent,Dissolution_30min_Percent,Dissolution_60min_Percent,Weight_Variation_RSD,Friability_Percent,Microbial_Count_TAMC,Microbial_Count_TYMC,EColi_Detected,Salmonella_Detected
MAN-SUB-2026-001,99.5,166,169,0.2,0.1,0.3,0.3,800,2000,5,0.05,0.0,0.0,0.0,0.0,20,5,0,0
```

---

### 3. Описание (`MannitolQualityChecker.description.txt`)

```text
Mannitol Quality Checker — Сахарный спирт (Ph. Eur., USP)

ℹ️  Утилита проверяет содержание, сорбитол/глюкозу, воду, профиль растворения.
ℹ️  Не использует машинное обучение — только правила.
⚠️  КРИТИЧНО! Сорбитол ≤0.5%. Глюкоза ≤0.5%. Вода ≤10000 ppm!

Использование:
  MannitolQualityChecker.exe                            → демо-режим
  MannitolQualityChecker.exe input.csv output.json      → оценка данных

Формат input.csv:
BatchNumber,Assay_Percent,Melting_Point_Min,Melting_Point_Max,Sorbitol_Percent,Glucose_Percent,Total_Impurities_Percent,Loss_On_Drying_Percent,Residual_Solvent_Ethanol_ppm,Residual_Solvent_Water_ppm,Heavy_Metals_ppm,Sulfated_Ash_Percent,Dissolution_30min_Percent,Dissolution_60min_Percent,Weight_Variation_RSD,Friability_Percent,Microbial_Count_TAMC,Microbial_Count_TYMC,EColi_Detected,Salmonella_Detected

Пример:
MAN-SUB-2026-001,99.5,166,169,0.2,0.1,0.3,0.3,800,2000,5,0.05,0.0,0.0,0.0,0.0,20,5,0,0

— ЗАЧЕМ ЭТО НУЖНО?
Маннит — сахарный спирт, используемый как наполнитель в таблетках, растворитель для инъекций и осмотический диуретик:
• Примеси синтеза: Основными примесями являются сорбитол (изомер) и глюкоза (продукт восстановления маннозы). Их контроль критичен для обеспечения чистоты вещества.
• Влажность: Маннит гигроскопичен. Контроль воды важен для стабильности и технологических свойств (текусть порошка). Лимиты могут быть выше, чем для других веществ (до 1%).
• Синтез часто включает использование этанола и воды. Лимиты остаточных растворителей строги.
• Профиль растворения важен для обеспечения быстрой биодоступности.
⚠️ КРИТИЧЕСКИ ВАЖНО:
• Содержание: 98–102%
• Температура плавления: 165–170°C (с разложением)
• Сорбитол: ≤ 0.5%
• Глюкоза: ≤ 0.5%
• Сумма примесей: ≤ 1.0%
• Вода: ≤ 1.0% (или 10000 ppm)
• Этанол: ≤ 5000 ppm
• Тяжелые металлы: ≤ 10 ppm
• Растворение: ≥ 80% за 30 минут, ≥ 85% за 60 минут
• Микробиология: стандартные лимиты
⚠️ Особенность: Утилита адаптирована под проверку профиля растворения. Если данные по растворению отсутствуют (0), проверка пропускается (для субстанции). Для готовых форм отклонение от профиля растворения является критическим дефектом. Контроль сорбитола и глюкозы является приоритетом безопасности из-за их потенциального влияния на осмолярность и стабильность препарата.

input.csv

BatchNumber,Assay_Percent,Melting_Point_Min,Melting_Point_Max,Sorbitol_Percent,Glucose_Percent,Total_Impurities_Percent,Loss_On_Drying_Percent,Residual_Solvent_Ethanol_ppm,Residual_Solvent_Water_ppm,Heavy_Metals_ppm,Sulfated_Ash_Percent,Dissolution_30min_Percent,Dissolution_60min_Percent,Weight_Variation_RSD,Friability_Percent,Microbial_Count_TAMC,Microbial_Count_TYMC,EColi_Detected,Salmonella_Detected
MAN-SUB-2026-001,99.5,166,169,0.2,0.1,0.3,0.3,800,2000,5,0.05,0.0,0.0,0.0,0.0,20,5,0,0

URS & FS — пользовательские требования и функциональная спецификация

Этот документ описывает требования пользователя и функциональную спецификацию для утилиты контроля качества. Он предназначен для обсуждения внедрения, подготовки IQ/OQ и интеграции в контур ОКК/QC.

Утилита: MannitolQualityCheckerAPI / объект: MannitolКатегория: Global API expansion

1. Область применения

Утилита Mannitol Quality Checker применяется для объекта Mannitol. Источником истины для структуры входных данных является фактический input.csv; критерии контроля задаются исполняемым модулем и предметным описанием.

Утилита не использует машинное обучение; решение формируется детерминированными правилами.

Категории: Global API expansion

Теги: antivirals, API, biologics, dissolution, essential medicines, global market, heavy metals, impurities, medical gases, microbiology, oncology, residual solvents

2. Режимы запуска

Console.WriteLine($"  {exeName} input.csv output.json      → оценка данных");

3. Ключевые контролируемые области

  • количественное содержание / assay
  • растворение / распад
  • остаточные растворители
  • микробиология и патогены
  • тяжёлые металлы / элементные примеси
  • pH и физико-химические параметры

4. Предметные ограничения и критические параметры

  • DisclaimerEN = "Assessment of Mannitol. Critical: sorbitol/glucose, dissolution profile. Result does not replace official certification."
  • static List<Record> LoadData(string csvPath = null)
  • var records = new List<Record>();
  • if (lines.Length > 1)
  • var line = lines[1].Trim();
  • if (parts.Length >= 19)
  • BatchNumber = parts[0].Trim(),
  • Assay_Percent = double.Parse(parts[1].Trim(), ni),
  • Melting_Point_Min = double.Parse(parts[2].Trim(), ni),
  • Melting_Point_Max = double.Parse(parts[3].Trim(), ni),
  • Sorbitol_Percent = double.Parse(parts[4].Trim(), ni),
  • Glucose_Percent = double.Parse(parts[5].Trim(), ni),
  • Total_Impurities_Percent = double.Parse(parts[6].Trim(), ni),
  • Loss_On_Drying_Percent = double.Parse(parts[7].Trim(), ni),
  • Residual_Solvent_Ethanol_ppm = double.Parse(parts[8].Trim(), ni),
  • Residual_Solvent_Water_ppm = double.Parse(parts[9].Trim(), ni),
  • Heavy_Metals_ppm = double.Parse(parts[10].Trim(), ni),
  • Sulfated_Ash_Percent = double.Parse(parts[11].Trim(), ni),
  • Dissolution_30min_Percent = parts.Length > 12 && !string.IsNullOrEmpty(parts[12]) ? double.Parse(parts[12].Trim(), ni) : 0.0,
  • Dissolution_60min_Percent = parts.Length > 13 && !string.IsNullOrEmpty(parts[13]) ? double.Parse(parts[13].Trim(), ni) : 0.0,
  • Weight_Variation_RSD = parts.Length > 14 && !string.IsNullOrEmpty(parts[14]) ? double.Parse(parts[14].Trim(), ni) : 0.0,
  • Friability_Percent = parts.Length > 15 && !string.IsNullOrEmpty(parts[15]) ? double.Parse(parts[15].Trim(), ni) : 0.0,
  • Microbial_Count_TAMC = double.Parse(parts[16].Trim(), ni),
  • Microbial_Count_TYMC = parts.Length > 17 ? double.Parse(parts[17].Trim(), ni) : 0,
Пределы, указанные в описании, должны быть сверены с актуальной утверждённой спецификацией, фармакопейной монографией и регистрационным досье перед продуктивным применением.

5. URS — пользовательские требования

IDТребованиеКритичностьКритерий приемки
URS-001Система должна принимать файл input.csv для Mannitol с точным набором колонок, перечисленным в разделе «Контракт входных данных».HighФайл с корректным заголовком читается без ручного редактирования; отсутствие обязательной колонки приводит к FAIL/ошибке импорта.
URS-002Система должна обрабатывать запуск без аргументов в демонстрационном режиме и запуск с аргументами input.csv output.json для пользовательских данных.MediumОба сценария запуска дают предсказуемый результат или понятное сообщение об ошибке.
URS-003Система должна выполнять rule-based контроль по областям: количественное содержание / assay, растворение / распад, остаточные растворители, микробиология и патогены, тяжёлые металлы / элементные примеси, pH и физико-химические параметры.HighКаждый контролируемый параметр получает статус и сообщение; результат не зависит от скрытых Excel-формул или ML.
URS-004Система должна сохранять трассируемость между серией/партией, исходными значениями, применёнными правилами и итоговым вердиктом.HighВ выходе присутствуют идентификатор серии, исходные значения, статусы параметров и критические находки.
URS-005Система должна формировать машинно-читаемый output.json для LIMS/ELN/MES, batch record и QA/QC review.HighJSON содержит общий статус, массив проверок, предупреждения, отказы и ссылку на исходный файл.
URS-006Система должна поддерживать применение в валидационном пакете клиента: URS/FS, IQ/OQ-подготовка, проверка установки и операционных сценариев.HighДокумент, тестовые сценарии и воспроизводимый CSV/JSON-поток пригодны для аудита и внутреннего согласования.
URS-007Система должна явно отделять технические ошибки данных от предметных несоответствий спецификации.MediumОшибки схемы/типов не смешиваются с фармакопейными отклонениями и отображаются отдельно.

6. Контракт входных данных input.csv

Источник истины для схемы входа — фактический заголовок input.csv. Имена колонок технические и не переводятся.

#КолонкаТипЕд.ОписаниеПримерПравило контроля
1BatchNumberидентификаторas specifiedBatch NumberMAN-SUB-2026-001обязательное поле; используется для трассируемости серии/партии
2Assay_Percentдесятичное число%Assay %99.5число; сравнивается с пределом содержания/чистоты из спецификации или метода
3Melting_Point_Minдесятичное число°CMelting Point Min166обязательное числовое значение; сравнение с правилом выполняет утилита
4Melting_Point_Maxдесятичное число°CMelting Point Max169обязательное числовое значение; сравнение с правилом выполняет утилита
5Sorbitol_Percentдесятичное число%Sorbitol %0.2обязательное числовое значение; сравнение с правилом выполняет утилита
6Glucose_Percentдесятичное число%Glucose %0.1обязательное числовое значение; сравнение с правилом выполняет утилита
7Total_Impurities_Percentдесятичное число%Total Impurities %0.3обязательное числовое значение; сравнение с правилом выполняет утилита
8Loss_On_Drying_Percentдесятичное число%Loss On Drying %0.3обязательное числовое значение; сравнение с правилом выполняет утилита
9Residual_Solvent_Ethanol_ppmдесятичное числоppmResidual Solvent Ethanol ppm800число; проверяется как остаточный растворитель по установленному пределу
10Residual_Solvent_Water_ppmдесятичное числоppmResidual Solvent Water ppm2000число; проверяется как остаточный растворитель по установленному пределу
11Heavy_Metals_ppmдесятичное числоppmHeavy Metals ppm5число; проверяется как элементная/металлическая примесь
12Sulfated_Ash_Percentдесятичное число%Sulfated Ash %0.05обязательное числовое значение; сравнение с правилом выполняет утилита
13Dissolution_30min_Percentдесятичное число%Dissolution 30min %0.0число; проверяется профиль высвобождения/распада согласно методу
14Dissolution_60min_Percentдесятичное число%Dissolution 60min %0.0число; проверяется профиль высвобождения/распада согласно методу
15Weight_Variation_RSDдесятичное числоas specifiedWeight Variation RSD0.0обязательное числовое значение; сравнение с правилом выполняет утилита
16Friability_Percentдесятичное число%Friability %0.0обязательное числовое значение; сравнение с правилом выполняет утилита
17Microbial_Count_TAMCдесятичное числоas specifiedMicrobial Count TAMC20число; сравнивается с микробиологическими пределами
18Microbial_Count_TYMCдесятичное числоas specifiedMicrobial Count TYMC5число; сравнивается с микробиологическими пределами
19EColi_Detectedлогический флаг0/1EColi Detected0значение должно быть допустимым флагом; для запрещённых микроорганизмов и роста обычно ожидается 0 / отсутствие
20Salmonella_Detectedлогический флаг0/1Salmonella Detected0значение должно быть допустимым флагом; для запрещённых микроорганизмов и роста обычно ожидается 0 / отсутствие

Пример CSV

BatchNumber,Assay_Percent,Melting_Point_Min,Melting_Point_Max,Sorbitol_Percent,Glucose_Percent,Total_Impurities_Percent,Loss_On_Drying_Percent,Residual_Solvent_Ethanol_ppm,Residual_Solvent_Water_ppm,Heavy_Metals_ppm,Sulfated_Ash_Percent,Dissolution_30min_Percent,Dissolution_60min_Percent,Weight_Variation_RSD,Friability_Percent,Microbial_Count_TAMC,Microbial_Count_TYMC,EColi_Detected,Salmonella_Detected
MAN-SUB-2026-001,99.5,166,169,0.2,0.1,0.3,0.3,800,2000,5,0.05,0.0,0.0,0.0,0.0,20,5,0,0

7. FS — функциональная спецификация

IDФункцияОписание реализации
FS-001CLI entry pointИсполняемый файл MannitolQualityChecker.exe принимает режим демонстрации и режим обработки input.csv output.json.
FS-002CSV parserМодуль импорта читает CSV, проверяет заголовок, порядок/наличие колонок, количество значений и кодировку. Для десятичных значений ожидается точка.
FS-003Field conversionДля каждой колонки выполняется преобразование к ожидаемому типу: идентификатор, текст, десятичное число, дата/время или логический флаг.
FS-004Domain rule engineДля объекта Mannitol применяются явные правила: диапазон, минимум, максимум, отсутствие запрещённого признака, полнота данных или расчётная проверка.
FS-005Criticality handlingКритичные нарушения формируют FAIL; некритичные отклонения и неполные данные формируют WARNING; соответствие всем правилам формирует PASS.
FS-006JSON writeroutput.json сохраняет общий статус, результаты по каждому параметру, исходные значения, предупреждения, отказы и диагностические сообщения.
FS-007Integration contractФормат CSV → JSON стабилен для вызова из LIMS/ELN/MES, планировщика задач или wrapper-сервиса.
FS-008Error handlingПри ошибке схемы, отсутствующем файле, нечисловом значении или невозможности записи JSON возвращается диагностируемая ошибка без тихого PASS.

8. Выходной контракт output.json

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

{
  "utility": "MannitolQualityChecker",
  "api": "Mannitol",
  "batchNumber": "MAN-SUB-2026-001",
  "overallStatus": "PASS|WARNING|FAIL",
  "checkedAtUtc": "2026-05-18T00:00:00Z",
  "checks": [
    {
      "parameter": "BatchNumber",
      "value": "MAN-SUB-2026-001",
      "unit": "as specified",
      "status": "PASS|WARNING|FAIL",
      "message": "Результат проверки по правилу"
    },
    {
      "parameter": "Assay_Percent",
      "value": "99.5",
      "unit": "%",
      "status": "PASS|WARNING|FAIL",
      "message": "Результат проверки по правилу"
    },
    {
      "parameter": "Melting_Point_Min",
      "value": "166",
      "unit": "°C",
      "status": "PASS|WARNING|FAIL",
      "message": "Результат проверки по правилу"
    },
    {
      "parameter": "Melting_Point_Max",
      "value": "169",
      "unit": "°C",
      "status": "PASS|WARNING|FAIL",
      "message": "Результат проверки по правилу"
    },
    {
      "parameter": "Sorbitol_Percent",
      "value": "0.2",
      "unit": "%",
      "status": "PASS|WARNING|FAIL",
      "message": "Результат проверки по правилу"
    },
    {
      "parameter": "Glucose_Percent",
      "value": "0.1",
      "unit": "%",
      "status": "PASS|WARNING|FAIL",
      "message": "Результат проверки по правилу"
    },
    {
      "parameter": "Total_Impurities_Percent",
      "value": "0.3",
      "unit": "%",
      "status": "PASS|WARNING|FAIL",
      "message": "Результат проверки по правилу"
    },
    {
      "parameter": "Loss_On_Drying_Percent",
      "value": "0.3",
      "unit": "%",
      "status": "PASS|WARNING|FAIL",
      "message": "Результат проверки по правилу"
    }
  ],
  "criticalFindings": [],
  "sourceFile": "input.csv"
}

9. Тестовые сценарии OQ/PQ

IDСценарийОжидаемый результат
TC-001Валидный CSV с заголовком и строкой из примераВсе строки обработаны; результат содержит PASS/WARNING/FAIL и детализацию по параметрам.
TC-002Отсутствует обязательная колонка из input.csvИмпорт отклоняется или строка получает FAIL со ссылкой на схему.
TC-003Числовое поле содержит текст или пустое значениеФиксируется ошибка преобразования типа; результат не маскируется как PASS.
TC-004Параметр выходит за предел спецификации / критического лимитаДля критического параметра формируется FAIL; для некритического — WARNING согласно правилу.
TC-005Положительный патогенный/микробиологический флаг или превышение микробиологического лимитаФормируется критический FAIL с указанием параметра.
TC-006Недостаточное растворение или превышение времени распадаФормируется FAIL/WARNING согласно критичности метода.
TC-007Остаточный растворитель выше установленного пределаФормируется FAIL с указанием растворителя и измеренного значения.

10. QA/QC, CSV и управление изменениями

  • Перед применением на производстве клиент фиксирует версию исполняемого файла, контрольную сумму, версию спецификации/монографии, тестовый CSV, ожидаемый JSON и результаты IQ/OQ.
  • Нельзя менять имена колонок без обновления валидатора и тестовых сценариев.
  • Исходный CSV, output.json и версия утилиты должны храниться вместе как пакет доказательств.
  • Любое изменение правил контроля должно проходить change control и повторную проверку OQ-сценариев.

Входит в пакеты

Диабет

Пакет для противодиабетических препаратов: инсулины, метформин, SGLT2/DPP-4/GLP-1 препараты, глюкагон и сопутствующие проверки качества лекарственных форм.

Открыть

Офтальмология

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

Открыть

World API Extension QC Suite

Расширенный пакет по 130 дополнительным востребованным API и биологическим продуктам, подготовленный как backlog для покрытия мирового рынка.

Открыть