Rozwój Produktu

Release Notes — 20 maja 2026

Release Notes — 20 maja 2026

Kolejny duży release — 63 commity w cztery dni od poprzedniego wydania. Centralny temat sprintu to integracja z Microsoft Outlook / Microsoft 365 — pięciofalowa praca, która kończy się pełnym parytetem z Google Calendar, dodaje Microsoft Teams jako protokół wideo i opcjonalną wysyłkę maili “w imieniu terapeuty”. Drugi nurt to uporządkowanie domeny sesjiAvailabilityTypeId zostaje jedynym źródłem prawdy o typie sesji (Variant B), zniknęły niespójności między modal’em “Dodaj sesję” a listą i szczegółami. Trzeci wątek to rozszerzenie katalogu narzędzi klinicznych — cztery nowe testy psychometryczne (4DKL, BAT-PL w wersji 12 i 23, PDSS-SR) oraz brakujący backend PDF/export dla restrukturyzacji poznawczej.

💚 Podziękowania dla zespołu testowego: Martyny, Bartłomieja, Pana Jacka, Jacka W. i Bohdana — Wasze zgłoszenia i sprinty warsztatowe zrobiły z tego release’u to, co widać poniżej.


1. 📅 Integracja z Microsoft Outlook / Microsoft 365 — pięć fal

Najgłębsza zmiana w obszarze integracji od czasu wprowadzenia Google Calendar. Sprint odbył się w pięciu falach commitowanych po kolei — każda fala dokłada konkretną warstwę funkcjonalności i jest niezależnie deployowalna.

Wave 1 — OAuth foundation

Fundament całej integracji: flow OAuth 2.0 z Microsoft Identity (tenant common, więc działa zarówno dla kont firmowych Aithentica, jak i osobistych @outlook.com), pełne entity scaffolding (TherapistMicrosoftToken, TherapistOutlookCalendar), kolumny Session.OutlookCalendarEventId i Session.TeamsMeetingLink, oraz dedykowany kontroler OutlookIntegrationsController z endpointami /login, /callback, /status, /disconnect. Tokeny są szyfrowane tym samym mechanizmem co Google (IOAuthTokenProtector). Background service odświeża tokeny co 30 minut na 15 minut przed wygaśnięciem. Decyzje architektoniczne udokumentowane w ADR-0027.

Wave 2 — Calendar event CRUD + WeekCalendar overlay

Sesje terapeuty trafiają teraz do kalendarza Outlook. W backendzie pełny OutlookCalendarService zbudowany na Microsoft Graph SDK v5 — single-event CRUD, multi-calendar listing, wybór głównego kalendarza, mapowanie na nasze SingleValueExtendedProperties (well-known GUID z metadanymi aits_session_id + aits_source=AITS, mirror Google extendedProperties). SessionsController ma 6 hook’ów na ścieżce create / update / delete / reschedule. Frontend — sekcja “Wybierz kalendarz główny” w Ustawienia → Integracje → Outlook oraz nowa warstwa wydarzeń Outlook na WeekCalendar (bg-blue-100, persisted toggle “Pokaż Outlook” w localStorage). Service jest self-graceful: brak primary calendar → zwraca null, nic się nie sypie.

Wave 3 — Microsoft Teams + recurring events + freebusy

Trzy duże feature’y w jednej fali. Po pierwsze — Microsoft Teams jako protokół wideo: nowy typ sesji MeetingType=TeamsMeeting=5 w modal’u “Dodaj sesję” (grid wybór platformy wideo rozszerzony z 2 na 3 kolumny), OutlookCalendarService.CreateEventAsync ustawia IsOnlineMeeting=true + OnlineMeetingProvider=TeamsForBusiness, po POST odczytuje OnlineMeeting.JoinUrl i zapisuje do Session.TeamsMeetingLink. Konta osobiste bez licencji Teams — graceful fallback (event tworzy się bez linku, log). Po drugie — sesje powtarzające się przez PatternedRecurrence (Daily / Weekly / Biweekly / Triweekly / EveryFourWeeks / EverySixWeeks / Monthly). Wszystkie sesje w serii dziedziczą OutlookCalendarEventId + TeamsMeetingLink z master eventu. Po trzecie — freebusy z /me/calendar/getScheduleBusyPeriod z BusySource.OutlookCalendar=6. Outlook’owa zajętość jest teraz brana pod uwagę w AvailabilityService razem z Google, Apple i sesjami AITS.

Wave 4 — Cross-provider exclusivity + import + self-heal

