[{"data":1,"prerenderedAt":998},["ShallowReactive",2],{"page-portfolio":3,"portfolio-all":46,"page-globals":945},{"id":4,"title":5,"actions":6,"areas":6,"aviationList":6,"blogChrome":6,"body":7,"canonical":18,"cards":6,"contact":6,"cta":19,"description":23,"extension":24,"eyebrow":6,"hero":25,"heroImage":6,"list":29,"meta":33,"navigation":34,"og":35,"partners":6,"path":39,"perspective":6,"portfolioChrome":6,"process":6,"sectionHeader":6,"sections":6,"seo":40,"serviceLabels":6,"services":6,"stem":42,"twitter":43,"__hash__":45},"pages\u002Fpages\u002Fportfolio.md","Portfolio — projekty i case studies",null,{"type":8,"value":9,"toc":14},"minimark",[10],[11,12,13],"p",{},"Strona listująca projekty i case studies. Nagłówek, etykiety filtrów i sekcja CTA są utrzymywane we frontmatterze powyżej.",{"title":15,"searchDepth":16,"depth":16,"links":17},"",3,[],"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio",{"eyebrow":20,"title":21,"text":22},"Twój projekt","Widzisz podobny problem u siebie?","Skorzystaj z mojego doświadczenia. Opisz kontekst — podpowiem, jak podejść do tematu i jak wyglądałby zakres współpracy.","Wybrane realizacje: architektura systemów, infrastruktura, backend .NET i integracje. Krótki opis problemu, podejścia i rezultatu.","md",{"eyebrow":26,"title":27,"lede":28},"Portfolio","Realizacje i case studies","Wybrane projekty z obszaru architektury, infrastruktury i backendu. Każdy opisany przez pryzmat problemu, decyzji technicznych i rezultatu — bez lania wody.",{"filterAllLabel":30,"filterAriaLabel":31,"emptyText":32},"wszystkie","Filtruj projekty","Brak projektów dla wybranego filtra.",{},true,{"title":36,"description":37,"type":38},"Portfolio — Jetstream Aviation","Wybrane realizacje z obszaru architektury, infrastruktury i backendu.","website","\u002Fpages\u002Fportfolio",{"title":5,"description":23,"robots":41},"index,follow","pages\u002Fportfolio",{"card":44},"summary_large_image","NWhjcBRlXTcgV8UIA4iOJw19cbwhtiDl0KJw3XnSMTM",[47,503,616,737],{"id":48,"title":49,"body":50,"canonical":473,"client":474,"date":475,"description":15,"extension":24,"featured":34,"image":476,"meta":477,"navigation":34,"og":478,"path":481,"seo":482,"stack":491,"stem":495,"summary":496,"tags":497,"twitter":500,"type":501,"__hash__":502},"portfolio\u002Fportfolio\u002Fproxmox-provider-vps-platform.md","Proxmox Provider dla platformy VPS",{"type":8,"value":51,"toc":463},[52,57,60,64,67,71,92,112,116,145,149,152,427,430,434,437,441,444,448,459],[53,54,56],"h2",{"id":55},"problem","Problem",[11,58,59],{},"Platforma sprzedająca serwery VPS zdecydowała się wdrożyć nowe środowisko wirtualizacji - PVE (Proxmox Virtual Environment). W związku z tym należało zaprojektować usługę, która weźmnie całą odpowiedzialność za zarządzanie cyklem życia maszyn wirtualnych klientów oraz ich monitorowaniem. Wymagana była niezawodność, idempotencja oraz spójność przy jednocześnie rosnącej liczbie klientów.",[53,61,63],{"id":62},"kontekst","Kontekst",[11,65,66],{},"Warstwa wykonawcza opierała się na Proxmox VE. Panel klienta i panel administracyjny potrzebowały jednego, spójnego sposobu zlecania operacji na maszynach oraz wglądu w ich aktualny stan, a także możliwości monitorowania klastra — najlepiej w czasie rzeczywistym, bez ręcznego odświeżania. W celach administracyjnych wymagane było zarządzanie takimi funkcjami jak sieci wirtualne lub polityki migawek.",[53,68,70],{"id":69},"podejście","Podejście",[11,72,73,74,78,79,78,82,78,85,78,88,91],{},"Zaprojektowałem osobny moduł — „provider\" — odpowiedzialny wyłącznie za komunikację z Proxmox i tłumaczenie intencji biznesowych (",[75,76,77],"code",{},"utwórz maszynę",", ",[75,80,81],{},"zmień plan",[75,83,84],{},"usuń",[75,86,87],{},"uruchom",[75,89,90],{},"wykonaj migawkę",") na konkretne operacje w infrastrukturze. Panel nigdy nie rozmawia z Proxmox bezpośrednio; zawsze przez jawny kontrakt providera.",[11,93,94,95,78,98,78,101,78,104,107,108,111],{},"Operacje długotrwałe potraktowałem jako zadania asynchroniczne z jawnym cyklem życia: ",[75,96,97],{},"zlecone",[75,99,100],{},"w toku",[75,102,103],{},"zakończone",[75,105,106],{},"błąd",". Za ich orkiestrację w obrębie modułu odpowiada mechanizm ",[75,109,110],{},"internal commands processor",". Dzięki temu interfejs mógł pokazywać rzeczywisty postęp, a nie tylko „proszę czekać\".",[53,113,115],{"id":114},"architektura","Architektura",[117,118,119,127,133,139],"ul",{},[120,121,122,126],"li",{},[123,124,125],"strong",{},"Provider"," jako granica: cała wiedza o Proxmox zamknięta za jednym kontraktem.",[120,128,129,132],{},[123,130,131],{},"Kolejka zadań"," dla operacji długotrwałych, z idempotentnymi krokami.",[120,134,135,138],{},[123,136,137],{},"PostgreSQL"," jako źródło prawdy o stanie zamówień i maszyn.",[120,140,141,144],{},[123,142,143],{},"Redis"," do koordynacji zadań i cache'owania stanu odpytywanego często.",[53,146,148],{"id":147},"implementacja","Implementacja",[11,150,151],{},"Kluczowa była idempotentność. Ponowienie zadania po awarii nie mogło tworzyć drugiej maszyny ani pozostawiać systemu w stanie niespójnym. Każdy krok sprawdzał najpierw, czy jego efekt już nie istnieje.",[153,154,158],"pre",{"className":155,"code":156,"language":157,"meta":15,"style":15},"language-csharp shiki shiki-themes one-dark-pro one-dark-pro","public async Task\u003CResult> Handle(ProvisionVm task, CancellationToken ct)\n{\n    var existing = await proxmox.FindByTagAsync(task.CorrelationId, ct);\n    if (existing is not null)\n        return Result.AlreadyDone(existing.VmId); \u002F\u002F idempotentnie: brak duplikatu\n\n    var vm = await proxmox.CreateAsync(task.Spec, task.CorrelationId, ct);\n    await progress.PublishAsync(task.OrderId, VmState.Provisioned, ct);\n    return Result.Ok(vm.VmId);\n}\n","csharp",[75,159,160,210,216,259,280,310,316,358,397,421],{"__ignoreMap":15},[161,162,165,169,172,176,180,183,186,190,193,196,199,201,204,207],"span",{"class":163,"line":164},"line",1,[161,166,168],{"class":167},"sCyHM","public",[161,170,171],{"class":167}," async",[161,173,175],{"class":174},"sZsuq"," Task",[161,177,179],{"class":178},"shasQ","\u003C",[161,181,182],{"class":174},"Result",[161,184,185],{"class":178},"> ",[161,187,189],{"class":188},"sw6ri","Handle",[161,191,192],{"class":178},"(",[161,194,195],{"class":174},"ProvisionVm",[161,197,198],{"class":174}," task",[161,200,78],{"class":178},[161,202,203],{"class":174},"CancellationToken",[161,205,206],{"class":174}," ct",[161,208,209],{"class":178},")\n",[161,211,213],{"class":163,"line":212},2,[161,214,215],{"class":178},"{\n",[161,217,218,221,225,229,232,235,238,241,243,246,248,251,253,256],{"class":163,"line":16},[161,219,220],{"class":167},"    var",[161,222,224],{"class":223},"sh-OU"," existing",[161,226,228],{"class":227},"seqx8"," =",[161,230,231],{"class":178}," await ",[161,233,234],{"class":174},"proxmox",[161,236,237],{"class":178},".",[161,239,240],{"class":188},"FindByTagAsync",[161,242,192],{"class":178},[161,244,245],{"class":174},"task",[161,247,237],{"class":178},[161,249,250],{"class":174},"CorrelationId",[161,252,78],{"class":178},[161,254,255],{"class":223},"ct",[161,257,258],{"class":178},");\n",[161,260,262,265,268,271,274,278],{"class":163,"line":261},4,[161,263,264],{"class":167},"    if",[161,266,267],{"class":178}," (",[161,269,270],{"class":223},"existing",[161,272,273],{"class":178}," is not ",[161,275,277],{"class":276},"sDrLb","null",[161,279,209],{"class":178},[161,281,283,286,289,291,294,296,298,300,303,306],{"class":163,"line":282},5,[161,284,285],{"class":167},"        return",[161,287,288],{"class":174}," Result",[161,290,237],{"class":178},[161,292,293],{"class":188},"AlreadyDone",[161,295,192],{"class":178},[161,297,270],{"class":174},[161,299,237],{"class":178},[161,301,302],{"class":174},"VmId",[161,304,305],{"class":178},"); ",[161,307,309],{"class":308},"sIbxt","\u002F\u002F idempotentnie: brak duplikatu\n",[161,311,313],{"class":163,"line":312},6,[161,314,315],{"emptyLinePlaceholder":34},"\n",[161,317,319,321,324,326,328,330,332,335,337,339,341,344,346,348,350,352,354,356],{"class":163,"line":318},7,[161,320,220],{"class":167},[161,322,323],{"class":223}," vm",[161,325,228],{"class":227},[161,327,231],{"class":178},[161,329,234],{"class":174},[161,331,237],{"class":178},[161,333,334],{"class":188},"CreateAsync",[161,336,192],{"class":178},[161,338,245],{"class":174},[161,340,237],{"class":178},[161,342,343],{"class":174},"Spec",[161,345,78],{"class":178},[161,347,245],{"class":174},[161,349,237],{"class":178},[161,351,250],{"class":174},[161,353,78],{"class":178},[161,355,255],{"class":223},[161,357,258],{"class":178},[161,359,361,364,367,369,372,374,376,378,381,383,386,388,391,393,395],{"class":163,"line":360},8,[161,362,363],{"class":178},"    await ",[161,365,366],{"class":174},"progress",[161,368,237],{"class":178},[161,370,371],{"class":188},"PublishAsync",[161,373,192],{"class":178},[161,375,245],{"class":174},[161,377,237],{"class":178},[161,379,380],{"class":174},"OrderId",[161,382,78],{"class":178},[161,384,385],{"class":174},"VmState",[161,387,237],{"class":178},[161,389,390],{"class":174},"Provisioned",[161,392,78],{"class":178},[161,394,255],{"class":223},[161,396,258],{"class":178},[161,398,400,403,405,407,410,412,415,417,419],{"class":163,"line":399},9,[161,401,402],{"class":167},"    return",[161,404,288],{"class":174},[161,406,237],{"class":178},[161,408,409],{"class":188},"Ok",[161,411,192],{"class":178},[161,413,414],{"class":174},"vm",[161,416,237],{"class":178},[161,418,302],{"class":174},[161,420,258],{"class":178},[161,422,424],{"class":163,"line":423},10,[161,425,426],{"class":178},"}\n",[11,428,429],{},"Stan każdego zadania był publikowany na bieżąco, więc panel administracyjny i panel klienta widziały te same, aktualne informacje.",[53,431,433],{"id":432},"stack","Stack",[11,435,436],{},".NET, ASP.NET Core, PostgreSQL, Redis, Docker oraz API Proxmox VE.",[53,438,440],{"id":439},"rezultat","Rezultat",[11,442,443],{},"Provisioning maszyn stał się w pełni automatyczny i powtarzalny. Czas realizacji typowego zamówienia skrócił się z minut liczonych ręcznie do operacji wykonywanej bez udziału człowieka. Panele pokazują rzeczywisty stan maszyn na żywo, a operacje można bezpiecznie ponawiać po awarii.",[53,445,447],{"id":446},"lessons-learned","Lessons learned",[117,449,450,453,456],{},[120,451,452],{},"Idempotentność nie jest dodatkiem — przy operacjach na infrastrukturze to warunek bezpieczeństwa.",[120,454,455],{},"Zamknięcie integracji za jednym kontraktem opłaciło się przy każdej późniejszej zmianie w Proxmox.",[120,457,458],{},"Komunikacja w czasie rzeczywistym najbardziej pomogła nie klientom, lecz administracji — skróciła czas reakcji na problemy.",[460,461,462],"style",{},"html pre.shiki code .sCyHM, html code.shiki .sCyHM{--shiki-default:#C678DD;--shiki-dark:#C678DD}html pre.shiki code .sZsuq, html code.shiki .sZsuq{--shiki-default:#E5C07B;--shiki-dark:#E5C07B}html pre.shiki code .shasQ, html code.shiki .shasQ{--shiki-default:#ABB2BF;--shiki-dark:#ABB2BF}html pre.shiki code .sw6ri, html code.shiki .sw6ri{--shiki-default:#61AFEF;--shiki-dark:#61AFEF}html pre.shiki code .sh-OU, html code.shiki .sh-OU{--shiki-default:#E06C75;--shiki-dark:#E06C75}html pre.shiki code .seqx8, html code.shiki .seqx8{--shiki-default:#56B6C2;--shiki-dark:#56B6C2}html pre.shiki code .sDrLb, html code.shiki .sDrLb{--shiki-default:#D19A66;--shiki-dark:#D19A66}html pre.shiki code .sIbxt, html code.shiki .sIbxt{--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":15,"searchDepth":16,"depth":16,"links":464},[465,466,467,468,469,470,471,472],{"id":55,"depth":212,"text":56},{"id":62,"depth":212,"text":63},{"id":69,"depth":212,"text":70},{"id":114,"depth":212,"text":115},{"id":147,"depth":212,"text":148},{"id":432,"depth":212,"text":433},{"id":439,"depth":212,"text":440},{"id":446,"depth":212,"text":447},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Fproxmox-provider-vps-platform","Confidential","2026-07-13","\u002Fimages\u002Fportfolio\u002Fproxmox-provider.webp",{},{"title":49,"description":479,"type":480},"Automatyzacja cyklu życia maszyn wirtualnych na Proxmox.","article","\u002Fportfolio\u002Fproxmox-provider-vps-platform",{"title":483,"description":484,"keywords":485,"robots":41},"Proxmox Provider — case study integracji platformy VPS","Case study systemu automatyzującego provisioning maszyn wirtualnych na Proxmox: architektura, kolejki zadań i komunikacja w czasie rzeczywistym.",[486,487,488,489,490,137],"Proxmox",".NET","VPS","infrastructure","Docker",[492,486,490,137,143,493,494],".NET Core","YARP","Grafana\u002FPrometheus\u002FLoki","portfolio\u002Fproxmox-provider-vps-platform","System integrujący panel platformy VPS z infrastrukturą Proxmox — automatyczne provisionowanie, cykl życia maszyn i komunikacja w czasie rzeczywistym.",[489,498,499],"backend","devops",{"card":44},"Software \u002F Infrastructure","XoEMuTt59FnMAoRcEmjDmSb_DQZQwFyH1Ag8vW7t7Z0",{"id":504,"title":505,"body":506,"canonical":586,"client":474,"date":587,"description":15,"extension":24,"featured":588,"image":589,"meta":590,"navigation":34,"og":591,"path":593,"seo":594,"stack":604,"stem":607,"summary":608,"tags":609,"twitter":613,"type":614,"__hash__":615},"portfolio\u002Fportfolio\u002Fcomarch-erp-optima-ksef-integration.md","Automatyczna integracja Comarch ERP Optima z KSeF",{"type":8,"value":507,"toc":578},[508,510,513,516,518,521,524,526,552,554,557,559,562,565,567],[53,509,56],{"id":55},[11,511,512],{},"Proces wysyłania faktur z Comarch ERP Optima do KSeF wymagał automatyzacji bez pomijania mechanizmów biznesowych i ewidencyjnych systemu ERP.",[11,514,515],{},"Rozwiązanie musiało poprawnie przetwarzać dokumenty, przekazywać je do KSeF, odbierać Urzędowe Poświadczenie Odbioru oraz zapisywać rezultat w taki sposób, aby cały proces pozostawał widoczny dla użytkowników Optimy.",[53,517,70],{"id":69},[11,519,520],{},"Zaprojektowałem i wdrożyłem usługę Windows działającą w tle. Co 10 sekund wyszukuje ona w bazie Microsoft SQL Server faktury oczekujące na przetworzenie.",[11,522,523],{},"Dostęp do danych został zaimplementowany przy użyciu Dappera, natomiast właściwa wysyłka dokumentów odbywa się przez bibliotekę DLL API Comarch ERP Optima. Dzięki temu faktury są obsługiwane zgodnie z mechanizmami Optimy, a informacje o wysyłce i odebranym UPO pozostają dostępne bezpośrednio w systemie ERP.",[53,525,115],{"id":114},[117,527,528,534,540,546],{},[120,529,530,533],{},[123,531,532],{},"Windows Service"," realizujący cykliczne przetwarzanie dokumentów.",[120,535,536,539],{},[123,537,538],{},"Dapper i SQL Server"," do szybkiego wyszukiwania faktur oczekujących na wysłanie.",[120,541,542,545],{},[123,543,544],{},"Comarch ERP Optima API"," do przetwarzania dokumentów i komunikacji z KSeF.",[120,547,548,551],{},[123,549,550],{},"Obsługa UPO"," wraz z zapisaniem wyniku procesu w Optimie.",[53,553,433],{"id":432},[11,555,556],{},".NET Framework 4.7.2, Windows Service, Microsoft SQL Server, Dapper oraz DLL API Comarch ERP Optima.",[53,558,440],{"id":439},[11,560,561],{},"Proces wysyłania faktur do KSeF został całkowicie zautomatyzowany. Dokumenty są regularnie wykrywane, poprawnie przetwarzane przez API Optimy i wysyłane do KSeF, a następnie uzupełniane o odebrane UPO.",[11,563,564],{},"Użytkownicy mogą kontrolować status faktur bezpośrednio w Comarch ERP Optima, bez korzystania z dodatkowego panelu lub ręcznego uruchamiania wysyłki.",[53,566,447],{"id":446},[117,568,569,572,575],{},[120,570,571],{},"Integracja przez oficjalne API systemu ERP pozwala zachować spójność jego procesów biznesowych.",[120,573,574],{},"Usługa działająca w tle dobrze sprawdza się przy regularnym przetwarzaniu niewielkich kolejek dokumentów.",[120,576,577],{},"Rezultat integracji powinien być widoczny w systemie, z którego użytkownicy korzystają na co dzień.",{"title":15,"searchDepth":16,"depth":16,"links":579},[580,581,582,583,584,585],{"id":55,"depth":212,"text":56},{"id":69,"depth":212,"text":70},{"id":114,"depth":212,"text":115},{"id":432,"depth":212,"text":433},{"id":439,"depth":212,"text":440},{"id":446,"depth":212,"text":447},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Fcomarch-optima-ksef-integration","2026-04-01",false,"\u002Fimages\u002Fportfolio\u002Foptima-ksef.webp",{},{"title":505,"description":592,"type":480},"Automatyczna wysyłka faktur do KSeF i odbieranie UPO bezpośrednio w środowisku Comarch ERP Optima.","\u002Fportfolio\u002Fcomarch-erp-optima-ksef-integration",{"title":595,"description":596,"keywords":597,"robots":41},"Integracja Comarch ERP Optima z KSeF | Case study","Usługa Windows automatyzująca wysyłkę faktur z Comarch ERP Optima do KSeF oraz pobieranie UPO.",[598,599,600,601,602,603],"Comarch ERP Optima","KSeF",".NET Framework","SQL Server","Dapper","integracja ERP",[605,544,606,602,532,599],".NET Framework 4.7.2","Microsoft SQL Server","portfolio\u002Fcomarch-erp-optima-ksef-integration","Usługa Windows automatyzująca przetwarzanie faktur z Comarch ERP Optima, wysyłkę do KSeF oraz odbieranie Urzędowego Poświadczenia Odbioru.",[498,610,611,612],"integration","erp","automation",{"card":44},"Software \u002F ERP Integration","pLQ3RV8OBlrJ-g4BY6Lv1mG64n_dKjWPXIPhvt0PHy0",{"id":617,"title":618,"body":619,"canonical":707,"client":474,"date":708,"description":15,"extension":24,"featured":34,"image":709,"meta":710,"navigation":34,"og":711,"path":714,"seo":715,"stack":724,"stem":729,"summary":730,"tags":731,"twitter":734,"type":735,"__hash__":736},"portfolio\u002Fportfolio\u002Faviation-training-systems.md","Cyfryzacja procesów w organizacji szkolenia lotniczego",{"type":8,"value":620,"toc":697},[621,623,626,628,631,633,636,639,643,669,671,674,676,679,681,684,686],[53,622,56],{"id":55},[11,624,625],{},"Organizacja szkoleniowa prowadziła ewidencję szkoleń i dokumentację operacyjną w sposób rozproszony: część w arkuszach, część w plikach, część w pamięci konkretnych osób. Przy audytach i codziennej pracy oznaczało to ryzyko niespójności oraz duży nakład czasu na odtworzenie stanu faktycznego.",[53,627,63],{"id":62},[11,629,630],{},"W lotnictwie dokumentacja nie jest formalnością — to element bezpieczeństwa i zgodności. Rozwiązanie musiało więc nie tylko usprawnić pracę, ale przede wszystkim być zgodne z wymaganiami i odporne na błędy wynikające z rutyny.",[53,632,70],{"id":69},[11,634,635],{},"Zamiast od razu wdrażać narzędzie, zacząłem od procesów. Zmapowałem realny przebieg szkolenia i obiegu dokumentów: gdzie powstają dane, kto za nie odpowiada i w którym momencie stają się wiążące. Dopiero na tej podstawie projektowałem uporządkowanie.",[11,637,638],{},"Przeniosłem podejście znane z operacji lotniczych: jasne role, checklisty dla kroków krytycznych oraz jeden, wiarygodny stan zamiast wielu równoległych wersji prawdy.",[53,640,642],{"id":641},"architektura-procesu","Architektura procesu",[117,644,645,651,657,663],{},[120,646,647,650],{},[123,648,649],{},"Jedno źródło prawdy"," dla ewidencji szkoleń i statusów.",[120,652,653,656],{},[123,654,655],{},"Jawne role i odpowiedzialności"," na każdym etapie obiegu.",[120,658,659,662],{},[123,660,661],{},"Checklisty"," dla kroków, w których błąd jest kosztowny.",[120,664,665,668],{},[123,666,667],{},"Ślad zmian"," — kto, co i kiedy zatwierdził.",[53,670,148],{"id":147},[11,672,673],{},"Wdrożenie było etapowe. Najpierw uporządkowaliśmy dane i ustaliliśmy pojedyncze źródło prawdy, potem sformalizowaliśmy procedury, a na końcu — dopiero na końcu — dobraliśmy narzędzia wspierające. Taka kolejność ograniczyła ryzyko, że narzędzie wymusi zły proces.",[53,675,433],{"id":432},[11,677,678],{},"Praca koncepcyjna i procesowa: projektowanie procesów, dokumentacja, procedury oraz podejście do compliance. Warstwa narzędziowa była wtórna wobec uporządkowanego procesu.",[53,680,440],{"id":439},[11,682,683],{},"Dokumentacja i ewidencja stały się spójne i możliwe do szybkiego odtworzenia. Przygotowanie do audytu przestało być awaryjnym zrywem, a codzienna praca zyskała jasne punkty odpowiedzialności. Organizacja zyskała stan, któremu można zaufać.",[53,685,447],{"id":446},[117,687,688,691,694],{},[120,689,690],{},"Narzędzie nie naprawi złego procesu — najpierw proces, potem software.",[120,692,693],{},"Checklisty sprawdzają się w dokumentacji tak samo dobrze jak w kokpicie.",[120,695,696],{},"Jedno źródło prawdy to najtańsza inwestycja w spokój podczas audytu.",{"title":15,"searchDepth":16,"depth":16,"links":698},[699,700,701,702,703,704,705,706],{"id":55,"depth":212,"text":56},{"id":62,"depth":212,"text":63},{"id":69,"depth":212,"text":70},{"id":641,"depth":212,"text":642},{"id":147,"depth":212,"text":148},{"id":432,"depth":212,"text":433},{"id":439,"depth":212,"text":440},{"id":446,"depth":212,"text":447},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Faviation-training-systems","2024-03-2","\u002Fimages\u002Fportfolio\u002Faviation-training-systems.jpg",{},{"title":618,"description":712,"image":713,"type":480},"Zgodność, powtarzalność i użyteczność operacyjna.","\u002Fimages\u002Fog\u002Faviation-training-systems.jpg","\u002Fportfolio\u002Faviation-training-systems",{"title":716,"description":717,"keywords":718,"robots":41},"Cyfryzacja procesów w ATO — case study","Case study porządkowania dokumentacji i procedur w organizacji szkolenia lotniczego: zgodność, powtarzalność i użyteczność operacyjna.",[719,720,721,722,723],"organizacja szkoleniowa","ATO","procedury lotnicze","compliance","dokumentacja",[725,726,727,728],"Aviation","Compliance","Process design","Documentation","portfolio\u002Faviation-training-systems","Uporządkowanie dokumentacji, ewidencji szkoleń i procedur w organizacji szkoleniowej — z naciskiem na zgodność, powtarzalność i realną użyteczność.",[732,722,733],"aviation","process",{"card":44},"Consulting \u002F Aviation IT","ivhmSUqIp6bgyU8lFp7Vuqv7lri6gNLjstlQqG0Tpcc",{"id":738,"title":739,"body":740,"canonical":917,"client":474,"date":918,"description":15,"extension":24,"featured":588,"image":919,"meta":920,"navigation":34,"og":921,"path":924,"seo":925,"stack":934,"stem":936,"summary":937,"tags":938,"twitter":942,"type":943,"__hash__":944},"portfolio\u002Fportfolio\u002Femvo-notification-system.md","EMVO EVI — system powiadomień o zdarzeniach EMVS",{"type":8,"value":741,"toc":907},[742,744,747,750,752,755,758,761,763,766,769,789,792,794,837,839,842,845,866,869,872,874,877,879,882,885,888,890],[53,743,56],{"id":55},[11,745,746],{},"Europejska infrastruktura weryfikacji leków składa się z wielu współpracujących ze sobą systemów krajowych i centralnych. Wystąpienie awarii, planowanej przerwy technicznej lub znanego problemu wymagało szybkiego przekazania precyzyjnej informacji do zainteresowanych organizacji i użytkowników.",[11,748,749],{},"Dotychczasowa obsługa takich komunikatów była trudna do skalowania. Odbiorcy potrzebowali informacji dotyczących wyłącznie wybranych krajów, systemów i rodzajów zdarzeń, natomiast operatorzy musieli kontrolować cały cykl życia zgłoszenia — od jego utworzenia, przez aktualizacje, aż po zamknięcie incydentu.",[53,751,63],{"id":62},[11,753,754],{},"EVI — European Medicines Verification System Information — pełniło rolę centralnego źródła informacji o zdarzeniach operacyjnych w ramach EMVS.",[11,756,757],{},"System musiał obsługiwać zdarzenia dotyczące wielu krajów, środowisk i komponentów infrastruktury. Każda publikacja mogła zmieniać się w czasie: otrzymywać aktualizacje, nowe przewidywane terminy rozwiązania, informacje o przyczynach lub potwierdzenie przywrócenia prawidłowego działania.",[11,759,760],{},"Równocześnie użytkownicy powinni otrzymywać tylko te wiadomości, które odpowiadały ich subskrypcjom, bez konieczności ręcznego śledzenia wszystkich komunikatów.",[53,762,70],{"id":69},[11,764,765],{},"Zaprojektowałem i rozwijałem backend systemu jako Web API w ASP.NET Core. Aplikacja rozdzielała odpowiedzialność za zarządzanie zgłoszeniami, publikowanie zdarzeń, obsługę subskrypcji oraz wysyłkę powiadomień.",[11,767,768],{},"Zdarzenie było traktowane jako obiekt posiadający własny cykl życia. Operator mógł określić między innymi:",[117,770,771,774,777,780,783,786],{},[120,772,773],{},"kraj lub grupę krajów,",[120,775,776],{},"system i środowisko, którego dotyczy zdarzenie,",[120,778,779],{},"rodzaj oraz poziom wpływu,",[120,781,782],{},"czas rozpoczęcia i zakończenia,",[120,784,785],{},"aktualny status,",[120,787,788],{},"opis problemu i kolejne aktualizacje.",[11,790,791],{},"Po opublikowaniu lub zmianie zgłoszenia system identyfikował właściwych subskrybentów i przygotowywał dla nich odpowiednie powiadomienia.",[53,793,115],{"id":114},[117,795,796,802,807,813,819,825,831],{},[120,797,798,801],{},[123,799,800],{},"ASP.NET Core Web API"," jako warstwa udostępniająca operacje związane ze zgłoszeniami, użytkownikami i subskrypcjami.",[120,803,804,806],{},[123,805,606],{}," jako źródło prawdy o zdarzeniach, odbiorcach, konfiguracji subskrypcji i historii wysyłki.",[120,808,809,812],{},[123,810,811],{},"SSO"," zapewniające kontrolowany dostęp do funkcji administracyjnych.",[120,814,815,818],{},[123,816,817],{},"Quartz.NET"," do realizacji zadań cyklicznych i przetwarzania operacji wykonywanych poza żądaniem HTTP.",[120,820,821,824],{},[123,822,823],{},"SendGrid"," jako usługa odpowiedzialna za dystrybucję wiadomości e-mail.",[120,826,827,830],{},[123,828,829],{},"Microsoft Azure"," jako środowisko uruchomieniowe aplikacji i jej infrastruktury.",[120,832,833,836],{},[123,834,835],{},"GitOps"," do kontrolowanego, powtarzalnego wdrażania zmian pomiędzy środowiskami.",[53,838,148],{"id":147},[11,840,841],{},"Jednym z kluczowych elementów był mechanizm dopasowywania zgłoszeń do subskrypcji. Użytkownik mógł określić zakres interesujących go informacji, a system oceniał każde nowe lub zaktualizowane zdarzenie na podstawie kilku kryteriów jednocześnie.",[11,843,844],{},"Proces obejmował:",[846,847,848,851,854,857,860,863],"ol",{},[120,849,850],{},"zapisanie lub aktualizację zgłoszenia,",[120,852,853],{},"określenie krajów, systemów i środowisk objętych zdarzeniem,",[120,855,856],{},"wyszukanie pasujących subskrypcji,",[120,858,859],{},"utworzenie powiadomień dla właściwych odbiorców,",[120,861,862],{},"przekazanie wiadomości do SendGrid,",[120,864,865],{},"zapisanie wyniku wysyłki i historii komunikacji.",[11,867,868],{},"Operacje wysyłania zostały oddzielone od podstawowej obsługi żądań API. Dzięki temu publikowanie zgłoszenia nie było uzależnione od czasu odpowiedzi zewnętrznego dostawcy poczty, a nieudane próby mogły być bezpiecznie ponawiane przez zadania Quartz.NET.",[11,870,871],{},"Istotnym elementem była również audytowalność. System przechowywał historię zmian zgłoszeń oraz informacje o wygenerowanych i wysłanych powiadomieniach, co ułatwiało analizowanie przebiegu komunikacji.",[53,873,433],{"id":432},[11,875,876],{},"ASP.NET Core Web API, Microsoft SQL Server, Microsoft Azure, Quartz.NET, SendGrid, SSO oraz proces wdrożeniowy oparty na GitOps.",[53,878,440],{"id":439},[11,880,881],{},"Powstał centralny system umożliwiający uporządkowane zarządzanie informacjami o zdarzeniach operacyjnych dotyczących wielu krajów i komponentów EMVS.",[11,883,884],{},"Operatorzy mogli prowadzić zgłoszenie przez cały jego cykl życia i publikować kolejne aktualizacje, natomiast odbiorcy automatycznie otrzymywali tylko te informacje, które odpowiadały zakresowi ich subskrypcji.",[11,886,887],{},"Rozdzielenie obsługi zgłoszeń od mechanizmu dystrybucji wiadomości zwiększyło odporność rozwiązania na błędy usług zewnętrznych i umożliwiło bezpieczne ponawianie niedostarczonych powiadomień.",[53,889,447],{"id":446},[117,891,892,895,898,901,904],{},[120,893,894],{},"W systemach wielokrajowych filtrowanie odbiorców jest równie istotne jak samo publikowanie komunikatów.",[120,896,897],{},"Powiadomienie powinno być osobnym elementem domeny z własnym statusem, historią i możliwością ponowienia wysyłki.",[120,899,900],{},"Operacje zależne od zewnętrznych dostawców nie powinny blokować podstawowego procesu publikowania zgłoszenia.",[120,902,903],{},"Historia zmian i wysyłki jest niezbędna w systemach, w których liczy się możliwość odtworzenia pełnego przebiegu komunikacji.",[120,905,906],{},"GitOps ułatwia utrzymanie spójnej konfiguracji aplikacji działającej w kilku środowiskach.",{"title":15,"searchDepth":16,"depth":16,"links":908},[909,910,911,912,913,914,915,916],{"id":55,"depth":212,"text":56},{"id":62,"depth":212,"text":63},{"id":69,"depth":212,"text":70},{"id":114,"depth":212,"text":115},{"id":147,"depth":212,"text":148},{"id":432,"depth":212,"text":433},{"id":439,"depth":212,"text":440},{"id":446,"depth":212,"text":447},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Femvo-notification-system","2022-05-01","\u002Fimages\u002Fportfolio\u002Femvo-evi.webp",{},{"title":922,"description":923,"type":480},"EMVO EVI — powiadomienia o zdarzeniach EMVS","System obsługi zgłoszeń, subskrypcji i wielokrajowej dystrybucji powiadomień dla infrastruktury weryfikacji leków.","\u002Fportfolio\u002Femvo-notification-system",{"title":926,"description":927,"keywords":928,"robots":41},"EMVO EVI — system powiadomień EMVS | Case study","Case study systemu EMVO EVI do obsługi zdarzeń operacyjnych, subskrypcji i automatycznych powiadomień dla europejskiej infrastruktury weryfikacji leków.",[929,930,931,932,933,601,823,817],"EMVO","EVI","EMVS","ASP.NET Core","Azure",[800,935,933,817,823,811,835],"MSSQL","portfolio\u002Femvo-notification-system","Wielokrajowy system obsługi zdarzeń operacyjnych i powiadomień dla europejskiej infrastruktury weryfikacji leków — od rejestracji zgłoszenia po dystrybucję informacji do subskrybentów.",[498,939,940,941,499],"healthcare","notifications","cloud",{"card":44},"Software \u002F Healthcare","N28g6OVh1jB1P1osWC_T6GYRKJxpvVqGOMbQvwz4G-c",{"id":946,"title":947,"actions":6,"areas":6,"aviationList":6,"blogChrome":948,"body":963,"canonical":6,"cards":6,"contact":6,"cta":974,"description":981,"extension":24,"eyebrow":6,"hero":6,"heroImage":6,"list":6,"meta":982,"navigation":34,"og":6,"partners":6,"path":983,"perspective":6,"portfolioChrome":984,"process":6,"sectionHeader":6,"sections":6,"seo":994,"serviceLabels":6,"services":6,"stem":996,"twitter":6,"__hash__":997},"pages\u002Fpages\u002Fglobals.md","Globalne teksty UI",{"readingTimeSuffix":949,"tocTitle":950,"cta":951,"related":955,"tag":958},"min czytania","Spis treści",{"eyebrow":952,"title":953,"text":954},"Współpraca","Ten temat dotyczy Twojego systemu?","Jeśli mierzysz się z podobnym problemem w praktyce — napisz. Spojrzę na konkret i podpowiem kierunek.",{"eyebrow":956,"title":957},"Czytaj dalej","Powiązane wpisy",{"eyebrow":959,"bodyPrefix":960,"backLabel":961,"emptyText":962},"Tag","Wpisy powiązane z tematem","Wróć do wszystkich wpisów →","Brak wpisów dla tego tagu.",{"type":8,"value":964,"toc":972},[965],[11,966,967,968,971],{},"Ten dokument nie ma własnej strony (",[75,969,970],{},"noindex","). Służy wyłącznie jako źródło współdzielonych tekstów interfejsu, które pojawiają się na wielu stronach naraz — dzięki temu można je edytować w jednym miejscu, bez wchodzenia w kod komponentów.",{"title":15,"searchDepth":16,"depth":16,"links":973},[],{"eyebrow":952,"title":975,"text":976,"primaryLabel":977,"primaryTo":978,"secondaryLabel":979,"secondaryTo":980},"Porozmawiajmy o projekcie","Opisz problem albo cel — odezwę się z konkretną propozycją następnego kroku: rozmowy, audytu, zakresu prac lub ofertą.","Napisz do mnie","\u002Fkontakt","Zobacz usługi IT","\u002Fuslugi-it","Współdzielone etykiety i domyślne treści renderowane na wielu stronach (CtaBlock, strony szczegółów blog\u002Fportfolio, tagi). Edytowane tutaj, bez zmian w kodzie.",{},"\u002Fpages\u002Fglobals",{"caseStudyEyebrow":985,"meta":986,"stackTitle":433,"areasTitle":990,"related":991},"Case study",{"type":987,"client":988,"date":989},"Typ","Klient","Data","Obszary",{"eyebrow":992,"title":993},"Więcej","Powiązane projekty",{"robots":995,"title":947,"description":981},"noindex,nofollow","pages\u002Fglobals","_s0SDmcqMxpaZUVz5yYm0sZItpGh6_JwnFv3nOsfADs",1784052984416]