types.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. export type ApiEnvelope<T> = { data: T };
  2. export interface AdminUser {
  3. username: string;
  4. must_change_password: boolean;
  5. csrf_token: string;
  6. }
  7. export interface BotRuntime {
  8. state: 'stopped' | 'starting' | 'running' | 'stopping' | 'failed';
  9. pid?: number;
  10. port?: number;
  11. started_at?: string;
  12. error?: string;
  13. }
  14. export interface BotPermissionDefinition {
  15. key: string;
  16. name: string;
  17. group: string;
  18. description: string;
  19. }
  20. export interface BotRole {
  21. role_id: string;
  22. name: string;
  23. description: string;
  24. permissions: string[];
  25. builtin: boolean;
  26. created_at?: string;
  27. updated_at?: string;
  28. }
  29. export interface BotProfile {
  30. bot_id: string;
  31. label: string;
  32. enabled: boolean;
  33. bot_token_configured: boolean;
  34. sudo_users_id: string[];
  35. log_group_id: string;
  36. gban_log_group_id: string;
  37. message_dump_chat: string;
  38. role_ids: string[];
  39. permissions: string[];
  40. identity?: {
  41. id: string;
  42. username: string;
  43. name: string;
  44. can_connect_to_business?: boolean;
  45. } | null;
  46. business_assistant?: AssistantProviderConfig;
  47. ready_to_connect: boolean;
  48. created_at?: string;
  49. updated_at?: string;
  50. runtime: BotRuntime;
  51. }
  52. export interface AssistantProviderConfig {
  53. base_url: string;
  54. api_key_configured: boolean;
  55. model: string;
  56. timeout_seconds: number;
  57. max_output_tokens: number;
  58. configured: boolean;
  59. }
  60. export interface AssistantRuntimeStatus {
  61. polling_state: string;
  62. business_mode_supported: boolean;
  63. webhook_conflict: boolean;
  64. webhook_url: string;
  65. model_configured: boolean;
  66. last_error?: string;
  67. last_poll_at?: string;
  68. usage: AssistantUsage;
  69. }
  70. export interface AssistantUsage {
  71. ai_calls: number;
  72. customers: number;
  73. conversations: number;
  74. handoffs: number;
  75. human_paused: number;
  76. }
  77. export interface AssistantAccountSettings {
  78. connection_id: string;
  79. assistant_enabled: boolean;
  80. system_prompt: string;
  81. language: string;
  82. tone: 'professional' | 'friendly' | 'concise';
  83. account_daily_limit: number;
  84. customer_daily_limit: number;
  85. human_pause_hours: number;
  86. notification_destination: 'owner' | 'ops' | 'both';
  87. ops_group_id: string;
  88. timezone: string;
  89. digest_enabled: boolean;
  90. digest_time: string;
  91. handoff_message: string;
  92. unsupported_message: string;
  93. feishu_webhook_enabled: boolean;
  94. feishu_webhook_url?: string;
  95. feishu_webhook_signing_secret?: string;
  96. feishu_webhook_configured: boolean;
  97. feishu_signing_secret_configured: boolean;
  98. feishu_message_preview_enabled: boolean;
  99. feishu_outgoing_message_enabled: boolean;
  100. }
  101. export interface AssistantConnection {
  102. connection_id: string;
  103. user: {
  104. id: string;
  105. username?: string;
  106. first_name?: string;
  107. last_name?: string;
  108. };
  109. user_chat_id: string;
  110. rights: Record<string, boolean>;
  111. is_enabled: boolean;
  112. connected_at?: string;
  113. last_event_at?: string;
  114. settings: AssistantAccountSettings;
  115. }
  116. export interface AssistantKnowledgeEntry {
  117. entry_id: string;
  118. connection_id: string;
  119. question: string;
  120. aliases: string[];
  121. keywords: string[];
  122. answer: string;
  123. tags: string[];
  124. priority: number;
  125. enabled: boolean;
  126. updated_at?: string;
  127. }
  128. export type AssistantKnowledgeSourceType = 'channel' | 'group' | 'business';
  129. export type AssistantKnowledgePublicationMode = 'review' | 'auto';
  130. export type AssistantKnowledgeAuthorPolicy =
  131. | 'admins_or_allowlist'
  132. | 'admins'
  133. | 'allowlist'
  134. | 'all';
  135. export interface AssistantKnowledgeSource {
  136. source_id: string;
  137. connection_id: string;
  138. source_type: AssistantKnowledgeSourceType;
  139. chat_id: string;
  140. title: string;
  141. publication_mode: AssistantKnowledgePublicationMode;
  142. author_policy: AssistantKnowledgeAuthorPolicy;
  143. allowed_user_ids: string[];
  144. include_linked_chat: boolean;
  145. linked_chat_id: string;
  146. linked_chat_title?: string;
  147. backfill_days: number;
  148. backfill_limit: number;
  149. enabled: boolean;
  150. access_status?: string;
  151. sync_status: 'idle' | 'queued' | 'running' | 'completed' | 'failed';
  152. last_sync_processed?: number;
  153. last_event_at?: string;
  154. last_error?: string;
  155. updated_at?: string;
  156. }
  157. export type AssistantKnowledgeCandidateStatus =
  158. | 'pending'
  159. | 'published'
  160. | 'rejected'
  161. | 'stale';
  162. export interface AssistantKnowledgeCandidate {
  163. candidate_id: string;
  164. connection_id: string;
  165. source_id: string;
  166. event_id: string;
  167. question: string;
  168. aliases: string[];
  169. keywords: string[];
  170. answer: string;
  171. tags: string[];
  172. confidence: number;
  173. status: AssistantKnowledgeCandidateStatus;
  174. knowledge_entry_id?: string;
  175. stale_reason?: string;
  176. source_snapshot: {
  177. title: string;
  178. source_type: AssistantKnowledgeSourceType | 'discussion';
  179. chat_id: string;
  180. message_id: string;
  181. author_id: string;
  182. };
  183. updated_at?: string;
  184. }
  185. export type AssistantConversationStatus =
  186. | 'auto'
  187. | 'handoff'
  188. | 'human_paused'
  189. | 'closed';
  190. export interface AssistantConversationMessage {
  191. message_key: string;
  192. direction: 'incoming' | 'assistant' | 'human';
  193. telegram_message_id: string;
  194. text: string;
  195. created_at: string;
  196. metadata?: Record<string, unknown>;
  197. }
  198. export interface AssistantConversation {
  199. conversation_id: string;
  200. connection_id: string;
  201. chat_id: string;
  202. customer?: {
  203. id: string;
  204. username?: string;
  205. first_name?: string;
  206. last_name?: string;
  207. };
  208. status: AssistantConversationStatus;
  209. summary?: string;
  210. handoff_reason?: string;
  211. paused_until?: string | null;
  212. last_message_at?: string;
  213. updated_at?: string;
  214. messages?: AssistantConversationMessage[];
  215. }
  216. export interface TelegramSettings {
  217. api_id?: number | null;
  218. api_hash_configured: boolean;
  219. api_ready: boolean;
  220. roles: BotRole[];
  221. permission_catalog: BotPermissionDefinition[];
  222. bots: BotProfile[];
  223. }
  224. export interface Paged<T> {
  225. items: T[];
  226. total: number;
  227. page: number;
  228. page_size: number;
  229. }
  230. export interface ChatSummary {
  231. chat_id: string;
  232. title: string;
  233. username?: string | null;
  234. type: string;
  235. accessible: boolean;
  236. member_count?: number;
  237. bot_status?: string;
  238. bot_privileges?: string[];
  239. last_seen_at?: string;
  240. }
  241. export interface ChatOverview extends ChatSummary {
  242. description?: string | null;
  243. permissions: Record<string, boolean>;
  244. automation: AutomationSettings;
  245. }
  246. export interface ChannelOverview extends ChatSummary {
  247. description?: string | null;
  248. photo_data_url?: string | null;
  249. }
  250. export interface ChannelPost {
  251. post_id: string;
  252. chat_id: string;
  253. message_id?: string | null;
  254. source: 'admin' | 'telegram';
  255. status: 'scheduled' | 'sending' | 'published' | 'uncertain' | 'failed' | 'canceled' | 'deleted';
  256. text: string;
  257. media_type?: string | null;
  258. file_id?: string | null;
  259. media_filename?: string | null;
  260. media_size?: number | null;
  261. media_mime_type?: string | null;
  262. pin: boolean;
  263. publish_at?: string | null;
  264. published_at?: string | null;
  265. created_at: string;
  266. last_error?: string;
  267. pin_error?: string;
  268. }
  269. export interface TelegramUser {
  270. id: string;
  271. username?: string | null;
  272. first_name?: string | null;
  273. last_name?: string | null;
  274. is_bot: boolean;
  275. is_deleted: boolean;
  276. }
  277. export interface ChatMember {
  278. user: TelegramUser;
  279. status: string;
  280. custom_title?: string | null;
  281. privileges: string[];
  282. until_date?: string | null;
  283. last_seen_at?: string | null;
  284. }
  285. export interface AutoReply {
  286. keyword: string;
  287. type: string;
  288. text: string;
  289. file_id?: string | null;
  290. }
  291. export interface RiskRule {
  292. rule_id: string;
  293. name: string;
  294. enabled: boolean;
  295. keywords: string[];
  296. match_images: boolean;
  297. match_links: boolean;
  298. actions: Array<'delete' | 'warn' | 'mute' | 'kick' | 'ban'>;
  299. duration_seconds: number;
  300. }
  301. export interface AutomationSettings {
  302. chat_id: string;
  303. auto_replies: AutoReply[];
  304. blacklist_words: string[];
  305. risk_rules: RiskRule[];
  306. risk_control?: {
  307. enabled: boolean;
  308. keywords: string[];
  309. match_images: boolean;
  310. match_links: boolean;
  311. actions: Array<'delete' | 'warn' | 'mute' | 'kick' | 'ban'>;
  312. duration_seconds?: number;
  313. ban_duration_seconds?: number;
  314. };
  315. identity_monitor: {
  316. enabled: boolean;
  317. notify_in_chat: boolean;
  318. };
  319. welcome: {
  320. enabled: boolean;
  321. text: string;
  322. media?: { type: string; file_id: string } | null;
  323. };
  324. captcha_enabled: boolean;
  325. chatbot_enabled: boolean;
  326. antiflood_enabled: boolean;
  327. }
  328. export interface PointRules {
  329. enabled: boolean;
  330. timezone: string;
  331. checkin_enabled: boolean;
  332. checkin_button_enabled: boolean;
  333. checkin_points: number;
  334. checkin_success_message: string;
  335. checkin_already_message: string;
  336. activity_enabled: boolean;
  337. activity_points: number;
  338. activity_cooldown_seconds: number;
  339. activity_daily_cap: number;
  340. upvote_enabled: boolean;
  341. upvote_points: number;
  342. upvote_pair_cooldown_seconds: number;
  343. upvote_daily_cap: number;
  344. }
  345. export interface PointAccount {
  346. chat_id: string;
  347. user_id: string;
  348. username?: string | null;
  349. first_name?: string | null;
  350. display_name?: string | null;
  351. balance: number;
  352. lifetime_earned: number;
  353. lifetime_spent: number;
  354. updated_at?: string;
  355. }
  356. export interface PointTransaction {
  357. transaction_id: string;
  358. chat_id: string;
  359. user_id: string;
  360. username?: string | null;
  361. first_name?: string | null;
  362. display_name?: string | null;
  363. delta: number;
  364. balance_after: number;
  365. source: string;
  366. actor_id?: string | null;
  367. reason?: string;
  368. reference_id?: string | null;
  369. created_at: string;
  370. }
  371. export interface MemberIdentitySnapshot {
  372. username?: string | null;
  373. first_name?: string | null;
  374. last_name?: string | null;
  375. display_name?: string | null;
  376. }
  377. export interface MemberIdentityChange {
  378. change_id: string;
  379. chat_id: string;
  380. user_id: string;
  381. before: MemberIdentitySnapshot;
  382. after: MemberIdentitySnapshot;
  383. changed_fields: Array<'username' | 'display_name'>;
  384. observed_at: string;
  385. }
  386. export interface Prize {
  387. name: string;
  388. count: number;
  389. points_reward: number;
  390. }
  391. export interface Giveaway {
  392. giveaway_id: string;
  393. chat_id: string;
  394. title: string;
  395. description?: string;
  396. status: string;
  397. prizes: Prize[];
  398. minimum_points: number;
  399. entry_cost: number;
  400. max_tickets_per_user?: number;
  401. eligibility_targets?: Array<{ chat_id: string; title?: string; type?: string }>;
  402. eligibility_mode?: 'all' | 'any';
  403. template_id?: string;
  404. period_key?: string;
  405. eligibility_error?: string;
  406. cancellation_reason?: string;
  407. participation_reward: number;
  408. starts_at?: string;
  409. ends_at: string;
  410. created_at: string;
  411. updated_at: string;
  412. winners?: Array<{
  413. user_id: string;
  414. username?: string | null;
  415. first_name?: string | null;
  416. last_name?: string | null;
  417. display_name?: string | null;
  418. tier: string;
  419. points_reward: number;
  420. }>;
  421. }
  422. export interface GiveawayTemplate {
  423. template_id: string;
  424. chat_id: string;
  425. title: string;
  426. description: string;
  427. prizes: Prize[];
  428. minimum_points: number;
  429. entry_cost: number;
  430. participation_reward: number;
  431. max_tickets_per_user: number;
  432. eligibility_targets: Array<{ chat_id: string; title?: string; type?: string }>;
  433. eligibility_mode: 'all' | 'any';
  434. day_of_month: number;
  435. start_time: string;
  436. draw_day_offset: 0 | 1;
  437. draw_time: string;
  438. active: boolean;
  439. last_error?: string;
  440. }
  441. export interface AuditLog {
  442. source: string;
  443. actor_id?: string | null;
  444. actor_name: string;
  445. action: string;
  446. chat_id?: string | null;
  447. target_id?: string | null;
  448. summary?: string;
  449. success: boolean;
  450. error?: string;
  451. created_at: string;
  452. }
  453. export type DirectoryApplicationStatus =
  454. | 'none'
  455. | 'pending'
  456. | 'approved'
  457. | 'rejected'
  458. | 'revoked';
  459. export interface DirectoryProfile {
  460. user_id: string;
  461. username?: string | null;
  462. first_name?: string | null;
  463. last_name?: string | null;
  464. display_name?: string | null;
  465. application_status: DirectoryApplicationStatus;
  466. application_reason?: string;
  467. application_source_bot_id?: string;
  468. listed: boolean;
  469. online: boolean;
  470. online_until?: string | null;
  471. applied_at?: string | null;
  472. decided_at?: string | null;
  473. location?: {
  474. source?: string;
  475. updated_at?: string;
  476. } | null;
  477. service_profile?: {
  478. is_complete: boolean;
  479. accepting_requests: boolean;
  480. service_modes: ServiceMode[];
  481. packages: ServicePackage[];
  482. } | null;
  483. }
  484. export interface DirectoryLocation {
  485. user_id: string;
  486. username?: string | null;
  487. display_name?: string | null;
  488. application_status: DirectoryApplicationStatus;
  489. longitude: number;
  490. latitude: number;
  491. source: string;
  492. updated_at: string;
  493. }
  494. export interface DirectoryEvent {
  495. event_id: string;
  496. event_type: string;
  497. user_id: string;
  498. actor_id?: string | null;
  499. actor_name?: string;
  500. source: string;
  501. reason?: string;
  502. bot_id?: string;
  503. chat_id?: string | null;
  504. created_at: string;
  505. }
  506. export interface DirectorySettings {
  507. online_duration_hours: number;
  508. nearby_radius_km: number;
  509. nearby_radius_options_km: number[];
  510. }
  511. export type ServiceMode = 'at_store' | 'onsite';
  512. export type ServiceOrderStatus =
  513. | 'requested'
  514. | 'quoted'
  515. | 'confirmed'
  516. | 'in_progress'
  517. | 'completion_pending'
  518. | 'completed'
  519. | 'rejected'
  520. | 'canceled_customer'
  521. | 'canceled_technician'
  522. | 'disputed'
  523. | 'voided';
  524. export interface ServiceAddon {
  525. addon_id?: string;
  526. name: string;
  527. unit: string;
  528. price: string;
  529. extra_minutes: number;
  530. }
  531. export interface ServicePackage {
  532. package_id: string;
  533. name: string;
  534. category: string;
  535. description: string;
  536. tags: string[];
  537. service_modes: ServiceMode[];
  538. price_mode: 'fixed' | 'starting_at' | 'range' | 'negotiable';
  539. currency: string;
  540. price_unit: 'per_service' | 'per_hour' | 'per_item' | 'per_visit';
  541. min_price: string;
  542. max_price: string;
  543. price_display?: string;
  544. duration_minutes?: number | null;
  545. included_items?: string;
  546. excluded_items?: string;
  547. preparation?: string;
  548. addons: ServiceAddon[];
  549. travel_fee: {
  550. mode: 'included' | 'fixed' | 'per_km' | 'quoted';
  551. amount?: string;
  552. per_km?: string;
  553. description?: string;
  554. };
  555. service_radius_km?: number | null;
  556. out_of_range_policy?: string;
  557. source_type?: 'template' | 'custom';
  558. source_template_id?: string | null;
  559. source_template_version?: number | null;
  560. customized_from_template?: boolean;
  561. }
  562. export interface ServicePackageTemplate {
  563. template_id: string;
  564. version: number;
  565. name: string;
  566. admin_note?: string;
  567. package: ServicePackage;
  568. status: 'enabled' | 'disabled' | 'archived';
  569. sort_order: number;
  570. created_at: string;
  571. updated_at: string;
  572. }
  573. export interface TechnicianServiceProfile {
  574. headline: string;
  575. bio: string;
  576. tags: string[];
  577. contact_hours: string;
  578. service_modes: ServiceMode[];
  579. accepting_requests: boolean;
  580. public_area_text: string;
  581. venue: {
  582. name: string;
  583. address_hint: string;
  584. };
  585. onsite_policy: {
  586. description: string;
  587. };
  588. packages: ServicePackage[];
  589. is_complete: boolean;
  590. version?: number;
  591. updated_at?: string;
  592. }
  593. export interface TechnicianSelfServiceContext {
  594. user_id: string;
  595. display_name: string;
  596. username?: string | null;
  597. eligibility: {
  598. ready: boolean;
  599. issues: Array<{ code: string; message: string }>;
  600. };
  601. service_profile?: TechnicianServiceProfile | null;
  602. review_topic?: {
  603. message_thread_id: number;
  604. topic_name: string;
  605. topic_url: string;
  606. status: 'active';
  607. } | null;
  608. }
  609. export interface TechnicianMiniAppBootstrap {
  610. technician: TechnicianSelfServiceContext;
  611. templates: Array<
  612. Pick<ServicePackageTemplate, 'template_id' | 'version' | 'name' | 'package'> & {
  613. builtin: boolean;
  614. }
  615. >;
  616. limits: {
  617. max_packages: number;
  618. };
  619. }
  620. export interface ServiceQuote {
  621. quote_id: string;
  622. order_id: string;
  623. version: number;
  624. currency: string;
  625. base_amount: string;
  626. travel_fee: string;
  627. addons: Array<{ name: string; quantity: number; unit_price: string }>;
  628. discount: string;
  629. total_amount: string;
  630. note?: string;
  631. created_at: string;
  632. }
  633. export interface ServiceOrder {
  634. order_id: string;
  635. customer_id: string;
  636. customer_name: string;
  637. technician_id: string;
  638. technician_name: string;
  639. package_snapshot: ServicePackage;
  640. category: string;
  641. service_mode: ServiceMode;
  642. requirements: string;
  643. distance_band: string;
  644. status: ServiceOrderStatus;
  645. version: number;
  646. quote_snapshot?: ServiceQuote;
  647. current_quote_id?: string;
  648. close_reason?: string;
  649. dispute_reason?: string;
  650. created_at: string;
  651. updated_at: string;
  652. closed_at?: string;
  653. quotes?: ServiceQuote[];
  654. events?: Array<Record<string, unknown>>;
  655. qr_records?: ServiceReviewQr[];
  656. }
  657. export interface ReviewQuestion {
  658. question_id: string;
  659. type: 'single_choice' | 'multiple_choice' | 'text';
  660. label: string;
  661. description?: string;
  662. required: boolean;
  663. weight?: number;
  664. max_length?: number;
  665. options?: Array<{
  666. value: string;
  667. label: string;
  668. score?: number;
  669. }>;
  670. }
  671. export interface ServiceReviewTemplate {
  672. template_id: string;
  673. version: number;
  674. status: 'active' | 'retired';
  675. questions: ReviewQuestion[];
  676. published_at: string;
  677. }
  678. export interface ServiceReview {
  679. review_id: string;
  680. technician_id: string;
  681. technician_name: string;
  682. customer_id: string;
  683. customer_name: string;
  684. source: 'qr_verified' | 'student_initiated';
  685. category: string;
  686. package_snapshot: ServicePackage;
  687. score: number;
  688. anonymous: boolean;
  689. status: 'pending' | 'approved' | 'rejected' | 'voided';
  690. choice_answers: Record<string, string | string[]>;
  691. text_answers: Record<string, string>;
  692. topic_url?: string;
  693. topic_message_id?: number;
  694. topic_publication?: {
  695. status: 'published' | 'unconfigured' | 'failed';
  696. topic_url?: string;
  697. message?: string;
  698. };
  699. customer_review_count: number;
  700. customer_review_count_30d: number;
  701. customer_review_count_90d: number;
  702. similar_content_count: number;
  703. created_at: string;
  704. moderated_at?: string;
  705. }
  706. export interface ServiceReviewQr {
  707. qr_id: string;
  708. order_id: string;
  709. technician_id: string;
  710. customer_id: string;
  711. status: 'issued' | 'claimed' | 'expired' | 'revoked' | 'voided';
  712. expires_at: string;
  713. created_at: string;
  714. claimed_at?: string;
  715. }
  716. export interface ServiceLeaderboardItem {
  717. technician_id: string;
  718. display_name?: string;
  719. username?: string;
  720. category: string;
  721. review_count: number;
  722. average_score: number;
  723. category_average: number;
  724. rank_score: number;
  725. last_review_at: string;
  726. }
  727. export interface ServiceCustomerBlock {
  728. scope: 'global' | 'technician';
  729. technician_id: string;
  730. customer_id: string;
  731. active: boolean;
  732. reason?: string;
  733. actor_id?: string;
  734. updated_at: string;
  735. }
  736. export interface ServiceReport {
  737. report_id: string;
  738. reporter_id: string;
  739. target_type: 'technician' | 'customer' | 'order' | 'review';
  740. target_id: string;
  741. reason: string;
  742. status: 'open' | 'resolved' | 'dismissed';
  743. created_at: string;
  744. handled_at?: string;
  745. handling_reason?: string;
  746. }
  747. export interface ServiceSettings {
  748. customer_max_open_orders: number;
  749. customer_daily_request_limit: number;
  750. review_qr_expiry_hours: number;
  751. review_forum_chat_id: string;
  752. review_forum_username: string;
  753. }
  754. export interface FulfillmentMetrics {
  755. counts: Record<ServiceOrderStatus, number>;
  756. valid_requests: number;
  757. quoted_requests: number;
  758. confirmed_requests: number;
  759. directory_views: number;
  760. technician_views: number;
  761. request_starts: number;
  762. request_submission_rate: number;
  763. quote_rate: number;
  764. customer_confirmation_rate: number;
  765. match_success_rate: number;
  766. fulfillment_rate: number;
  767. cancellation_rate: number;
  768. cancellation_distribution: { customer: number; technician: number };
  769. scope_note: string;
  770. }