{"id":494387,"date":"2026-09-11T12:00:04","date_gmt":"2026-09-11T12:00:04","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=494387"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=494387","title":{"rendered":"\u041a\u0430\u043a \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442 \u0441\u0430\u043c \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043e\u043a\u043d\u0430 \u0432 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0441\u0442\u043e\u043c\u0430\u0442\u043e\u043b\u043e\u0433\u043e\u0432"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/499\/4a7\/86e\/4994a786e35665a8c0391c53c811c42f.png\" width=\"1667\" height=\"943\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/499\/4a7\/86e\/4994a786e35665a8c0391c53c811c42f.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/499\/4a7\/86e\/4994a786e35665a8c0391c53c811c42f.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044e: \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440 \u0441\u0442\u043e\u043c\u0430\u0442\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043a\u043b\u0438\u043d\u0438\u043a\u0438 \u0443\u0442\u0440\u043e\u043c \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438 \u0432\u0438\u0434\u0438\u0442, \u0447\u0442\u043e \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043e\u0442\u043c\u0435\u043d\u0438\u043b \u043f\u0440\u0438\u0435\u043c \u0432 10:00, \u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0443 \u044d\u0442\u043e\u0433\u043e \u0432\u0440\u0430\u0447\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0435\u0447\u0435\u0440\u043e\u043c. \u0412\u0440\u0435\u043c\u044f \u043f\u0440\u043e\u043f\u0430\u0434\u0430\u0435\u0442, \u0432\u0440\u0430\u0447 \u0436\u0434\u0435\u0442. \u0410 \u0440\u044f\u0434\u043e\u043c, \u0432 \u043b\u0438\u0441\u0442\u0435 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f, \u043d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430 \u0435\u0441\u0442\u044c \u0447\u0435\u043b\u043e\u0432\u0435\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044b\u043b \u0431\u044b \u0440\u0430\u0434 \u043f\u043e\u043f\u0430\u0441\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u043d\u0430 \u044d\u0442\u043e \u0432\u0440\u0435\u043c\u044f. \u041d\u0430\u0434\u043e \u0442\u043e\u043b\u044c\u043a\u043e, \u0447\u0442\u043e\u0431\u044b \u043a\u0442\u043e-\u0442\u043e \u0435\u043c\u0443 \u043e\u0431 \u044d\u0442\u043e\u043c \u0441\u043e\u043e\u0431\u0449\u0438\u043b.\u00a0<\/p>\n<p>\u041f\u043e\u0447\u0442\u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u044f\u0442\u044b\u0439 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043d\u0430 \u043f\u0440\u0438\u0435\u043c \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f. \u0412\u0440\u0443\u0447\u043d\u0443\u044e \u043e\u0431\u0437\u0432\u0430\u043d\u0438\u0432\u0430\u0442\u044c \u0434\u043e\u043b\u0433\u043e \u0438 \u043d\u0435\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e, \u0430 \u0441\u043b\u043e\u0442 \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u043a\u0440\u044b\u0442\u044c \u0431\u044b\u0441\u0442\u0440\u043e, \u043f\u043e\u043a\u0430 \u043e\u043d \u0435\u0449\u0435 \u0430\u043a\u0442\u0443\u0430\u043b\u0435\u043d.<\/p>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u043f\u043e\u043a\u0430\u0436\u0435\u043c, \u043a\u0430\u043a \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0438\u0441\u044f\u043c\u0438 \u043a \u0441\u0442\u043e\u043c\u0430\u0442\u043e\u043b\u043e\u0433\u0443. <a href=\"https:\/\/exolve.ru\/products\/razrabotka-golosovykh-robotov\/\">\u0413\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442 \u041c\u0422\u0421 Exolve<\/a> \u0431\u0443\u0434\u0435\u0442 \u0441\u0430\u043c \u0437\u0432\u043e\u043d\u0438\u0442\u044c \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0443, \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432\u0438\u0437\u0438\u0442 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0438.<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u0442\u0435\u043a:<\/summary>\n<div class=\"spoiler__content\">\n<p> Python 3.10+, FastAPI, SQLite, Pydantic Settings, \u0413\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442 \u041c\u0422\u0421 Exolve.<\/p>\n<\/div>\n<\/details>\n<h2>\u041e\u0431\u0449\u0430\u044f \u0441\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b<\/h2>\n<p>\u0412\u0441\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 \u043c\u0435\u0434\u0438\u0446\u0438\u043d\u0441\u043a\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b (\u041c\u0418\u0421), \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0432\u0435\u0434\u0435\u0442\u0441\u044f \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0432\u0440\u0430\u0447\u0435\u0439. \u041a\u043e\u0433\u0434\u0430 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043e\u0442\u043c\u0435\u043d\u044f\u0435\u0442 \u043f\u0440\u0438\u0435\u043c, \u043e\u043d\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043e \u0432\u0440\u0430\u0447\u0435 \u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u043e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0432\u0448\u0435\u0433\u043e\u0441\u044f \u043e\u043a\u043d\u0430. \u0417\u0430\u0442\u0435\u043c \u0431\u044d\u043a\u0435\u043d\u0434 \u043f\u0435\u0440\u0435\u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u041c\u0418\u0421, \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043f\u0440\u0438\u0435\u043c \u0438 \u043f\u0440\u0430\u0432\u0434\u0430 \u043e\u0442\u043c\u0435\u043d\u0435\u043d, \u0430 \u043c\u0435\u0441\u0442\u043e \u0437\u0430 \u043f\u0440\u043e\u0448\u0435\u0434\u0448\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0435 \u0437\u0430\u043d\u044f\u043b \u043a\u0442\u043e-\u0442\u043e \u0434\u0440\u0443\u0433\u043e\u0439.<\/p>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u044b, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u043d\u0430 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u043e\u0432 \u0438\u0437 \u043b\u0438\u0441\u0442\u0430 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u043a \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442\u0443, \u0443 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u044b\u043b\u0430 \u043e\u0442\u043c\u0435\u043d\u0430, \u0437\u0430\u0442\u0435\u043c \u0442\u0435\u0445, \u043a\u0442\u043e \u0437\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0437\u0434\u043d\u0438\u0435 \u0434\u0430\u0442\u044b. \u0410 \u043f\u0435\u0440\u0435\u0434 \u0437\u0432\u043e\u043d\u043a\u043e\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u043e\u0433\u043e\u043d\u044f\u044e\u0442 \u0435\u0449\u0435 \u0440\u0430\u0437, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0434\u0435\u0440\u0433\u0430\u0442\u044c \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0441\u0430\u043c \u043f\u0435\u0440\u0435\u043d\u0435\u0441 \u043f\u0440\u0438\u0435\u043c \u0438\u043b\u0438 \u043e\u0442\u043c\u0435\u043d\u0438\u043b \u0435\u0433\u043e.<\/p>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u0432 \u0434\u0435\u043b\u043e \u0432\u0441\u0442\u0443\u043f\u0430\u0435\u0442 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442. \u041e\u043d \u0437\u0432\u043e\u043d\u0438\u0442 \u2014 \u0438 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043b\u0438\u0431\u043e \u0441\u043e\u0433\u043b\u0430\u0448\u0430\u0435\u0442\u0441\u044f, \u043b\u0438\u0431\u043e \u043e\u0442\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f, \u043b\u0438\u0431\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0431\u0435\u0440\u0435\u0442 \u0442\u0440\u0443\u0431\u043a\u0443. \u0415\u0441\u043b\u0438 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u0441\u043a\u0430\u0437\u0430\u043b \u00ab\u0434\u0430\u00bb, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0441\u0440\u0430\u0437\u0443 \u0431\u0440\u043e\u043d\u0438\u0440\u0443\u0435\u0442 \u043e\u043a\u043d\u043e \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u0432\u0438\u0437\u0438\u0442. \u041e\u0442\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0438\u043b\u0438 \u043d\u0435 \u0432\u0437\u044f\u043b \u0442\u0440\u0443\u0431\u043a\u0443 \u2014 \u0440\u043e\u0431\u043e\u0442 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441, \u0430 \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430\u0431\u0438\u0440\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u0438.<\/p>\n<p>\u041d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430 \u0432\u0438\u0434\u043d\u043e \u0442\u0440\u0438 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u043a\u043d\u0430: \u043f\u0440\u0438\u0435\u043c \u043f\u0435\u0440\u0435\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d, \u0441\u043b\u043e\u0442 \u0437\u0430\u043d\u044f\u043b\u0438 \u0434\u043e \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u0434\u043e\u0448\u043b\u0430 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0437\u0432\u043e\u043d\u0438\u0442\u044c, \u0438\u043b\u0438 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u044b \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0438\u0441\u044c.<\/p>\n<h2>\u041a\u0430\u043a \u0432\u0441\u0435 \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u043e<\/h2>\n<p>\u0411\u044d\u043a\u0435\u043d\u0434 \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u0435\u0442 \u041c\u0418\u0421 \u0438 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0433\u043e \u0440\u043e\u0431\u043e\u0442\u0430. FastAPI \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0435\u0431\u0445\u0443\u043a\u0438 \u043e\u0431 \u043e\u0442\u043c\u0435\u043d\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c \u0441\u043b\u043e\u0442\u0430, \u043f\u043e\u0434\u0431\u0438\u0440\u0430\u0435\u0442 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043e\u0431\u0437\u0432\u043e\u043d.<\/p>\n<p>\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b (\u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431 \u043e\u043a\u043d\u0430\u0445, \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430\u0445 \u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0439) \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 SQLite. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u043a\u043b\u044e\u0447\u0430\u043c slot_id, candidate_id, appointment_id \u0438 call_id \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043e\u0442\u0432\u0435\u0442 \u043e\u0442 \u0440\u043e\u0431\u043e\u0442\u0430 \u0437\u0430\u043f\u0430\u0437\u0434\u044b\u0432\u0430\u0435\u0442. \u0420\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 ClinicApi \u2014 \u0432 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043c\u043e\u043a-\u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u0432\u0435\u0440\u0445 SQLite.<\/p>\n<p>\u0413\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442 \u041c\u0422\u0421 Exolve \u043e\u0431\u0437\u0432\u0430\u043d\u0438\u0432\u0430\u0435\u0442 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432 \u043e\u0434\u043d\u043e\u0433\u043e \u0437\u0430 \u0434\u0440\u0443\u0433\u0438\u043c. \u0421\u043e\u0433\u043b\u0430\u0441\u0438\u0435 \u2014 \u043d\u043e\u0432\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c; \u043e\u0442\u043a\u0430\u0437 \u0438\u043b\u0438 \u043c\u043e\u043b\u0447\u0430\u043d\u0438\u0435 \u2014 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442 \u043f\u0440\u0438\u0447\u0438\u043d\u0443 \u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0443.<\/p>\n<details class=\"spoiler\">\n<summary>\u041e\u0431\u0449\u0430\u044f \u0441\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ea6\/d9c\/a9c\/ea6d9ca9c49155dfd439e2009ad140df.jpeg\" width=\"745\" height=\"590\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/ea6\/d9c\/a9c\/ea6d9ca9c49155dfd439e2009ad140df.jpeg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ea6\/d9c\/a9c\/ea6d9ca9c49155dfd439e2009ad140df.jpeg 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<\/div>\n<\/details>\n<p>\u041b\u043e\u0433\u0438\u043a\u0430 \u0440\u0430\u0437\u0431\u0438\u0442\u0430 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432:<\/p>\n<ul>\n<li>\n<p>app.py \u2014 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0432\u0435\u0431\u0445\u0443\u043a\u0438, \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0442\u043e\u043a\u0435\u043d\u044b \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u043e\u0442\u0434\u0430\u0435\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c;<\/p>\n<\/li>\n<li>\n<p>slot_filling_service.py \u2014 \u0434\u0435\u0440\u0436\u0438\u0442 \u043d\u0430 \u0441\u0435\u0431\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439: \u0441\u043b\u0435\u0434\u0438\u0442 \u0437\u0430 \u0441\u0442\u0430\u0442\u0443\u0441\u0430\u043c\u0438 \u043e\u043a\u043e\u043d, \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u043e\u0432, \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0437\u0432\u043e\u043d\u043a\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>clinic_api.py \u2014 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u043c\u0435\u0434\u0438\u0446\u0438\u043d\u0441\u043a\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b;<\/p>\n<\/li>\n<li>\n<p>reaper.py \u2014 \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u0438 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0437\u0432\u043e\u043d\u043a\u0438, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0440\u043e\u0431\u043e\u0442 \u0442\u0430\u043a \u0438 \u043d\u0435 \u043f\u0440\u0438\u0441\u043b\u0430\u043b \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438\u0437-\u0437\u0430 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0441\u0431\u043e\u0435\u0432;<\/p>\n<\/li>\n<li>\n<p>config.py \u2014 \u0445\u0440\u0430\u043d\u0438\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u043b\u0438\u043c\u0438\u0442\u044b \u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0444\u043e\u043d\u043e\u0432\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.<\/p>\n<\/li>\n<\/ul>\n<h3>\u0427\u0442\u043e \u0438 \u043a\u0430\u043a \u043c\u044b \u0445\u0440\u0430\u043d\u0438\u043c<\/h3>\n<p>\u0412\u0441\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 \u0432\u0435\u0431\u0445\u0443\u043a\u0430 \u043e\u0442 \u041c\u0418\u0421 \u043e\u0431 \u043e\u0442\u043c\u0435\u043d\u0435 \u0437\u0430\u043f\u0438\u0441\u0438. \u0417\u0430\u0442\u0435\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0438\u0434\u0435\u0442 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e: \u0440\u043e\u0431\u043e\u0442 \u0437\u0432\u043e\u043d\u0438\u0442, \u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c. \u0427\u0442\u043e\u0431\u044b \u043c\u0435\u0436\u0434\u0443 \u044d\u0442\u0438\u043c\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u043c\u0438 \u043d\u0435 \u043f\u043e\u0442\u0435\u0440\u044f\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442, \u0431\u044d\u043a\u0435\u043d\u0434 \u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435: \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0435 \u043e\u043a\u043d\u043e, \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u0438 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0430.<\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0435 \u043f\u043e \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u043c \u0441\u043b\u043e\u0442\u0430\u043c (\u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442, \u0443\u0441\u043b\u0443\u0433\u0430, \u0432\u0440\u0435\u043c\u044f, \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c) \u043b\u0435\u0436\u0430\u0442 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 canceled_slots. \u041f\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u0443 \u0432\u0438\u0434\u043d\u043e, \u0447\u0435\u043c \u0432\u0441\u0435 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u043e\u0441\u044c.<\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0435 \u043f\u043e \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430\u043c \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 slot_candidates: \u043a\u0430\u0436\u0434\u044b\u0439 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d \u043a \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u043c\u0443 \u043e\u043a\u043d\u0443 \u0438 \u0441\u0432\u043e\u0435\u043c\u0443 \u0442\u0435\u043a\u0443\u0449\u0435\u043c\u0443 \u0432\u0438\u0437\u0438\u0442\u0443. \u0421\u0442\u0430\u0442\u0443\u0441 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u044d\u0442\u0430\u043f: \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u0438, \u0438\u0434\u0435\u0442 \u043e\u0431\u0437\u0432\u043e\u043d, \u0441\u043e\u0433\u043b\u0430\u0441\u0438\u0435, \u043e\u0442\u043a\u0430\u0437 \u0438\u043b\u0438 \u043f\u0440\u043e\u043f\u0443\u0441\u043a (\u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u0430).<\/p>\n<p>\u0412\u0441\u0435 \u0437\u0432\u043e\u043d\u043a\u0438 \u043a \u0431\u043e\u0442\u0443 \u043b\u043e\u0433\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432 call_attempts. \u0411\u044d\u043a\u0435\u043d\u0434 \u0445\u0440\u0430\u043d\u0438\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0432\u043e\u043d\u043a\u0430 \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0441\u043b\u043e\u0442 \u0438 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u043f\u043e ID, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043b\u043e\u0432\u0438\u0442 \u0432 \u0432\u0435\u0431\u0445\u0443\u043a\u0435. \u0422\u0430\u043a \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0441\u0432\u044f\u0437\u0438, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0441 \u043e\u043f\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043c.<\/p>\n<p>\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u043a\u043d\u043e\u043c \u0438 \u043e\u0431\u0437\u0432\u043e\u043d \u043c\u044b \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u043b\u0438. \u0423 \u043e\u0434\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u0442\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441 \u043b\u043e\u0433\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u0441\u0432\u044f\u0437\u0430\u0442\u044c\u0441\u044f \u0441 \u043a\u0430\u0436\u0434\u044b\u043c \u0438\u0437 \u043d\u0438\u0445.\u00a0<\/p>\n<h3>\u0426\u0435\u043b\u043e\u0441\u0442\u043d\u043e\u0441\u0442\u044c \u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u044b<\/h3>\n<p>\u0425\u0443\u0434\u0448\u0435\u0435, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0441\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u044f, \u2014 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0443 \u0437\u0430\u043d\u044f\u0442\u043e\u0433\u043e \u0441\u043b\u043e\u0442\u0430. \u0427\u0442\u043e\u0431\u044b \u044d\u0442\u043e\u0433\u043e \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c, \u0431\u044d\u043a\u0435\u043d\u0434 \u043f\u0435\u0440\u0435\u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0435\u0433\u043e \u0442\u0440\u0438 \u0440\u0430\u0437\u0430: \u043f\u0440\u0438 \u043e\u0442\u043c\u0435\u043d\u0435, \u043f\u0435\u0440\u0435\u0434 \u043e\u0431\u0437\u0432\u043e\u043d\u043e\u043c \u0438 \u043f\u043e\u0441\u043b\u0435 \u0441\u043e\u0433\u043b\u0430\u0441\u0438\u044f \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430.\u00a0<\/p>\n<p>\u0421\u0435\u0440\u0432\u0438\u0441 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0440\u043e\u0432\u043d\u043e \u043e\u0434\u0438\u043d \u0440\u0430\u0437. \u0411\u044d\u043a\u0435\u043d\u0434 \u0437\u0430\u0431\u0438\u0440\u0430\u0435\u0442 \u043a\u043b\u044e\u0447 \u0438\u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438 \u0438\u0449\u0435\u0442 \u043f\u043e \u043d\u0435\u043c\u0443 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 canceled_slot. \u0415\u0441\u043b\u0438 \u041c\u0418\u0421 \u043f\u0440\u0438\u0448\u043b\u0435\u0442 \u0432\u0435\u0431\u0445\u0443\u043a \u0435\u0449\u0435 \u0440\u0430\u0437, \u0442\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043e\u0442\u0434\u0430\u0441\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443.<\/p>\n<p>\u041f\u0435\u0440\u0435\u0434 \u0437\u0432\u043e\u043d\u043a\u043e\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u0440\u043e\u0433\u043e\u043d\u044f\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0447\u0435\u0440\u0435\u0437 skipineligible_pending \u0438 \u043e\u0442\u0441\u0435\u0438\u0432\u0430\u0435\u0442 \u0442\u0435\u0445, \u0447\u044c\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0443\u0441\u043f\u0435\u043b\u0438 \u0443\u0441\u0442\u0430\u0440\u0435\u0442\u044c, \u043f\u043e\u043a\u0430 \u0441\u0442\u0440\u043e\u0438\u043b\u0430\u0441\u044c \u043e\u0447\u0435\u0440\u0435\u0434\u044c. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043c\u043e\u0433 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432\u0438\u0437\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430 \u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0435\u0433\u043e \u043c\u043e\u0433\u043b\u0438 \u043f\u043e\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0430\u043a \u043e\u0442\u043a\u0430\u0437\u0430\u0432\u0448\u0435\u0433\u043e\u0441\u044f \u043e\u0442 \u0430\u0432\u0442\u043e\u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438.<\/p>\n<h2>\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b\u00a0<\/h2>\n<p>\u0421\u0435\u0440\u0432\u0438\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 Python 3.10+. \u0418\u0437 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0443 \u043d\u0435\u0433\u043e FastAPI, Uvicorn \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.<\/p>\n<pre><code class=\"bash\">python -m venv .venvsource .venv\/bin\/activatepip install fastapi uvicorn pydantic-settings requests\u00a0<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0436\u0435\u043d\u0438\u043c \u0432\u0441\u0435 \u0441 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u044b\u043c \u0440\u043e\u0431\u043e\u0442\u043e\u043c \u041c\u0422\u0421 Exolve. \u0427\u0435\u0440\u0435\u0437 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u043e\u0431\u0437\u0432\u043e\u043d\u0430, \u0433\u0434\u0435 \u0440\u043e\u0431\u043e\u0442 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0442\u044c \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430\u043c \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0437\u0430\u043f\u0438\u0441\u044c \u043f\u043e\u0440\u0430\u043d\u044c\u0448\u0435. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c HTTPS-\u0430\u0434\u0440\u0435\u0441, \u043a\u0443\u0434\u0430 \u0440\u043e\u0431\u043e\u0442 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0437\u0432\u043e\u043d\u043a\u043e\u0432. URL API \u0438 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0437\u0430\u043e\u0434\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u041c\u0418\u0421 \u0443\u043c\u0435\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u0430 \u0432\u0430\u0448 FastAPI-\u0441\u0435\u0440\u0432\u0438\u0441 \u0432\u0435\u0431\u0445\u0443\u043a \u043e\u0431 \u043e\u0442\u043c\u0435\u043d\u0435 \u0437\u0430\u043f\u0438\u0441\u0438.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b .env \u0441\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u0438 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432\u043c\u0435\u0441\u0442\u043e \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445:<\/p>\n<pre><code class=\"bash\">APP_NAME=\"Dental Slot Filler\"DB_PATH=\".\/dental_slots.sqlite3\"CLINIC_NAME=\"\u0414\u0435\u043d\u0442\u0430\u043b \u041a\u043b\u0438\u043d\u0438\u043a\"WEBHOOK_TOKEN=\"change-me\"ROBOT_WEBHOOK_TOKEN=\"change-me\"ADMIN_TOKEN=\"change-me\"ROBOT_API_URL=\"\"ROBOT_API_TOKEN=\"\"PUBLIC_BASE_URL=\"https:\/\/example.com\"RECENT_CALL_HOURS=24AUTO_CALL_NEXT_CANDIDATE=trueCALL_TIMEOUT_MINUTES=15REAPER_INTERVAL_SECONDS=60RUN_INPROCESS_REAPER=trueENABLE_DEMO_ENDPOINTS=true<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h2>\u0428\u0430\u0433 1. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u0435\u0431\u0445\u0443\u043a\u0430 \u043e\u0431 \u043e\u0442\u043c\u0435\u043d\u0435 \u0437\u0430\u043f\u0438\u0441\u0438<\/h2>\n<p>FastAPI \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0435\u0431\u0445\u0443\u043a \u043e\u0442 \u041c\u0418\u0421 \u0441 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u043f\u0438\u0441\u0438, \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0432\u0440\u0430\u0447\u0430, \u0434\u0430\u0442\u043e\u0439, \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u0438 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u043a\u043d\u0430. \u041f\u043e \u044d\u0442\u0438\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u0439 \u0432\u0438\u0437\u0438\u0442.\u00a0<\/p>\n<pre><code class=\"python\">app.py@app.post(\u00a0 \u00a0 \"\/webhooks\/appointment-canceled\",\u00a0 \u00a0 response_model=CancellationResponse,\u00a0 \u00a0 dependencies=[Depends(require_webhook_token)],)def appointment_canceled(payload: CancellationWebhook):\u00a0 \u00a0 try:\u00a0 \u00a0 \u00a0 \u00a0 return service.handle_cancellation(payload)\u00a0 \u00a0 except ValueError as exc:\u00a0 \u00a0 \u00a0 \u00a0 raise HTTPException(status_code=400, detail=str(exc)) from exc<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0442\u043e\u043a\u0435\u043d \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0445, \u0438 \u0435\u0441\u043b\u0438 \u0432\u0441\u0435 \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435, \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0441\u0435\u0440\u0432\u0438\u0441 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043b\u043e\u0442\u0430\u043c\u0438. \u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u0440\u0430\u0437\u0443 \u043e\u0442\u0441\u0435\u043a\u0430\u044e\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u043e\u0439 400, \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u043c\u0438 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0432 \u0431\u0430\u0437\u0435.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u0432\u0435\u0431\u0445\u0443\u043a \u043d\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u0437\u0432\u043e\u043d\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043a\u043b\u044e\u0447 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044d\u043a\u0435\u043d\u0434 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 canceled_slot. \u0415\u0441\u043b\u0438 \u043e\u043d \u0435\u0441\u0442\u044c, \u0442\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u043d\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f.<\/p>\n<pre><code class=\"python\">slot_filling_service.pystarts_at = datetime.combine(payload.date, payload.time).isoformat(timespec=\"seconds\")idempotency_key = self._make_idempotency_key(payload, starts_at)existing = conn.execute(\u00a0 \u00a0 \"\"\"\u00a0 \u00a0 SELECT id, status, stop_reason\u00a0 \u00a0 FROM canceled_slots\u00a0 \u00a0 WHERE idempotency_key = ?\u00a0 \u00a0 \"\"\",\u00a0 \u00a0 (idempotency_key,),).fetchone()if existing:\u00a0 \u00a0 return {\u00a0 \u00a0 \u00a0 \u00a0 \"slot_id\": existing[\"id\"],\u00a0 \u00a0 \u00a0 \u00a0 \"status\": existing[\"status\"],\u00a0 \u00a0 \u00a0 \u00a0 \"stop_reason\": existing[\"stop_reason\"],\u00a0 \u00a0 \u00a0 \u00a0 \"candidates_created\": 0,\u00a0 \u00a0 }<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0441\u043b\u043e\u0442 \u0443\u0436\u0435 \u043e\u0442\u043c\u0435\u043d\u0435\u043d, \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447 \u043d\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438 \u043f\u0440\u0438\u0447\u0438\u043d\u0443.<\/p>\n<h2>\u0428\u0430\u0433 2. \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c \u0441\u043b\u043e\u0442\u0430<\/h2>\n<p>\u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0432\u0435\u0431\u0445\u0443\u043a \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e\u0431 \u043e\u0442\u043c\u0435\u043d\u0435, \u0442\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0437\u0430\u043d\u044f\u0442\u043e. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441 \u0441\u043d\u043e\u0432\u0430 \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e \u043f\u0440\u0438\u0435\u043c\u0435, \u043f\u043e\u043f\u0443\u0442\u043d\u043e \u0441\u0432\u0435\u0440\u044f\u044f \u0441\u0442\u0430\u0442\u0443\u0441, \u0432\u0440\u0430\u0447\u0430 \u0438 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043e\u043a\u043d\u0430.<\/p>\n<pre><code class=\"python\">slot_filling_service.pycanceled_appointment = self.clinic_api.get_appointment(\u00a0 \u00a0 conn,\u00a0 \u00a0 payload.canceled_appointment_id,)if not canceled_appointment:\u00a0 \u00a0 raise ValueError(\u00a0 \u00a0 \u00a0 \u00a0 f\"Canceled appointment not found: {payload.canceled_appointment_id}\"\u00a0 \u00a0 )if canceled_appointment[\"status\"] != \"canceled\":\u00a0 \u00a0 raise ValueError(\u00a0 \u00a0 \u00a0 \u00a0 f\"Appointment is not canceled: {payload.canceled_appointment_id} \"\u00a0 \u00a0 \u00a0 \u00a0 f\"(status={canceled_appointment['status']})\"\u00a0 \u00a0 )if canceled_appointment[\"doctor_id\"] != payload.doctor_id:\u00a0 \u00a0 raise ValueError(\"Canceled appointment doctor mismatch\")if canceled_appointment[\"duration_minutes\"] != payload.duration_minutes:\u00a0 \u00a0 raise ValueError(\"Canceled appointment duration mismatch\")\u0417\u0430\u0442\u0435\u043c ClinicApi \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0435\u0441\u0442\u044c \u043b\u0438 \u0443 \u0432\u0440\u0430\u0447\u0430 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0432\u0438\u0437\u0438\u0442\u044b \u043a\u0440\u043e\u043c\u0435 \u0441\u0430\u043c\u043e\u0439 \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438. clinic_api.pyrow = conn.execute(\u00a0 \u00a0 f\"\"\"\u00a0 \u00a0 SELECT id\u00a0 \u00a0 FROM appointments\u00a0 \u00a0 WHERE doctor_id = ?\u00a0 \u00a0 \u00a0 AND status = ?\u00a0 \u00a0 \u00a0 AND datetime(starts_at) &lt; datetime(?)\u00a0 \u00a0 \u00a0 AND datetime(starts_at, '+'  duration_minutes  ' minutes') &gt; datetime(?)\u00a0 \u00a0 \u00a0 {ignore_sql}\u00a0 \u00a0 LIMIT 1\u00a0 \u00a0 \"\"\",\u00a0 \u00a0 params,).fetchone()return row is None<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u043d\u044f\u0442\u043e, \u0431\u044d\u043a\u0435\u043d\u0434 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \u0441\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c occupied \u0438 \u043f\u0440\u0438\u0447\u0438\u043d\u043e\u0439 slot_already_occupied.\u00a0<\/p>\n<h2>\u0428\u0430\u0433 3. \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438 \u0432\u044b\u0431\u043e\u0440 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432<\/h2>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043e\u043a\u043d\u0430 \u0431\u044d\u043a\u0435\u043d\u0434 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u044c \u0432 canceled_slots \u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u043f\u043e\u0434\u0431\u043e\u0440 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u043e\u0432. \u0414\u043b\u044f \u043e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0432\u0448\u0435\u0433\u043e\u0441\u044f \u0441\u043b\u043e\u0442\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0437\u0430\u043f\u0438\u0441\u0438 \u0441 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438: \u0442\u0435\u043c\u0438 \u0436\u0435 \u0432\u0440\u0430\u0447\u043e\u043c, \u0443\u0441\u043b\u0443\u0433\u043e\u0439 \u0438 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e.<\/p>\n<pre><code class=\"python\">slot_filling_service.pyslot_id = self._create_canceled_slot(\u00a0 \u00a0 conn,\u00a0 \u00a0 idempotency_key=idempotency_key,\u00a0 \u00a0 canceled_appointment_id=payload.canceled_appointment_id,\u00a0 \u00a0 doctor_id=payload.doctor_id,\u00a0 \u00a0 service_id=service_id,\u00a0 \u00a0 starts_at=starts_at,\u00a0 \u00a0 duration_minutes=payload.duration_minutes,\u00a0 \u00a0 status=\"task_created\",\u00a0 \u00a0 stop_reason=None,)candidates_created = self._create_candidates(conn, slot_id=slot_id)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043b\u0438\u0441\u0442 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u0438, \u0435\u0441\u043b\u0438 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u043b\u044e\u0434\u0435\u0439 \u043d\u0435\u0442, \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u0437\u0430\u043f\u0438\u0441\u0438 \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0437\u0434\u043d\u0438\u0435 \u0434\u0430\u0442\u044b \u043a \u044d\u0442\u043e\u043c\u0443 \u0436\u0435 \u0432\u0440\u0430\u0447\u0443 \u0438 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432\u0438\u0437\u0438\u0442 \u043f\u043e\u0440\u0430\u043d\u044c\u0448\u0435.\u00a0<\/p>\n<pre><code class=\"python\">slot_filling_service.pydef createcandidates(self, conn: Connection, *, slot_id: int) -&gt; int:\u00a0 \u00a0 slot = self._get_slot(conn, slot_id)\u00a0 \u00a0 if not slot:\u00a0 \u00a0 \u00a0 \u00a0 raise ValueError(f\"Slot not found: {slot_id}\")\u00a0 \u00a0 created_from_waiting_list = self._create_candidates_from_waiting_list(conn, slot)\u00a0 \u00a0 if created_from_waiting_list &gt; 0:\u00a0 \u00a0 \u00a0 \u00a0 return created_from_waiting_list\u00a0 \u00a0 return self._create_candidates_from_later_appointments(conn, slot)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0449\u0435 \u0434\u043e \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0437\u0432\u043e\u043d\u043a\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0447\u0435\u0440\u0435\u0437 SQL-\u0444\u0438\u043b\u044c\u0442\u0440\u044b \u043e\u0442\u0441\u0435\u0438\u0432\u0430\u0435\u0442 \u0442\u0435\u0445, \u043a\u0442\u043e \u0443\u0436\u0435 \u043e\u0442\u043a\u0430\u0437\u044b\u0432\u0430\u043b\u0441\u044f \u043e\u0442 \u043e\u0431\u0437\u0432\u043e\u043d\u0430, \u043a\u043e\u043c\u0443 \u043d\u0435\u043b\u044c\u0437\u044f \u0437\u0432\u043e\u043d\u0438\u0442\u044c \u043f\u043e \u044d\u0442\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438, \u043a\u043e\u043c\u0443 \u0443\u0436\u0435 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u043b\u0438 \u044d\u0442\u043e\u0442 \u0441\u043b\u043e\u0442 \u0438 \u043a\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u043b \u0437\u0432\u043e\u043d\u043a\u0438 \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u043f\u0435\u0440\u0438\u043e\u0434\u0430 RECENT_CALL_HOURS.\u00a0<\/p>\n<pre><code class=\"python\">slot_filling_service.pyWHERE wl.doctor_id = ?\u00a0 AND wl.service_id = ?\u00a0 AND wl.active = 1\u00a0 AND a.status = 'scheduled'\u00a0 AND datetime(a.starts_at) &gt; datetime(?)\u00a0 AND a.duration_minutes = ?\u00a0 AND p.global_opt_out = 0\u00a0 AND a.offer_opt_out = 0\u00a0 AND NOT EXISTS (\u00a0 \u00a0 \u00a0 SELECT 1\u00a0 \u00a0 \u00a0 FROM offered_slots os\u00a0 \u00a0 \u00a0 WHERE os.slot_id = ?\u00a0 \u00a0 \u00a0 \u00a0 AND os.patient_id = wl.patient_id\u00a0 )\u00a0 AND NOT EXISTS (\u00a0 \u00a0 \u00a0 SELECT 1\u00a0 \u00a0 \u00a0 FROM call_attempts ca\u00a0 \u00a0 \u00a0 WHERE ca.patient_id = wl.patient_id\u00a0 \u00a0 \u00a0 \u00a0 AND datetime(ca.created_at) &gt;= datetime(?)\u00a0 )ORDER BY wl.created_at ASC<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432 \u043d\u0435\u0442, \u0441\u0435\u0440\u0432\u0438\u0441 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \u0431\u0435\u0437 \u0437\u0432\u043e\u043d\u043a\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0431\u0435\u0441\u043f\u043e\u043a\u043e\u0438\u0442\u044c \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u043e\u0432 \u043b\u0438\u0448\u043d\u0438\u0439 \u0440\u0430\u0437.\u00a0<\/p>\n<h2>\u0428\u0430\u0433 4. \u0417\u0430\u043f\u0443\u0441\u043a \u0437\u0432\u043e\u043d\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0433\u043e \u0440\u043e\u0431\u043e\u0442\u0430<\/h2>\n<p>\u041f\u0435\u0440\u0435\u0434 \u043e\u0431\u0437\u0432\u043e\u043d\u043e\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u0435\u0449\u0435 \u0440\u0430\u0437. \u0420\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043a\u043b\u0438\u043d\u0438\u043a\u0438 \u0432\u043f\u043e\u043b\u043d\u0435 \u043c\u043e\u0433\u043b\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0441\u044f \u0441 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0447\u0435\u0440\u0435\u0434\u0438. \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440 \u043c\u043e\u0433 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432\u0438\u0437\u0438\u0442 \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u0430 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u2014 \u043f\u043e\u043f\u0430\u0441\u0442\u044c \u0432 \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0442\u043a\u0430\u0437\u043e\u0432 \u043f\u0440\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0441\u043e\u0441\u0435\u0434\u043d\u0435\u0433\u043e \u0441\u043b\u043e\u0442\u0430.\u00a0<\/p>\n<pre><code class=\"python\">if slot[\"status\"] in TERMINAL_SLOT_STATUSES:\u00a0 \u00a0 return {\u00a0 \u00a0 \u00a0 \u00a0 \"slot_id\": slot_id,\u00a0 \u00a0 \u00a0 \u00a0 \"status\": slot[\"status\"],\u00a0 \u00a0 \u00a0 \u00a0 \"action\": \"nothing_to_call\",\u00a0 \u00a0 }self._skip_ineligible_pending(conn, slot)candidate = conn.execute(\u00a0 \u00a0 \"\"\"\u00a0 \u00a0 SELECT\u00a0 \u00a0 \u00a0 \u00a0 sc.*,\u00a0 \u00a0 \u00a0 \u00a0 p.full_name AS patient_name,\u00a0 \u00a0 \u00a0 \u00a0 p.phone AS patient_phone,\u00a0 \u00a0 \u00a0 \u00a0 d.full_name AS doctor_name,\u00a0 \u00a0 \u00a0 \u00a0 a.starts_at AS current_appointment_starts_at\u00a0 \u00a0 FROM slot_candidates sc\u00a0 \u00a0 JOIN patients p ON p.id = sc.patient_id\u00a0 \u00a0 JOIN doctors d ON d.id = (\u00a0 \u00a0 \u00a0 \u00a0 SELECT doctor_id FROM canceled_slots WHERE id = sc.slot_id\u00a0 \u00a0 )\u00a0 \u00a0 JOIN appointments a ON a.id = sc.appointment_id\u00a0 \u00a0 WHERE sc.slot_id = ?\u00a0 \u00a0 \u00a0 AND sc.status = 'pending'\u00a0 \u00a0 ORDER BY sc.id\u00a0 \u00a0 LIMIT 1\u00a0 \u00a0 \"\"\",\u00a0 \u00a0 (slot_id,),).fetchone()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0432\u044b\u0431\u043e\u0440\u0430 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u0431\u044d\u043a\u0435\u043d\u0434 \u043c\u0435\u043d\u044f\u0435\u0442 \u0443 \u043d\u0435\u0433\u043e \u0441\u0442\u0430\u0442\u0443\u0441 \u0437\u0432\u043e\u043d\u043a\u0430 \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0432\u043e\u043d\u043a\u0430. \u0412\u043c\u0435\u0441\u0442\u0435 \u0441 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430\u0431\u043e\u0440 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432: \u0441\u043b\u043e\u0442, \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442, \u043f\u0430\u0446\u0438\u0435\u043d\u0442, \u0437\u0430\u043f\u0438\u0441\u044c. \u041a\u043e\u0433\u0434\u0430 \u0440\u043e\u0431\u043e\u0442 \u043e\u0442\u0432\u0435\u0442\u0438\u0442, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u043e\u0439\u043c\u0435\u0442, \u043a \u043a\u0430\u043a\u043e\u043c\u0443 \u0438\u043c\u0435\u043d\u043d\u043e \u043e\u043a\u043d\u0443 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0441\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442.<\/p>\n<pre><code class=\"python\">metadata = {\u00a0\u00a0\u00a0\u00a0\"slot_id\": slot_id,\u00a0\u00a0\u00a0\u00a0\"candidate_id\": candidate[\"id\"],\u00a0\u00a0\u00a0\u00a0\"patient_id\": candidate[\"patient_id\"],\u00a0\u00a0\u00a0\u00a0\"appointment_id\": candidate[\"appointment_id\"],}self.robot_client.start_offer_call(\u00a0\u00a0\u00a0\u00a0call_id=call_id,\u00a0\u00a0\u00a0\u00a0patient_phone=patient_phone,\u00a0\u00a0\u00a0\u00a0phrase=phrase,\u00a0\u00a0\u00a0\u00a0metadata=metadata,)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 API \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0438\u0438 \u0432\u0435\u0440\u043d\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0443, \u0442\u043e \u0431\u044d\u043a\u0435\u043d\u0434 \u043e\u0442\u043c\u0435\u0442\u0438\u0442 \u043d\u0435\u0443\u0434\u0430\u0447\u043d\u0443\u044e \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0438 \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u0442 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0443.\u00a0<\/p>\n<h2>\u0428\u0430\u0433 5. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430<\/h2>\n<p>\u0420\u043e\u0431\u043e\u0442 \u043d\u0430 \u0432\u0435\u0431\u0445\u0443\u043a \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0437\u0432\u043e\u043d\u043a\u0430, \u0437\u0430\u0442\u0435\u043c \u0431\u044d\u043a\u0435\u043d\u0434 \u043f\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0443 \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u043d\u0443\u0436\u043d\u0443\u044e \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0438 \u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043e\u043a\u043d\u0430 \u2014 \u0432\u0440\u0435\u043c\u044f, \u0432\u0440\u0430\u0447\u0430, \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u0411\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043a \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044e.<\/p>\n<pre><code class=\"python\">metadata = {\u00a0 \u00a0 \"slot_id\": slot_id,\u00a0 \u00a0 \"candidate_id\": candidate[\"id\"],\u00a0 \u00a0 \"patient_id\": candidate[\"patient_id\"],\u00a0 \u00a0 \"appointment_id\": candidate[\"appointment_id\"],}self.robot_client.start_offer_call(\u00a0 \u00a0 call_id=call_id,\u00a0 \u00a0 patient_phone=patient_phone,\u00a0 \u00a0 phrase=phrase,\u00a0 \u00a0 metadata=metadata,)d = ?\u00a0 \u00a0 \"\"\",\u00a0 \u00a0 (payload.call_id,),).fetchone()if not call:\u00a0 \u00a0 raise ValueError(f\"Call not found: {payload.call_id}\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \u0437\u0432\u043e\u043d\u043a\u0430. \u0415\u0441\u043b\u0438 \u0432\u0435\u0431\u0445\u0443\u043a \u043f\u0440\u0438\u0448\u0435\u043b \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0438\u043b\u0438 \u0444\u043e\u043d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0443\u0436\u0435 \u0437\u0430\u043a\u0440\u044b\u043b \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u0443, \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u0442, \u0447\u0442\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0443\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430, \u0438 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043c\u0435\u043d\u044f\u0435\u0442 \u0432 \u0431\u0430\u0437\u0435.<\/p>\n<pre><code class=\"python\">if call[\"status\"] != \"started\":\u00a0 \u00a0 return {\u00a0 \u00a0 \u00a0 \u00a0 \"call_id\": payload.call_id,\u00a0 \u00a0 \u00a0 \u00a0 \"result\": payload.result,\u00a0 \u00a0 \u00a0 \u00a0 \"action\": \"already_processed\",\u00a0 \u00a0 \u00a0 \u00a0 \"slot_status\": call[\"slot_status\"],\u00a0 \u00a0 }<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0442\u0432\u0435\u0442 \u0440\u043e\u0431\u043e\u0442\u0430 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \u0432 \u043e\u0434\u043d\u043e \u0438\u0437 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439:<\/p>\n<ul>\n<li>\n<p>accepted \u2014 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043b \u043f\u0435\u0440\u0435\u043d\u043e\u0441;<\/p>\n<\/li>\n<li>\n<p>refused_allow_future \u2014 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043e\u0442\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u043e\u0442 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043d\u043e \u0433\u043e\u0442\u043e\u0432 \u043a \u0437\u0432\u043e\u043d\u043a\u0430\u043c \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c;<\/p>\n<\/li>\n<li>\n<p>refused_appointment_opt_out \u2014 \u043a\u043b\u0438\u0435\u043d\u0442 \u0437\u0430\u043f\u0440\u0435\u0442\u0438\u043b \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438;<\/p>\n<\/li>\n<li>\n<p>do_not_call_appointment \u2014 \u043a\u043b\u0438\u0435\u043d\u0442 \u0437\u0430\u043f\u0440\u0435\u0442\u0438\u043b \u043e\u0431\u0437\u0432\u043e\u043d \u043f\u043e \u044d\u0442\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438;<\/p>\n<\/li>\n<li>\n<p>do_not_call_global \u2014 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043f\u043e\u043f\u0440\u043e\u0441\u0438\u043b \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0437\u0432\u043e\u043d\u0438\u0442\u044c;<\/p>\n<\/li>\n<li>\n<p>no_answer \u2014 \u043d\u043e\u043c\u0435\u0440 \u043d\u0435 \u043e\u0442\u0432\u0435\u0442\u0438\u043b \u0438\u043b\u0438 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u0435\u0440\u0432\u0430\u043b\u043e\u0441\u044c;<\/p>\n<\/li>\n<li>\n<p>unclear_end \u2014 \u0440\u043e\u0431\u043e\u0442 \u043d\u0435 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u043b \u043e\u0442\u0432\u0435\u0442, \u0437\u0430\u043f\u0438\u0441\u044c \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">if payload.result == RobotResult.accepted:\u00a0 \u00a0 action = self._handle_accept(conn, call)elif payload.result == RobotResult.refused_allow_future:\u00a0 \u00a0 action = self._handle_refused_allow_future(conn, call)elif payload.result == RobotResult.refused_appointment_opt_out:\u00a0 \u00a0 action = self._handle_appointment_opt_out(conn, call)elif payload.result == RobotResult.do_not_call_appointment:\u00a0 \u00a0 action = self._handle_appointment_opt_out(conn, call)elif payload.result == RobotResult.do_not_call_global:\u00a0 \u00a0 action = self._handle_global_opt_out(conn, call)elif payload.result == RobotResult.no_answer:\u00a0 \u00a0 action = self._handle_no_answer(conn, call)elif payload.result == RobotResult.unclear_end:\u00a0 \u00a0 action = self._handle_unclear(conn, call)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u0438 \u043e\u0442\u043a\u0430\u0437\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043f\u0440\u0438\u0447\u0438\u043d\u0443 \u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u0438.\u00a0<\/p>\n<h2>\u0428\u0430\u0433 6. \u041f\u0435\u0440\u0435\u043d\u043e\u0441 \u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438<\/h2>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043e\u0442\u0432\u0435\u0442\u0438\u043b \u0440\u043e\u0431\u043e\u0442\u0443 \u0443\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e, \u0442\u043e \u0431\u044d\u043a\u0435\u043d\u0434 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u0432\u0438\u0437\u0438\u0442\u0435, \u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0437\u0430\u043d\u043e\u0432\u043e \u0447\u0438\u0442\u0430\u0435\u0442 appointment.<\/p>\n<pre><code class=\"python\">appointment = self.clinic_api.get_appointment(conn, call[\"appointment_id\"])if (\u00a0 \u00a0 appointment is None\u00a0 \u00a0 or appointment[\"status\"] != \"scheduled\"\u00a0 \u00a0 or appointment[\"doctor_id\"] != call[\"slot_doctor_id\"]\u00a0 \u00a0 or appointment[\"service_id\"] != call[\"slot_service_id\"]\u00a0 \u00a0 or appointment[\"duration_minutes\"] != call[\"slot_duration_minutes\"]):\u00a0 \u00a0 conn.execute(\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"\u00a0 \u00a0 \u00a0 \u00a0 UPDATE slot_candidates\u00a0 \u00a0 \u00a0 \u00a0 SET status = 'skipped',\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 result = 'appointment_changed_during_call',\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 updated_at = CURRENT_TIMESTAMP\u00a0 \u00a0 \u00a0 \u00a0 WHERE id = ?\u00a0 \u00a0 \u00a0 \u00a0 \"\"\",\u00a0 \u00a0 \u00a0 \u00a0 (call[\"candidate_id\"],),\u00a0 \u00a0 )\u00a0 \u00a0 self._update_slot_status(\u00a0 \u00a0 \u00a0 \u00a0 conn,\u00a0 \u00a0 \u00a0 \u00a0 slot_id=call[\"slot_id\"],\u00a0 \u00a0 \u00a0 \u00a0 status=\"calling\",\u00a0 \u00a0 \u00a0 \u00a0 stop_reason=None,\u00a0 \u00a0 )\u00a0 \u00a0 return \"appointment_changed_skip\"<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u044c \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0430\u0441\u044c, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u043e\u043c\u0435\u0447\u0430\u0435\u0442 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u043a\u0430\u043a \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u043e\u0433\u043e \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u043b\u043e\u0442 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043e\u0431\u0437\u0432\u043e\u043d\u0430. \u0422\u0430\u043a \u043e\u043d\u0430 \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u0430 \u043e\u0442 \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u043e\u0432.\u00a0<\/p>\n<p>\u0417\u0430\u0442\u0435\u043c \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u043b\u0438 \u043e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0432\u0448\u0435\u0435\u0441\u044f \u043e\u043a\u043d\u043e. \u0415\u0441\u043b\u0438 \u0437\u0430 \u0432\u0440\u0435\u043c\u044f \u0437\u0432\u043e\u043d\u043a\u0430 \u043c\u0435\u0441\u0442\u043e \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043d\u044f\u0442\u044b\u043c, \u0442\u043e \u0441\u043b\u043e\u0442 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f, \u0430 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u043f\u0440\u0435\u043a\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f.\u00a0<\/p>\n<p>\u041f\u0440\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 ClinicApi \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442 \u0432\u0438\u0437\u0438\u0442 \u0434\u0432\u0443\u043c\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u043c\u0438 \u0432 SQLite. \u0421\u0442\u0430\u0440\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u043e\u0442\u043c\u0435\u0447\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0435\u043d\u043d\u0430\u044f, \u0430 \u043d\u043e\u0432\u0430\u044f \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0432\u0448\u0435\u0435\u0441\u044f \u0432\u0440\u0435\u043c\u044f \u0438 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c \u0441\u043b\u043e\u0442\u0430. old_appointment_id \u0432\u0437\u044f\u0442 \u0438\u0437 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u0438 \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0432\u0438\u0437\u0438\u0442\u0430 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430.\u00a0<\/p>\n<pre><code class=\"python\">conn.execute(\u00a0 \u00a0 \"\"\"\u00a0 \u00a0 UPDATE appointments\u00a0 \u00a0 SET status = 'moved'\u00a0 \u00a0 WHERE id = ?\u00a0 \u00a0 \"\"\",\u00a0 \u00a0 (old_appointment_id,),)conn.execute(\u00a0 \u00a0 \"\"\"\u00a0 \u00a0 INSERT INTO appointments (\u00a0 \u00a0 \u00a0 \u00a0 id,\u00a0 \u00a0 \u00a0 \u00a0 patient_id,\u00a0 \u00a0 \u00a0 \u00a0 doctor_id,\u00a0 \u00a0 \u00a0 \u00a0 service_id,\u00a0 \u00a0 \u00a0 \u00a0 starts_at,\u00a0 \u00a0 \u00a0 \u00a0 duration_minutes,\u00a0 \u00a0 \u00a0 \u00a0 status,\u00a0 \u00a0 \u00a0 \u00a0 offer_opt_out,\u00a0 \u00a0 \u00a0 \u00a0 moved_from_slot_id\u00a0 \u00a0 )\u00a0 \u00a0 VALUES (?, ?, ?, ?, ?, ?, 'scheduled', 0, ?)\u00a0 \u00a0 \"\"\",\u00a0 \u00a0 (\u00a0 \u00a0 \u00a0 \u00a0 new_appointment_id,\u00a0 \u00a0 \u00a0 \u00a0 old[\"patient_id\"],\u00a0 \u00a0 \u00a0 \u00a0 old[\"doctor_id\"],\u00a0 \u00a0 \u00a0 \u00a0 old[\"service_id\"],\u00a0 \u00a0 \u00a0 \u00a0 new_starts_at,\u00a0 \u00a0 \u00a0 \u00a0 duration_minutes,\u00a0 \u00a0 \u00a0 \u00a0 slot_id,\u00a0 \u00a0 ),)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u043a\u043e\u043d\u0446\u0435 \u0431\u044d\u043a\u0435\u043d\u0434 \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u0442 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043e\u043a\u043d\u0430 \u0438 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442 \u0432\u044b\u0431\u043e\u0440 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u0435\u0442\u0435\u043d\u0434\u0435\u043d\u0442\u044b \u0438\u0441\u043a\u043b\u044e\u0447\u0430\u044e\u0442\u0441\u044f, \u0447\u0442\u043e\u0431\u044b \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442 \u043d\u0435 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u043b \u0438\u043c \u0437\u0430\u043d\u044f\u0442\u043e\u0435 \u0432\u0440\u0435\u043c\u044f.\u00a0<\/p>\n<h2>\u0428\u0430\u0433 7. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043d\u0435\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044b\u0445 \u0432\u044b\u0437\u043e\u0432\u043e\u0432<\/h2>\n<p>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441\u043e \u0441\u0432\u044f\u0437\u044c\u044e \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043a \u043f\u043e\u0442\u0435\u0440\u0435 \u0432\u0435\u0431\u0445\u0443\u043a\u0430 \u0438 \u0437\u0430\u0432\u0438\u0441\u0430\u043d\u0438\u044e \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u0432\u044b\u0437\u043e\u0432\u0430. \u041f\u043e\u043f\u044b\u0442\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0440\u044f\u0442\u044c \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 started, \u0430 \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0435\u0437 \u0441\u0438\u0433\u043d\u0430\u043b\u0430 \u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0437\u0432\u043e\u043d\u043a\u0430 \u043d\u0435 \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u0442 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0443.\u00a0<\/p>\n<p>\u041f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043c\u0435\u0442\u043e\u0434 expire_stale_calls. \u041e\u043d \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u0437\u0432\u043e\u043d\u043a\u0438, \u0432\u0440\u0435\u043c\u044f \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043f\u0440\u0435\u0432\u044b\u0441\u0438\u043b\u043e \u043b\u0438\u043c\u0438\u0442 CALL_TIMEOUT_MINUTES, \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441 timeout, \u0430 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u0432 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044e \u00ab\u043d\u0435\u0442 \u043e\u0442\u0432\u0435\u0442\u0430\u00bb. \u0422\u0430\u043a \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u0442 \u043e\u0431\u0437\u0432\u043e\u043d \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u043e\u0432 \u0434\u0430\u043b\u044c\u0448\u0435.\u00a0<\/p>\n<pre><code class=\"python\">slot_filling_service.pystale = conn.execute(\u00a0 \u00a0 \"\"\"\u00a0 \u00a0 SELECT id, slot_id, candidate_id, call_id\u00a0 \u00a0 FROM call_attempts\u00a0 \u00a0 WHERE status = 'started'\u00a0 \u00a0 \u00a0 AND datetime(created_at) &lt; datetime(?)\u00a0 \u00a0 \"\"\",\u00a0 \u00a0 (cutoff,),).fetchall()for row in stale:\u00a0 \u00a0 conn.execute(\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"\u00a0 \u00a0 \u00a0 \u00a0 UPDATE call_attempts\u00a0 \u00a0 \u00a0 \u00a0 SET status = 'timeout',\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 robot_result = 'timeout',\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 updated_at = CURRENT_TIMESTAMP\u00a0 \u00a0 \u00a0 \u00a0 WHERE id = ?\u00a0 \u00a0 \u00a0 \u00a0 \"\"\",\u00a0 \u00a0 \u00a0 \u00a0 (row[\"id\"],),\u00a0 \u00a0 )\u00a0 \u00a0 conn.execute(\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"\u00a0 \u00a0 \u00a0 \u00a0 UPDATE slot_candidates\u00a0 \u00a0 \u00a0 \u00a0 SET status = 'no_answer',\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 result = 'call_timeout',\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 updated_at = CURRENT_TIMESTAMP\u00a0 \u00a0 \u00a0 \u00a0 WHERE id = ?\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 AND status = 'calling'\u00a0 \u00a0 \u00a0 \u00a0 \"\"\",\u00a0 \u00a0 \u00a0 \u00a0 (row[\"candidate_id\"],),\u00a0 \u00a0 )conn.commit()\u00a0<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0447\u0438\u0441\u0442\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u0434\u0432\u0443\u0445 \u0440\u0435\u0436\u0438\u043c\u0430\u0445. \u0412 \u043f\u0440\u043e\u0441\u0442\u043e\u043c \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0435 \u0437\u0430\u0434\u0430\u0447\u0443 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0444\u043e\u043d\u043e\u0432\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041f\u0440\u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043b\u0443\u0447\u0448\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 <a href=\"http:\/\/reaper.py\">reaper.py<\/a>, \u0447\u0442\u043e\u0431\u044b \u0432\u043e\u0440\u043a\u0435\u0440\u044b FastAPI \u043d\u0435 \u043f\u044b\u0442\u0430\u043b\u0438\u0441\u044c \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043e\u0434\u043d\u0438 \u0438 \u0442\u0435 \u0436\u0435 \u0437\u0432\u043e\u043d\u043a\u0438.<\/p>\n<pre><code class=\"python\">slot_filling_service.pystale = conn.execute(\u00a0 \u00a0 \"\"\"\u00a0 \u00a0 SELECT id, slot_id, candidate_id, call_id\u00a0 \u00a0 FROM call_attempts\u00a0 \u00a0 WHERE status = 'started'\u00a0 \u00a0 \u00a0 AND datetime(created_at) &lt; datetime(?)\u00a0 \u00a0 \"\"\",\u00a0 \u00a0 (cutoff,),).fetchall()for row in stale:\u00a0 \u00a0 conn.execute(\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"\u00a0 \u00a0 \u00a0 \u00a0 UPDATE call_attempts\u00a0 \u00a0 \u00a0 \u00a0 SET status = 'timeout',\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 robot_result = 'timeout',\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 updated_at = CURRENT_TIMESTAMP\u00a0 \u00a0 \u00a0 \u00a0 WHERE id = ?\u00a0 \u00a0 \u00a0 \u00a0 \"\"\",\u00a0 \u00a0 \u00a0 \u00a0 (row[\"id\"],),\u00a0 \u00a0 )\u00a0 \u00a0 conn.execute(\u00a0 \u00a0 \u00a0 \u00a0 \"\"\"\u00a0 \u00a0 \u00a0 \u00a0 UPDATE slot_candidates\u00a0 \u00a0 \u00a0 \u00a0 SET status = 'no_answer',\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 result = 'call_timeout',\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 updated_at = CURRENT_TIMESTAMP\u00a0 \u00a0 \u00a0 \u00a0 WHERE id = ?\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 AND status = 'calling'\u00a0 \u00a0 \u00a0 \u00a0 \"\"\",\u00a0 \u00a0 \u00a0 \u00a0 (row[\"candidate_id\"],),\u00a0 \u00a0 )conn.commit()\u00a0<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h2>\u0417\u0430\u043f\u0443\u0441\u043a \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430<\/h2>\n<p>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c FastAPI-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.<\/p>\n<pre><code class=\"python\">cp .env.example .envuvicorn app:app --reload --port 8000<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u043d\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0431\u0430\u0437\u0443 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0447\u0435\u0440\u0435\u0437 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442 \/demo\/seed. \u0427\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u043f\u0435\u0440\u0435\u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0441\u043b\u043e\u0442\u043e\u0432, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u043f\u0440\u043e\u0444\u0438\u043b\u0438 \u0432\u0440\u0430\u0447\u0435\u0439, \u0441\u043f\u0438\u0441\u043e\u043a \u0443\u0441\u043b\u0443\u0433 \u0438 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435.\u00a0<\/p>\n<pre><code class=\"python\">curl -X POST \"http:\/\/localhost:8000\/demo\/seed\" \\\u00a0 -H \"X-Admin-Token: change-me\"\u0418\u043c\u0438\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u043e\u0442\u043c\u0435\u043d\u0443 \u0432\u0438\u0437\u0438\u0442\u0430 \u0432 \u041c\u0418\u0421: \u043e\u0442\u043f\u0440\u0430\u0432\u044c\u0442\u0435 \u0432\u0435\u0431\u0445\u0443\u043a \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u043e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0432\u0448\u0435\u0433\u043e\u0441\u044f \u0441\u043b\u043e\u0442\u0430. curl -X POST \"http:\/\/localhost:8000\/webhooks\/appointment-canceled\" \\\u00a0 -H \"Content-Type: application\/json\" \\\u00a0 -H \"X-Webhook-Token: change-me\" \\\u00a0 -d '{\u00a0 \u00a0 \"canceled_appointment_id\": \"appt_cancelled\",\u00a0 \u00a0 \"doctor_id\": \"doctor_1\",\u00a0 \u00a0 \"date\": \"2026-06-20\",\u00a0 \u00a0 \"time\": \"10:00:00\",\u00a0 \u00a0 \"duration_minutes\": 60\u00a0 }'<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0411\u044d\u043a\u0435\u043d\u0434 \u0432\u0435\u0440\u043d\u0435\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0430\u0434\u0430\u0447\u0438, \u0435\u0435 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0445 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432. \u0417\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u043b\u043e\u0442\u0430 \u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0432\u044b\u0437\u043e\u0432\u043e\u0432 \u043c\u043e\u0436\u043d\u043e \u0447\u0435\u0440\u0435\u0437 \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0439 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442.\u00a0<\/p>\n<pre><code class=\"bash\">curl \"http:\/\/localhost:8000\/slots\/1\" \\\u00a0 -H \"X-Admin-Token: change-me\"<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043e\u0442 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430, \u0441\u0435\u0440\u0432\u0438\u0441 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \u0441\u0442\u0430\u0440\u043e\u0433\u043e \u0432\u0438\u0437\u0438\u0442\u0430 \u0438 \u0431\u0440\u043e\u043d\u0438\u0440\u0443\u0435\u0442 \u043d\u043e\u0432\u043e\u0435 \u0432\u0440\u0435\u043c\u044f. \u041c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043e\u0442\u0432\u0435\u0442\u044b \u0441 \u0442\u0430\u043a\u0438\u043c\u0438 \u043a\u043e\u0434\u0430\u043c\u0438 \u043e\u0448\u0438\u0431\u043e\u043a:<\/p>\n<p>401 \u2014 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0441 \u0442\u043e\u043a\u0435\u043d\u0430\u043c\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438;<br \/>400 \u2014 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0432\u0435\u0431\u0445\u0443\u043a\u0435 \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u0437\u0430\u043f\u0438\u0441\u044f\u043c \u0432 \u0431\u0430\u0437\u0435;<br \/>404 \u2014 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0432\u043e\u043d\u043a\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u0432 \u0438\u0441\u0442\u043e\u0440\u0438\u0438 \u043f\u043e\u043f\u044b\u0442\u043e\u043a.\u00a0<\/p>\n<hr\/>\n<p>\u041f\u043e \u0441\u0443\u0442\u0438<a href=\"https:\/\/github.com\/duckdevdotdev\/postprod-june-2026-time-gaps-dental-clinic\"> \u0441\u0435\u0440\u0432\u0438\u0441<\/a> \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u043f\u0443\u0442\u044f\u043c\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u043d\u0435 \u043e\u0442\u043c\u0435\u043d\u0438\u043b \u043b\u0438 \u043a\u0442\u043e-\u043d\u0438\u0431\u0443\u0434\u044c \u043f\u0440\u0438\u0435\u043c. \u041c\u0435\u0434\u0438\u0446\u0438\u043d\u0441\u043a\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u0442 \u043e\u0431 \u044d\u0442\u043e\u043c \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e, FastAPI \u0442\u0440\u0438\u0436\u0434\u044b \u0443\u0431\u0435\u0436\u0434\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0441\u043b\u043e\u0442 \u0438 \u043f\u0440\u0430\u0432\u0434\u0430 \u0441\u0432\u043e\u0431\u043e\u0434\u0435\u043d, \u0431\u0430\u0437\u0430 \u0445\u0440\u0430\u043d\u0438\u0442 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432. \u0418 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e <a href=\"https:\/\/exolve.ru\/products\/razrabotka-golosovykh-robotov\/\">\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442 \u041c\u0422\u0421 Exolve<\/a> \u043e\u0431\u0437\u0432\u0430\u043d\u0438\u0432\u0430\u0435\u0442 \u043b\u044e\u0434\u0435\u0439 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0447\u0435\u0440\u0435\u0437 \u0432\u0435\u0431\u0445\u0443\u043a. \u0412\u0441\u044f \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0434\u0435\u0441\u044c, \u0441 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u044b, \u0432 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0432\u0441\u0435\u043c\u0438 \u043f\u0440\u0430\u0432\u0434\u0430\u043c\u0438 \u0438 \u043d\u0435\u043f\u0440\u0430\u0432\u0434\u0430\u043c\u0438 \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0437\u0430\u043d\u044f\u0442\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u0430 \u0441 \u0434\u0440\u0443\u0433\u043e\u0439 \u2014 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u0440\u043e\u0431\u043e\u0442 \u043e\u0442\u0432\u0435\u0447\u0430\u043b \u0432\u043e\u0432\u0440\u0435\u043c\u044f.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1081178\/\">https:\/\/habr.com\/ru\/articles\/1081178\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044e: \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440 \u0441\u0442\u043e\u043c\u0430\u0442\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043a\u043b\u0438\u043d\u0438\u043a\u0438 \u0443\u0442\u0440\u043e\u043c \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438 \u0432\u0438\u0434\u0438\u0442, \u0447\u0442\u043e \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043e\u0442\u043c\u0435\u043d\u0438\u043b \u043f\u0440\u0438\u0435\u043c \u0432 10:00, \u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u0443 \u044d\u0442\u043e\u0433\u043e \u0432\u0440\u0430\u0447\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0435\u0447\u0435\u0440\u043e\u043c. \u0412\u0440\u0435\u043c\u044f \u043f\u0440\u043e\u043f\u0430\u0434\u0430\u0435\u0442, \u0432\u0440\u0430\u0447 \u0436\u0434\u0435\u0442. \u0410 \u0440\u044f\u0434\u043e\u043c, \u0432 \u043b\u0438\u0441\u0442\u0435 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f, \u043d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430 \u0435\u0441\u0442\u044c \u0447\u0435\u043b\u043e\u0432\u0435\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044b\u043b \u0431\u044b \u0440\u0430\u0434 \u043f\u043e\u043f\u0430\u0441\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u043d\u0430 \u044d\u0442\u043e \u0432\u0440\u0435\u043c\u044f. \u041d\u0430\u0434\u043e \u0442\u043e\u043b\u044c\u043a\u043e, \u0447\u0442\u043e\u0431\u044b \u043a\u0442\u043e-\u0442\u043e \u0435\u043c\u0443 \u043e\u0431 \u044d\u0442\u043e\u043c \u0441\u043e\u043e\u0431\u0449\u0438\u043b.\u00a0\u041f\u043e\u0447\u0442\u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u044f\u0442\u044b\u0439 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043d\u0430 \u043f\u0440\u0438\u0435\u043c \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f. \u0412\u0440\u0443\u0447\u043d\u0443\u044e \u043e\u0431\u0437\u0432\u0430\u043d\u0438\u0432\u0430\u0442\u044c \u0434\u043e\u043b\u0433\u043e \u0438 \u043d\u0435\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e, \u0430 \u0441\u043b\u043e\u0442 \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u043a\u0440\u044b\u0442\u044c \u0431\u044b\u0441\u0442\u0440\u043e, \u043f\u043e\u043a\u0430 \u043e\u043d \u0435\u0449\u0435 \u0430\u043a\u0442\u0443\u0430\u043b\u0435\u043d.\u0414\u0430\u043b\u044c\u0448\u0435 \u043f\u043e\u043a\u0430\u0436\u0435\u043c, \u043a\u0430\u043a \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0438\u0441\u044f\u043c\u0438 \u043a \u0441\u0442\u043e\u043c\u0430\u0442\u043e\u043b\u043e\u0433\u0443. \u0413\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442 \u041c\u0422\u0421 Exolve \u0431\u0443\u0434\u0435\u0442 \u0441\u0430\u043c \u0437\u0432\u043e\u043d\u0438\u0442\u044c \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0443, \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432\u0438\u0437\u0438\u0442 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0438.\u0421\u0442\u0435\u043a: Python 3.10+, FastAPI, SQLite, Pydantic Settings, \u0413\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442 \u041c\u0422\u0421 Exolve.\u041e\u0431\u0449\u0430\u044f \u0441\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b\u0412\u0441\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 \u043c\u0435\u0434\u0438\u0446\u0438\u043d\u0441\u043a\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b (\u041c\u0418\u0421), \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0432\u0435\u0434\u0435\u0442\u0441\u044f \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0432\u0440\u0430\u0447\u0435\u0439. \u041a\u043e\u0433\u0434\u0430 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043e\u0442\u043c\u0435\u043d\u044f\u0435\u0442 \u043f\u0440\u0438\u0435\u043c, \u043e\u043d\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043e \u0432\u0440\u0430\u0447\u0435 \u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u043e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0432\u0448\u0435\u0433\u043e\u0441\u044f \u043e\u043a\u043d\u0430. \u0417\u0430\u0442\u0435\u043c \u0431\u044d\u043a\u0435\u043d\u0434 \u043f\u0435\u0440\u0435\u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u041c\u0418\u0421, \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043f\u0440\u0438\u0435\u043c \u0438 \u043f\u0440\u0430\u0432\u0434\u0430 \u043e\u0442\u043c\u0435\u043d\u0435\u043d, \u0430 \u043c\u0435\u0441\u0442\u043e \u0437\u0430 \u043f\u0440\u043e\u0448\u0435\u0434\u0448\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0435 \u0437\u0430\u043d\u044f\u043b \u043a\u0442\u043e-\u0442\u043e \u0434\u0440\u0443\u0433\u043e\u0439.\u041a\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u044b, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u043d\u0430 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u043e\u0432 \u0438\u0437 \u043b\u0438\u0441\u0442\u0430 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u043a \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442\u0443, \u0443 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u044b\u043b\u0430 \u043e\u0442\u043c\u0435\u043d\u0430, \u0437\u0430\u0442\u0435\u043c \u0442\u0435\u0445, \u043a\u0442\u043e \u0437\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0437\u0434\u043d\u0438\u0435 \u0434\u0430\u0442\u044b. \u0410 \u043f\u0435\u0440\u0435\u0434 \u0437\u0432\u043e\u043d\u043a\u043e\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u043e\u0433\u043e\u043d\u044f\u044e\u0442 \u0435\u0449\u0435 \u0440\u0430\u0437, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0434\u0435\u0440\u0433\u0430\u0442\u044c \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0441\u0430\u043c \u043f\u0435\u0440\u0435\u043d\u0435\u0441 \u043f\u0440\u0438\u0435\u043c \u0438\u043b\u0438 \u043e\u0442\u043c\u0435\u043d\u0438\u043b \u0435\u0433\u043e.\u0414\u0430\u043b\u044c\u0448\u0435 \u0432 \u0434\u0435\u043b\u043e \u0432\u0441\u0442\u0443\u043f\u0430\u0435\u0442 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442. \u041e\u043d \u0437\u0432\u043e\u043d\u0438\u0442 \u2014 \u0438 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043b\u0438\u0431\u043e \u0441\u043e\u0433\u043b\u0430\u0448\u0430\u0435\u0442\u0441\u044f, \u043b\u0438\u0431\u043e \u043e\u0442\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f, \u043b\u0438\u0431\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0431\u0435\u0440\u0435\u0442 \u0442\u0440\u0443\u0431\u043a\u0443. \u0415\u0441\u043b\u0438 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u0441\u043a\u0430\u0437\u0430\u043b \u00ab\u0434\u0430\u00bb, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0441\u0440\u0430\u0437\u0443 \u0431\u0440\u043e\u043d\u0438\u0440\u0443\u0435\u0442 \u043e\u043a\u043d\u043e \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u0432\u0438\u0437\u0438\u0442. \u041e\u0442\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0438\u043b\u0438 \u043d\u0435 \u0432\u0437\u044f\u043b \u0442\u0440\u0443\u0431\u043a\u0443 \u2014 \u0440\u043e\u0431\u043e\u0442 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441, \u0430 \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430\u0431\u0438\u0440\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u0438.\u041d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430 \u0432\u0438\u0434\u043d\u043e \u0442\u0440\u0438 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u043a\u043d\u0430: \u043f\u0440\u0438\u0435\u043c \u043f\u0435\u0440\u0435\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d, \u0441\u043b\u043e\u0442 \u0437\u0430\u043d\u044f\u043b\u0438 \u0434\u043e \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u0434\u043e\u0448\u043b\u0430 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0437\u0432\u043e\u043d\u0438\u0442\u044c, \u0438\u043b\u0438 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u044b \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0438\u0441\u044c.\u041a\u0430\u043a \u0432\u0441\u0435 \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u043e\u0411\u044d\u043a\u0435\u043d\u0434 \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u0435\u0442 \u041c\u0418\u0421 \u0438 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0433\u043e \u0440\u043e\u0431\u043e\u0442\u0430. FastAPI \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0435\u0431\u0445\u0443\u043a\u0438 \u043e\u0431 \u043e\u0442\u043c\u0435\u043d\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c \u0441\u043b\u043e\u0442\u0430, \u043f\u043e\u0434\u0431\u0438\u0440\u0430\u0435\u0442 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043e\u0431\u0437\u0432\u043e\u043d.\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b (\u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431 \u043e\u043a\u043d\u0430\u0445, \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430\u0445 \u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0439) \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 SQLite. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u043a\u043b\u044e\u0447\u0430\u043c slot_id, candidate_id, appointment_id \u0438 call_id \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043e\u0442\u0432\u0435\u0442 \u043e\u0442 \u0440\u043e\u0431\u043e\u0442\u0430 \u0437\u0430\u043f\u0430\u0437\u0434\u044b\u0432\u0430\u0435\u0442. \u0420\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 ClinicApi \u2014 \u0432 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043c\u043e\u043a-\u043a\u043b\u0438\u0435\u043d\u0442 \u043f\u043e\u0432\u0435\u0440\u0445 SQLite.\u0413\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0440\u043e\u0431\u043e\u0442 \u041c\u0422\u0421 Exolve \u043e\u0431\u0437\u0432\u0430\u043d\u0438\u0432\u0430\u0435\u0442 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432 \u043e\u0434\u043d\u043e\u0433\u043e \u0437\u0430 \u0434\u0440\u0443\u0433\u0438\u043c. \u0421\u043e\u0433\u043b\u0430\u0441\u0438\u0435 \u2014 \u043d\u043e\u0432\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c; \u043e\u0442\u043a\u0430\u0437 \u0438\u043b\u0438 \u043c\u043e\u043b\u0447\u0430\u043d\u0438\u0435 \u2014 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442 \u043f\u0440\u0438\u0447\u0438\u043d\u0443 \u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0443.\u041e\u0431\u0449\u0430\u044f \u0441\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b\u041b\u043e\u0433\u0438\u043a\u0430 \u0440\u0430\u0437\u0431\u0438\u0442\u0430 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432:app.py \u2014 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0432\u0435\u0431\u0445\u0443\u043a\u0438, \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0442\u043e\u043a\u0435\u043d\u044b \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u043e\u0442\u0434\u0430\u0435\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c;slot_filling_service.py \u2014 \u0434\u0435\u0440\u0436\u0438\u0442 \u043d\u0430 \u0441\u0435\u0431\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439: \u0441\u043b\u0435\u0434\u0438\u0442 \u0437\u0430 \u0441\u0442\u0430\u0442\u0443\u0441\u0430\u043c\u0438 \u043e\u043a\u043e\u043d, \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u043e\u0432, \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0437\u0432\u043e\u043d\u043a\u043e\u0432;clinic_api.py \u2014 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u043c\u0435\u0434\u0438\u0446\u0438\u043d\u0441\u043a\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b;reaper.py \u2014 \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u0438 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0437\u0432\u043e\u043d\u043a\u0438, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0440\u043e\u0431\u043e\u0442 \u0442\u0430\u043a \u0438 \u043d\u0435 \u043f\u0440\u0438\u0441\u043b\u0430\u043b \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438\u0437-\u0437\u0430 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0441\u0431\u043e\u0435\u0432;config.py \u2014 \u0445\u0440\u0430\u043d\u0438\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u043b\u0438\u043c\u0438\u0442\u044b \u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0444\u043e\u043d\u043e\u0432\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.\u0427\u0442\u043e \u0438 \u043a\u0430\u043a \u043c\u044b \u0445\u0440\u0430\u043d\u0438\u043c\u0412\u0441\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 \u0432\u0435\u0431\u0445\u0443\u043a\u0430 \u043e\u0442 \u041c\u0418\u0421 \u043e\u0431 \u043e\u0442\u043c\u0435\u043d\u0435 \u0437\u0430\u043f\u0438\u0441\u0438. \u0417\u0430\u0442\u0435\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0438\u0434\u0435\u0442 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e: \u0440\u043e\u0431\u043e\u0442 \u0437\u0432\u043e\u043d\u0438\u0442, \u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c. \u0427\u0442\u043e\u0431\u044b \u043c\u0435\u0436\u0434\u0443 \u044d\u0442\u0438\u043c\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u043c\u0438 \u043d\u0435 \u043f\u043e\u0442\u0435\u0440\u044f\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442, \u0431\u044d\u043a\u0435\u043d\u0434 \u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435: \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0435 \u043e\u043a\u043d\u043e, \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u0438 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0430.\u0414\u0430\u043d\u043d\u044b\u0435 \u043f\u043e \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u043c \u0441\u043b\u043e\u0442\u0430\u043c (\u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442, \u0443\u0441\u043b\u0443\u0433\u0430, \u0432\u0440\u0435\u043c\u044f, \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c) \u043b\u0435\u0436\u0430\u0442 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 canceled_slots. \u041f\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u0443 \u0432\u0438\u0434\u043d\u043e, \u0447\u0435\u043c \u0432\u0441\u0435 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u043e\u0441\u044c.\u0414\u0430\u043d\u043d\u044b\u0435 \u043f\u043e \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430\u043c \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 slot_candidates: \u043a\u0430\u0436\u0434\u044b\u0439 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d \u043a \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u043c\u0443 \u043e\u043a\u043d\u0443 \u0438 \u0441\u0432\u043e\u0435\u043c\u0443 \u0442\u0435\u043a\u0443\u0449\u0435\u043c\u0443 \u0432\u0438\u0437\u0438\u0442\u0443. \u0421\u0442\u0430\u0442\u0443\u0441 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u044d\u0442\u0430\u043f: \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u0438, \u0438\u0434\u0435\u0442 \u043e\u0431\u0437\u0432\u043e\u043d, \u0441\u043e\u0433\u043b\u0430\u0441\u0438\u0435, \u043e\u0442\u043a\u0430\u0437 \u0438\u043b\u0438 \u043f\u0440\u043e\u043f\u0443\u0441\u043a (\u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u0430).\u0412\u0441\u0435 \u0437\u0432\u043e\u043d\u043a\u0438 \u043a \u0431\u043e\u0442\u0443 \u043b\u043e\u0433\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432 call_attempts. \u0411\u044d\u043a\u0435\u043d\u0434 \u0445\u0440\u0430\u043d\u0438\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0432\u043e\u043d\u043a\u0430 \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0441\u043b\u043e\u0442 \u0438 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u0430 \u043f\u043e ID, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043b\u043e\u0432\u0438\u0442 \u0432 \u0432\u0435\u0431\u0445\u0443\u043a\u0435. \u0422\u0430\u043a \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0441\u0432\u044f\u0437\u0438, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0441 \u043e\u043f\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043c.\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u043a\u043d\u043e\u043c \u0438 \u043e\u0431\u0437\u0432\u043e\u043d \u043c\u044b \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u043b\u0438. \u0423 \u043e\u0434\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u0442\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441 \u043b\u043e\u0433\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u0441\u0432\u044f\u0437\u0430\u0442\u044c\u0441\u044f \u0441 \u043a\u0430\u0436\u0434\u044b\u043c \u0438\u0437 \u043d\u0438\u0445.\u00a0\u0426\u0435\u043b\u043e\u0441\u0442\u043d\u043e\u0441\u0442\u044c \u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u044b\u0425\u0443\u0434\u0448\u0435\u0435, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0441\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u044f, \u2014 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0443 \u0437\u0430\u043d\u044f\u0442\u043e\u0433\u043e \u0441\u043b\u043e\u0442\u0430. \u0427\u0442\u043e\u0431\u044b \u044d\u0442\u043e\u0433\u043e \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c, \u0431\u044d\u043a\u0435\u043d\u0434 \u043f\u0435\u0440\u0435\u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0435\u0433\u043e \u0442\u0440\u0438 \u0440\u0430\u0437\u0430: \u043f\u0440\u0438 \u043e\u0442\u043c\u0435\u043d\u0435, \u043f\u0435\u0440\u0435\u0434 \u043e\u0431\u0437\u0432\u043e\u043d\u043e\u043c \u0438 \u043f\u043e\u0441\u043b\u0435 \u0441\u043e\u0433\u043b\u0430\u0441\u0438\u044f \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430.\u00a0\u0421\u0435\u0440\u0432\u0438\u0441 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0440\u043e\u0432\u043d\u043e \u043e\u0434\u0438\u043d \u0440\u0430\u0437. \u0411\u044d\u043a\u0435\u043d\u0434 \u0437\u0430\u0431\u0438\u0440\u0430\u0435\u0442 \u043a\u043b\u044e\u0447 \u0438\u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0438 \u0438\u0449\u0435\u0442 \u043f\u043e \u043d\u0435\u043c\u0443 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 canceled_slot. \u0415\u0441\u043b\u0438 \u041c\u0418\u0421 \u043f\u0440\u0438\u0448\u043b\u0435\u0442 \u0432\u0435\u0431\u0445\u0443\u043a \u0435\u0449\u0435 \u0440\u0430\u0437, \u0442\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043e\u0442\u0434\u0430\u0441\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443.\u041f\u0435\u0440\u0435\u0434 \u0437\u0432\u043e\u043d\u043a\u043e\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043f\u0440\u043e\u0433\u043e\u043d\u044f\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0447\u0435\u0440\u0435\u0437 skipineligible_pending \u0438 \u043e\u0442\u0441\u0435\u0438\u0432\u0430\u0435\u0442 \u0442\u0435\u0445, \u0447\u044c\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0443\u0441\u043f\u0435\u043b\u0438 \u0443\u0441\u0442\u0430\u0440\u0435\u0442\u044c, \u043f\u043e\u043a\u0430 \u0441\u0442\u0440\u043e\u0438\u043b\u0430\u0441\u044c \u043e\u0447\u0435\u0440\u0435\u0434\u044c. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043c\u043e\u0433 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432\u0438\u0437\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430 \u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0435\u0433\u043e \u043c\u043e\u0433\u043b\u0438 \u043f\u043e\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0430\u043a \u043e\u0442\u043a\u0430\u0437\u0430\u0432\u0448\u0435\u0433\u043e\u0441\u044f \u043e\u0442 \u0430\u0432\u0442\u043e\u0440\u0430\u0441\u0441\u044b\u043b\u043a\u0438.\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b\u00a0\u0421\u0435\u0440\u0432\u0438\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 Python 3.10+. \u0418\u0437 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0443 \u043d\u0435\u0433\u043e FastAPI, Uvicorn \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f.python -m venv .venvsource .venv\/bin\/activatepip install fastapi uvicorn pydantic-settings requests\u00a0\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0436\u0435\u043d\u0438\u043c \u0432\u0441\u0435 \u0441 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u044b\u043c \u0440\u043e\u0431\u043e\u0442\u043e\u043c \u041c\u0422\u0421 Exolve. \u0427\u0435\u0440\u0435\u0437 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u043e\u0431\u0437\u0432\u043e\u043d\u0430, \u0433\u0434\u0435 \u0440\u043e\u0431\u043e\u0442 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0442\u044c \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430\u043c \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0437\u0430\u043f\u0438\u0441\u044c \u043f\u043e\u0440\u0430\u043d\u044c\u0448\u0435. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c HTTPS-\u0430\u0434\u0440\u0435\u0441, \u043a\u0443\u0434\u0430 \u0440\u043e\u0431\u043e\u0442 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0437\u0432\u043e\u043d\u043a\u043e\u0432. URL API \u0438 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0437\u0430\u043e\u0434\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u041c\u0418\u0421 \u0443\u043c\u0435\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u0430 \u0432\u0430\u0448 FastAPI-\u0441\u0435\u0440\u0432\u0438\u0441 \u0432\u0435\u0431\u0445\u0443\u043a \u043e\u0431 \u043e\u0442\u043c\u0435\u043d\u0435 \u0437\u0430\u043f\u0438\u0441\u0438.\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b .env \u0441\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u0438 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432\u043c\u0435\u0441\u0442\u043e \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445:APP_NAME=&#187;Dental Slot Filler&#187;DB_PATH=&#187;.\/dental_slots.sqlite3&#8243;CLINIC_NAME=&#187;\u0414\u0435\u043d\u0442\u0430\u043b \u041a\u043b\u0438\u043d\u0438\u043a&#187;WEBHOOK_TOKEN=&#187;change-me&#187;ROBOT_WEBHOOK_TOKEN=&#187;change-me&#187;ADMIN_TOKEN=&#187;change-me&#187;ROBOT_API_URL=&#187;&#187;ROBOT_API_TOKEN=&#187;&#187;PUBLIC_BASE_URL=&#187;https:\/\/example.com&#187;RECENT_CALL_HOURS=24AUTO_CALL_NEXT_CANDIDATE=trueCALL_TIMEOUT_MINUTES=15REAPER_INTERVAL_SECONDS=60RUN_INPROCESS_REAPER=trueENABLE_DEMO_ENDPOINTS=true\u0428\u0430\u0433 1. \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0432\u0435\u0431\u0445\u0443\u043a\u0430 \u043e\u0431 \u043e\u0442\u043c\u0435\u043d\u0435 \u0437\u0430\u043f\u0438\u0441\u0438FastAPI \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0435\u0431\u0445\u0443\u043a \u043e\u0442 \u041c\u0418\u0421 \u0441 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u043f\u0438\u0441\u0438, \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0432\u0440\u0430\u0447\u0430, \u0434\u0430\u0442\u043e\u0439, \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u0438 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u043a\u043d\u0430. \u041f\u043e \u044d\u0442\u0438\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u0439 \u0432\u0438\u0437\u0438\u0442.\u00a0app.py@app.post(\u00a0 \u00a0 &#171;\/webhooks\/appointment-canceled&#187;,\u00a0 \u00a0 response_model=CancellationResponse,\u00a0 \u00a0 dependencies=[Depends(require_webhook_token)],)def appointment_canceled(payload: CancellationWebhook):\u00a0 \u00a0 try:\u00a0 \u00a0 \u00a0 \u00a0 return service.handle_cancellation(payload)\u00a0 \u00a0 except ValueError as exc:\u00a0 \u00a0 \u00a0 \u00a0 raise HTTPException(status_code=400, detail=str(exc)) from exc\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0442\u043e\u043a\u0435\u043d \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0445, \u0438 \u0435\u0441\u043b\u0438 \u0432\u0441\u0435 \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435, \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0441\u0435\u0440\u0432\u0438\u0441 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043b\u043e\u0442\u0430\u043c\u0438. \u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u0440\u0430\u0437\u0443 \u043e\u0442\u0441\u0435\u043a\u0430\u044e\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u043e\u0439 400, \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u043c\u0438 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0432 \u0431\u0430\u0437\u0435.\u0427\u0442\u043e\u0431\u044b \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u0432\u0435\u0431\u0445\u0443\u043a \u043d\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u0437\u0432\u043e\u043d\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043a\u043b\u044e\u0447 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044d\u043a\u0435\u043d\u0434 \u0432\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 canceled_slot. \u0415\u0441\u043b\u0438 \u043e\u043d \u0435\u0441\u0442\u044c, \u0442\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u043d\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f.slot_filling_service.pystarts_at = datetime.combine(payload.date, payload.time).isoformat(timespec=&#187;seconds&#187;)idempotency_key = self._make_idempotency_key(payload, starts_at)existing = conn.execute(\u00a0 \u00a0 &#171;&#187;&#187;\u00a0 \u00a0 SELECT id, status, stop_reason\u00a0 \u00a0 FROM canceled_slots\u00a0 \u00a0 WHERE idempotency_key = ?\u00a0 \u00a0 &#171;&#187;&#187;,\u00a0 \u00a0 (idempotency_key,),).fetchone()if existing:\u00a0 \u00a0 return {\u00a0 \u00a0 \u00a0 \u00a0 &#171;slot_id&#187;: existing[&#171;id&#187;],\u00a0 \u00a0 \u00a0 \u00a0 &#171;status&#187;: existing[&#171;status&#187;],\u00a0 \u00a0 \u00a0 \u00a0 &#171;stop_reason&#187;: existing[&#171;stop_reason&#187;],\u00a0 \u00a0 \u00a0 \u00a0 &#171;candidates_created&#187;: 0,\u00a0 \u00a0 }\u0415\u0441\u043b\u0438 \u0441\u043b\u043e\u0442 \u0443\u0436\u0435 \u043e\u0442\u043c\u0435\u043d\u0435\u043d, \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447 \u043d\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438 \u043f\u0440\u0438\u0447\u0438\u043d\u0443.\u0428\u0430\u0433 2. \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c \u0441\u043b\u043e\u0442\u0430\u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0432\u0435\u0431\u0445\u0443\u043a \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e\u0431 \u043e\u0442\u043c\u0435\u043d\u0435, \u0442\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0437\u0430\u043d\u044f\u0442\u043e. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441 \u0441\u043d\u043e\u0432\u0430 \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e \u043f\u0440\u0438\u0435\u043c\u0435, \u043f\u043e\u043f\u0443\u0442\u043d\u043e \u0441\u0432\u0435\u0440\u044f\u044f \u0441\u0442\u0430\u0442\u0443\u0441, \u0432\u0440\u0430\u0447\u0430 \u0438 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043e\u043a\u043d\u0430.slot_filling_service.pycanceled_appointment = self.clinic_api.get_appointment(\u00a0 \u00a0 conn,\u00a0 \u00a0 payload.canceled_appointment_id,)if not canceled_appointment:\u00a0 \u00a0 raise ValueError(\u00a0 \u00a0 \u00a0 \u00a0 f&#187;Canceled appointment not found: {payload.canceled_appointment_id}&#187;\u00a0 \u00a0 )if canceled_appointment[&#171;status&#187;] != &#171;canceled&#187;:\u00a0 \u00a0 raise ValueError(\u00a0 \u00a0 \u00a0 \u00a0 f&#187;Appointment is not canceled: {payload.canceled_appointment_id} &#187;\u00a0 \u00a0 \u00a0 \u00a0 f&#187;(status={canceled_appointment[&#8216;status&#8217;]})&#187;\u00a0 \u00a0 )if canceled_appointment[&#171;doctor_id&#187;] != payload.doctor_id:\u00a0 \u00a0 raise ValueError(&#171;Canceled appointment doctor mismatch&#187;)if canceled_appointment[&#171;duration_minutes&#187;] != payload.duration_minutes:\u00a0 \u00a0 raise ValueError(&#171;Canceled appointment duration mismatch&#187;)\u0417\u0430\u0442\u0435\u043c ClinicApi \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0435\u0441\u0442\u044c \u043b\u0438 \u0443 \u0432\u0440\u0430\u0447\u0430 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0432\u0438\u0437\u0438\u0442\u044b \u043a\u0440\u043e\u043c\u0435 \u0441\u0430\u043c\u043e\u0439 \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438. clinic_api.pyrow = conn.execute(\u00a0 \u00a0 f&#187;&#187;&#187;\u00a0 \u00a0 SELECT id\u00a0 \u00a0 FROM appointments\u00a0 \u00a0 WHERE doctor_id = ?\u00a0 \u00a0 \u00a0 AND status = ?\u00a0 \u00a0 \u00a0 AND datetime(starts_at) &lt; datetime(?)\u00a0 \u00a0 \u00a0 AND datetime(starts_at, &#8216;+&#8217;  duration_minutes  &#8216; minutes&#8217;) &gt; datetime(?)\u00a0 \u00a0 \u00a0 {ignore_sql}\u00a0 \u00a0 LIMIT 1\u00a0 \u00a0 &#171;&#187;&#187;,\u00a0 \u00a0 params,).fetchone()return row is None\u0415\u0441\u043b\u0438 \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u043d\u044f\u0442\u043e, \u0431\u044d\u043a\u0435\u043d\u0434 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \u0441\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c occupied \u0438 \u043f\u0440\u0438\u0447\u0438\u043d\u043e\u0439 slot_already_occupied.\u00a0\u0428\u0430\u0433 3. \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438 \u0432\u044b\u0431\u043e\u0440 \u043a\u0430\u043d\u0434\u0438\u0434\u0430\u0442\u043e\u0432\u041f\u043e\u0441\u043b\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043e\u043a\u043d\u0430 \u0431\u044d\u043a\u0435\u043d\u0434 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u044c \u0432 canceled_slots \u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442 \u043f\u043e\u0434\u0431\u043e\u0440 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u043e\u0432. \u0414\u043b\u044f \u043e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0432\u0448\u0435\u0433\u043e\u0441\u044f \u0441\u043b\u043e\u0442\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0437\u0430\u043f\u0438\u0441\u0438 \u0441 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438: \u0442\u0435\u043c\u0438 \u0436\u0435 \u0432\u0440\u0430\u0447\u043e\u043c, \u0443\u0441\u043b\u0443\u0433\u043e\u0439 \u0438 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e.slot_filling_service.pyslot_id = self._create_canceled_slot(\u00a0 \u00a0 conn,\u00a0 \u00a0 idempotency_key=idempotency_key,\u00a0 \u00a0 canceled_appointment_id=payload.canceled_appointment_id,\u00a0 \u00a0 doctor_id=payload.doctor_id,\u00a0 \u00a0 service_id=service_id,\u00a0 \u00a0 starts_at=starts_at,\u00a0 \u00a0 duration_minutes=payload.duration_minutes,\u00a0 \u00a0 status=&#187;task_created&#187;,\u00a0 \u00a0 stop_reason=None,)candidates_created = self._create_candidates(conn, slot_id=slot_id)\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043b\u0438\u0441\u0442 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u0438, \u0435\u0441\u043b\u0438 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u043b\u044e\u0434\u0435\u0439 \u043d\u0435\u0442, \u043d\u0430\u0445\u043e\u0434\u0438\u0442 \u0437\u0430\u043f\u0438\u0441\u0438 \u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0437\u0434\u043d\u0438\u0435 \u0434\u0430\u0442\u044b \u043a \u044d\u0442\u043e\u043c\u0443 \u0436\u0435 \u0432\u0440\u0430\u0447\u0443 \u0438 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432\u0438\u0437\u0438\u0442 \u043f\u043e\u0440\u0430\u043d\u044c\u0448\u0435.\u00a0slot_filling_service.pydef createcandidates(self, conn: Connection, *, slot_id: int) -&gt; int:\u00a0 \u00a0 slot = self._get_slot(conn, slot_id)\u00a0 \u00a0 if not slot:\u00a0 \u00a0 \u00a0 \u00a0 raise ValueError(f&#187;Slot not found: {slot_id}&#187;)\u00a0 \u00a0 &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-494387","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/494387","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=494387"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/494387\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=494387"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=494387"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=494387"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}