Single-primary calendar enforcement across providers: terapeuta może mieć w danym momencie tylko jeden główny kalendarz (Google ALBO Outlook). Ustawienie Outlook primary atomic zeruje TherapistGoogleCalendar.IsPrimary (i odwrotnie). Frontend pokazuje amber warning banner gdy Google ma primary a Outlook jeszcze nie. SessionsController.ResolveCalendarRoutingAsync woła tylko jeden service per session create — żadnego dual-write. Dorzucony też import wydarzeń z Outlook jako sesje AITS (OutlookCalendarImportService w 3 trybach: patientId / attendeeEmail / isAdHoc — mirror Google) plus self-heal background service który raz na 12h fetchuje CalendarView w oknie [jutro, +6 dni] i odtwarza eventy ręcznie usunięte z Outlooka.

Wave 5 — Mail integration + per-therapist toggle

Opcjonalna wysyłka maili “w imieniu terapeuty” przez Microsoft Graph (/me/sendMail) zamiast SMTP. Pole TherapistProfile.UseOutlookForMail (bit NOT NULL DEFAULT 0, świadomie nie Always Encrypted — flaga administracyjna, wyjątek ADR-0018), nowy endpoint PUT /api/therapist/profile/outlook-mail-toggle, sekcja “Wysyłka maili przez Outlook” w Ustawienia → Integracje → Outlook widoczna gdy Outlook connected. Maile eligible: rezerwacje, przypomnienia sesji, anulowania, reschedule, wiadomości do pacjentów, powiadomienia finansowe. Maile autoryzacyjne (MFA, password reset, temporary password) zawsze lecą przez SMTP — by design. Fallback do SMTP transparentny: 401/403/429 → log + cichy fallback, nigdy nie rzuca exception.

Co to daje testerom: terapeuci pracujący w Microsoft 365 mogą podpiąć Outlook tak samo jak Google — z eventami, Teams meetingami, freebusy i opcjonalnymi mailami “z ich adresu”. Wszystko z fallbackami, więc nawet jak coś po stronie Microsoftu padnie — AITS dalej działa.

2. 🎯 AvailabilityTypeId jako single source of truth (Variant B)

Refaktor architektoniczny porządkujący typ sesji. Wcześniej Session.Location był free-textem ("Online", "Gabinet", custom), a Session.MeetingType osobnym enumem — i te dwie wartości potrafiły się rozjeżdżać. Teraz Session.AvailabilityTypeId (FK do TherapistAvailabilityType z ustawień terapeuty) jest jedynym źródłem prawdy. Backend resolve’uje Location / MeetingType / MeetingLink z encji typu lokalizacji; legacy pola zostają jako fallback dla starych sesji (AvailabilityTypeId=NULL).

  • Migracja 20260519140000_AddAvailabilityTypeIdToSession — kolumna NULL + FK z ON DELETE NO ACTION (SET NULL był zablokowany przez SQL Server cykl FK przez ApplicationUser), defensywny backfill po (TerapeutaId, Name=Location)
  • CreateSessionRequest / UpdateSessionRequestAvailabilityTypeId nullable; gdy podany, backend nadpisuje Location/MeetingType/MeetingLink z encji
  • FrontendSessionForm i AddSessionModal wysyłają availabilityTypeId derive’owany z wybranego typu (in-person match po nazwie, online match po DefaultMeetingType + fallback)
  • Refaktor AddSessionModal — 2-stopniowy picker (Stacjonarna/Online + AITS/Teams/External) zastąpiony jednym dropdownem filtrowanym po IsOnline. Items pochodzą z TherapistAvailabilityType z ustawień (toggle Stacjonarna/Online zostaje jako kontekst)
  • SessionCard — pokazuje teraz nazwę lokalizacji z ustawień (np. "Online external link" zamiast generic "Online", "W Gabinecie" zamiast normalizowanego "Office")
  • SessionDetailViewNew — gdy sesja ma custom Location (np. "kolorowa"), dropdown dodaje dla niej osobną opcję na początku zamiast fallbackować na pierwszy aktywny typ z listy

Co to daje testerom: spójność nazw lokalizacji między modal’em “Dodaj sesję”, listą sesji i widokiem szczegółów. Praca Bohdana.

3. 💳 iDEAL w Stripe Connect dla pacjentów

