From 2e5fb3972e5b6a6e6377ecf1938362a9fb394eb9 Mon Sep 17 00:00:00 2001 From: Sergey Karmanov Date: Thu, 6 Feb 2025 07:14:44 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D0=BD=D0=B0=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=B4=D1=80?= =?UTF-8?q?=D1=83=D0=B3=D0=B8=D0=B5=20=D0=BC=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5?= =?UTF-8?q?=20=D1=84=D0=B8=D0=BA=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Layout/NavMenu.razor | 4 +- .../Components/Pages/ChooseInstitut.razor | 8 +- .../Components/Pages/Congratulation.razor | 4 +- .../Components/Pages/Congratulation.razor.js | 10 +++ src/Otchinslator/Components/Pages/Home.razor | 2 +- .../Components/Pages/Questionnaire.razor | 9 ++- .../Components/Pages/Questionnaire.razor.js | 32 +++++++- .../Components/Pages/Result.razor | 72 +++++------------- .../Components/Pages/Statement.razor.js | 12 ++- .../Controllers/PublicDataController.cs | 26 +++++++ .../Controllers/StatementController.cs | 25 +++--- src/Otchinslator/DTO/UserDTO.cs | 7 +- src/Otchinslator/DatabaseContext.cs | 49 ++++++++++++ src/Otchinslator/Program.cs | 5 ++ .../Services/IStatementGenerator.cs | 4 +- src/Otchinslator/Tables/Institut.cs | 10 +++ src/Otchinslator/Tables/Specialty.cs | 10 +++ src/Otchinslator/Templates/ictis.docx | Bin 28840 -> 28809 bytes src/Otchinslator/UserData.cs | 4 +- 19 files changed, 205 insertions(+), 88 deletions(-) create mode 100644 src/Otchinslator/Components/Pages/Congratulation.razor.js create mode 100644 src/Otchinslator/Controllers/PublicDataController.cs create mode 100644 src/Otchinslator/DatabaseContext.cs create mode 100644 src/Otchinslator/Tables/Institut.cs create mode 100644 src/Otchinslator/Tables/Specialty.cs diff --git a/src/Otchinslator/Components/Layout/NavMenu.razor b/src/Otchinslator/Components/Layout/NavMenu.razor index 8c40302..aafd392 100644 --- a/src/Otchinslator/Components/Layout/NavMenu.razor +++ b/src/Otchinslator/Components/Layout/NavMenu.razor @@ -7,7 +7,7 @@ { { "Анкета", "/questionnaire" }, { "Заявление", "/statement" }, - { "Отправка", "/otchislenie/result" }, + { "Отправка", "/result" }, { "Свобода", "/otchislenie/congratulation" } }; @@ -23,7 +23,7 @@ case "statement": _lvl = 2; break; - case "otchislenie/result": + case "result": _lvl = 3; break; case "otchislenie/congratulation": diff --git a/src/Otchinslator/Components/Pages/ChooseInstitut.razor b/src/Otchinslator/Components/Pages/ChooseInstitut.razor index 69a6cbe..20152e6 100644 --- a/src/Otchinslator/Components/Pages/ChooseInstitut.razor +++ b/src/Otchinslator/Components/Pages/ChooseInstitut.razor @@ -16,19 +16,19 @@
- - - - diff --git a/src/Otchinslator/Components/Pages/Congratulation.razor b/src/Otchinslator/Components/Pages/Congratulation.razor index d44599e..07095f4 100644 --- a/src/Otchinslator/Components/Pages/Congratulation.razor +++ b/src/Otchinslator/Components/Pages/Congratulation.razor @@ -3,14 +3,16 @@ @using Otchinslator.Components.Layout @layout OtchislenieLayout @attribute [Authorize] +@using BlazorPageScript Поздравляем! | Отчислятор 3000 +
Поздравляем!
@* TODO: Исправить расположение названия *@ -

Отчислятор 3000

pre-alpha

+

Отчислятор 3000

alpha

Мне нужно...

diff --git a/src/Otchinslator/Components/Pages/Questionnaire.razor b/src/Otchinslator/Components/Pages/Questionnaire.razor index 6ee5021..03d20ed 100644 --- a/src/Otchinslator/Components/Pages/Questionnaire.razor +++ b/src/Otchinslator/Components/Pages/Questionnaire.razor @@ -114,12 +114,11 @@
-
Небольшая анкета + class="text-center font-bold text-4xl md:text-5xl w-max absolute left-1/2 -top-1/4 transform -translate-x-1/2 italic"> +
Кто ты воин?
-