Pacjenci holenderscy mogą teraz płacić terapeutom przez iDEAL (najpopularniejsza metoda płatności online w NL). Płatność idzie przez Stripe Connect terapeuty — jeśli ma aktywne capability ideal_payments na koncie Stripe, opcja pojawia się obok karty.

  • Nowy helper GetConnectPaymentMethodTypesAsync odpytuje capabilities konta Connect — iDEAL dodawane do PaymentMethodTypes tylko jeśli capability jest aktywne (fallback do samej karty przy błędzie API)
  • Dotyczy obu ścieżek: CreateSessionPaymentCheckoutAsync (pojedyncza sesja) i CreateMultiSessionPaymentCheckoutAsync (wiele sesji)
  • Restrykcja dodana w hotfixie: iDEAL pokazywany tylko terapeutom z kontem Connect w NL — wcześniej feature był aktywowany na podstawie samego capability, ale Stripe wyrzucał błąd przy odpalaniu Connect z innego kraju
  • Stripe robi auto-konwersję EUR ↔ waluta terapeuty

Co to daje: niższy próg wejścia dla pacjentów z Holandii. Konto terapeuty musi mieć aktywne ideal_payments w Stripe Dashboard.

4. 🧠 Cztery nowe testy psychometryczne w katalogu

Rozszerzenie katalogu formularzy klinicznych o cztery nowe, ważne polskie i międzynarodowe skale:

  • PDSS-SR (Panic Disorder Severity Scale — Self Report; Houck i wsp. 2002) — 7 pytań, skala 0–4 z indywidualnymi etykietami per pytanie, 5-stopniowa klasyfikacja nasilenia (minimalne / łagodne / umiarkowane / ciężkie / bardzo ciężkie)
  • BAT-PL 12 (Burnout Assessment Tool — wersja krótka; Schaufeli/De Witte/Desart 2020, pol. Baka/Basińska 2020) — 12 pytań w 4 podskalach (Wyczerpanie, Dystans psychiczny, Osłabienie kontroli poznawczej, Osłabienie kontroli emocjonalnej), skala 1–5
  • BAT-PL 23 — wersja pełna BAT, 23 pytania w tych samych 4 podskalach (cutoffy total 23–115)
  • 4DKL / 4DSQ (Terluin; pol. Czachowski/Buszman 2014) — 50 pytań, skala 0–4 z mapowaniem punktacji {0:0, 1:1, 2:2, 3:2, 4:2} (odpowiedzi 3 i 4 mapują się na 2 pkt), 4 podskale (Distres / Depresja / Lęk / Somatyzacja) z osobnymi progami nasilenia

Zmiana techniczna w mechanizmie scoringu: SubscaleRule ma nowe pole ValueMap (Dictionary<string,int>) i metodę sum_with_map. Frontend scoring.ts zsynchronizowany z FormScoringService — wcześniej draft pacjenta dla 4DKL pokazywał błędny preview (FE liczyło 3 i 4 jako 3/4 zamiast 2). Każdy test ma osobny plik partial w FormTemplateCatalog — łatwa weryfikacja i izolacja. Seeder uruchamia się przy starcie kontenera i automatycznie tworzy 4 nowe szablony.

5. 📄 PDF / export / share dla restrukturyzacji poznawczej

Zgłoszenie SCRUM-1580 od Martyny. UI w CognitiveRestructuringTab.tsx już od dawna renderował 3-stanowy FSM (draftapprovedpublished) z przyciskami PDF / share / send, ale kliknięcie któregokolwiek zwracało 404 “Nieznany typ analizy” — backend handler nie był zarejestrowany.

Fix: nowy CognitiveRestructuringHandler czyta thoughts[] z merged Session.AbcV2Json (kolejność TherapistPart → AiPart → Legacy, zgodnie z ADR-0017), GenerateCognitiveRestructuringPdf w ConceptualizationPdfService z layoutem karta-per-myśl (myśl automatyczna + zniekształcenie badge + myśl alternatywna — wizualnie spójny z UI). Zero zmian w modelu danych — wykorzystany istniejący plugin pattern IAnalysisHandler.

Co to daje testerom: pełny eksport PDF, share na publiczny link, send do pacjenta dla każdej zatwierdzonej restrukturyzacji poznawczej.

6. 🔕 Toggle “Blokuj zapisy online” w ustawieniach kalendarza

Zgłoszenie SCRUM-1021 od Bartłomieja. Terapeuta może teraz tymczasowo wstrzymać zapisywanie się pacjentów przez publiczny profil (/t/:slug) bez ruszania per-slot availability. Toggle pojawia się w Ustawienia → Profil publiczny → “Sesje i płatności”, semantyka odwrócona (checked = blokada włączona), bindowany do istniejącego pola TherapistProfile.PublicBookingEnabled z auto-save. Po wyłączeniu — dostępność wraca automatycznie (backend od dawna respektował tę flagę i zwracał 404 dla publicznych endpointów GetAvailability/BookSession/GetBookingInfo gdy zablokowane).

7. 🗓️ Toggle “Uwzględniaj zajętość z kalendarzy zewnętrznych”

Per-therapist flaga TherapistProfile.RespectExternalCalendarBusy (default true) kontrolująca, czy /api/therapist/availability/slots blokuje wolne terminy gdy Google / Outlook / Apple Calendar pokazuje zajętość. Patient-facing booking (SCRUM-1378) miał to zawsze włączone — teraz analogicznie dla therapist-facing tworzenia sesji. Nowy komponent RespectExternalCalendarBusyToggle.tsx w sekcji Ustawienia → Integracje pod toggle’em conflict-bell.

Bonus: AvailabilityService.GetAllBusyPeriodsAsync dorzucił Apple Calendar do listy badanych źródeł zajętości (wcześniej był tylko Google + Outlook + Sessions + Holidays + TherapistEvent).

8. ⌨️ Command palette (⌘K) na stronie Ustawienia

Wyszukiwarka funkcji w stylu Linear / Notion — modal otwierany skrótem ⌘K / Ctrl+K lub chipem nad sidebarem. Pozwala szybko nawigować po 17 sekcjach Ustawień zamiast scrollować i czytać listę.

  • Strict substring match z normalizacją diakrytyków (NFD + explicit ł→l przed normalizacją — ł to atomowy codepoint U+0142, NFD go nie dekomponuje, więc "platnosci" faktycznie matchuje "Płatności")
  • Aliasy PL+EN per sekcja — np. "stripe" / "prowizja" → Płatności, "rodo" / "gdpr" → Prywatność, "mfa" / "pin" → Bezpieczeństwo
  • Nawigacja klawiaturą — ↑↓ Enter Esc, highlight matchowanego fragmentu
  • Grupy — Sekcje + Akcje (na razie 1 akcja: Wyloguj)
  • 11 nowych kluczy i18n (PL + EN); pozostałe locale matchują po label

Bug fix po pierwszej iteracji: fuzzy fallback (all-chars-in-order) generował false positives — "rodo" matchował 8 sekcji, "mfa" 5, "stripe" matchował też Profil. Wyrzucone, został czysty substring na znormalizowanym labelu + aliasach.

9. 🔃 SessionsList — przełącznik kierunku sortowania (desc / asc)

Wcześniej lista sesji była zawsze sortowana malejąco (najświeższe na górze) i nie dało się tego zmienić. Teraz przełącznik kierunku respektujący wszystkie aktywne filtry (data, status, pacjent, typ).

10. 📨 SMTP via webd.pl zamiast Azure Comm Email

Tymczasowa zmiana transportu maila w aits-agent. Azure Communication Email blokowane przez DomainNotLinked — domena aitherapy.support nieverified w DNS providera (DKIM OK, ale TXT records Domain / SPF nie zostały ustawione). Zamiast czekać na DNS provisioning — SMTP webd.pl działa od ręki: nodemailer port 465 (implicit TLS), hasło + host + user z Key Vault, sender domyślnie agent@aitherapy.support.

11. 🤖 aits-agent — pilot na Container Apps

Nowy mikroserwis aits-agent do automatyzacji zadań kodowych przy użyciu Claude Agent SDK na Azure.

  • Gateway (ASP.NET Minimal API) — webhooki Telegram / Jira → routing przez QueueDispatcher do Azure Service Bus
  • Worker (Node 20 + Claude Code CLI) — pięć typów jobów: jira-triage, ddd-audit, weekly-report, pr-review, telegram-echo
  • Split queues: agent-jobs-fast (telegram / callback) + agent-jobs-claude (heavy Claude jobs) — DLQ + lockDuration tuning per queue (fast 1min / claude 5min)
  • Auth: subskrypcja Claude (OAuth z Key Vault), fallback na API key
  • Infra: infrastructure/agent.bicep (rg-aits-agents, polandcentral), replicaTimeout=1800s zgodne z AGENT_TIMEOUT_MS
  • CI/CD: trzy workflow’y — gateway-deploy, worker-deploy, oraz nowy aits-agent-infra-deploy.yml (az deployment group create z what-if preview) — żeby zmiany w Bicepie nie triggerowały redundant rebuild image
  • OIDC federated identity w GitHub Actions (3 secrety client/tenant/subscription ID) zamiast creds JSON
  • Cron job aits-pilot-cron-weekly — Container Apps Job typu Schedule, cron 0 9 * * 1 (poniedziałek 9:00 UTC), curluje /cron/weekly-report z managed identity tokenem
  • App Insights SDK w workerze (trackException + flush 2s hard timeout) zamiast 5min lag z Log Analytics