Кто ты воин?

@@ -133,6 +132,10 @@
+

Направление

+

Платник?

diff --git a/src/Otchinslator/Components/Pages/Questionnaire.razor.js b/src/Otchinslator/Components/Pages/Questionnaire.razor.js index 3c6351b..80c57bb 100644 --- a/src/Otchinslator/Components/Pages/Questionnaire.razor.js +++ b/src/Otchinslator/Components/Pages/Questionnaire.razor.js @@ -19,6 +19,7 @@ export function onLoad() { } }); + const specialitySelect = document.getElementById('speciality'); const continueButton = document.querySelector('a[href="/statement"]'); const options = document.querySelectorAll('input[name="options"]'); const kursElements = document.querySelectorAll('input[name="kurs"]'); @@ -31,6 +32,26 @@ export function onLoad() { const kursElement4 = Array.from(kursElements).find(k => k.getAttribute('aria-label') === '4'); const kursElement5 = Array.from(kursElements).find(k => k.getAttribute('aria-label') === '5'); + fetch('/api/getSpecialities?institutId=' + localStorage.getItem('institut')) + .then(response => response.json()) + .then(data => { + const specialities = data.filter(speciality => speciality.name === 'Программная инженерия'); + const otherSpecialities = data.filter(speciality => speciality.name !== 'Программная инженерия'); + + specialities.concat(otherSpecialities).forEach(speciality => { + const option = document.createElement('option'); + option.value = speciality.id; + option.text = speciality.code + " " + (speciality.name.includes('Программная инженерия') ? '🏆 ' + speciality.name : speciality.name); + specialitySelect.add(option); + }); + + let specialityData = localStorage.getItem('speciality'); + if (specialityData) { + specialitySelect.value = specialityData; + } + validateForm(); + }); + // Загрузка данных из хранилища если данные есть function loadFromLocalStorage() { const phoneNumber = localStorage.getItem('phoneNumber'); @@ -71,6 +92,7 @@ export function onLoad() { const isKursSelected = Array.from(kursElements).some(k => k.checked); const isPhoneNumberValid = phoneNumberInput.value.length === 12; + // Блокировка курсов в зависимости от выбранного типа обучения if (isOptionSelected) { var typeOfEducation = (Array.from(options).find(option => option.checked).getAttribute('aria-label')); if (typeOfEducation === "Баклан") { @@ -100,26 +122,28 @@ export function onLoad() { kursElement5.disabled = false; } } - - if (isOptionSelected && isKursSelected && isPhoneNumberValid) { - + + if (isOptionSelected && isKursSelected && isPhoneNumberValid && specialitySelect.value !== '0') { continueButton.classList.remove('btn-disabled'); localStorage.setItem('phoneNumber', phoneNumberInput.value); localStorage.setItem('paid', checkboxPaid.checked); localStorage.setItem('ochno', !checkboxOchno.checked); localStorage.setItem('option', typeOfEducation); localStorage.setItem('kurs', Array.from(kursElements).find(k => k.checked).getAttribute('aria-label')); + localStorage.setItem('speciality', specialitySelect.value); } else { continueButton.classList.add('btn-disabled'); } } + loadFromLocalStorage(); + options.forEach(option => option.addEventListener('change', validateForm)); kursElements.forEach(k => k.addEventListener('change', validateForm)); phoneNumberInput.addEventListener('input', validateForm); checkboxPaid.addEventListener('change', validateForm); checkboxOchno.addEventListener('change', validateForm); + specialitySelect.addEventListener('change', validateForm); - loadFromLocalStorage(); validateForm(); } \ No newline at end of file diff --git a/src/Otchinslator/Components/Pages/Result.razor b/src/Otchinslator/Components/Pages/Result.razor index 6bdd72e..e93dd3b 100644 --- a/src/Otchinslator/Components/Pages/Result.razor +++ b/src/Otchinslator/Components/Pages/Result.razor @@ -1,23 +1,23 @@ -@page "/otchislenie/result" -@using Microsoft.AspNetCore.Authorization +@page "/result" @using Otchinslator.Components.Layout @layout OtchislenieLayout +@using Microsoft.AspNetCore.Authorization @attribute [Authorize] Уже почти | Отчислятор 3000 - -
- +@* *@ +@* *@ +@* *@
Заявление готово
-

Не удалось отобразить заявление, попробуйте скачать (тык)

+

Не удалось отобразить заявление, попробуйте скачать (тык) или открыть в браузере на основе Firefox

- + + Печать + @* Скачать PDF *@ @@ -43,37 +42,4 @@
- - @*
*@ - @*
*@ - @* *@ - @* *@ - @* Скачать PDF *@ - @*
*@ - @* *@ - @*
*@ - @*
*@ - @*
*@ - @*
*@ - @*
*@ - @* $1$ TODO: Сделать адаптив #1# *@ - @* +
\ No newline at end of file diff --git a/src/Otchinslator/Components/Pages/Statement.razor.js b/src/Otchinslator/Components/Pages/Statement.razor.js index b726d25..839ae46 100644 --- a/src/Otchinslator/Components/Pages/Statement.razor.js +++ b/src/Otchinslator/Components/Pages/Statement.razor.js @@ -27,13 +27,17 @@ export function onLoad() { console.log('Начата генерация заявления'); generateStatementModal.showModal(); + console.log(localStorage.getItem('ochno')) + const data = { "phone": "+7 " + localStorage.getItem('phoneNumber'), "kurs": localStorage.getItem('kurs'), "isFreeEducation": localStorage.getItem('paid') === "false", - "isOchno": localStorage.getItem('ochno') === true, - "speciality": optionMapping[localStorage.getItem('option')], - "reason": localStorage.getItem('statement') + "isOchno": localStorage.getItem('ochno') === "true", + "typeOfEducation": optionMapping[localStorage.getItem('option')], + "reason": localStorage.getItem('statement'), + "institut": localStorage.getItem('institut'), + "direction": localStorage.getItem('speciality') } try { @@ -47,7 +51,7 @@ export function onLoad() { if (response.ok) { console.log('Заявление успешно сгенерировано'); - location.href='otchislenie/result'; + location.href='/result'; } else { hideLoadingModal(); console.log('Ошибка при генерации заявления'); diff --git a/src/Otchinslator/Controllers/PublicDataController.cs b/src/Otchinslator/Controllers/PublicDataController.cs new file mode 100644 index 0000000..369ac57 --- /dev/null +++ b/src/Otchinslator/Controllers/PublicDataController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Otchinslator.Controllers; + +[ApiController] +public class PublicDataController(DatabaseContext db) : Controller +{ + [HttpGet("/api/getSpecialities")] + public IActionResult GetSpecialities(int institutId) + { + if (institutId <= 0 || institutId > 4) + { + return BadRequest("Некорректный институт\nДоступные институты: 1 - ИКТИБ, 2 - ИРТСУ, 3 - ИУЭС, 4 - ИНЭП"); + } + + var specialities = db.Specialties.Where(s => s.InstitutId == institutId).ToList(); + var specialitiesDto = specialities.Select(s => new + { + s.Id, + s.Name, + s.Code, + }); + + return Ok(specialitiesDto); + } +} \ No newline at end of file diff --git a/src/Otchinslator/Controllers/StatementController.cs b/src/Otchinslator/Controllers/StatementController.cs index 02361b2..68fd5ba 100644 --- a/src/Otchinslator/Controllers/StatementController.cs +++ b/src/Otchinslator/Controllers/StatementController.cs @@ -7,17 +7,17 @@ namespace Otchinslator; [ApiController] [Authorize] -public class StatementController(IStatementGenerator statementGenerator, IConfiguration configuration) : Controller +public class StatementController(IStatementGenerator statementGenerator, IConfiguration configuration, DatabaseContext db) : Controller { [HttpPost("/generateStatement")] public async Task GenerateStatement(UserDTO userDto) { - var speciality = userDto.speciality switch + var speciality = userDto.typeOfEducation switch { 1 => SpecialityType.Bakalavriat, 2 => SpecialityType.Magistatura, 3 => SpecialityType.Specialitet, - _ => throw new ArgumentOutOfRangeException() + _ => throw new ArgumentOutOfRangeException() // Модель валидации не позволит передать некорректное значение }; if (speciality == SpecialityType.Bakalavriat && userDto.kurs > 4) @@ -45,6 +45,13 @@ public class StatementController(IStatementGenerator statementGenerator, IConfig userFIO = petrovich.InflectFirstNameTo(Case.Genitive) + " " + petrovich.InflectLastNameTo(Case.Genitive) + " " + petrovich.InflectMiddleNameTo(Case.Genitive); + var direction = db.Specialties.FirstOrDefault(x => x.Id == userDto.direction); + if (direction == null) + { + Utils.LogToTg("Походу ломают `direction == null`", configuration); + return BadRequest("Некорректное направление"); + } + var userData = new UserData { reason = userDto.reason, @@ -54,7 +61,8 @@ public class StatementController(IStatementGenerator statementGenerator, IConfig kurs = userDto.kurs, isFreeEducation = userDto.isFreeEducation, isOchno = userDto.isOchno, - speciality = speciality + speciality = speciality, + direction = direction.Code + " " + direction.Name.ToLower() }; var statement = await statementGenerator.GenerateStatementAsync(userData); var pdf = await statementGenerator.ConvertToPDFAsync(statement); @@ -79,12 +87,5 @@ public class StatementController(IStatementGenerator statementGenerator, IConfig outStream.Position = 0; return File(outStream, "application/pdf"); } - - // [HttpGet("/getUserData")] - // public async Task getUserdata() - // { - // var userFIO = User.Claims.FirstOrDefault(x => x.Type == "name")?.Value; - // var userdata = User.Identities.FirstOrDefault()?.Claims.Select(x => new {x.Type, x.Value}); - // return Ok(userdata); - // } + } \ No newline at end of file diff --git a/src/Otchinslator/DTO/UserDTO.cs b/src/Otchinslator/DTO/UserDTO.cs index a7a3488..b2bdb1a 100644 --- a/src/Otchinslator/DTO/UserDTO.cs +++ b/src/Otchinslator/DTO/UserDTO.cs @@ -20,6 +20,11 @@ public class UserDTO public bool isOchno { get; set; } [Required] [Range(1, 3, ErrorMessage = "Некорректный тип специальности")] - public int speciality { get; set; } + public int typeOfEducation { get; set; } + [Required] + [Range(1, 4, ErrorMessage = "Некорректный институт")] + public int institut { get; set; } + [Required] + public int direction { get; set; } } diff --git a/src/Otchinslator/DatabaseContext.cs b/src/Otchinslator/DatabaseContext.cs new file mode 100644 index 0000000..79cb0c6 --- /dev/null +++ b/src/Otchinslator/DatabaseContext.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; +using Otchinslator.Tables; + +namespace Otchinslator; + +public class DatabaseContext : DbContext +{ + public DbSet Instituts { get; set; } + public DbSet Specialties { get; set; } + public string DbPath { get; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(i => i.Id); + modelBuilder.Entity().HasKey(s => s.Id); + + modelBuilder.Entity().HasData( + new Institut() { Id = 1, ShortName = "ИКТИБ", Name = "Институт компьютерных технологий и информационной безопасности" }, + new Institut() { Id = 2, ShortName = "ИРТСУ", Name = "Институт радиотехнических систем и управления" }, + new Institut() { Id = 3, ShortName = "ИУЭС", Name = "Институт управления в экономических, экологических и социальных системах" }, + new Institut() { Id = 4, ShortName = "ИНЭП", Name = "Институт нанотехнологий, электроники и приборостроения" } + ); + + modelBuilder.Entity().HasData( + // ИКТИБ + // Бакалавриат + new Specialty() { Id = 1, Name = "Математическое обеспечение и администрирование информационных систем", Code = "02.03.03", InstitutId = 1 }, + new Specialty() { Id = 2, Name = "Информатика и вычислительная техника", Code = "09.03.01", InstitutId = 1 }, // + заочка + new Specialty() { Id = 3, Name = "Информационные системы и технологии", Code = "09.03.02", InstitutId = 1 }, + new Specialty() { Id = 4, Name = "Программная инженерия", Code = "09.03.04", InstitutId = 1 }, + new Specialty() { Id = 5, Name = "Информационная безопасность", Code = "10.03.01", InstitutId = 1 }, + new Specialty() { Id = 6, Name = "Системный анализ и управление", Code = "27.03.03", InstitutId = 1 }, + // Специалитет + new Specialty() { Id = 7, Name = "Применение и эксплуатация автоматизированных систем специального назначения", Code = "09.05.01", InstitutId = 1 }, + new Specialty() { Id = 8, Name = "Информационная безопасность телекоммуникационных систем", Code = "10.05.02", InstitutId = 1 }, + new Specialty() { Id = 9, Name = "Информационная безопасность автоматизированных систем", Code = "10.05.03", InstitutId = 1 }, + new Specialty() { Id = 10, Name = "Безопасность информационных технологий в правоохранительной сфере", Code = "10.05.05", InstitutId = 1 } + ); + } + + public DatabaseContext() + { + var folder = Environment.SpecialFolder.LocalApplicationData; + DbPath = System.IO.Path.Join("./", "database.db"); + } + + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options.UseSqlite($"Data Source={DbPath}"); +} \ No newline at end of file diff --git a/src/Otchinslator/Program.cs b/src/Otchinslator/Program.cs index a33b566..3fd6a28 100644 --- a/src/Otchinslator/Program.cs +++ b/src/Otchinslator/Program.cs @@ -4,8 +4,10 @@ using Gotenberg.Sharp.API.Client.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI; +using Otchinslator; using Otchinslator.Components; using Otchinslator.Services; @@ -26,6 +28,9 @@ builder.Services.Configure(options => options.KnownProxies.Clear(); options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; }); + +builder.Services.AddDbContext(); + // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); diff --git a/src/Otchinslator/Services/IStatementGenerator.cs b/src/Otchinslator/Services/IStatementGenerator.cs index 8afccc4..6312218 100644 --- a/src/Otchinslator/Services/IStatementGenerator.cs +++ b/src/Otchinslator/Services/IStatementGenerator.cs @@ -18,7 +18,7 @@ public class StatementGenerator(GotenbergSharpClient gotenbergSharpClient) : ISt private const string SpecialitetText = "специалитета по специальности"; private const string BakalavriatText = "бакалавриата по направлению подготовки"; private const string MagistaturaText = "магистратуры по направлению подготовки"; - private const string FreeEducationText = "обучение за счет ассигнований федерального бюджета"; + private const string FreeEducationText = "за счет ассигнований федерального бюджета"; private const string PaidEducationText = "на договорной (платной) основе"; public async Task GenerateStatementAsync(UserData userData) @@ -68,7 +68,7 @@ public class StatementGenerator(GotenbergSharpClient gotenbergSharpClient) : ISt SpecialityType.Magistatura => MagistaturaText, SpecialityType.Specialitet => SpecialitetText, _ => throw new ArgumentOutOfRangeException() - } + " ничего не делания"; + } + " " + userData.direction; break; } diff --git a/src/Otchinslator/Tables/Institut.cs b/src/Otchinslator/Tables/Institut.cs new file mode 100644 index 0000000..0193f88 --- /dev/null +++ b/src/Otchinslator/Tables/Institut.cs @@ -0,0 +1,10 @@ +namespace Otchinslator.Tables; + +public class Institut +{ + public int Id { get; set; } + public string Name { get; set; } + public string ShortName { get; set; } + + public List Specialties { get; } = new(); +} \ No newline at end of file diff --git a/src/Otchinslator/Tables/Specialty.cs b/src/Otchinslator/Tables/Specialty.cs new file mode 100644 index 0000000..c5711a2 --- /dev/null +++ b/src/Otchinslator/Tables/Specialty.cs @@ -0,0 +1,10 @@ +namespace Otchinslator.Tables; + +public class Specialty +{ + public int Id { get; set; } + public string Name { get; set; } + public string Code { get; set; } + public int InstitutId { get; set; } + public Institut Institut { get; set; } +} \ No newline at end of file diff --git a/src/Otchinslator/Templates/ictis.docx b/src/Otchinslator/Templates/ictis.docx index a539c500709a646d84410cbc41acd32811b5cc45..eafdbf848233da2778aef9747b76d569b62713eb 100644 GIT binary patch delta 2441 zcmZWrc{r497oQo%kTCYG>}!Q2gR&$PW2Y}h)-jVQGuE+uJ`@!>$)C_=$qV$Ppx{jkJJNk5}u;`}q)7BnthaQKqA)GC(j8}z8q zo1sEBUq61>C8OkEkkZhZ2|+Cv8~T_0me_@8k*QOSKJ5OrAC-$9 zm%|uhbrRnM82CpLh)0=2U7jT#&TimhiRoRHamm)OP!q~mo!k8&Q8+^LYkIt5ptLFa z)VwbbxsOZbz5Uy8E1~;)>&220D+uM&5M4c@b$QkU3!3Jjuhm@Fo+uHXOsfPxp>u_a zhlA3RbhTx8^Th6iqu{$o2U0XO&@WDx@e^6z_)q6V7CN3PqJ^nnc{QuAd1e^B$*Dv< z!93iJ!7X5LQM8knlDd57JTYN5nMK3*ZeVE5H%U>SY%;y`! z$g}sK(a+eg7&{pp?g{+#4e9lEXT+wf?Bp?0zkW>ozOrWbS&{u%Qj@04`qOi9y3Xi| z2M#0$d|3hsrzy5%;+y6FrfnVhO z{ieArkLX?b$u;eDpF)WsgR9hQz$fPw@)6er7?Us6r)^V`sqoW{AGQP&UAU>sCrn8? zzoJ~04=GIKF8c^yvTZWv>aT8pWl z?(A#}?j5;9w0(lC=Ih{2RBbV}r0er7n}=4sXbQ?zJ}|K6DJ&c)(0!1zG0F!UCLfum3%y)=*W0Qn#s~2R>#-%~Z226=J+Rvd6s-|6; zhF^*?JCyojj@o;3_Owq!NZn*C6vli`Vuf#-MP|Q5bL-0R^#74n;MNIRY zxr8atPZ}Wpbs}`*Y3|IzY4W|4zI0D_=0&4O8#~J|eK$S9IYv#5dpUXHsQvNRYQ=Il zajAN53u4|ykyMSFiWs+^pB1vbR#US&eYbM8x~yRnh@c>1!W;sgN0e54OTqWb97>Pl z=>kusm3Lx{WU?1N@jhvEIl?bW8RE|S{{6t>Ub7T(dp=Vrdf=DdISJD^b)8<<4n{XM zNc(>E5K&svr+N7l^L3whw8X61^-)sIM`pxKpn0`{-ep78w?EJ!?IUY(nV2HuxSfG# z-%5v{x+=8^t_%G&3S;$XAT4Sy?ao@TxqiYd(su+5|Sn_LfczKTD49(^Ow>HRU=&KT;Ml1J`Td*&pe~$;|Hy?eS7{BGmnJK zE;oL#{2bUZ)=)?h>(%Ko+Jmi?-A5B99x~sslUdwdVJmL+7@*T!tgv7F!08?bpURz0kEBhlsy5hXZ(F zI3EBJY#qnZgN*|7ZZ-J1GHV2IJV2iODrn#cyvwJP(NY)K!dErb1aop!P!#4KZ}13q z<=-4wc>T~oquemQgP(es8%HnWo7sDLGby~@4n(GYN{h)ss z+D_E9yz}C|X1T)RqhFd%sv1a@YZ98GUzKJgU%rCV6nNFdq(e6Ds%K~&5H+b?E8GF= z;l7@<4~so0zyp5#UFIHg14nU<%*6)1lwB*U7mfX%{W5j}kC5$E3g~3PR_6cuSo7X> zqqPZZF^9A^Y(e$l)3MO&6e*z6NNlkHX*? zEU|Aa7~?56yWgg?@~%VcJONtESkxXlZ~0R{|Ga+%|MU7PM$LQ)tWiUWih6V30Y}!2 zwJK6Es7EqkPqq7N6R+11FKoO*=1)f+ejSq4H)1&Czjz860{g4h zGPPq=A&6P3+MI%1ZPtURhW>>0Sl^4RsNBsIDZx02b^Jj;=hp6V4TfSQ=zIKpWGQ%f zSl(2+`8d%?()j)4iu^wd>ChP{r$~UKcvDVoB;XN(|9skkZoDqX#lp)%CWQbgf;Mg=EZ zFa!kJ8a6m3?m-%srDm{EBYSU;ARrK!ZG%7rzu)92EZP;250d1_+6Bu(bkP7FEw}IA zQe);GWMMY9gBt|e#gX_UHH`)o{pA3Y;GO@|zyNs6|7*;wwE?kg?k)CaC(l{i=}cn) zMXbEgzb*xs!Ls07U?W&w;m?mmW5Hq)$ delta 2435 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 aGL|YJ