telegram-chat — slash commands + AppInsights + Jira write

Telegram-owy bot do “rozmawiania z PROD” zyskał trzy nowe capabilities:

  • Read-only dostęp do Application Insights PROD — Dockerfile workera ma az CLI (~150MB), bootstrapAzCli robi az login --identity raz na start replicy. Allowed: Bash(az monitor app-insights query:*) + Bash(az monitor metrics list:*). Bot odpowiada teraz na pytania typu “ile było 500 ostatnio” / “co się dzieje na PROD”. Worker-fast managed identity ma Log Analytics Reader + Reader na aits-appinsights-prod
  • Slash commands/help, /pr <N> (review pull requesta), /jira <KEY> (triage ticketu), /audit, /weekly. Plus dwa nowe handlery: /newjira <temat> (tworzy ticket — POST do /rest/api/3/issue z basic auth) i /analyse <KEY> (głębsza analiza istniejącego ticketu z dostępem do kodu)
  • Jira write capability — agenci jira-triage i jira-analyse mogą postować komentarze (POST /comment w ADF) i dodawać etykiety (agent-triaged, agent-analysed) bez MCP atlassian, używając samego curl z creds w env vars

12. ⚙️ Admin endpoint — ręczna regeneracja sesji powtarzających się

Nowy endpoint POST /api/admin/maintenance/recurring-sessions/regenerate (rola Administrator) do ręcznego odpalania generatora sesji z terminów stałych — poza niedzielnym cronem o 23:00 Warsaw. Używane do testów po fixach lub awaryjnej regeneracji.

Przy okazji fix RecurringSessionGenerationService: generator wygenerował 107× SqlException unique violation. Root cause — dedup query sprawdzała (PatientId, TerapeutaId, StartDateTime), ale unique index chroni (TerapeutaId, StartDateTime) WHERE StatusId != 4 (bez PatientId). Slot zajęty przez innego pacjenta był niewidzialny dla dedup, ale baza go widziała.

  • Dedup zgodny z indexem: (TerapeutaId, StartDateTime) + StatusId != Cancelled
  • latestSession / latestSessionDate filtrują Cancelled (niezależny bug: jeśli wszystkie przyszłe sesje pacjenta były anulowane, generator nie generował nowych myśląc że już są)
  • Safety net — per-session SaveChangesAsync z catch DbUpdateException (SqlException.Number == 2601). Race condition nie wywala batcha, tylko loguje warning i pomija konkretną sesję

13. 🎥 Bulk-create sesji — synchronizacja VideoInviteToken (SCRUM-1597)

Zgłoszenie od Bohdana. CreateBatchSessions ustawiał Session.VideoInviteToken na nowy GUID per sesja, zamiast skopiować Patient.VideoInviteToken. Skutek: pacjentka klikała link z maila (zawierający token pacjenta) i wpadała w fallback do legacy pokoju patient-{id}, bo /api/video/join filtruje sesje po s.VideoInviteToken == inviteToken. Terapeuta przez /api/video/token trafiał do session-{id}obie strony same w różnych pokojach.

Fix mirroruje wzorzec z RecurringSessionsController (już istniejący): bulk-create kopiuje patient.VideoInviteToken z lazy-create jeśli null + zerowanie ExpiredAt dla AITS. DB hotfix: 203 mismatched sesji w przyszłości zsynchronizowane przed deployem.

14. 📆 Public booking respektuje Google Calendar busy (SCRUM-1378)

Zgłoszenie od Bohdana. Wcześniej PublicTherapistController.GetAvailability miał własną pętlę slot-generation która ignorowała zajętość z Google / Outlook / TherapistEvent — pacjent widział slot jako wolny mimo że terapeuta miał już blokadę w kalendarzu. Plus race window: między fetch slotów a POST booking dało się “wrzucić” rezerwację na slot, który właśnie został zajęty.

Fix:

  • Krok 1: IAvailabilityService.GetAvailableSlotsAsync zyskał bool includeExternalBusy=false, plus nowa metoda FindExternalConflictAsync dla race-check
  • Krok 2: PublicTherapistController.GetAvailability — usunięta ręczna pętla, delegacja do _availabilityService.GetAvailableSlotsAsync(isPatientBooking:true, includeExternalBusy:true)
  • Krok 3: Logged-in patient surfaces opt-in (PatientAvailabilityController.GetAvailableSlots, PatientSessionsController.GetRescheduleSlots)
  • Krok 4: Race-window guards — BookSession (×2) i RescheduleSession woła FindExternalConflictAsync w transakcji booking; przy konflikcie HTTP 409 z komunikatem “Wybrany termin został właśnie zajęty w kalendarzu terapeuty. Wybierz inny.”

Regresja od tej zmiany (i jej naprawa): response shape GetAvailability zmienił się (pole IsAvailable usunięte, bo backend pre-filtruje; startTime/endTimestartDateTime/endDateTime), ale frontend public booking widget nie został zsynchronizowany. Memo zakładało że slots.filter(slot => slot.isAvailable) w PublicBookingCalendar.tsx:180 jest no-op po pre-filtracji — ale undefined === true to false → drop-all filter, kalendarz pusty. Frontend dostosowany.

15. 🗑️ Kompletne usunięcie integracji Docplanner

Likwidacja całej integracji z Docplanner / znanylekarz.pl — była opcjonalna, bez UI terapeuty, w produkcji nieaktywna. Backend: DocplannerController, DocplannerSyncService, DocplannerClient, IDocplannerClient, DocplannerOptions, DocplannerModels + klasy DocplannerAccount/PatientLink/BookingLink/PaymentLink z Models.cs + DbSets + fluent API. Migracja RemoveDocplannerIntegration — z poprawką drop auto-named DEFAULT constraints przed DROP COLUMN (SQL Server tworzy default constraint o losowej nazwie typu DF__Therapist__Docpl__1234ABCD, bez explicit drop migracja się sypała).

Drobne poprawki (bug fixes)

  • SCRUM-1591 (Bohdan): multi-replica regresja “Lokalny plik audio nie istnieje”NormalizationJob z LocalFilePath != null wymaga, żeby worker normalize i upload landowały na tej samej replice. Na PROD (2+ repliki) audio zapisane na replice A nie istnieje na replice B. 5 transkrypcji 2026-05-18 (TxId 3681, 3691, 3692, 3695, 3698) straciło audio. Fix: pre-upload audio do Azure Blob przed enqueue, wymuszenie BlobBased branch w workerze
  • SCRUM-1590 (Jacek): Patient.Email NULL crashuje POST /api/patients — nullable field nie był chroniony przed użyciem w stringu, wywalał całą ścieżkę tworzenia pacjenta
  • TherapistProfile.StickyNote — Always Encrypted nvarchar(4000) clash na każdym SaveChanges — migracja 20260514150000 utworzyła kolumnę jako nvarchar(4000) AE RANDOMIZED, ale entity property nie miał HasMaxLength — EF defaultuje do nvarchar(max). Każdy INSERT/UPDATE TherapistProfile (nie tylko sticky-note endpoint!) wysyłał nvarchar(max) → SqlException “Operand type clash”. Observed: 3× SqlException w 5 min na PROD przy edycji profili
  • Bump Azure OpenAI health probe timeout 5s → 10s — probe azureOpenAI flapował down↔ok bo reasoning model gpt-5-5-1 spike’ował latency do 4–6s nawet dla ping z max_completion_tokens=16 (internal reasoning). Średnia 0.8s, spike’i do 3.9s, okazjonalne >5s → TaskCanceledException. TimeoutMs 5000 → 10000
  • Batch transcription — odróżnij realny 404 od transient błędówGetStatusAsync zwracał Unknown dla 4 różnych sytuacji (HTTP 404, 5xx/408/429, exception). Poller traktował jednakowo i po 15 min markował Failed z “Transcription job no longer exists on Azure”. ~25 transkrypcji na PROD fałszywie failowane. Fix: nowa wartość BatchTranscriptionStatus.NotFound zwracana tylko dla HTTP 404. Plus safety net CleanupOrphanedTranscriptionsAsync zamyka stuck-forever joby po 6h
  • IBAN walidacja — akceptuj zagraniczne konta (NL, DE itd.) — frontend blokował zapis konta bankowego dla terapeutów spoza PL, mimo że backend IbanValidator.IsValid akceptuje dowolny IBAN ISO 13616. Nowa funkcja isValidIban (mod-97 + tabela długości per kraj — mirror BE). Zachowany isValidPolishIban na wszelki wypadek
  • Profil pacjenta — hardcoded “PLN” zamiast waluty terapeuty — 5 miejsc bypassowało istniejący mechanizm formatPrice / useCurrentTherapistCurrency (cena sesji w edytorze, zaległość w headerze, label przy polu w PatientProfileSettingsTab, label przy edycji ceny w SessionDetailViewNew)
  • Historia płatności subskrypcji — hardcoded “PLN”TherapistSubscription.tsx pokazywał {payment.amount.toFixed(2)} PLN mimo że obok faktura w tej samej tabeli używała już currency z BE. Fix: walutę bierze z billing.currency aktywnej subskrypcji
  • TherapistSubscription — kwoty subskrypcji jako symbol waluty, nie ISO code — sekcja “nadchodząca opłata” pokazywała 319.20 PLN zamiast 319,20 zł. Wszystkie kwoty przez formatPrice z Intl.NumberFormat (separator tysięcy, symbol waluty)
  • Modal “Edytuj sesję” (Pan Jacek): formatowanie pól “Aktualny termin” / “Nowy termin” — ikona kalendarza nachodziła na datę, padding’i się nie zgadzały. Wrapper <div className="relative"><div className="relative group"> (specificity hack pod CSS override) + pl-12pl-10 + shadow-sm + focus:outline-none + tabIndex=-1 na readonly inpucie
  • Formularze pacjenta — fallback języka dla wariantów regionalnych — pacjent / terapeuta z i18n.language='pl-NL' lub 'pl-PL' widział formularze po angielsku, mimo że template miał polskie etykiety. SUPPORTED_CULTURES.includes(i18n.language) sprawdzało tylko krótkie kody. Nowa funkcja resolveCulture(lang) ucina region (pl-NLpl) i waliduje względem SUPPORTED_CULTURES. Plus: dla culture='pl' też próbuje fallback do *En gdy slot PL pusty
  • Pokój video — gość z wygasłym tokenem przekierowywany na /loginNotificationContext przy mount bezwarunkowo fetchował /api/notifications jeśli localStorage.token istniał (nawet wygasły). 401+refresh fail → window.location='/login', ale /video/join/ nie było na liście publicznych stron. Gość klikający link zaproszenia z wygasłym tokenem (np. po wcześniejszym logowaniu jako pacjent w PWA) leciał na ekran logowania. Dodane /video/join/ i /video/test-join/ do onPublicPage w handleSessionExpired
  • handleAudioFileUpload ReferenceError — wołał setTranscriptionPollingError i setTranscriptionPollingStatus, których useTranscriptionPolling nie eksponuje. W prod buildzie minifikacja zachowała te wywołania → ReferenceError w onChange inputa pliku → upload nigdy nie startował (UI pokazywał “Uploading…”, backend zero traffic). Fix: destrukturyzacja clearPollingError z hooka
  • Synchronizacja preview scoringu FE z BE dla 4DKL — szczegóły w sekcji 4 powyżej
  • iOS build bump 30 → 31 — TestFlight 90189 redundant binary, ApplicationDisplayVersion 1.7.10 bez zmian (TestFlight External nie wymaga ponownego Beta App Review)

Infrastruktura i diagnostyka

  • flushTelemetry hard timeout 2s — worker exit hangs gdy AppInsights endpoint nieosiągalny: flush callback nie odpala, process.exit(0) nigdy nie wywoła, Container Apps Job execution wisi w Running aż do replicaTimeout (30 min) blokując parallelism=1 slot. Promise.race z setTimeout(2000) wymusza exit
  • cron job fetches token via managed identity + az CLI — Container Apps KV secretRef sync padał 2× mimo poprawnego RBAC. Workaround: image mcr.microsoft.com/azure-cli, az login --identity + az keyvault secret show przy runtime, potem curl gateway
  • checkoutRepo obsługuje commit SHAprReview przekazuje head.sha jako refgit clone --branch <sha> nie działa (SHA nie jest branchem). Nowy flow: jeśli ref pasuje do /^[0-9a-f]{7,40}$/, clone --no-checkout + fetch + checkout FETCH_HEAD
  • aits-agent prompts + README odznaczone z globalnego *.md gitignore — worker docker build padał na “COPY aits-agent/prompts /app/prompts: not found” bo *.md w root .gitignore blokował commit prompts
  • Diagnostyka OAuth fix dla Claude SDK — trzy commity diagnostyczne (diag: dump SDK error details, diag: monkey-patch child_process.spawn, diag: revert spawn monkey-patch) doprowadziły do zlokalizowania bugu i zostały cofnięte. Dług diagnostyczny zwrócony — kod jest czysty
  • Docs: nowa sekcja “Dostęp do Application Insights dla Claude Agent SDK” w ai-agent-guide.md z opisem SP claude-agent-sdk-appinsights-reader (rola Reader na 4 App Insights, credentials w KV agent-bohdana, przykłady Python / az CLI / REST, rotacja secretu). Plus: docs/contexts/transcription.md zaktualizowany o F2b BlobBased-only normalize
  • Telegram messaging: jira-triage / jira-analyse wysyłają finalText do Telegrama, plus final message verbatim z komentarza Jira (zamiast podsumowania)

QA Checklist

Co testujeJakStatus
Outlook OAuth (Wave 1)Ustawienia → Integracje → Outlook → Connect → status connected
Outlook event CRUD (Wave 2)Stwórz sesję → event pojawia się w Outlooku
Outlook primary calendar select (Wave 2)Wybierz inny kalendarz w sekcji “Wybierz kalendarz główny”
WeekCalendar Outlook overlayToggle “Pokaż Outlook” → eventy widoczne na niebiesko
Microsoft Teams meeting (Wave 3)AddSessionModal → Online → “Microsoft Teams” → sesja ma teamsMeetingLink
Recurring sessions w Outlooku (Wave 3)Stwórz sesję cykliczną → wszystkie sesje mają ten sam OutlookEventId
Outlook freebusy (Wave 3)Zajęte termin w Outlooku → AvailabilityService blokuje slot
Cross-provider exclusivity (Wave 4)Ustaw Outlook primary → Google primary wyzerowane
Outlook self-heal (Wave 4)Usuń event w Outlooku → po 12h serwis odtwarza
Outlook mail toggle (Wave 5)Włącz toggle → przypomnienie sesji idzie z Outlooka
AvailabilityTypeId SSOTStwórz sesję typu “online external link” → SessionCard pokazuje tę nazwę
AddSessionModal custom location”Inna lokalizacja” → wpisz “kolorowa” → SessionDetailView ma “kolorowa” w dropdownie
iDEAL dla pacjenta NLTerapeuta z Connect NL → pacjent widzi iDEAL obok karty
iDEAL nieaktywne dla PLTerapeuta z Connect PL → pacjent widzi tylko kartę
PDSS-SR / BAT-PL / 4DKLWyślij formularz → pacjent wypełnia → scoring zgodny z PDF manuala
4DKL scoring valueMapOdpowiedź 3 lub 4 → score = 2 (nie 3 / 4)
PDF restrukturyzacji poznawczejZatwierdź restrukturyzację → kliknij PDF → plik się pobiera
Toggle “Blokuj zapisy online”Włącz → publiczny profil zwraca 404 dla GetAvailability
Toggle “Respect external busy”Wyłącz → therapist-side widzi sloty mimo busy w Google
Command palette ⌘K⌘K na /settings → wpisz “platnosci” → matchuje Płatności
Command palette substring strictWpisz “rodo” → matchuje tylko Prywatność (nie 8 sekcji)
SessionsList sort directionKliknij toggle → najstarsze sesje na górze
aits-agent telegram + AppInsightsWyślij “/help” → bot odpowiada listą komend
aits-agent /newjira/newjira <temat> → ticket SCRUM-NNNN utworzony
aits-agent /analyse/analyse SCRUM-1591 → analiza pojawia się jako komentarz
Admin regenerate recurring sessionsPOST /api/admin/maintenance/recurring-sessions/regenerate → 200 OK
Bulk-create VideoInviteToken syncStwórz batch sesji → terapeuta i pacjent w tym samym pokoju
Public booking respektuje Google busyTerapeuta blokuje slot w Google → publiczny widget go nie pokazuje
IBAN zagranicznyWpisz IBAN NL/DE → walidacja przechodzi
Waluta zamiast PLNZmień walutę na EUR → profil pacjenta + historia płatności w EUR
Modal “Edytuj sesję” formatowanieOtwórz modal → “Aktualny” i “Nowy termin” mają identyczny wygląd
Formularze pacjenta pl-NLi18n.language='pl-NL' → formularz po polsku, nie po angielsku
Pokój video z wygasłym tokenemWygaśnięty JWT w localStorage + klik linka video → zostaje w pokoju
Multi-replica audio (SCRUM-1591)Upload audio na 2 replikach → transkrypcja kończy się Completed
Docplanner removedGET /api/docplanner/* → 404

Artykuł przygotowany przez zespół Therapy Support

Program feedbackowy · Dołącz teraz

Odzyskaj czas dla siebie
i swoich pacjentów

Jesteś terapeutą / terapeutką CBT?
Sprawdź, jak platforma wspiera Twoją codzienną pracę.
Podsumowania sesji, które porządkują materiał kliniczny. Administracja, która nie przeszkadza.