+ Показаны наиболее релевантные шаблоны на основе вашего запроса
+
+
+ );
+}
diff --git a/frontend/src/hooks/useClassify.ts b/frontend/src/hooks/useClassify.ts
new file mode 100644
index 0000000..b7670dd
--- /dev/null
+++ b/frontend/src/hooks/useClassify.ts
@@ -0,0 +1,20 @@
+/**
+ * useClassify Hook
+ *
+ * React Query hook for classifying customer inquiries.
+ *
+ * Features:
+ * - Automatic error handling
+ * - Loading states
+ * - Type-safe responses
+ */
+
+import { useMutation } from '@tanstack/react-query';
+import { classifyInquiry } from '../services/classificationService';
+import type { ClassificationResult, ErrorResponse } from '../types/classification';
+
+export function useClassify() {
+ return useMutation({
+ mutationFn: classifyInquiry,
+ });
+}
diff --git a/frontend/src/hooks/useHealth.ts b/frontend/src/hooks/useHealth.ts
new file mode 100644
index 0000000..f2fc344
--- /dev/null
+++ b/frontend/src/hooks/useHealth.ts
@@ -0,0 +1,24 @@
+/**
+ * useHealth Hook
+ *
+ * React Query hook for checking API health status.
+ *
+ * Features:
+ * - Automatic polling (optional)
+ * - Type-safe responses
+ * - Ready/degraded status detection
+ */
+
+import { useQuery } from '@tanstack/react-query';
+import { checkHealth } from '../services/retrievalService';
+import type { HealthResponse } from '../types/retrieval';
+
+export function useHealth(options?: { refetchInterval?: number }) {
+ return useQuery({
+ queryKey: ['health'],
+ queryFn: checkHealth,
+ refetchInterval: options?.refetchInterval,
+ // Don't retry on failure (health check)
+ retry: false,
+ });
+}
diff --git a/frontend/src/hooks/useRetrieve.ts b/frontend/src/hooks/useRetrieve.ts
new file mode 100644
index 0000000..4b9be7d
--- /dev/null
+++ b/frontend/src/hooks/useRetrieve.ts
@@ -0,0 +1,21 @@
+/**
+ * useRetrieve Hook
+ *
+ * React Query hook for retrieving template responses.
+ *
+ * Features:
+ * - Automatic error handling
+ * - Loading states
+ * - Type-safe responses
+ */
+
+import { useMutation } from '@tanstack/react-query';
+import { retrieveTemplates } from '../services/retrievalService';
+import type { RetrievalRequest, RetrievalResponse } from '../types/retrieval';
+import type { ErrorResponse } from '../types/classification';
+
+export function useRetrieve() {
+ return useMutation({
+ mutationFn: retrieveTemplates,
+ });
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
new file mode 100644
index 0000000..7e35710
--- /dev/null
+++ b/frontend/src/index.css
@@ -0,0 +1,30 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* Custom scrollbar styles */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb {
+ background: #888;
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: #555;
+}
+
+/* Smooth transitions */
+* {
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 0000000..698e3c0
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,42 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import './index.css'
+import App from './App.tsx'
+
+/**
+ * React Query Client Configuration
+ *
+ * Configured for Smart Support API requirements:
+ * - No automatic retries (API should respond reliably)
+ * - Short stale time (data changes frequently during operator workflow)
+ * - Cache time for performance (avoid re-fetching during active session)
+ */
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ // Don't retry failed requests automatically (let user retry manually)
+ retry: false,
+ // Mark data as stale after 30 seconds (balance freshness vs. performance)
+ staleTime: 30 * 1000,
+ // Keep unused data in cache for 5 minutes
+ gcTime: 5 * 60 * 1000,
+ // Don't refetch on window focus (operator may switch windows frequently)
+ refetchOnWindowFocus: false,
+ // Don't refetch on reconnect (operator should manually retry if needed)
+ refetchOnReconnect: false,
+ },
+ mutations: {
+ // Don't retry mutations (classification/retrieval should be user-initiated)
+ retry: false,
+ },
+ },
+})
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+
+ ,
+)
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts
new file mode 100644
index 0000000..4090c55
--- /dev/null
+++ b/frontend/src/services/api.ts
@@ -0,0 +1,212 @@
+/**
+ * Axios HTTP Client Configuration
+ *
+ * Provides configured Axios instance for Smart Support API communication.
+ * Includes interceptors for error handling, request/response logging, and timeout management.
+ *
+ * Constitution Compliance:
+ * - Principle II: User-Centric Design (user-friendly error messages)
+ * - Principle IV: API-First Integration (type-safe API communication)
+ */
+
+import axios from 'axios';
+import type { AxiosError, AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
+import type { ErrorResponse } from '../types/classification';
+import { isErrorResponse } from '../types/classification';
+
+/**
+ * Base API URL
+ *
+ * In development, Vite proxy will route /api/* to http://localhost:8000/api/*
+ * In production, this will be the actual backend URL
+ */
+const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
+
+/**
+ * Request timeout values aligned with API performance requirements
+ * - Classification: 15000ms (increased to accommodate LLM API response times)
+ * - Retrieval: 2000ms (FR-010 requires <1s, add 1s buffer)
+ * - Default: 5000ms for health checks and other endpoints
+ */
+export const API_TIMEOUTS = {
+ CLASSIFICATION: 15000,
+ RETRIEVAL: 2000,
+ DEFAULT: 5000,
+} as const;
+
+/**
+ * Create configured Axios instance
+ */
+const apiClient: AxiosInstance = axios.create({
+ baseURL: API_BASE_URL,
+ timeout: API_TIMEOUTS.DEFAULT,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+});
+
+/**
+ * Request interceptor
+ *
+ * Logs outgoing requests and adds custom headers if needed.
+ */
+apiClient.interceptors.request.use(
+ (config: InternalAxiosRequestConfig) => {
+ // Log request in development
+ if (import.meta.env.DEV) {
+ console.log(`[API Request] ${config.method?.toUpperCase()} ${config.url}`, {
+ data: config.data,
+ params: config.params,
+ });
+ }
+
+ // Add timestamp to track request duration
+ config.metadata = { startTime: Date.now() };
+
+ return config;
+ },
+ (error) => {
+ console.error('[API Request Error]', error);
+ return Promise.reject(error);
+ }
+);
+
+/**
+ * Response interceptor
+ *
+ * Logs responses, calculates request duration, and transforms error responses.
+ */
+apiClient.interceptors.response.use(
+ (response: AxiosResponse) => {
+ // Calculate request duration
+ const startTime = response.config.metadata?.startTime;
+ const duration = startTime ? Date.now() - startTime : 0;
+
+ // Log response in development
+ if (import.meta.env.DEV) {
+ console.log(
+ `[API Response] ${response.config.method?.toUpperCase()} ${response.config.url}`,
+ {
+ status: response.status,
+ duration: `${duration}ms`,
+ data: response.data,
+ }
+ );
+ }
+
+ // Warn if request exceeded expected duration
+ if (response.config.url?.includes('/classify') && duration > 2000) {
+ console.warn(`Classification took ${duration}ms (expected <2000ms)`);
+ } else if (response.config.url?.includes('/retrieve') && duration > 1000) {
+ console.warn(`Retrieval took ${duration}ms (expected <1000ms)`);
+ }
+
+ return response;
+ },
+ (error: AxiosError) => {
+ // Handle error responses
+ return handleApiError(error);
+ }
+);
+
+/**
+ * Handle API errors and transform to user-friendly format
+ *
+ * @param error - Axios error object
+ * @returns Rejected promise with ErrorResponse
+ */
+function handleApiError(error: AxiosError): Promise {
+ const startTime = error.config?.metadata?.startTime;
+ const duration = startTime ? Date.now() - startTime : 0;
+
+ // Log error in development
+ if (import.meta.env.DEV) {
+ console.error(
+ `[API Error] ${error.config?.method?.toUpperCase()} ${error.config?.url}`,
+ {
+ status: error.response?.status,
+ duration: `${duration}ms`,
+ message: error.message,
+ data: error.response?.data,
+ }
+ );
+ }
+
+ // If response has ErrorResponse format, use it directly
+ if (error.response?.data && isErrorResponse(error.response.data)) {
+ return Promise.reject(error.response.data);
+ }
+
+ // FastAPI wraps error in "detail" field - check there too
+ if (error.response?.data && typeof error.response.data === 'object' && 'detail' in error.response.data) {
+ const detail = (error.response.data as { detail: unknown }).detail;
+ if (detail && typeof detail === 'object' && isErrorResponse(detail)) {
+ return Promise.reject(detail);
+ }
+ }
+
+ // Create user-friendly error based on error type
+ let errorResponse: ErrorResponse;
+
+ if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) {
+ // Timeout error
+ errorResponse = {
+ error: 'Request timed out. Please try again.',
+ error_type: 'timeout',
+ details: error.message,
+ timestamp: new Date().toISOString(),
+ };
+ } else if (!error.response) {
+ // Network error (no response from server)
+ errorResponse = {
+ error: 'Cannot connect to server. Please check your internet connection.',
+ error_type: 'api_error',
+ details: error.message,
+ timestamp: new Date().toISOString(),
+ };
+ } else if (error.response.status >= 500) {
+ // Server error (5xx)
+ errorResponse = {
+ error: 'Server error occurred. Please try again or contact support.',
+ error_type: 'api_error',
+ details: `HTTP ${error.response.status}: ${error.message}`,
+ timestamp: new Date().toISOString(),
+ };
+ } else if (error.response.status === 400) {
+ // Validation error (400)
+ const errorMessage =
+ typeof error.response.data === 'object' && error.response.data !== null && 'detail' in error.response.data
+ ? String((error.response.data as { detail: unknown }).detail)
+ : 'Invalid request. Please check your input.';
+
+ errorResponse = {
+ error: errorMessage,
+ error_type: 'validation',
+ details: error.message,
+ timestamp: new Date().toISOString(),
+ };
+ } else {
+ // Other client errors (4xx)
+ errorResponse = {
+ error: 'An error occurred. Please try again.',
+ error_type: 'unknown',
+ details: `HTTP ${error.response.status}: ${error.message}`,
+ timestamp: new Date().toISOString(),
+ };
+ }
+
+ return Promise.reject(errorResponse);
+}
+
+/**
+ * Extend Axios config to include metadata
+ */
+declare module 'axios' {
+ export interface InternalAxiosRequestConfig {
+ metadata?: {
+ startTime: number;
+ };
+ }
+}
+
+export default apiClient;
diff --git a/frontend/src/services/classificationService.ts b/frontend/src/services/classificationService.ts
new file mode 100644
index 0000000..8688350
--- /dev/null
+++ b/frontend/src/services/classificationService.ts
@@ -0,0 +1,35 @@
+/**
+ * Classification API Service
+ *
+ * Provides functions for interacting with the classification API endpoint.
+ */
+
+import apiClient, { API_TIMEOUTS } from './api';
+import type {
+ ClassificationRequest,
+ ClassificationResult,
+ ErrorResponse,
+} from '../types/classification';
+
+/**
+ * Classify a customer inquiry
+ *
+ * POST /api/classify
+ *
+ * @param inquiry - Customer inquiry text in Russian
+ * @returns Classification result with category, subcategory, and confidence
+ * @throws ErrorResponse if classification fails
+ */
+export async function classifyInquiry(inquiry: string): Promise {
+ const request: ClassificationRequest = { inquiry };
+
+ const response = await apiClient.post(
+ '/classify',
+ request,
+ {
+ timeout: API_TIMEOUTS.CLASSIFICATION, // 3 seconds
+ }
+ );
+
+ return response.data;
+}
diff --git a/frontend/src/services/retrievalService.ts b/frontend/src/services/retrievalService.ts
new file mode 100644
index 0000000..1f78313
--- /dev/null
+++ b/frontend/src/services/retrievalService.ts
@@ -0,0 +1,48 @@
+/**
+ * Retrieval API Service
+ *
+ * Provides functions for interacting with the retrieval API endpoint.
+ */
+
+import apiClient, { API_TIMEOUTS } from './api';
+import type {
+ RetrievalRequest,
+ RetrievalResponse,
+ HealthResponse,
+} from '../types/retrieval';
+
+/**
+ * Retrieve template responses for a classified inquiry
+ *
+ * POST /api/retrieve
+ *
+ * @param request - Retrieval request with query, category, subcategory
+ * @returns Retrieval response with ranked template results
+ * @throws ErrorResponse if retrieval fails
+ */
+export async function retrieveTemplates(request: RetrievalRequest): Promise {
+ const response = await apiClient.post(
+ '/retrieve',
+ request,
+ {
+ timeout: API_TIMEOUTS.RETRIEVAL, // 2 seconds
+ }
+ );
+
+ return response.data;
+}
+
+/**
+ * Check API health status
+ *
+ * GET /api/health
+ *
+ * @returns Health status with service availability
+ */
+export async function checkHealth(): Promise {
+ const response = await apiClient.get('/health', {
+ timeout: API_TIMEOUTS.DEFAULT, // 5 seconds
+ });
+
+ return response.data;
+}
diff --git a/frontend/src/types/classification.ts b/frontend/src/types/classification.ts
new file mode 100644
index 0000000..adece41
--- /dev/null
+++ b/frontend/src/types/classification.ts
@@ -0,0 +1,47 @@
+export interface ClassificationRequest {
+ inquiry: string;
+}
+
+export interface ClassificationResult {
+ inquiry: string;
+ category: string;
+ subcategory: string;
+ confidence: number;
+ processing_time_ms: number;
+ timestamp: string;
+}
+
+export interface ErrorResponse {
+ error: string;
+ error_type: 'validation' | 'api_error' | 'timeout' | 'unknown';
+ details?: string;
+ timestamp: string;
+}
+
+export function isErrorResponse(obj: unknown): obj is ErrorResponse {
+ return (
+ typeof obj === 'object' &&
+ obj !== null &&
+ 'error' in obj &&
+ 'error_type' in obj
+ );
+}
+
+export function validateInquiry(inquiry: string): string | null {
+ const trimmed = inquiry.trim();
+
+ if (trimmed.length < 5) {
+ return "Inquiry must be at least 5 characters";
+ }
+
+ if (trimmed.length > 5000) {
+ return "Inquiry must not exceed 5000 characters";
+ }
+
+ const hasCyrillic = /[а-яА-ЯёЁ]/.test(trimmed);
+ if (!hasCyrillic) {
+ return "Please enter inquiry in Russian (Cyrillic characters required)";
+ }
+
+ return null;
+}
diff --git a/frontend/src/types/classification.ts.bak b/frontend/src/types/classification.ts.bak
new file mode 100644
index 0000000..7c447e8
--- /dev/null
+++ b/frontend/src/types/classification.ts.bak
@@ -0,0 +1,107 @@
+/**
+ * TypeScript Type Definitions for Classification API
+ *
+ * Mirrors backend/src/api/models.py ClassificationRequest and ClassificationResult.
+ * Used for type-safe API communication and frontend data handling.
+ *
+ * Constitution Compliance:
+ * - Principle I: Modular Architecture (mirrors backend models exactly)
+ * - Principle IV: API-First Integration (enables type-safe API calls)
+ */
+
+/**
+ * Request payload for POST /api/classify
+ *
+ * Validates customer inquiry text before classification.
+ */
+export interface ClassificationRequest {
+ /**
+ * Customer inquiry text in Russian
+ * Must be 5-5000 characters and contain at least one Cyrillic character
+ */
+ inquiry: string;
+}
+
+/**
+ * Response from POST /api/classify
+ *
+ * Contains classification results with category, subcategory, and confidence.
+ */
+export interface ClassificationResult {
+ /** Original inquiry text (echoed back) */
+ inquiry: string;
+
+ /** Top-level product category */
+ category: string;
+
+ /** Second-level classification */
+ subcategory: string;
+
+ /** Classification confidence (0.0-1.0) */
+ confidence: number;
+
+ /** Processing time in milliseconds */
+ processing_time_ms: number;
+
+ /** When classification was performed (ISO 8601 UTC) */
+ timestamp: string;
+}
+
+/**
+ * Standard error response format
+ *
+ * Used for all API failures (validation, service errors, timeouts).
+ */
+export interface ErrorResponse {
+ /** User-friendly, actionable error message (no technical jargon) */
+ error: string;
+
+ /** Error category for frontend handling */
+ error_type: 'validation' | 'api_error' | 'timeout' | 'unknown';
+
+ /** Technical details for logging (not shown to user) */
+ details?: string;
+
+ /** When error occurred (ISO 8601 UTC) */
+ timestamp: string;
+}
+
+/**
+ * Type guard to check if an error response is an ErrorResponse
+ */
+export function isErrorResponse(obj: unknown): obj is ErrorResponse {
+ return (
+ typeof obj === 'object' &&
+ obj !== null &&
+ 'error' in obj &&
+ 'error_type' in obj &&
+ typeof (obj as ErrorResponse).error === 'string' &&
+ ['validation', 'api_error', 'timeout', 'unknown'].includes((obj as ErrorResponse).error_type)
+ );
+}
+
+/**
+ * Validation helper: Check if inquiry text meets requirements
+ *
+ * @param inquiry - Text to validate
+ * @returns Error message if invalid, null if valid
+ */
+export function validateInquiry(inquiry: string): string | null {
+ const trimmed = inquiry.trim();
+
+ if (trimmed.length < 5) {
+ return "Inquiry must be at least 5 characters";
+ }
+
+ if (trimmed.length > 5000) {
+ return "Inquiry must not exceed 5000 characters";
+ }
+
+ // Check for at least one Cyrillic character (Russian)
+ const hasCyrillic = /[а-яА-ЯёЁ]/.test(trimmed);
+ if (!hasCyrillic) {
+ return "Please enter inquiry in Russian (Cyrillic characters required)";
+ }
+
+ return null; // Valid
+}
diff --git a/frontend/src/types/retrieval.ts b/frontend/src/types/retrieval.ts
new file mode 100644
index 0000000..cbaf5fe
--- /dev/null
+++ b/frontend/src/types/retrieval.ts
@@ -0,0 +1,179 @@
+/**
+ * TypeScript Type Definitions for Retrieval API
+ *
+ * Mirrors backend/src/api/models.py RetrievalRequest, TemplateResult, and RetrievalResponse.
+ * Used for type-safe API communication and frontend data handling.
+ *
+ * Constitution Compliance:
+ * - Principle I: Modular Architecture (mirrors backend models exactly)
+ * - Principle IV: API-First Integration (enables type-safe API calls)
+ */
+
+/**
+ * Request payload for POST /api/retrieve
+ *
+ * Constructed from ClassificationResult on frontend.
+ */
+export interface RetrievalRequest {
+ /**
+ * Customer inquiry text (must match classified inquiry)
+ * Must be 5-5000 characters and contain Russian text
+ */
+ query: string;
+
+ /** Category from classification */
+ category: string;
+
+ /** Subcategory from classification */
+ subcategory: string;
+
+ /** Confidence score from classification (optional) */
+ classification_confidence?: number;
+
+ /**
+ * Number of templates to return (1-10, default: 5)
+ */
+ top_k?: number;
+
+ /**
+ * Enable weighted scoring (not used in MVP)
+ * @default false
+ */
+ use_historical_weighting?: boolean;
+}
+
+/**
+ * Single retrieved template with ranking metadata
+ *
+ * Denormalized for UI display (includes question, answer, scores).
+ */
+export interface TemplateResult {
+ /** Unique template identifier */
+ template_id: string;
+
+ /** FAQ question text */
+ template_question: string;
+
+ /** FAQ answer text (for copy-to-clipboard) */
+ template_answer: string;
+
+ /** Template category */
+ category: string;
+
+ /** Template subcategory */
+ subcategory: string;
+
+ /** Cosine similarity (0.0-1.0) */
+ similarity_score: number;
+
+ /** Final ranking score */
+ combined_score: number;
+
+ /** Position in result list (1=best) */
+ rank: number;
+}
+
+/**
+ * Response from POST /api/retrieve
+ *
+ * Contains ranked template results with metadata and warnings.
+ */
+export interface RetrievalResponse {
+ /** Original inquiry (echoed back) */
+ query: string;
+
+ /** Category used for filtering */
+ category: string;
+
+ /** Subcategory used for filtering */
+ subcategory: string;
+
+ /** Ranked template results (max 10) */
+ results: TemplateResult[];
+
+ /** Number of templates in category before ranking */
+ total_candidates: number;
+
+ /** Time to embed query + rank (ms) */
+ processing_time_ms: number;
+
+ /** When retrieval completed (ISO 8601 UTC) */
+ timestamp: string;
+
+ /** Warnings (e.g., low confidence, no templates) */
+ warnings: string[];
+}
+
+/**
+ * Health check response for GET /api/health
+ *
+ * Used by frontend to detect service availability.
+ */
+export interface HealthResponse {
+ /** Overall health status */
+ status: 'healthy' | 'unhealthy';
+
+ /** Whether classification service can handle requests */
+ classification_available: boolean;
+
+ /** Whether retrieval service can handle requests */
+ retrieval_available: boolean;
+
+ /** Number of FAQ templates in embeddings database */
+ embeddings_count: number;
+}
+
+/**
+ * Type guard to check if health status is healthy
+ */
+export function isHealthy(health: HealthResponse): boolean {
+ return (
+ health.status === 'healthy' &&
+ health.classification_available &&
+ health.retrieval_available &&
+ health.embeddings_count > 0
+ );
+}
+
+/**
+ * Validation helper: Check if retrieval request is valid
+ *
+ * @param request - Request to validate
+ * @returns Error message if invalid, null if valid
+ */
+export function validateRetrievalRequest(request: RetrievalRequest): string | null {
+ const trimmedQuery = request.query.trim();
+
+ if (trimmedQuery.length < 5) {
+ return "Query must be at least 5 characters";
+ }
+
+ if (trimmedQuery.length > 5000) {
+ return "Query must not exceed 5000 characters";
+ }
+
+ if (!request.category || request.category.trim().length === 0) {
+ return "Category is required";
+ }
+
+ if (!request.subcategory || request.subcategory.trim().length === 0) {
+ return "Subcategory is required";
+ }
+
+ if (request.top_k !== undefined) {
+ if (request.top_k < 1) {
+ return "Number of results must be at least 1";
+ }
+ if (request.top_k > 10) {
+ return "Number of results must not exceed 10";
+ }
+ }
+
+ if (request.classification_confidence !== undefined) {
+ if (request.classification_confidence < 0.0 || request.classification_confidence > 1.0) {
+ return "Classification confidence must be between 0.0 and 1.0";
+ }
+ }
+
+ return null; // Valid
+}
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
new file mode 100644
index 0000000..6047830
--- /dev/null
+++ b/frontend/tailwind.config.js
@@ -0,0 +1,29 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ colors: {
+ primary: {
+ 50: '#eff6ff',
+ 100: '#dbeafe',
+ 200: '#bfdbfe',
+ 300: '#93c5fd',
+ 400: '#60a5fa',
+ 500: '#3b82f6',
+ 600: '#2563eb',
+ 700: '#1d4ed8',
+ 800: '#1e40af',
+ 900: '#1e3a8a',
+ },
+ },
+ animation: {
+ 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
+ },
+ },
+ },
+ plugins: [],
+}
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
new file mode 100644
index 0000000..a9b5a59
--- /dev/null
+++ b/frontend/tsconfig.app.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json
new file mode 100644
index 0000000..8a67f62
--- /dev/null
+++ b/frontend/tsconfig.node.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 0000000..8dabc53
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,34 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 5173,
+ proxy: {
+ // Proxy all /api requests to FastAPI backend
+ '/api': {
+ target: 'http://localhost:8000',
+ changeOrigin: true,
+ secure: false,
+ // Don't rewrite the path - backend expects /api prefix
+ rewrite: (path) => path,
+ // Log proxy requests in development
+ configure: (proxy, _options) => {
+ proxy.on('error', (err, _req, _res) => {
+ console.log('[Vite Proxy] Error:', err);
+ });
+ proxy.on('proxyReq', (proxyReq, req, _res) => {
+ console.log(`[Vite Proxy] ${req.method} ${req.url} -> ${proxyReq.path}`);
+ });
+ proxy.on('proxyRes', (proxyRes, req, _res) => {
+ console.log(
+ `[Vite Proxy] ${req.method} ${req.url} <- ${proxyRes.statusCode}`
+ );
+ });
+ },
+ },
+ },
+ },
+})
diff --git a/specs/004-smart-support-operator/checklists/requirements.md b/specs/004-smart-support-operator/checklists/requirements.md
new file mode 100644
index 0000000..a2492d5
--- /dev/null
+++ b/specs/004-smart-support-operator/checklists/requirements.md
@@ -0,0 +1,70 @@
+# Specification Quality Checklist: Operator Web Interface
+
+**Purpose**: Validate specification completeness and quality before proceeding to planning
+**Created**: 2025-10-15
+**Feature**: [spec.md](../spec.md)
+
+## Content Quality
+
+- [x] No implementation details (languages, frameworks, APIs)
+- [x] Focused on user value and business needs
+- [x] Written for non-technical stakeholders
+- [x] All mandatory sections completed
+
+## Requirement Completeness
+
+- [x] No [NEEDS CLARIFICATION] markers remain
+- [x] Requirements are testable and unambiguous
+- [x] Success criteria are measurable
+- [x] Success criteria are technology-agnostic (no implementation details)
+- [x] All acceptance scenarios are defined
+- [x] Edge cases are identified
+- [x] Scope is clearly bounded
+- [x] Dependencies and assumptions identified
+
+## Feature Readiness
+
+- [x] All functional requirements have clear acceptance criteria
+- [x] User scenarios cover primary flows
+- [x] Feature meets measurable outcomes defined in Success Criteria
+- [x] No implementation details leak into specification
+
+## Validation Notes
+
+### Content Quality
+✅ **PASS** - Specification focuses entirely on user needs, operator workflows, and business value. No mention of React, FastAPI, or other implementation technologies. Written in plain language suitable for business stakeholders.
+
+### Requirement Completeness
+✅ **PASS** - All requirements are testable (e.g., "System MUST complete inquiry classification within 2 seconds"). No [NEEDS CLARIFICATION] markers - all potential ambiguities resolved with documented assumptions. Edge cases comprehensively identified.
+
+### Success Criteria Quality
+✅ **PASS** - All success criteria are measurable and technology-agnostic:
+- SC-001: "Operators can process a customer inquiry from input to copied response in under 10 seconds" (user-focused, measurable)
+- SC-002: "Classification results are displayed within 2 seconds of inquiry submission for 95% of requests" (measurable performance)
+- SC-010: "Interface scores at least 16/20 points on hackathon UI/UX evaluation criteria" (business metric)
+- No implementation details (no mention of APIs, frameworks, databases)
+
+### User Scenarios
+✅ **PASS** - Five user stories prioritized by business value (P1-P3). Each story is independently testable with clear acceptance scenarios using Given/When/Then format. Covers core workflow (P1), enhancements (P2), and nice-to-haves (P3).
+
+### Scope Management
+✅ **PASS** - Clear boundaries established:
+- 15 assumptions documented (workflow, integration, user, scope)
+- 7 dependencies identified (internal and external)
+- 10 explicitly excluded features in "Out of Scope" section
+- Future enhancements listed separately
+
+## Overall Assessment
+
+**STATUS**: ✅ **READY FOR PLANNING**
+
+The specification is complete, high-quality, and ready to proceed to `/speckit.plan`. All checklist items pass validation. The spec successfully:
+
+1. Defines clear user value and business outcomes
+2. Avoids all implementation details
+3. Provides testable, unambiguous requirements
+4. Establishes measurable success criteria
+5. Documents assumptions and dependencies
+6. Clearly defines scope boundaries
+
+No revisions needed. Proceed to implementation planning phase.
diff --git a/specs/004-smart-support-operator/contracts/classification-api.yaml b/specs/004-smart-support-operator/contracts/classification-api.yaml
new file mode 100644
index 0000000..0049883
--- /dev/null
+++ b/specs/004-smart-support-operator/contracts/classification-api.yaml
@@ -0,0 +1,326 @@
+openapi: 3.0.3
+info:
+ title: Smart Support Classification API
+ version: 1.0.0
+ description: |
+ REST API for customer inquiry classification.
+
+ Wraps the existing Classification Module with FastAPI HTTP endpoints.
+ Performance requirement: <2 seconds response time (95th percentile).
+ contact:
+ name: Smart Support Team
+ url: https://github.com/pandarun/smart-support
+
+servers:
+ - url: http://localhost:8000
+ description: Local development server
+ - url: http://localhost:8000/api
+ description: Local development (with /api prefix)
+
+tags:
+ - name: classification
+ description: Inquiry classification operations
+ - name: health
+ description: Service health checks
+
+paths:
+ /api/classify:
+ post:
+ summary: Classify customer inquiry
+ description: |
+ Classifies a Russian-language customer inquiry into product category and subcategory.
+
+ **Performance**: Must complete within 2 seconds (95th percentile - FR-015).
+ **Accuracy**: ≥70% accuracy on validation dataset (QR-001 from constitution).
+ operationId: classifyInquiry
+ tags:
+ - classification
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ClassificationRequest'
+ examples:
+ savings_account:
+ summary: Savings account inquiry
+ value:
+ inquiry: "Как открыть накопительный счет в мобильном приложении?"
+ password_reset:
+ summary: Password reset inquiry
+ value:
+ inquiry: "Забыл пароль от мобильного банка"
+ responses:
+ '200':
+ description: Successful classification
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ClassificationResult'
+ examples:
+ high_confidence:
+ summary: High confidence classification
+ value:
+ inquiry: "Как открыть накопительный счет в мобильном приложении?"
+ category: "Счета и вклады"
+ subcategory: "Открытие счета"
+ confidence: 0.89
+ processing_time_ms: 1247
+ timestamp: "2025-10-15T10:30:45Z"
+ medium_confidence:
+ summary: Medium confidence classification
+ value:
+ inquiry: "Забыл пароль от мобильного банка"
+ category: "Техническая поддержка"
+ subcategory: "Проблемы и решения"
+ confidence: 0.67
+ processing_time_ms: 1523
+ timestamp: "2025-10-15T10:31:12Z"
+ '400':
+ description: Validation error (invalid input)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ no_cyrillic:
+ summary: No Russian text
+ value:
+ error: "Please enter inquiry in Russian (at least 5 characters)"
+ error_type: "validation"
+ timestamp: "2025-10-15T10:35:22Z"
+ too_short:
+ summary: Too short
+ value:
+ error: "Inquiry must be at least 5 characters"
+ error_type: "validation"
+ timestamp: "2025-10-15T10:35:30Z"
+ '503':
+ description: Service unavailable (Scibox API error)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ service_down:
+ summary: Classification service unavailable
+ value:
+ error: "Unable to connect to classification service. Please check your connection and try again."
+ error_type: "api_error"
+ details: "Connection timeout to Scibox API"
+ timestamp: "2025-10-15T10:40:15Z"
+ '504':
+ description: Gateway timeout (>2s response time)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ timeout:
+ summary: Classification timeout
+ value:
+ error: "The classification service is taking longer than expected. Please try again."
+ error_type: "timeout"
+ timestamp: "2025-10-15T10:42:33Z"
+
+ /api/health:
+ get:
+ summary: Health check
+ description: |
+ Check if classification service is operational.
+
+ Used by frontend to detect service availability before submitting inquiries.
+ operationId: healthCheck
+ tags:
+ - health
+ responses:
+ '200':
+ description: Service is healthy
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/HealthResponse'
+ examples:
+ healthy:
+ summary: All systems operational
+ value:
+ status: "healthy"
+ classification_available: true
+ retrieval_available: true
+ embeddings_count: 201
+ '503':
+ description: Service is unhealthy
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/HealthResponse'
+ examples:
+ classification_down:
+ summary: Classification service unavailable
+ value:
+ status: "unhealthy"
+ classification_available: false
+ retrieval_available: true
+ embeddings_count: 201
+
+components:
+ schemas:
+ ClassificationRequest:
+ type: object
+ required:
+ - inquiry
+ properties:
+ inquiry:
+ type: string
+ minLength: 5
+ maxLength: 5000
+ description: Customer inquiry text in Russian (must contain Cyrillic characters)
+ example: "Как открыть накопительный счет в мобильном приложении?"
+ description: |
+ Input model for classification endpoint.
+
+ **Validation Rules** (FR-018):
+ - VR-001: Text stripped of leading/trailing whitespace
+ - VR-002: Must contain at least one Cyrillic character (Russian language)
+ - VR-003: Minimum 5 characters, maximum 5000 characters
+
+ ClassificationResult:
+ type: object
+ required:
+ - inquiry
+ - category
+ - subcategory
+ - confidence
+ - processing_time_ms
+ - timestamp
+ properties:
+ inquiry:
+ type: string
+ description: Original inquiry text (echoed back for reference)
+ example: "Как открыть накопительный счет в мобильном приложении?"
+ category:
+ type: string
+ description: |
+ Top-level product category (one of 6 valid categories).
+
+ Valid categories:
+ - Новые клиенты
+ - Продукты - Вклады
+ - Продукты - Карты
+ - Продукты - Кредиты
+ - Техническая поддержка
+ - Частные клиенты
+ example: "Счета и вклады"
+ subcategory:
+ type: string
+ description: Second-level classification within category (35 total subcategories)
+ example: "Открытие счета"
+ confidence:
+ type: number
+ format: float
+ minimum: 0.0
+ maximum: 1.0
+ description: |
+ Classification confidence score (0.0 to 1.0).
+
+ **Visual Indicators** (FR-010):
+ - High: ≥0.8 (green)
+ - Medium: 0.6-0.8 (yellow)
+ - Low: <0.6 (red)
+ example: 0.89
+ processing_time_ms:
+ type: integer
+ minimum: 1
+ description: Time taken for classification in milliseconds (must be <2000 for FR-015)
+ example: 1247
+ timestamp:
+ type: string
+ format: date-time
+ description: When classification was performed (ISO 8601 UTC)
+ example: "2025-10-15T10:30:45Z"
+ description: |
+ Classification result with category, subcategory, and confidence.
+
+ **Requirement Mapping**:
+ - FR-002: Display classification results
+ - FR-010: Visual confidence indicators
+ - FR-013: Processing time display
+ - FR-015: <2 seconds performance requirement
+
+ ErrorResponse:
+ type: object
+ required:
+ - error
+ - error_type
+ - timestamp
+ properties:
+ error:
+ type: string
+ description: |
+ Human-readable, user-actionable error message (VR-017).
+ No technical jargon or stack traces.
+ example: "Unable to connect to classification service. Please check your connection and try again."
+ error_type:
+ type: string
+ enum:
+ - validation
+ - api_error
+ - timeout
+ - unknown
+ description: |
+ Error category for frontend handling.
+
+ - validation: Input validation failed (400)
+ - api_error: Service unavailable (503)
+ - timeout: Request exceeded time limit (504)
+ - unknown: Unexpected server error (500)
+ example: "api_error"
+ details:
+ type: string
+ description: Technical details for logging (optional, not shown to user)
+ example: "Connection timeout after 5000ms to Scibox API"
+ timestamp:
+ type: string
+ format: date-time
+ description: When error occurred (ISO 8601 UTC)
+ example: "2025-10-15T10:35:22Z"
+ description: |
+ Standardized error response format.
+
+ **Requirement Mapping**:
+ - FR-018: Validation error handling
+ - FR-019: Classification service error handling
+ - FR-021: Network timeout handling
+ - FR-022: Actionable error guidance
+
+ HealthResponse:
+ type: object
+ required:
+ - status
+ - classification_available
+ - retrieval_available
+ - embeddings_count
+ properties:
+ status:
+ type: string
+ enum:
+ - healthy
+ - unhealthy
+ description: Overall system health status
+ example: "healthy"
+ classification_available:
+ type: boolean
+ description: Whether classification service can handle requests
+ example: true
+ retrieval_available:
+ type: boolean
+ description: Whether retrieval service can handle requests
+ example: true
+ embeddings_count:
+ type: integer
+ description: Number of FAQ templates in embeddings database
+ example: 201
+ description: |
+ Health check response for service monitoring.
+
+ Used by frontend to detect service availability before showing error messages.
diff --git a/specs/004-smart-support-operator/contracts/retrieval-api.yaml b/specs/004-smart-support-operator/contracts/retrieval-api.yaml
new file mode 100644
index 0000000..5238841
--- /dev/null
+++ b/specs/004-smart-support-operator/contracts/retrieval-api.yaml
@@ -0,0 +1,408 @@
+openapi: 3.0.3
+info:
+ title: Smart Support Retrieval API
+ version: 1.0.0
+ description: |
+ REST API for template response retrieval.
+
+ Wraps the existing Retrieval Module with FastAPI HTTP endpoints.
+ Performance requirement: <1 second response time (95th percentile).
+ contact:
+ name: Smart Support Team
+ url: https://github.com/pandarun/smart-support
+
+servers:
+ - url: http://localhost:8000
+ description: Local development server
+ - url: http://localhost:8000/api
+ description: Local development (with /api prefix)
+
+tags:
+ - name: retrieval
+ description: Template retrieval operations
+
+paths:
+ /api/retrieve:
+ post:
+ summary: Retrieve ranked template responses
+ description: |
+ Retrieves and ranks FAQ template responses based on customer inquiry and classification.
+
+ **Performance**: Must complete within 1 second (95th percentile - FR-016).
+ **Accuracy**: ≥80% top-3 accuracy on validation dataset (QR-002 from constitution).
+
+ **Workflow**: Automatically triggered after classification (FR-003).
+ operationId: retrieveTemplates
+ tags:
+ - retrieval
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RetrievalRequest'
+ examples:
+ savings_account:
+ summary: Savings account inquiry
+ value:
+ query: "Как открыть накопительный счет в мобильном приложении?"
+ category: "Счета и вклады"
+ subcategory: "Открытие счета"
+ classification_confidence: 0.89
+ top_k: 5
+ password_reset:
+ summary: Password reset inquiry
+ value:
+ query: "Забыл пароль от мобильного банка"
+ category: "Техническая поддержка"
+ subcategory: "Проблемы и решения"
+ classification_confidence: 0.67
+ top_k: 5
+ responses:
+ '200':
+ description: Successful retrieval with ranked templates
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RetrievalResponse'
+ examples:
+ high_similarity:
+ summary: High similarity matches found
+ value:
+ query: "Как открыть накопительный счет в мобильном приложении?"
+ category: "Счета и вклады"
+ subcategory: "Открытие счета"
+ results:
+ - template_id: "tmpl_savings_001"
+ template_question: "Как открыть накопительный счет через мобильное приложение?"
+ template_answer: "Для открытия накопительного счета в мобильном приложении: 1) Войдите в приложение ВТБ..."
+ category: "Счета и вклады"
+ subcategory: "Открытие счета"
+ similarity_score: 0.892
+ combined_score: 0.892
+ rank: 1
+ - template_id: "tmpl_savings_002"
+ template_question: "Какие документы нужны для открытия счета физическому лицу?"
+ template_answer: "Для открытия счета вам потребуется: паспорт, идентификационный номер..."
+ category: "Счета и вклады"
+ subcategory: "Открытие счета"
+ similarity_score: 0.856
+ combined_score: 0.856
+ rank: 2
+ total_candidates: 12
+ processing_time_ms: 487.3
+ timestamp: "2025-10-15T10:30:46Z"
+ warnings: []
+ low_similarity:
+ summary: Low similarity matches (warning)
+ value:
+ query: "Забыл пароль от мобильного банка"
+ category: "Техническая поддержка"
+ subcategory: "Проблемы и решения"
+ results:
+ - template_id: "tmpl_tech_001"
+ template_question: "Как восстановить пароль в мобильном приложении?"
+ template_answer: "Для восстановления пароля: 1) Нажмите 'Забыли пароль?' на экране входа..."
+ category: "Техническая поддержка"
+ subcategory: "Проблемы и решения"
+ similarity_score: 0.42
+ combined_score: 0.42
+ rank: 1
+ total_candidates: 3
+ processing_time_ms: 312.1
+ timestamp: "2025-10-15T10:31:15Z"
+ warnings:
+ - "Low confidence matches - all scores < 0.5"
+ no_templates:
+ summary: No templates found in category
+ value:
+ query: "Как открыть счет?"
+ category: "Неизвестная категория"
+ subcategory: "Неизвестная подкатегория"
+ results: []
+ total_candidates: 0
+ processing_time_ms: 45.2
+ timestamp: "2025-10-15T10:32:05Z"
+ warnings:
+ - "No templates found in category 'Неизвестная категория' > 'Неизвестная подкатегория'"
+ '400':
+ description: Validation error (invalid category or query)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ invalid_category:
+ summary: Invalid category
+ value:
+ error: "Category 'Invalid' does not exist in FAQ database"
+ error_type: "validation"
+ timestamp: "2025-10-15T10:33:12Z"
+ no_cyrillic:
+ summary: No Russian text in query
+ value:
+ error: "Query must contain at least one Cyrillic character"
+ error_type: "validation"
+ timestamp: "2025-10-15T10:33:25Z"
+ '503':
+ description: Service unavailable (embedding service error)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ service_down:
+ summary: Retrieval service unavailable
+ value:
+ error: "Unable to connect to retrieval service. Please check your connection and try again."
+ error_type: "api_error"
+ details: "Scibox embeddings API connection failed"
+ timestamp: "2025-10-15T10:35:45Z"
+ '504':
+ description: Gateway timeout (>1s response time)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ timeout:
+ summary: Retrieval timeout
+ value:
+ error: "The retrieval service is taking longer than expected. Please try again."
+ error_type: "timeout"
+ timestamp: "2025-10-15T10:36:20Z"
+
+components:
+ schemas:
+ RetrievalRequest:
+ type: object
+ required:
+ - query
+ - category
+ - subcategory
+ properties:
+ query:
+ type: string
+ minLength: 5
+ maxLength: 5000
+ description: Customer inquiry text in Russian (must match classified inquiry)
+ example: "Как открыть накопительный счет в мобильном приложении?"
+ category:
+ type: string
+ description: Category from classification (must exist in FAQ database)
+ example: "Счета и вклады"
+ subcategory:
+ type: string
+ description: Subcategory from classification (must match category)
+ example: "Открытие счета"
+ classification_confidence:
+ type: number
+ format: float
+ minimum: 0.0
+ maximum: 1.0
+ description: Confidence score from classification (optional, informational only)
+ example: 0.89
+ top_k:
+ type: integer
+ minimum: 1
+ maximum: 10
+ default: 5
+ description: Number of templates to return (default: 5 per FR-005)
+ example: 5
+ use_historical_weighting:
+ type: boolean
+ default: false
+ description: Enable weighted scoring with historical success rates (not used in MVP)
+ example: false
+ description: |
+ Input model for retrieval endpoint.
+
+ **Construction**: Automatically built from ClassificationResult (FR-003).
+ **Validation Rules**:
+ - VR-008: Query must match inquiry from classification
+ - VR-009: Category/subcategory must match classification result
+ - VR-010: top_k clamped to 1-10 range
+
+ TemplateResult:
+ type: object
+ required:
+ - template_id
+ - template_question
+ - template_answer
+ - category
+ - subcategory
+ - similarity_score
+ - combined_score
+ - rank
+ properties:
+ template_id:
+ type: string
+ description: Unique template identifier
+ example: "tmpl_savings_001"
+ template_question:
+ type: string
+ description: FAQ question text (denormalized for UI display)
+ example: "Как открыть накопительный счет через мобильное приложение?"
+ template_answer:
+ type: string
+ description: FAQ answer text (denormalized for UI display, copy-to-clipboard)
+ example: "Для открытия накопительного счета в мобильном приложении: 1) Войдите в приложение ВТБ..."
+ category:
+ type: string
+ description: Template category (denormalized for UI)
+ example: "Счета и вклады"
+ subcategory:
+ type: string
+ description: Template subcategory (denormalized for UI)
+ example: "Открытие счета"
+ similarity_score:
+ type: number
+ format: float
+ minimum: 0.0
+ maximum: 1.0
+ description: |
+ Cosine similarity between query and template (0.0 to 1.0).
+
+ **Visual Indicators** (FR-011):
+ - High: ≥0.7 (green)
+ - Medium: 0.5-0.7 (yellow)
+ - Low: <0.5 (red)
+ example: 0.892
+ combined_score:
+ type: number
+ format: float
+ minimum: 0.0
+ maximum: 1.0
+ description: Final ranking score (same as similarity_score for MVP)
+ example: 0.892
+ rank:
+ type: integer
+ minimum: 1
+ description: Position in result list (1 = best match, sorted ascending)
+ example: 1
+ description: |
+ Single retrieved template with ranking metadata.
+
+ **Requirement Mapping**:
+ - FR-004: Ranked templates by relevance
+ - FR-005: Top 5 templates with question, answer, similarity score
+ - FR-006: template_answer used for copy-to-clipboard
+ - FR-007: template_answer editable before copy
+ - FR-011: Visual similarity indicators
+
+ RetrievalResponse:
+ type: object
+ required:
+ - query
+ - category
+ - subcategory
+ - results
+ - total_candidates
+ - processing_time_ms
+ - timestamp
+ - warnings
+ properties:
+ query:
+ type: string
+ description: Original inquiry (echoed back for reference)
+ example: "Как открыть накопительный счет в мобильном приложении?"
+ category:
+ type: string
+ description: Category used for filtering (echoed back)
+ example: "Счета и вклады"
+ subcategory:
+ type: string
+ description: Subcategory used for filtering (echoed back)
+ example: "Открытие счета"
+ results:
+ type: array
+ maxItems: 10
+ items:
+ $ref: '#/components/schemas/TemplateResult'
+ description: |
+ Ranked template results (0 to top_k items).
+
+ **Validation** (VR-014): Must be sorted by rank ascending (1, 2, 3, ...).
+ total_candidates:
+ type: integer
+ minimum: 0
+ description: Number of templates in category before ranking (informational)
+ example: 12
+ processing_time_ms:
+ type: number
+ format: float
+ minimum: 0.0
+ description: Time to embed query + rank templates in milliseconds (must be <1000 for FR-016)
+ example: 487.3
+ timestamp:
+ type: string
+ format: date-time
+ description: When retrieval completed (ISO 8601 UTC)
+ example: "2025-10-15T10:30:46Z"
+ warnings:
+ type: array
+ items:
+ type: string
+ description: |
+ Warnings about retrieval quality (empty if no issues).
+
+ **Possible Warnings**:
+ - "No templates found in category '...' > '...'" (VR-015)
+ - "Low confidence matches - all scores < 0.5"
+ - "Very low top score (...) - may not be relevant"
+ - "Slow retrieval: ...ms (target: <1000ms)"
+ example: []
+ description: |
+ Complete retrieval result with ranked templates and metadata.
+
+ **Requirement Mapping**:
+ - FR-003: Auto-triggered after classification
+ - FR-004: Ranked by relevance (similarity_score)
+ - FR-005: Top 5 templates with metadata
+ - FR-014: Processing time display
+ - FR-016: <1 second performance requirement
+
+ ErrorResponse:
+ type: object
+ required:
+ - error
+ - error_type
+ - timestamp
+ properties:
+ error:
+ type: string
+ description: |
+ Human-readable, user-actionable error message (VR-017).
+ No technical jargon or stack traces.
+ example: "Unable to connect to retrieval service. Please check your connection and try again."
+ error_type:
+ type: string
+ enum:
+ - validation
+ - api_error
+ - timeout
+ - unknown
+ description: |
+ Error category for frontend handling.
+
+ - validation: Input validation failed (400)
+ - api_error: Service unavailable (503)
+ - timeout: Request exceeded time limit (504)
+ - unknown: Unexpected server error (500)
+ example: "api_error"
+ details:
+ type: string
+ description: Technical details for logging (optional, not shown to user)
+ example: "Scibox embeddings API connection timeout"
+ timestamp:
+ type: string
+ format: date-time
+ description: When error occurred (ISO 8601 UTC)
+ example: "2025-10-15T10:35:45Z"
+ description: |
+ Standardized error response format.
+
+ **Requirement Mapping**:
+ - FR-020: Retrieval service error handling
+ - FR-021: Network timeout handling
+ - FR-022: Actionable error guidance
diff --git a/specs/004-smart-support-operator/data-model.md b/specs/004-smart-support-operator/data-model.md
new file mode 100644
index 0000000..875ecf9
--- /dev/null
+++ b/specs/004-smart-support-operator/data-model.md
@@ -0,0 +1,567 @@
+# Phase 1: Data Model - Operator Web Interface
+
+**Feature**: Operator Web Interface
+**Branch**: `004-smart-support-operator`
+**Date**: 2025-10-15
+
+## Purpose
+
+This document defines all data entities, their relationships, validation rules, and state transitions for the operator web interface. These models serve as the contract between frontend and backend.
+
+---
+
+## Entity Definitions
+
+### E-001: InquiryInput
+
+**Purpose**: Captures customer inquiry text from operator input
+
+**Attributes**:
+| Field | Type | Constraints | Description |
+|-------|------|-------------|-------------|
+| `text` | string | min_length=5, max_length=5000, must contain Cyrillic | Customer inquiry text in Russian |
+
+**Validation Rules**:
+- VR-001: Text must be stripped of leading/trailing whitespace
+- VR-002: Text must contain at least one Cyrillic character (Russian language)
+- VR-003: Empty or whitespace-only text rejected with error
+
+**State Transitions**:
+```
+Created (operator types) → Validated (passes VR-001-003) → Submitted (sent to backend)
+ ↓
+ Validation Failed (user-friendly error displayed)
+```
+
+**Requirement Mapping**: FR-001 (accept inquiry text)
+
+**Example**:
+```json
+{
+ "text": "Как открыть накопительный счет в мобильном приложении?"
+}
+```
+
+---
+
+### E-002: ClassificationResult
+
+**Purpose**: Contains category/subcategory assignment from classification module
+
+**Attributes**:
+| Field | Type | Constraints | Description |
+|-------|------|-------------|-------------|
+| `inquiry` | string | min_length=5 | Original inquiry text (echoed back) |
+| `category` | string | min_length=1, must match FAQ categories | Top-level product category |
+| `subcategory` | string | min_length=1, must match FAQ subcategories | Second-level classification |
+| `confidence` | float | 0.0 ≤ x ≤ 1.0 | Classification confidence score |
+| `processing_time_ms` | integer | > 0 | Time taken for classification (milliseconds) |
+| `timestamp` | string | ISO 8601 format | When classification was performed (UTC) |
+
+**Computed Fields**:
+- `confidence_level`: string (`"high"` if ≥0.8, `"medium"` if 0.6-0.8, `"low"` if <0.6)
+
+**Validation Rules**:
+- VR-004: Category must exist in FAQ database (6 valid categories)
+- VR-005: Subcategory must belong to the given category (35 total valid subcategories)
+- VR-006: Confidence score normalized to 0.0-1.0 range
+- VR-007: Timestamp must be valid ISO 8601 string
+
+**State Transitions**:
+```
+Pending (API request sent) → Loading (awaiting response) → Success (result displayed)
+ ↓
+ Cached (5 min TTL)
+ ↓
+ Failed (network error, timeout, validation error)
+```
+
+**Requirement Mapping**: FR-002 (display classification), FR-010 (visual confidence indicators), FR-013 (processing time)
+
+**Example**:
+```json
+{
+ "inquiry": "Как открыть накопительный счет в мобильном приложении?",
+ "category": "Счета и вклады",
+ "subcategory": "Открытие счета",
+ "confidence": 0.89,
+ "processing_time_ms": 1247,
+ "timestamp": "2025-10-15T10:30:45Z"
+}
+```
+
+---
+
+### E-003: RetrievalRequest
+
+**Purpose**: Input for template retrieval API (constructed from classification result)
+
+**Attributes**:
+| Field | Type | Constraints | Description |
+|-------|------|-------------|-------------|
+| `query` | string | min_length=5, max_length=5000, must contain Cyrillic | Customer inquiry text |
+| `category` | string | min_length=1, must exist in FAQ | Category from classification |
+| `subcategory` | string | min_length=1, must match category | Subcategory from classification |
+| `classification_confidence` | float (optional) | 0.0 ≤ x ≤ 1.0 | Confidence score from classification |
+| `top_k` | integer | 1 ≤ x ≤ 10, default=5 | Number of templates to return |
+| `use_historical_weighting` | boolean | default=false | Enable weighted scoring (not used in MVP) |
+
+**Validation Rules**:
+- VR-008: Query must match inquiry from classification (referential integrity)
+- VR-009: Category/subcategory must match classification result
+- VR-010: top_k clamped to 1-10 range (enforces FR-005: display top 5)
+
+**Construction**:
+```typescript
+// Frontend auto-constructs from ClassificationResult
+const retrievalRequest: RetrievalRequest = {
+ query: classificationResult.inquiry,
+ category: classificationResult.category,
+ subcategory: classificationResult.subcategory,
+ classification_confidence: classificationResult.confidence,
+ top_k: 5
+};
+```
+
+**Requirement Mapping**: FR-003 (auto-retrieve after classification)
+
+---
+
+### E-004: TemplateResult
+
+**Purpose**: Single retrieved template with ranking and similarity metadata
+
+**Attributes**:
+| Field | Type | Constraints | Description |
+|-------|------|-------------|-------------|
+| `template_id` | string | min_length=1 | Unique template identifier (e.g., "tmpl_001") |
+| `template_question` | string | min_length=10 | FAQ question text (denormalized for UI) |
+| `template_answer` | string | min_length=20 | FAQ answer text (denormalized for UI) |
+| `category` | string | min_length=1 | Template category (denormalized) |
+| `subcategory` | string | min_length=1 | Template subcategory (denormalized) |
+| `similarity_score` | float | 0.0 ≤ x ≤ 1.0 | Cosine similarity between query and template |
+| `combined_score` | float | 0.0 ≤ x ≤ 1.0 | Final ranking score (same as similarity_score for MVP) |
+| `rank` | integer | ≥ 1 | Position in result list (1 = best match) |
+
+**Computed Fields**:
+- `confidence_level`: string (`"high"` if combined_score ≥0.7, `"medium"` if 0.5-0.7, `"low"` if <0.5)
+
+**Validation Rules**:
+- VR-011: Results must be sorted by rank ascending (1, 2, 3, ...)
+- VR-012: Similarity scores must be valid cosine similarity (0.0 to 1.0)
+- VR-013: Question and answer must contain Cyrillic characters
+
+**Requirement Mapping**: FR-004 (ranked templates), FR-005 (top 5 with metadata), FR-011 (visual similarity indicators)
+
+**Example**:
+```json
+{
+ "template_id": "tmpl_savings_001",
+ "template_question": "Как открыть накопительный счет через мобильное приложение?",
+ "template_answer": "Для открытия накопительного счета в мобильном приложении: 1) Войдите в приложение ВТБ...",
+ "category": "Счета и вклады",
+ "subcategory": "Открытие счета",
+ "similarity_score": 0.892,
+ "combined_score": 0.892,
+ "rank": 1
+}
+```
+
+---
+
+### E-005: RetrievalResponse
+
+**Purpose**: Complete retrieval result with ranked templates and metadata
+
+**Attributes**:
+| Field | Type | Constraints | Description |
+|-------|------|-------------|-------------|
+| `query` | string | min_length=5 | Original inquiry (echoed back) |
+| `category` | string | min_length=1 | Category used for filtering (echoed back) |
+| `subcategory` | string | min_length=1 | Subcategory used for filtering (echoed back) |
+| `results` | TemplateResult[] | max_length=10 | Ranked template results |
+| `total_candidates` | integer | ≥ 0 | Number of templates in category before ranking |
+| `processing_time_ms` | float | ≥ 0.0 | Time to embed query + rank (milliseconds) |
+| `timestamp` | datetime | ISO 8601 format | When retrieval completed (UTC) |
+| `warnings` | string[] | - | Warnings (e.g., low confidence, no templates) |
+
+**Validation Rules**:
+- VR-014: Results array must be sorted by rank (1, 2, 3, ...)
+- VR-015: If total_candidates = 0, results must be empty array
+- VR-016: Warnings must contain actionable messages (no technical jargon)
+
+**State Transitions**:
+```
+Pending (API request sent) → Loading (awaiting response) → Success (templates displayed)
+ ↓
+ Cached (5 min TTL)
+ ↓
+ Failed (network error, timeout, no templates found)
+```
+
+**Requirement Mapping**: FR-003 (auto-retrieve), FR-004 (ranked), FR-005 (top 5), FR-014 (processing time)
+
+**Example**:
+```json
+{
+ "query": "Как открыть накопительный счет в мобильном приложении?",
+ "category": "Счета и вклады",
+ "subcategory": "Открытие счета",
+ "results": [
+ {
+ "template_id": "tmpl_savings_001",
+ "template_question": "Как открыть накопительный счет через мобильное приложение?",
+ "template_answer": "Для открытия накопительного счета...",
+ "category": "Счета и вклады",
+ "subcategory": "Открытие счета",
+ "similarity_score": 0.892,
+ "combined_score": 0.892,
+ "rank": 1
+ }
+ ],
+ "total_candidates": 12,
+ "processing_time_ms": 487.3,
+ "timestamp": "2025-10-15T10:30:46Z",
+ "warnings": []
+}
+```
+
+---
+
+### E-006: ErrorResponse
+
+**Purpose**: Standardized error format for all API failures
+
+**Attributes**:
+| Field | Type | Constraints | Description |
+|-------|------|-------------|-------------|
+| `error` | string | min_length=1 | Human-readable error message |
+| `error_type` | string | enum: validation, api_error, timeout, unknown | Error category |
+| `details` | string (optional) | - | Additional technical details (for logging) |
+| `timestamp` | string | ISO 8601 format | When error occurred |
+
+**Error Types**:
+- `validation`: Input validation failed (FR-018, FR-022)
+ - Example: "Please enter your inquiry in Russian (at least 5 characters)"
+- `api_error`: Classification/retrieval service unavailable (FR-019, FR-020)
+ - Example: "Unable to connect to classification service. Please check your connection and try again."
+- `timeout`: Request exceeded time limit (FR-021)
+ - Example: "The classification service is taking longer than expected. Please try again."
+- `unknown`: Unexpected server error
+ - Example: "An unexpected error occurred. Please try again or contact support."
+
+**Validation Rules**:
+- VR-017: Error message must be user-actionable (FR-022)
+- VR-018: No technical stack traces or internal error codes in `error` field
+- VR-019: `details` field may contain technical info for logging (not displayed to user)
+
+**Requirement Mapping**: FR-018 (validation errors), FR-019 (classification errors), FR-020 (retrieval errors), FR-021 (timeout errors), FR-022 (actionable guidance)
+
+**Example**:
+```json
+{
+ "error": "Unable to connect to classification service. Please check your connection and try again.",
+ "error_type": "api_error",
+ "details": "Connection timeout after 5000ms to POST /api/classify",
+ "timestamp": "2025-10-15T10:35:22Z"
+}
+```
+
+---
+
+### E-007: EditableTemplate (Frontend-Only)
+
+**Purpose**: Local state for template editing feature (FR-007, FR-009)
+
+**Attributes**:
+| Field | Type | Constraints | Description |
+|-------|------|-------------|-------------|
+| `original_answer` | string | min_length=20 | Original template answer (immutable) |
+| `edited_answer` | string | min_length=20 | Current edited text (mutable) |
+| `is_editing` | boolean | - | Whether template is in edit mode |
+| `is_modified` | boolean | computed | True if edited_answer ≠ original_answer |
+
+**Validation Rules**:
+- VR-020: original_answer never changes after initialization
+- VR-021: edited_answer initialized to original_answer value
+- VR-022: Restore operation sets edited_answer = original_answer
+
+**State Transitions**:
+```
+Display Mode (is_editing=false) → Edit Mode (is_editing=true) → Display Mode
+ ↓ ↓
+ User edits text Save (keeps edits)
+ ↓ ↓
+ Restore Original Copy (uses edited_answer)
+ ↓
+ edited_answer = original_answer
+```
+
+**Requirement Mapping**: FR-007 (edit template), FR-009 (restore original)
+
+**Example**:
+```typescript
+interface EditableTemplate {
+ original_answer: string; // "Для открытия счета вам потребуется..."
+ edited_answer: string; // "Для открытия счета Вам потребуется паспорт и..."
+ is_editing: boolean; // true
+ is_modified: boolean; // true (computed)
+}
+```
+
+---
+
+### E-008: UIState (Frontend-Only)
+
+**Purpose**: Global UI state management (not persisted)
+
+**Attributes**:
+| Field | Type | Constraints | Description |
+|-------|------|-------------|-------------|
+| `current_inquiry` | string | - | Text currently in inquiry input field |
+| `classification_loading` | boolean | - | True while classification API request in flight |
+| `retrieval_loading` | boolean | - | True while retrieval API request in flight |
+| `classification_error` | ErrorResponse \| null | - | Last classification error (null if success) |
+| `retrieval_error` | ErrorResponse \| null | - | Last retrieval error (null if success) |
+| `clipboard_feedback` | boolean | - | True for 2s after successful copy |
+
+**State Transitions**:
+```
+Idle (no inquiry submitted)
+ ↓ User clicks "Submit"
+Classification Loading (classification_loading=true)
+ ↓ Classification API response
+Classification Success (classification_loading=false) OR Classification Error
+ ↓ Auto-trigger retrieval (if success)
+Retrieval Loading (retrieval_loading=true)
+ ↓ Retrieval API response
+Retrieval Success (retrieval_loading=false) OR Retrieval Error
+ ↓ User copies template
+Clipboard Feedback (clipboard_feedback=true for 2s)
+```
+
+**Requirement Mapping**: FR-012 (loading state), FR-017 (responsive UI), FR-019-021 (error display)
+
+---
+
+## Entity Relationships
+
+### ER-001: InquiryInput → ClassificationResult
+**Type**: One-to-One (per submission)
+**Flow**: User submits InquiryInput → Backend returns ClassificationResult
+**Constraint**: Classification must complete within 2s (FR-015)
+
+### ER-002: ClassificationResult → RetrievalRequest
+**Type**: One-to-One (auto-constructed)
+**Flow**: Frontend constructs RetrievalRequest from ClassificationResult fields
+**Constraint**: Auto-triggered on classification success (FR-003)
+
+### ER-003: RetrievalRequest → RetrievalResponse
+**Type**: One-to-One
+**Flow**: Backend processes RetrievalRequest → Returns RetrievalResponse with ranked templates
+**Constraint**: Retrieval must complete within 1s (FR-016)
+
+### ER-004: RetrievalResponse → TemplateResult[]
+**Type**: One-to-Many
+**Flow**: RetrievalResponse contains 0-5 TemplateResult objects
+**Constraint**: Results sorted by rank ascending (VR-011)
+
+### ER-005: TemplateResult → EditableTemplate
+**Type**: One-to-One (per template in UI)
+**Flow**: Frontend wraps each TemplateResult in EditableTemplate for editing capability
+**Constraint**: original_answer never modified (VR-020)
+
+---
+
+## Data Flow Diagram
+
+```
+┌──────────────────────────────────────────────────────────────────────┐
+│ Operator Interface (Frontend) │
+│ │
+│ ┌──────────────┐ │
+│ │ InquiryInput │ (E-001) │
+│ │ text: string │ │
+│ └──────┬───────┘ │
+│ │ Submit │
+│ ▼ │
+│ ┌──────────────────────┐ │
+│ │ POST /api/classify │ HTTP Request │
+│ │ {"inquiry": "..."} │ │
+│ └──────┬───────────────┘ │
+│ │ <2s (FR-015) │
+│ ▼ │
+│ ┌──────────────────────────┐ │
+│ │ ClassificationResult │ (E-002) │
+│ │ category, subcategory, │ │
+│ │ confidence │ │
+│ └──────┬───────────────────┘ │
+│ │ Auto-trigger (FR-003) │
+│ ▼ │
+│ ┌──────────────────────┐ │
+│ │ RetrievalRequest │ (E-003) │
+│ │ query, category, │ │
+│ │ subcategory, top_k=5 │ │
+│ └──────┬───────────────┘ │
+│ │ │
+│ ▼ │
+│ ┌──────────────────────┐ │
+│ │ POST /api/retrieve │ HTTP Request │
+│ └──────┬───────────────┘ │
+│ │ <1s (FR-016) │
+│ ▼ │
+│ ┌──────────────────────────┐ │
+│ │ RetrievalResponse │ (E-005) │
+│ │ results: [ │ │
+│ │ TemplateResult (E-004) │ │
+│ │ rank=1, score=0.89 │ │
+│ │ ] │ │
+│ └──────┬───────────────────┘ │
+│ │ Display │
+│ ▼ │
+│ ┌──────────────────────────┐ │
+│ │ EditableTemplate (E-007) │ (Frontend wrapping) │
+│ │ original_answer, │ │
+│ │ edited_answer, │ │
+│ │ is_editing │ │
+│ └──────┬───────────────────┘ │
+│ │ User edits (FR-007) or Copies (FR-006) │
+│ ▼ │
+│ ┌──────────────────────────┐ │
+│ │ Clipboard.writeText() │ Browser API │
+│ │ (answer text copied) │ │
+│ └──────────────────────────┘ │
+│ │
+│ Error Handling (any step): │
+│ ┌──────────────────────────┐ │
+│ │ ErrorResponse (E-006) │ HTTP 400/503/504 │
+│ │ error: "user message", │ │
+│ │ error_type: "..." │ │
+│ └──────────────────────────┘ │
+└──────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## Validation Matrix
+
+| Entity | Validation Rule | Error Type | User Message |
+|--------|----------------|------------|--------------|
+| InquiryInput | VR-001 (whitespace) | validation | "Please enter your inquiry text" |
+| InquiryInput | VR-002 (Cyrillic) | validation | "Please enter inquiry in Russian" |
+| InquiryInput | VR-003 (min length) | validation | "Inquiry must be at least 5 characters" |
+| ClassificationResult | VR-004 (invalid category) | api_error | "Classification service returned invalid data" |
+| ClassificationResult | VR-005 (invalid subcategory) | api_error | "Classification service returned invalid data" |
+| ClassificationResult | VR-006 (confidence range) | api_error | "Classification service returned invalid data" |
+| RetrievalRequest | VR-008 (query mismatch) | validation | "Internal error: query mismatch" |
+| RetrievalRequest | VR-009 (category mismatch) | validation | "Internal error: category mismatch" |
+| TemplateResult | VR-011 (rank order) | api_error | "Retrieval service returned invalid data" |
+| TemplateResult | VR-012 (similarity range) | api_error | "Retrieval service returned invalid data" |
+| RetrievalResponse | VR-014 (sort order) | api_error | "Retrieval service returned invalid data" |
+| RetrievalResponse | VR-015 (empty results) | warning | "No templates found in this category" |
+| ErrorResponse | VR-017 (actionable message) | - | (enforced by backend) |
+| EditableTemplate | VR-020 (immutable original) | - | (enforced by frontend) |
+
+---
+
+## Performance Constraints
+
+| Entity | Performance Requirement | Acceptance Criteria |
+|--------|------------------------|---------------------|
+| ClassificationResult | FR-015: <2s response | `processing_time_ms` < 2000 for 95% of requests |
+| RetrievalResponse | FR-016: <1s response | `processing_time_ms` < 1000 for 95% of requests |
+| Full Workflow | SC-001: <10s total | InquiryInput submit → TemplateResult copy < 10000ms |
+| UI Actions | FR-017: Responsive | Edit mode toggle, copy button < 500ms |
+
+---
+
+## TypeScript Interface Definitions (Frontend)
+
+```typescript
+// frontend/src/types/classification.ts
+export interface ClassificationRequest {
+ inquiry: string;
+}
+
+export interface ClassificationResult {
+ inquiry: string;
+ category: string;
+ subcategory: string;
+ confidence: number;
+ processing_time_ms: number;
+ timestamp: string;
+}
+
+// frontend/src/types/retrieval.ts
+export interface RetrievalRequest {
+ query: string;
+ category: string;
+ subcategory: string;
+ classification_confidence?: number;
+ top_k?: number;
+ use_historical_weighting?: boolean;
+}
+
+export interface TemplateResult {
+ template_id: string;
+ template_question: string;
+ template_answer: string;
+ category: string;
+ subcategory: string;
+ similarity_score: number;
+ combined_score: number;
+ rank: number;
+}
+
+export interface RetrievalResponse {
+ query: string;
+ category: string;
+ subcategory: string;
+ results: TemplateResult[];
+ total_candidates: number;
+ processing_time_ms: number;
+ timestamp: string;
+ warnings: string[];
+}
+
+// frontend/src/types/error.ts
+export interface ErrorResponse {
+ error: string;
+ error_type: 'validation' | 'api_error' | 'timeout' | 'unknown';
+ details?: string;
+ timestamp: string;
+}
+
+// frontend/src/types/ui.ts
+export interface EditableTemplate extends TemplateResult {
+ original_answer: string;
+ edited_answer: string;
+ is_editing: boolean;
+ is_modified: boolean;
+}
+```
+
+---
+
+## Constitution Compliance
+
+**Principle I (Modular Architecture)**: ✅
+- Backend entities (E-001 through E-006) mirror existing Pydantic models
+- Frontend entities (E-007, E-008) isolated to UI layer
+- Clean API contract via HTTP JSON
+
+**Principle II (User-Centric Design)**: ✅
+- ErrorResponse (E-006) enforces user-actionable messages (VR-017)
+- EditableTemplate (E-007) supports editing + restore workflow (FR-007, FR-009)
+- Performance constraints explicitly modeled (processing_time_ms fields)
+
+**Principle III (Data-Driven Validation)**: ✅
+- All validation rules (VR-001 through VR-022) explicitly documented
+- Validation matrix maps rules → error types → user messages
+- TypeScript interfaces enable compile-time validation
+
+**Principle IV (API-First Integration)**: ✅
+- RetrievalRequest constructed from ClassificationResult (ER-002)
+- OpenAPI contracts will auto-generate from these models (Phase 1)
+
+**Ready for Phase 1 Contracts**: ✅
diff --git a/specs/004-smart-support-operator/plan.md b/specs/004-smart-support-operator/plan.md
new file mode 100644
index 0000000..6494154
--- /dev/null
+++ b/specs/004-smart-support-operator/plan.md
@@ -0,0 +1,163 @@
+# Implementation Plan: Operator Web Interface
+
+**Branch**: `004-smart-support-operator` | **Date**: 2025-10-15 | **Spec**: [spec.md](./spec.md)
+**Input**: Feature specification from `/specs/004-smart-support-operator/spec.md`
+
+**Note**: This document is generated by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
+
+## Summary
+
+Build a professional React + FastAPI web interface that enables support operators to analyze customer inquiries and retrieve relevant template responses in under 10 seconds. The system integrates existing Classification Module (90% accuracy, <2s) and Retrieval Module (93% top-3 accuracy, <1s) through a REST API backend, providing real-time classification, ranked template recommendations, visual confidence indicators, response editing, and copy-to-clipboard functionality. The interface must meet hackathon UI/UX criteria (20 points) with professional appearance and fast response times.
+
+## Technical Context
+
+**Language/Version**: Python 3.11+ (backend), Node.js 18+ (frontend)
+**Primary Dependencies**:
+- Backend: FastAPI 0.104+, Uvicorn, python-multipart, pydantic 2.x
+- Frontend: React 18+, TypeScript, Tailwind CSS / Material-UI, Axios, React Query
+
+**Storage**: SQLite (existing embeddings.db with 201 FAQ templates)
+**Testing**:
+- Backend: pytest, testcontainers-python (API contract tests)
+- Frontend: Jest, React Testing Library
+- E2E: Chrome DevTools MCP (end-to-end user scenarios)
+
+**Target Platform**: Web browsers (Chrome, Firefox, Safari, Edge - desktop only)
+**Project Type**: Web application (frontend + backend)
+
+**Performance Goals**:
+- Classification API: <2 seconds response time (95th percentile)
+- Retrieval API: <1 second response time (95th percentile)
+- Full operator workflow: <10 seconds (input → classify → retrieve → copy)
+- UI response: All actions complete in <500ms (excluding API calls)
+
+**Constraints**:
+- Must integrate existing Classification and Retrieval modules without modification
+- Hackathon demonstration focus (MVP over production-ready features)
+- Desktop-only (no mobile responsive design required for MVP)
+- Russian language only (no i18n required)
+
+**Scale/Scope**:
+- Single operator at a time (no concurrent user support needed for MVP)
+- 201 FAQ templates across 6 categories, 35 subcategories
+- Expected demo dataset: 10-15 sample inquiries
+
+## Constitution Check
+
+*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
+
+### Principle I: Modular Architecture ✅
+**Status**: PASS
+**Compliance**: The operator interface is designed as a standalone module (Module 3) with clean API boundaries. Backend exposes REST endpoints that wrap existing Classifier and TemplateRetriever classes without modifying them. Frontend communicates solely through HTTP APIs.
+
+### Principle II: User-Centric Design ✅
+**Status**: PASS
+**Compliance**: Specification focuses heavily on operator experience (20 points UI/UX in evaluation). Requirements prioritize speed (FR-015: <2s classification, FR-016: <1s retrieval), clarity (FR-010: visual confidence indicators), and efficiency (FR-006: one-click copy, FR-007: inline editing).
+
+### Principle III: Data-Driven Validation ✅
+**Status**: PASS
+**Compliance**: Implementation plan includes:
+- **Integration Tests**: testcontainers with real SQLite database to verify API contracts (FR-001 through FR-022)
+- **E2E Tests**: Chrome DevTools MCP for complete user story validation (all 5 user stories with acceptance scenarios)
+- **Performance Validation**: Automated measurement of classification/retrieval response times against requirements
+
+### Principle IV: API-First Integration ✅
+**Status**: PASS
+**Compliance**: Backend wraps existing modules through FastAPI REST endpoints. Uses Pydantic models from `src/classification/models.py` and `src/retrieval/models.py` for type safety. No direct Scibox API calls from frontend.
+
+### Principle V: Deployment Simplicity ✅
+**Status**: PASS
+**Compliance**: Will extend existing `docker-compose.yml` with new `operator-ui` service. Frontend build process integrated into Dockerfile. Single command launch: `docker-compose up operator-ui`.
+
+### Principle VI: Knowledge Base Integration ✅
+**Status**: PASS
+**Compliance**: Uses existing pre-populated `data/embeddings.db` (201 templates). No new FAQ parsing required. Retrieval module handles all knowledge base operations.
+
+**Overall Constitution Assessment**: ✅ **ALL GATES PASSED** - No violations. Ready to proceed to Phase 0.
+
+## Project Structure
+
+### Documentation (this feature)
+
+```
+specs/004-smart-support-operator/
+├── plan.md # This file (/speckit.plan command output)
+├── research.md # Phase 0 output (technology decisions)
+├── data-model.md # Phase 1 output (entity definitions)
+├── quickstart.md # Phase 1 output (development setup)
+├── contracts/ # Phase 1 output (OpenAPI specs)
+│ ├── classification-api.yaml
+│ └── retrieval-api.yaml
+└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
+```
+
+### Source Code (repository root)
+
+```
+# Web application structure (frontend + backend)
+backend/
+├── src/
+│ ├── api/ # FastAPI application
+│ │ ├── __init__.py # App factory
+│ │ ├── main.py # FastAPI app instance, CORS, routes
+│ │ ├── routes/
+│ │ │ ├── classification.py # POST /api/classify endpoint
+│ │ │ └── retrieval.py # POST /api/retrieve endpoint
+│ │ ├── models.py # Request/Response Pydantic models
+│ │ └── middleware.py # CORS, logging, error handling
+│ └── __init__.py
+├── tests/
+│ ├── integration/ # testcontainers API tests
+│ │ ├── test_classification_api.py
+│ │ ├── test_retrieval_api.py
+│ │ └── test_full_workflow.py
+│ └── unit/
+│ └── test_api_models.py
+├── pyproject.toml # Poetry/pip dependencies
+└── requirements.txt
+
+frontend/
+├── src/
+│ ├── components/ # React components
+│ │ ├── InquiryInput.tsx # Text input + submit button
+│ │ ├── ClassificationDisplay.tsx # Category/subcategory/confidence
+│ │ ├── TemplateList.tsx # Ranked template results
+│ │ ├── TemplateCard.tsx # Individual template with copy/edit
+│ │ ├── LoadingSpinner.tsx # Processing state indicator
+│ │ ├── ErrorMessage.tsx # User-friendly error display
+│ │ └── ConfidenceBadge.tsx # Visual high/medium/low indicator
+│ ├── services/ # API client layer
+│ │ ├── api.ts # Axios instance with base URL
+│ │ ├── classification.ts # classify(inquiry) → ClassificationResult
+│ │ └── retrieval.ts # retrieve(request) → RetrievalResponse
+│ ├── types/ # TypeScript interfaces
+│ │ ├── classification.ts # Mirror of backend Pydantic models
+│ │ └── retrieval.ts
+│ ├── hooks/ # React custom hooks
+│ │ └── useClipboard.ts # Copy-to-clipboard with feedback
+│ ├── App.tsx # Main application component
+│ ├── index.tsx # React entry point
+│ └── index.css # Tailwind imports
+├── public/
+│ └── index.html
+├── package.json # npm dependencies
+├── tsconfig.json # TypeScript configuration
+├── tailwind.config.js # Tailwind CSS configuration
+└── vite.config.ts # Vite build configuration
+
+tests/
+└── e2e/ # End-to-end tests (Chrome DevTools MCP)
+ ├── test_user_story_1.py # P1: Inquiry Analysis and Template Retrieval
+ ├── test_user_story_2.py # P2: Response Customization
+ └── test_edge_cases.py # Edge case validation
+```
+
+**Structure Decision**: Selected **Option 2: Web application** structure because the specification explicitly describes a web-based operator interface with both frontend (React) and backend (FastAPI) components. This maps directly to the `frontend/` + `backend/` directories as outlined above. The backend integrates existing `src/classification/` and `src/retrieval/` modules through API routes without modifying them (Principle I: Modular Architecture).
+
+## Complexity Tracking
+
+*No Constitution Check violations - this section is empty.*
+
+| Violation | Why Needed | Simpler Alternative Rejected Because |
+|-----------|------------|--------------------------------------|
+| N/A | N/A | N/A |
diff --git a/specs/004-smart-support-operator/quickstart.md b/specs/004-smart-support-operator/quickstart.md
new file mode 100644
index 0000000..1fe86dc
--- /dev/null
+++ b/specs/004-smart-support-operator/quickstart.md
@@ -0,0 +1,601 @@
+# Quickstart: Operator Web Interface Development
+
+**Feature**: Operator Web Interface
+**Branch**: `004-smart-support-operator`
+**Date**: 2025-10-15
+
+## Purpose
+
+This guide provides step-by-step instructions for setting up the development environment, running the operator web interface locally, and executing tests.
+
+---
+
+## Prerequisites
+
+Before starting development, ensure you have:
+
+- ✅ **Python 3.11+** installed (`python --version`)
+- ✅ **Node.js 18+** installed (`node --version`)
+- ✅ **npm 9+** installed (`npm --version`)
+- ✅ **Git** installed (`git --version`)
+- ✅ **Scibox API key** ([Get one here](https://llm.t1v.scibox.tech/))
+- ✅ **Docker & Docker Compose** (for deployment testing)
+- ✅ **Existing smart-support repository** cloned with working Classification and Retrieval modules
+
+### Verify Existing Modules
+
+```bash
+# Ensure Classification Module works
+python -m src.cli.classify "Как открыть счет?"
+
+# Ensure Retrieval Module works
+python -m src.cli.retrieve "Как открыть счет?" --category "Новые клиенты" --subcategory "Регистрация и онбординг"
+
+# Verify embeddings database exists
+ls -lh data/embeddings.db # Should show ~1MB file
+```
+
+---
+
+## Initial Setup
+
+### Step 1: Create Feature Branch
+
+```bash
+# Checkout and pull latest main
+git checkout main
+git pull origin main
+
+# Create feature branch
+git checkout -b 004-smart-support-operator
+```
+
+### Step 2: Backend Setup
+
+```bash
+# Create backend directory structure
+mkdir -p backend/src/api/routes
+mkdir -p backend/tests/integration
+mkdir -p backend/tests/unit
+
+# Create Python package files
+touch backend/src/__init__.py
+touch backend/src/api/__init__.py
+touch backend/src/api/routes/__init__.py
+touch backend/tests/__init__.py
+touch backend/tests/integration/__init__.py
+touch backend/tests/unit/__init__.py
+
+# Create backend requirements file
+cat > backend/requirements.txt << 'EOF'
+# FastAPI and ASGI server
+fastapi==0.104.1
+uvicorn[standard]==0.24.0
+python-multipart==0.0.6
+pydantic==2.5.0
+
+# CORS and middleware
+python-json-logger==2.0.7
+
+# Testing
+pytest==7.4.3
+pytest-asyncio==0.21.1
+httpx==0.25.1
+testcontainers==3.7.1
+EOF
+
+# Install backend dependencies (in project venv)
+pip install -r backend/requirements.txt
+```
+
+### Step 3: Frontend Setup
+
+```bash
+# Create frontend using Vite
+npm create vite@latest frontend -- --template react-ts
+
+# Navigate to frontend
+cd frontend
+
+# Install dependencies
+npm install
+
+# Install additional packages
+npm install axios react-query @headlessui/react
+npm install -D tailwindcss postcss autoprefixer
+npx tailwindcss init -p
+
+# Return to project root
+cd ..
+```
+
+### Step 4: Configure Tailwind CSS
+
+```bash
+# Update frontend/tailwind.config.js
+cat > frontend/tailwind.config.js << 'EOF'
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
+EOF
+
+# Update frontend/src/index.css
+cat > frontend/src/index.css << 'EOF'
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+EOF
+```
+
+### Step 5: Environment Configuration
+
+```bash
+# Ensure .env exists in project root (from existing setup)
+# Should already contain:
+# SCIBOX_API_KEY=your_key_here
+# FAQ_PATH=docs/smart_support_vtb_belarus_faq_final.xlsx
+
+# Verify .env is in .gitignore
+grep -q "^\.env$" .gitignore || echo ".env" >> .gitignore
+```
+
+---
+
+## Running the Application
+
+### Development Mode (Backend + Frontend)
+
+**Terminal 1 - Backend Server:**
+
+```bash
+# From project root
+cd backend
+
+# Run FastAPI development server
+uvicorn src.api.main:app --reload --host 0.0.0.0 --port 8000
+
+# Expected output:
+# INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
+# INFO: Started reloader process [12345] using WatchFiles
+# INFO: Application startup complete.
+```
+
+**Terminal 2 - Frontend Dev Server:**
+
+```bash
+# From project root
+cd frontend
+
+# Run Vite development server
+npm run dev
+
+# Expected output:
+# VITE v5.0.0 ready in 234 ms
+#
+# ➜ Local: http://localhost:5173/
+# ➜ Network: use --host to expose
+```
+
+**Access the Interface:**
+
+Open browser to http://localhost:5173/
+
+The frontend will proxy API requests to `http://localhost:8000` (configure in `vite.config.ts`).
+
+---
+
+## Testing
+
+### Backend Unit Tests
+
+```bash
+# From project root
+cd backend
+
+# Run unit tests (fast, mocked)
+pytest tests/unit/ -v
+
+# With coverage
+pytest tests/unit/ -v --cov=src --cov-report=term-missing
+```
+
+### Backend Integration Tests
+
+```bash
+# From project root
+cd backend
+
+# Run integration tests (testcontainers - requires Docker running)
+pytest tests/integration/ -v -m integration
+
+# Test specific endpoint
+pytest tests/integration/test_classification_api.py -v
+
+# Test full workflow
+pytest tests/integration/test_full_workflow.py -v
+```
+
+### Frontend Unit Tests
+
+```bash
+# From project root
+cd frontend
+
+# Run component tests
+npm test
+
+# With coverage
+npm test -- --coverage
+```
+
+### End-to-End Tests
+
+```bash
+# From project root
+
+# Ensure backend and frontend are running (see "Running the Application")
+# Terminal 1: backend server
+# Terminal 2: frontend server
+
+# Terminal 3: Run E2E tests
+pytest tests/e2e/ -v -m e2e
+
+# Test specific user story
+pytest tests/e2e/test_user_story_1.py -v
+```
+
+---
+
+## API Documentation
+
+Once the backend is running, view auto-generated API docs:
+
+- **Swagger UI**: http://localhost:8000/docs
+- **ReDoc**: http://localhost:8000/redoc
+- **OpenAPI JSON**: http://localhost:8000/openapi.json
+
+Example API calls with curl:
+
+```bash
+# Health check
+curl http://localhost:8000/api/health
+
+# Classification
+curl -X POST http://localhost:8000/api/classify \
+ -H "Content-Type: application/json" \
+ -d '{"inquiry": "Как открыть счет?"}'
+
+# Retrieval
+curl -X POST http://localhost:8000/api/retrieve \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "Как открыть счет?",
+ "category": "Новые клиенты",
+ "subcategory": "Регистрация и онбординг",
+ "top_k": 5
+ }'
+```
+
+---
+
+## Docker Deployment
+
+### Build and Run
+
+```bash
+# From project root
+
+# Build Docker image (includes backend + frontend production build)
+docker build -t smart-support-ui:latest -f Dockerfile.ui .
+
+# Run with docker-compose
+docker-compose up operator-ui
+
+# Expected output:
+# operator-ui_1 | INFO: Uvicorn running on http://0.0.0.0:8000
+# operator-ui_1 | INFO: Application startup complete.
+
+# Access at http://localhost:8080
+```
+
+### Stop Services
+
+```bash
+docker-compose down
+```
+
+---
+
+## Project Structure (After Setup)
+
+```
+smart-support/
+├── backend/
+│ ├── src/
+│ │ ├── api/
+│ │ │ ├── __init__.py
+│ │ │ ├── main.py # FastAPI app instance
+│ │ │ ├── routes/
+│ │ │ │ ├── __init__.py
+│ │ │ │ ├── classification.py # POST /api/classify
+│ │ │ │ └── retrieval.py # POST /api/retrieve
+│ │ │ ├── models.py # Pydantic request/response models
+│ │ │ └── middleware.py # CORS, logging, error handling
+│ │ └── __init__.py
+│ ├── tests/
+│ │ ├── integration/
+│ │ │ ├── test_classification_api.py
+│ │ │ ├── test_retrieval_api.py
+│ │ │ └── test_full_workflow.py
+│ │ └── unit/
+│ │ └── test_api_models.py
+│ └── requirements.txt
+│
+├── frontend/
+│ ├── src/
+│ │ ├── components/
+│ │ │ ├── InquiryInput.tsx
+│ │ │ ├── ClassificationDisplay.tsx
+│ │ │ ├── TemplateList.tsx
+│ │ │ ├── TemplateCard.tsx
+│ │ │ ├── LoadingSpinner.tsx
+│ │ │ ├── ErrorMessage.tsx
+│ │ │ └── ConfidenceBadge.tsx
+│ │ ├── services/
+│ │ │ ├── api.ts
+│ │ │ ├── classification.ts
+│ │ │ └── retrieval.ts
+│ │ ├── types/
+│ │ │ ├── classification.ts
+│ │ │ └── retrieval.ts
+│ │ ├── hooks/
+│ │ │ └── useClipboard.ts
+│ │ ├── App.tsx
+│ │ ├── index.tsx
+│ │ └── index.css
+│ ├── package.json
+│ ├── tsconfig.json
+│ ├── tailwind.config.js
+│ └── vite.config.ts
+│
+├── tests/
+│ └── e2e/
+│ ├── test_user_story_1.py
+│ ├── test_user_story_2.py
+│ └── test_edge_cases.py
+│
+├── specs/004-smart-support-operator/
+│ ├── spec.md
+│ ├── plan.md
+│ ├── research.md
+│ ├── data-model.md
+│ ├── quickstart.md # This file
+│ └── contracts/
+│ ├── classification-api.yaml
+│ └── retrieval-api.yaml
+│
+└── docker-compose.yml # Updated with operator-ui service
+```
+
+---
+
+## Development Workflow
+
+### 1. Implement a Feature
+
+```bash
+# Example: Add classification endpoint
+
+# 1. Write API contract test first (TDD)
+code backend/tests/integration/test_classification_api.py
+
+# 2. Implement endpoint
+code backend/src/api/routes/classification.py
+
+# 3. Run test to verify
+pytest backend/tests/integration/test_classification_api.py -v
+
+# 4. Commit when green
+git add backend/src/api/routes/classification.py backend/tests/integration/test_classification_api.py
+git commit -m "Add classification endpoint (FR-002)"
+```
+
+### 2. Frontend Component Development
+
+```bash
+# Example: Add InquiryInput component
+
+# 1. Create component file
+code frontend/src/components/InquiryInput.tsx
+
+# 2. Write component test
+code frontend/src/components/__tests__/InquiryInput.test.tsx
+
+# 3. Run test
+cd frontend && npm test InquiryInput
+
+# 4. Commit when complete
+git add frontend/src/components/InquiryInput.tsx frontend/src/components/__tests__/InquiryInput.test.tsx
+git commit -m "Add InquiryInput component (FR-001)"
+```
+
+### 3. Integration Testing
+
+```bash
+# After implementing backend endpoint + frontend component
+
+# 1. Start backend + frontend servers
+# Terminal 1: cd backend && uvicorn src.api.main:app --reload
+# Terminal 2: cd frontend && npm run dev
+
+# 2. Manual smoke test in browser (http://localhost:5173)
+
+# 3. Write E2E test
+code tests/e2e/test_user_story_1.py
+
+# 4. Run E2E test
+pytest tests/e2e/test_user_story_1.py -v
+
+# 5. Commit when passing
+git add tests/e2e/test_user_story_1.py
+git commit -m "Add E2E test for user story 1 (P1)"
+```
+
+---
+
+## Troubleshooting
+
+### Backend Issues
+
+**Problem**: `ImportError: No module named 'fastapi'`
+**Solution**: Activate venv and reinstall dependencies
+```bash
+pip install -r backend/requirements.txt
+```
+
+**Problem**: `Classification service unavailable`
+**Solution**: Verify SCIBOX_API_KEY is set
+```bash
+echo $SCIBOX_API_KEY # Should show API key
+# If empty, load .env:
+export $(grep -v '^#' .env | xargs)
+```
+
+**Problem**: `FileNotFoundError: data/embeddings.db`
+**Solution**: Populate embeddings database
+```bash
+./scripts/populate_database.sh --force
+```
+
+### Frontend Issues
+
+**Problem**: `Cannot find module 'axios'`
+**Solution**: Install dependencies
+```bash
+cd frontend && npm install
+```
+
+**Problem**: CORS error in browser console
+**Solution**: Ensure backend CORS middleware is configured
+```bash
+# backend/src/api/main.py should include:
+# app.add_middleware(CORSMiddleware, allow_origins=["http://localhost:5173"], ...)
+```
+
+**Problem**: Tailwind styles not applying
+**Solution**: Verify Tailwind configured correctly
+```bash
+# Check frontend/src/index.css has @tailwind directives
+# Check frontend/tailwind.config.js content paths
+```
+
+### Docker Issues
+
+**Problem**: `docker-compose up operator-ui` fails
+**Solution**: Build image first
+```bash
+docker build -t smart-support-ui:latest -f Dockerfile.ui .
+```
+
+**Problem**: Port 8080 already in use
+**Solution**: Stop conflicting service or change port
+```bash
+# Option 1: Find and kill process
+lsof -ti:8080 | xargs kill
+
+# Option 2: Change port in docker-compose.yml
+# ports:
+# - "8081:8000" # Use 8081 instead
+```
+
+---
+
+## Performance Validation
+
+### Check Classification Response Time
+
+```bash
+# Measure classification latency (should be <2000ms)
+time curl -X POST http://localhost:8000/api/classify \
+ -H "Content-Type: application/json" \
+ -d '{"inquiry": "Как открыть счет?"}'
+
+# Expected:
+# {"inquiry":"Как открыть счет?","category":"...","processing_time_ms":1247,...}
+# real 0m1.300s # <2s ✅
+```
+
+### Check Retrieval Response Time
+
+```bash
+# Measure retrieval latency (should be <1000ms)
+time curl -X POST http://localhost:8000/api/retrieve \
+ -H "Content-Type: application/json" \
+ -d '{"query":"Как открыть счет?","category":"Новые клиенты","subcategory":"Регистрация и онбординг","top_k":5}'
+
+# Expected:
+# {"query":"...","results":[...],"processing_time_ms":487.3,...}
+# real 0m0.550s # <1s ✅
+```
+
+### Check Full Workflow Time
+
+```bash
+# Use E2E test with timing
+pytest tests/e2e/test_user_story_1.py::test_full_workflow_under_10_seconds -v
+
+# Should report: Full workflow completed in 3.2s ✅ (target: <10s)
+```
+
+---
+
+## Next Steps
+
+After completing setup:
+
+1. **Read [tasks.md](./tasks.md)** (generated by `/speckit.tasks`) for step-by-step implementation
+2. **Review [data-model.md](./data-model.md)** for entity definitions and validation rules
+3. **Review [contracts/](./contracts/)** for OpenAPI specifications
+4. **Start with backend** (FastAPI endpoints) before frontend
+5. **Follow TDD** (tests first, then implementation)
+6. **Commit frequently** (one feature = one commit)
+
+---
+
+## Resources
+
+- **FastAPI Documentation**: https://fastapi.tiangolo.com/
+- **React Documentation**: https://react.dev/
+- **Tailwind CSS**: https://tailwindcss.com/docs
+- **React Query**: https://tanstack.com/query/latest
+- **Vite**: https://vitejs.dev/
+- **pytest**: https://docs.pytest.org/
+- **testcontainers**: https://testcontainers-python.readthedocs.io/
+- **Chrome DevTools MCP**: (MCP tool documentation)
+
+---
+
+## Constitution Compliance Checklist
+
+Before pushing code, verify:
+
+- ✅ **Principle I**: Backend doesn't modify existing `src/classification/` or `src/retrieval/` modules
+- ✅ **Principle II**: All user-facing messages are actionable (no technical jargon)
+- ✅ **Principle III**: Integration tests use testcontainers, E2E tests use Chrome DevTools MCP
+- ✅ **Principle IV**: API contracts match OpenAPI specs in `contracts/`
+- ✅ **Principle V**: Docker deployment works with `docker-compose up operator-ui`
+- ✅ **Principle VI**: No changes to `docs/smart_support_vtb_belarus_faq_final.xlsx`
+
+---
+
+**Ready to start implementation!** 🚀
+
+Next command: `/speckit.tasks` (generate task breakdown from this plan)
diff --git a/specs/004-smart-support-operator/research.md b/specs/004-smart-support-operator/research.md
new file mode 100644
index 0000000..bf77292
--- /dev/null
+++ b/specs/004-smart-support-operator/research.md
@@ -0,0 +1,428 @@
+# Phase 0: Technical Research - Operator Web Interface
+
+**Feature**: Operator Web Interface
+**Branch**: `004-smart-support-operator`
+**Date**: 2025-10-15
+
+## Purpose
+
+This document resolves technical ambiguities and makes concrete technology decisions for implementing the operator web interface. All research is driven by constitution principles and specification requirements.
+
+## Research Questions & Decisions
+
+### RQ-001: Frontend Framework Selection
+
+**Question**: Should we use React with TypeScript or an alternative framework?
+
+**Options Evaluated**:
+1. React 18 + TypeScript + Vite
+2. Vue 3 + TypeScript
+3. Svelte + TypeScript
+4. Plain JavaScript (no framework)
+
+**Decision**: ✅ **React 18 + TypeScript + Vite**
+
+**Rationale**:
+- **Developer Familiarity**: Most widely adopted framework (simplifies handoff after hackathon)
+- **TypeScript Integration**: First-class TypeScript support with type-safe API client generation from Pydantic models
+- **Vite**: Ultra-fast dev server (<100ms HMR) meets UI response requirement (FR-017: responsive during processing)
+- **Component Ecosystem**: Rich component libraries (Material-UI, Headless UI) accelerate development
+- **Testing**: React Testing Library + Jest well-established for unit/component tests
+
+**Constitution Alignment**: Principle II (User-Centric Design) - React's component model enables reusable UI elements like ConfidenceBadge, TemplateCard for consistent visual language.
+
+---
+
+### RQ-002: UI Component Library Selection
+
+**Question**: Should we use Tailwind CSS, Material-UI, or build custom components?
+
+**Options Evaluated**:
+1. Tailwind CSS + Headless UI
+2. Material-UI (MUI)
+3. Ant Design
+4. Custom CSS (no library)
+
+**Decision**: ✅ **Tailwind CSS + Headless UI**
+
+**Rationale**:
+- **Rapid Development**: Utility-first CSS enables faster iteration for hackathon timeline
+- **Small Bundle Size**: ~10KB gzipped (vs MUI ~300KB) - faster initial load meets performance goals
+- **Customization**: No opinionated design system - easier to achieve "professional appearance" (SC-010: ≥16/20 UI/UX points)
+- **Headless UI**: Accessible components (focus management, ARIA) with full visual control
+- **No JavaScript Overhead**: Pure CSS utilities don't impact UI response time (FR-017)
+
+**Alternative Considered**: Material-UI rejected because predefined design system may not align with "professional banking interface" aesthetic expectations, and larger bundle size risks slower initial load.
+
+**Constitution Alignment**: Principle V (Deployment Simplicity) - Tailwind's build step integrates cleanly into Vite without complex configuration.
+
+---
+
+### RQ-003: State Management Approach
+
+**Question**: Do we need Redux/Zustand or is React state sufficient?
+
+**Options Evaluated**:
+1. React useState + useContext
+2. Redux Toolkit
+3. Zustand
+4. React Query (server state) + useState (UI state)
+
+**Decision**: ✅ **React Query + useState (hybrid approach)**
+
+**Rationale**:
+- **Specification Analysis**: State needs are minimal:
+ - **Server State**: Classification result, retrieval results (React Query caches and invalidates)
+ - **UI State**: Current inquiry text, edit mode, loading states (simple useState)
+- **No Global State Needed**: Single-operator interface (A-003: one inquiry at a time) doesn't require complex state sharing
+- **React Query Benefits**:
+ - Automatic caching of API responses
+ - Built-in loading/error states (simplifies FR-012: loading indicators)
+ - Request deduplication (prevents double-submission during loading)
+- **Simplicity**: useState for local component state keeps codebase simple for hackathon demo
+
+**Constitution Alignment**: Principle I (Modular Architecture) - React Query cleanly separates server state management from UI logic.
+
+---
+
+### RQ-004: Backend API Framework
+
+**Question**: Confirm FastAPI is appropriate or consider alternatives?
+
+**Options Evaluated**:
+1. FastAPI
+2. Flask + Flask-RESTX
+3. Django REST Framework
+
+**Decision**: ✅ **FastAPI**
+
+**Rationale**:
+- **Existing Ecosystem**: Project already uses Pydantic models in `src/classification/models.py` and `src/retrieval/models.py`
+- **Type Safety**: FastAPI auto-validates requests against Pydantic models (prevents invalid input reaching classification module)
+- **Performance**: ASGI async support enables concurrent request handling
+- **Auto-Documentation**: Automatic OpenAPI spec generation (satisfies contracts/ requirement)
+- **CORS Support**: Built-in middleware for frontend-backend communication
+- **Minimal Boilerplate**: Faster to implement than Django for simple REST API
+
+**Constitution Alignment**: Principle IV (API-First Integration) - FastAPI's OpenAPI generation ensures clear contract between frontend and backend.
+
+---
+
+### RQ-005: API Contract Design
+
+**Question**: What REST endpoints are needed to satisfy functional requirements?
+
+**Decision**: ✅ **Two primary endpoints + one health check**
+
+**Endpoint Specifications**:
+
+**1. Classification Endpoint**
+```
+POST /api/classify
+Request: {"inquiry": "Как открыть счет?"}
+Response: {
+ "inquiry": "Как открыть счет?",
+ "category": "Новые клиенты",
+ "subcategory": "Регистрация и онбординг",
+ "confidence": 0.92,
+ "processing_time_ms": 1247,
+ "timestamp": "2025-10-15T10:30:45Z"
+}
+```
+- **Requirement Mapping**: FR-001 (accept text), FR-002 (display classification), FR-015 (<2s)
+- **Error Cases**: 400 (validation), 503 (service unavailable - FR-019)
+
+**2. Retrieval Endpoint**
+```
+POST /api/retrieve
+Request: {
+ "query": "Как открыть счет?",
+ "category": "Новые клиенты",
+ "subcategory": "Регистрация и онбординг",
+ "classification_confidence": 0.92,
+ "top_k": 5
+}
+Response: {
+ "query": "Как открыть счет?",
+ "category": "Новые клиенты",
+ "subcategory": "Регистрация и онбординг",
+ "results": [
+ {
+ "template_id": "tmpl_001",
+ "template_question": "Как зарегистрироваться в банке?",
+ "template_answer": "Для регистрации вам потребуется...",
+ "category": "Новые клиенты",
+ "subcategory": "Регистрация и онбординг",
+ "similarity_score": 0.892,
+ "combined_score": 0.892,
+ "rank": 1
+ }
+ ],
+ "total_candidates": 12,
+ "processing_time_ms": 487.3,
+ "timestamp": "2025-10-15T10:30:46Z",
+ "warnings": []
+}
+```
+- **Requirement Mapping**: FR-003 (auto-retrieve), FR-004 (ranked), FR-005 (top 5), FR-016 (<1s)
+- **Error Cases**: 400 (invalid category), 503 (service unavailable - FR-020)
+
+**3. Health Check Endpoint**
+```
+GET /api/health
+Response: {
+ "status": "healthy",
+ "classification_available": true,
+ "retrieval_available": true,
+ "embeddings_count": 201
+}
+```
+- **Requirement Mapping**: FR-019, FR-020 (service availability detection)
+
+**Constitution Alignment**: Principle IV (API-First Integration) - Endpoints mirror existing module interfaces (`Classifier.classify()`, `TemplateRetriever.retrieve()`) for clean integration.
+
+---
+
+### RQ-006: Frontend-Backend Communication
+
+**Question**: How should frontend call backend APIs (polling, SSE, WebSockets, HTTP)?
+
+**Decision**: ✅ **Simple HTTP REST (Axios)**
+
+**Rationale**:
+- **Specification Analysis**: No real-time requirements or progressive updates
+- **Workflow**: Linear request-response pattern (submit → classify → retrieve → display)
+- **Simplicity**: HTTP sufficient for <10s full workflow (SC-001)
+- **Axios Benefits**:
+ - Request/response interceptors for error handling (FR-019, FR-020, FR-021)
+ - Automatic JSON serialization
+ - TypeScript-friendly type definitions
+ - Timeout configuration (detect slow APIs)
+
+**Rejected Alternatives**:
+- **WebSockets**: Overkill for request-response pattern
+- **Server-Sent Events**: No need for server-initiated updates
+- **Polling**: No background data changes to monitor
+
+**Constitution Alignment**: Principle II (User-Centric Design) - Simple HTTP keeps latency predictable (no handshake overhead), supporting <2s classification requirement.
+
+---
+
+### RQ-007: Error Handling Strategy
+
+**Question**: How should we handle classification/retrieval service failures (FR-019, FR-020, FR-021)?
+
+**Decision**: ✅ **Graceful degradation with user-actionable messages**
+
+**Strategy**:
+
+**1. Network Timeouts** (FR-021)
+- Frontend timeout: 5s classification, 3s retrieval
+- Backend timeout: Matches module defaults (1.8s classification, 1.0s retrieval)
+- User message: "The classification service is taking longer than expected. Please try again."
+
+**2. Service Unavailable** (FR-019, FR-020)
+- Backend catches module exceptions → HTTP 503
+- User message: "Unable to connect to [classification/retrieval] service. Please check your connection and try again."
+
+**3. Validation Errors** (FR-018, FR-022)
+- Frontend pre-validation: Minimum 5 characters, Cyrillic detection
+- Backend validation: Pydantic models enforce constraints
+- User message: "Please enter your inquiry in Russian (at least 5 characters)."
+
+**4. Unknown Errors**
+- Backend logs stack trace, returns generic 500
+- User message: "An unexpected error occurred. Please try again or contact support."
+
+**Constitution Alignment**: Principle II (User-Centric Design) - All error messages provide actionable guidance (FR-022) rather than technical details.
+
+---
+
+### RQ-008: Testing Strategy
+
+**Question**: How to implement integration tests with testcontainers and E2E tests with Chrome DevTools MCP?
+
+**Decision**: ✅ **Three-layer testing pyramid**
+
+**Layer 1: Unit Tests** (Fast, isolated)
+- **Backend**: `tests/unit/test_api_models.py` - Pydantic validation logic
+- **Frontend**: Jest + React Testing Library - Component rendering, user interactions
+- **Execution**: `pytest tests/unit/` (backend), `npm test` (frontend)
+
+**Layer 2: Integration Tests** (testcontainers - real SQLite)
+- **File**: `backend/tests/integration/test_classification_api.py`
+- **Setup**: testcontainers spins up API server + mounts `data/embeddings.db`
+- **Tests**:
+ - `test_classify_endpoint_returns_valid_classification()` - FR-002 validation
+ - `test_classify_endpoint_performance()` - FR-015 (<2s requirement)
+ - `test_classify_endpoint_validation_errors()` - FR-018 validation
+- **File**: `backend/tests/integration/test_retrieval_api.py`
+- **Tests**:
+ - `test_retrieve_endpoint_returns_ranked_templates()` - FR-004, FR-005
+ - `test_retrieve_endpoint_performance()` - FR-016 (<1s requirement)
+- **File**: `backend/tests/integration/test_full_workflow.py`
+- **Tests**:
+ - `test_full_operator_workflow()` - SC-001 (<10s full workflow)
+- **Execution**: `pytest tests/integration/ -m integration`
+
+**Layer 3: E2E Tests** (Chrome DevTools MCP - real browser)
+- **File**: `tests/e2e/test_user_story_1.py`
+- **Test**: Complete P1 user story (FR-001 through FR-005)
+ ```python
+ @pytest.mark.e2e
+ def test_inquiry_analysis_and_template_retrieval():
+ # Given operator has received inquiry
+ page.navigate("http://localhost:3000")
+
+ # When they enter inquiry text
+ page.fill("textarea[data-testid='inquiry-input']", "Как открыть счет?")
+ page.click("button[data-testid='submit-button']")
+
+ # Then classification displays within 2 seconds
+ start = time.time()
+ page.wait_for("div[data-testid='classification-result']", timeout=2000)
+ assert time.time() - start < 2.0
+
+ # And templates display within 1 second
+ start = time.time()
+ page.wait_for("div[data-testid='template-list']", timeout=1000)
+ assert time.time() - start < 1.0
+
+ # And top 5 templates shown with scores
+ templates = page.query_all("div[data-testid='template-card']")
+ assert len(templates) == 5
+ ```
+- **Execution**: `pytest tests/e2e/ -m e2e`
+
+**Constitution Alignment**: Principle III (Data-Driven Validation) - Three-layer approach provides fast feedback (unit), contract verification (integration), and user scenario validation (E2E).
+
+---
+
+### RQ-009: Copy-to-Clipboard Implementation
+
+**Question**: How to implement FR-006 (one-click copy) across browsers?
+
+**Decision**: ✅ **Clipboard API with fallback**
+
+**Implementation**:
+```typescript
+// frontend/src/hooks/useClipboard.ts
+export const useClipboard = () => {
+ const [copied, setCopied] = useState(false);
+
+ const copyToClipboard = async (text: string) => {
+ try {
+ // Modern Clipboard API (Chrome, Firefox, Safari)
+ await navigator.clipboard.writeText(text);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ // Fallback for older browsers
+ const textarea = document.createElement('textarea');
+ textarea.value = text;
+ document.body.appendChild(textarea);
+ textarea.select();
+ document.execCommand('copy');
+ document.body.removeChild(textarea);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ }
+ };
+
+ return { copied, copyToClipboard };
+};
+```
+
+**Testing**: SC-015 (copy works across browsers) verified via Chrome DevTools MCP with different user agents.
+
+---
+
+### RQ-010: Response Editing Implementation
+
+**Question**: How to implement FR-007 (edit template text) and FR-009 (restore original)?
+
+**Decision**: ✅ **Controlled component with local state**
+
+**Implementation**:
+```typescript
+// frontend/src/components/TemplateCard.tsx
+const TemplateCard = ({ template }: { template: RetrievalResult }) => {
+ const [isEditing, setIsEditing] = useState(false);
+ const [editedAnswer, setEditedAnswer] = useState(template.template_answer);
+
+ const handleReset = () => {
+ setEditedAnswer(template.template_answer);
+ setIsEditing(false);
+ };
+
+ return (
+
+ {isEditing ? (
+
+ );
+};
+```
+
+**Rationale**: Keeps edited state local to component (no need to sync with backend). Original template preserved in props for FR-009 restore.
+
+---
+
+## Technology Stack Summary
+
+| Layer | Technology | Version | Rationale |
+|-------|-----------|---------|-----------|
+| **Frontend Framework** | React | 18.2+ | Developer familiarity, TypeScript support, rich ecosystem |
+| **Frontend Build Tool** | Vite | 5.0+ | Fast HMR, optimized production builds |
+| **Frontend Language** | TypeScript | 5.0+ | Type safety with Pydantic model mirroring |
+| **UI Styling** | Tailwind CSS | 3.4+ | Rapid development, small bundle, customization |
+| **UI Components** | Headless UI | 1.7+ | Accessible components with full visual control |
+| **Frontend State** | React Query + useState | 5.0+ | Server state caching + simple local state |
+| **HTTP Client** | Axios | 1.6+ | Request interceptors, timeout support, TypeScript |
+| **Frontend Testing** | Jest + React Testing Library | Latest | Component testing standard |
+| **Backend Framework** | FastAPI | 0.104+ | Pydantic integration, async, auto-docs |
+| **Backend Language** | Python | 3.11+ | Existing project standard |
+| **ASGI Server** | Uvicorn | 0.24+ | Production-ready ASGI server |
+| **Backend Testing** | pytest + testcontainers | Latest | Integration testing with real dependencies |
+| **E2E Testing** | Chrome DevTools MCP | Latest | Browser automation for user scenarios |
+| **Deployment** | Docker + docker-compose | Latest | Existing project infrastructure |
+
+---
+
+## Open Questions (for Phase 1)
+
+1. **Q**: Should we implement request rate limiting on backend?
+ - **Answer**: No - single operator assumption (A-003) makes rate limiting unnecessary for MVP
+
+2. **Q**: Do we need authentication for MVP?
+ - **Answer**: No - A-014 explicitly states no authentication for MVP
+
+3. **Q**: Should we cache classification/retrieval results in frontend?
+ - **Answer**: Yes - React Query automatically caches for 5 minutes, preventing redundant API calls during demo
+
+4. **Q**: Do we need logging/analytics for operator actions?
+ - **Answer**: No - OS-002 explicitly excludes response analytics
+
+---
+
+## Research Validation
+
+**All "NEEDS CLARIFICATION" markers resolved**: ✅
+
+**Constitution Compliance Re-Check**:
+- ✅ Principle I: Backend/frontend separation maintained
+- ✅ Principle II: All technology choices support <10s workflow
+- ✅ Principle III: Three-layer testing strategy defined
+- ✅ Principle IV: OpenAPI contracts auto-generated from Pydantic
+- ✅ Principle V: Docker integration planned
+- ✅ Principle VI: No FAQ database changes needed
+
+**Ready to Proceed to Phase 1**: ✅
diff --git a/specs/004-smart-support-operator/spec.md b/specs/004-smart-support-operator/spec.md
new file mode 100644
index 0000000..2ca4006
--- /dev/null
+++ b/specs/004-smart-support-operator/spec.md
@@ -0,0 +1,247 @@
+# Feature Specification: Operator Web Interface
+
+**Feature Branch**: `004-smart-support-operator`
+**Created**: 2025-10-15
+**Status**: Draft
+**Input**: User description: "Smart Support Operator Web Interface - A professional web application that provides support operators with an intuitive interface to classify customer inquiries and retrieve relevant template responses."
+
+## User Scenarios & Testing *(mandatory)*
+
+### User Story 1 - Inquiry Analysis and Template Retrieval (Priority: P1)
+
+As a support operator, I need to quickly analyze customer inquiries and retrieve relevant template responses so that I can respond to customers accurately and efficiently.
+
+**Why this priority**: This is the core workflow that delivers the primary business value - enabling operators to handle customer inquiries faster with higher accuracy. Without this, the system provides no value.
+
+**Independent Test**: Can be fully tested by entering a customer inquiry, receiving classification results, viewing ranked template responses, and successfully copying a response. Delivers immediate value by reducing response time.
+
+**Acceptance Scenarios**:
+
+1. **Given** an operator has received a customer inquiry, **When** they enter the inquiry text into the interface, **Then** the system displays the classification (category and subcategory) within 2 seconds
+2. **Given** an inquiry has been classified, **When** the classification completes, **Then** the system automatically displays top 5 ranked template responses within 1 second
+3. **Given** template responses are displayed, **When** the operator reviews the results, **Then** each template shows the question, answer, and similarity score with visual confidence indicators (high/medium/low)
+4. **Given** the operator has found a suitable template, **When** they click to copy the response, **Then** the answer text is copied to their clipboard for use in their communication tool
+
+---
+
+### User Story 2 - Response Customization (Priority: P2)
+
+As a support operator, I need to edit template responses before sending them so that I can personalize answers and adapt them to specific customer situations.
+
+**Why this priority**: While template responses provide good starting points, operators often need to customize them. This enhances response quality but isn't strictly required for basic functionality.
+
+**Independent Test**: Can be tested by selecting a template response, editing the text, and verifying the edited version can be copied. Delivers value by allowing response personalization.
+
+**Acceptance Scenarios**:
+
+1. **Given** a template response is displayed, **When** the operator selects it for editing, **Then** the answer text becomes editable in a text editor
+2. **Given** the operator has edited a response, **When** they save or copy it, **Then** the edited version is available for copying
+3. **Given** the operator is editing a response, **When** they want to revert changes, **Then** they can restore the original template text
+
+---
+
+### User Story 3 - Classification Confidence Assessment (Priority: P2)
+
+As a support operator, I need to see confidence scores for classifications and template matches so that I can make informed decisions about which responses to use.
+
+**Why this priority**: Helps operators understand system confidence and make better judgments, but the system can function without it. Improves decision quality rather than enabling core functionality.
+
+**Independent Test**: Can be tested by analyzing various inquiries and verifying confidence scores are displayed with appropriate visual indicators. Delivers value by improving operator decision-making.
+
+**Acceptance Scenarios**:
+
+1. **Given** an inquiry has been classified, **When** results are displayed, **Then** the classification confidence score is shown as a percentage with a visual indicator (green >80%, yellow 60-80%, red <60%)
+2. **Given** template responses are ranked, **When** results are displayed, **Then** each template shows its similarity score with corresponding visual indicators
+3. **Given** confidence is low (<60%), **When** results are displayed, **Then** the operator sees a visual warning suggesting manual review
+
+---
+
+### User Story 4 - Error Recovery and System Feedback (Priority: P3)
+
+As a support operator, I need clear feedback when the system encounters errors so that I can take appropriate action and continue working.
+
+**Why this priority**: Error handling is important for production use but not critical for demonstrating core functionality. Can be basic for MVP and enhanced later.
+
+**Independent Test**: Can be tested by simulating various error conditions and verifying operators receive clear, actionable feedback. Delivers value by preventing operator confusion during issues.
+
+**Acceptance Scenarios**:
+
+1. **Given** the system cannot connect to classification service, **When** an error occurs, **Then** the operator sees a user-friendly message explaining the issue and suggested next steps
+2. **Given** an inquiry is being processed, **When** the classification takes longer than expected, **Then** the operator sees a loading indicator showing work in progress
+3. **Given** the system receives an invalid inquiry (e.g., non-Russian text), **When** validation fails, **Then** the operator sees a clear explanation of what needs to be corrected
+
+---
+
+### User Story 5 - Performance Monitoring (Priority: P3)
+
+As a support operator, I want to see processing times for classification and retrieval so that I can understand system performance and manage customer expectations.
+
+**Why this priority**: Provides transparency but isn't essential for core functionality. Helps with performance awareness but doesn't block primary workflow.
+
+**Independent Test**: Can be tested by processing inquiries and verifying processing time metrics are displayed accurately. Delivers value by setting appropriate operator expectations.
+
+**Acceptance Scenarios**:
+
+1. **Given** an inquiry has been processed, **When** results are displayed, **Then** the classification processing time is shown (e.g., "Classified in 1.2s")
+2. **Given** templates have been retrieved, **When** results are displayed, **Then** the retrieval processing time is shown (e.g., "Retrieved in 0.5s")
+3. **Given** processing times exceed thresholds (>2s classification or >1s retrieval), **When** results are displayed, **Then** the time is highlighted as slow
+
+---
+
+### Edge Cases
+
+- **Empty inquiry**: What happens when operator submits empty text or very short inquiry (<5 words)?
+- **Non-Russian text**: How does system handle inquiries in languages other than Russian?
+- **Ambiguous classification**: What happens when confidence is very low (<40%) across all categories?
+- **No template matches**: How does system respond when no relevant templates are found for a category?
+- **Concurrent operations**: What happens if operator submits a new inquiry while previous one is still processing?
+- **Network timeout**: How does system handle slow or failed API responses?
+- **Very long inquiries**: How does system handle customer complaints or inquiries exceeding 1000 words?
+- **Special characters**: How are inquiries with formatting, emojis, or code snippets handled?
+
+## Requirements *(mandatory)*
+
+### Functional Requirements
+
+#### Core Workflow
+- **FR-001**: System MUST accept customer inquiry text input with minimum 5 characters and maximum 5000 characters
+- **FR-002**: System MUST display classification results showing category, subcategory, and confidence score
+- **FR-003**: System MUST automatically retrieve and display template responses after classification completes
+- **FR-004**: System MUST rank template responses by relevance (similarity score) from highest to lowest
+- **FR-005**: System MUST display top 5 template responses with question, answer, and similarity score for each
+
+#### User Interactions
+- **FR-006**: Operators MUST be able to copy any template answer text to clipboard with a single action
+- **FR-007**: Operators MUST be able to edit template answer text before copying
+- **FR-008**: Operators MUST be able to submit a new inquiry at any time (clearing previous results)
+- **FR-009**: System MUST provide a way to restore original template text after editing
+
+#### Visual Feedback
+- **FR-010**: System MUST display visual confidence indicators for classification scores (high: >80%, medium: 60-80%, low: <60%)
+- **FR-011**: System MUST display visual similarity indicators for each template response using the same thresholds
+- **FR-012**: System MUST show loading state while classification and retrieval are in progress
+- **FR-013**: System MUST display processing time for classification operation
+- **FR-014**: System MUST display processing time for retrieval operation
+
+#### Performance
+- **FR-015**: System MUST complete inquiry classification within 2 seconds from submission
+- **FR-016**: System MUST complete template retrieval within 1 second after classification
+- **FR-017**: System MUST remain responsive during processing (no UI freezing)
+
+#### Error Handling
+- **FR-018**: System MUST validate inquiry text before submission (minimum length, Russian language)
+- **FR-019**: System MUST display user-friendly error messages when classification service is unavailable
+- **FR-020**: System MUST display user-friendly error messages when retrieval service is unavailable
+- **FR-021**: System MUST handle network timeouts gracefully with clear user messaging
+- **FR-022**: System MUST provide actionable guidance when validation fails (e.g., "Please enter inquiry in Russian")
+
+#### User Experience
+- **FR-023**: Interface MUST be designed for desktop use with appropriate layout and spacing
+- **FR-024**: System MUST provide visual distinction between different confidence levels using color coding
+- **FR-025**: System MUST highlight the highest-ranked template response as the primary recommendation
+- **FR-026**: System MUST maintain inquiry text in the input field after submission for reference
+
+### Key Entities
+
+- **Customer Inquiry**: Text input from customer that needs to be analyzed; contains the question or problem description that operators need to address
+- **Classification Result**: Category and subcategory assignment with confidence score; represents the system's understanding of the inquiry type
+- **Template Response**: Pre-defined Q&A pair with similarity score; includes the template question, answer text, category, subcategory, and relevance score
+- **Confidence Score**: Numerical measure (0-1 or percentage) indicating system certainty; used for both classification confidence and template similarity
+- **Processing Metrics**: Time measurements for classification and retrieval operations; helps operators understand system performance
+
+## Success Criteria *(mandatory)*
+
+### Measurable Outcomes
+
+#### Performance Metrics
+- **SC-001**: Operators can process a customer inquiry from input to copied response in under 10 seconds
+- **SC-002**: Classification results are displayed within 2 seconds of inquiry submission for 95% of requests
+- **SC-003**: Template retrieval results are displayed within 1 second of classification completion for 95% of requests
+- **SC-004**: System maintains responsiveness (no UI freezing) during all operations
+
+#### Accuracy & Quality
+- **SC-005**: System displays classification confidence scores that accurately reflect prediction quality (validated against existing classification module metrics)
+- **SC-006**: Template ranking places the most relevant response in top 3 positions for 90% of inquiries (validated against existing retrieval module metrics)
+- **SC-007**: Visual confidence indicators correctly categorize scores (high/medium/low) according to defined thresholds
+
+#### Usability
+- **SC-008**: Operators can successfully complete the full workflow (input → classify → retrieve → copy) on first attempt without training
+- **SC-009**: All error messages are actionable and help operators understand what to do next
+- **SC-010**: Interface scores at least 16/20 points on hackathon UI/UX evaluation criteria
+
+#### Operator Efficiency
+- **SC-011**: Average time to find and copy a suitable response is reduced by 60% compared to manual FAQ search
+- **SC-012**: Operators can handle 40% more customer inquiries per hour using the interface
+- **SC-013**: 90% of operators report the interface is faster than previous methods
+
+#### System Reliability
+- **SC-014**: System gracefully handles 100% of error conditions without crashing or requiring page reload
+- **SC-015**: Copy-to-clipboard functionality works consistently across all supported browsers
+- **SC-016**: System correctly validates and rejects invalid inputs (empty, non-Russian, too short) in all test cases
+
+### Validation Approach
+
+- Measure processing times across 50+ sample inquiries covering all categories
+- Conduct usability testing with 3-5 support operators using realistic scenarios
+- Compare operator efficiency metrics before and after system introduction
+- Validate against existing classification (90% accuracy) and retrieval (93% top-3 accuracy) module benchmarks
+- Test error handling with simulated service failures and invalid inputs
+
+## Assumptions *(optional)*
+
+### Workflow Assumptions
+- **A-001**: Operators use the interface as a tool to prepare responses, not as a complete customer communication system
+- **A-002**: Operators will copy responses to external communication tools (email, chat, ticketing system) rather than sending directly
+- **A-003**: One inquiry is processed at a time; operators complete one inquiry before starting another
+- **A-004**: Operators work at desktop computers with standard screen sizes (1920x1080 or larger)
+
+### Integration Assumptions
+- **A-005**: Existing classification module API is available and functional (90% accuracy, <2s response time)
+- **A-006**: Existing retrieval module API is available and functional (93% top-3 accuracy, <1s response time)
+- **A-007**: FAQ database is already populated with 201 embeddings in persistent storage
+- **A-008**: System operates within the same infrastructure as existing modules (no cross-datacenter latency)
+
+### User Assumptions
+- **A-009**: Operators understand Russian language and can evaluate template quality
+- **A-010**: Operators have basic computer literacy (typing, copy/paste, basic editing)
+- **A-011**: Operators can make judgment calls about which template best fits the customer inquiry
+
+### Scope Assumptions
+- **A-012**: System does not track response history or analytics (future enhancement)
+- **A-013**: System does not support multi-user collaboration or shared inquiries
+- **A-014**: System does not require authentication for MVP (focus on functionality demonstration)
+- **A-015**: System does not need to handle attachments, images, or multimedia inquiries
+
+## Dependencies *(optional)*
+
+### Internal Dependencies
+- **D-001**: Classification Module - Provides category/subcategory classification with confidence scores; must be operational
+- **D-002**: Retrieval Module - Provides ranked template responses with similarity scores; must be operational
+- **D-003**: FAQ Database - Pre-populated with 201 template embeddings; must be accessible
+- **D-004**: Scibox API - Underlying service for classification and embeddings; must be available
+
+### External Dependencies
+- **D-005**: Operator's clipboard functionality - Required for copy-to-clipboard feature
+- **D-006**: Network connectivity - Required for communication with classification and retrieval services
+- **D-007**: Modern web browser - Chrome, Firefox, Safari, or Edge with JavaScript enabled
+
+## Out of Scope *(optional)*
+
+### Explicitly Excluded Features
+- **OS-001**: Direct email/chat integration - Operators use external tools for actual customer communication
+- **OS-002**: Response analytics and tracking - No logging of which responses were used or sent
+- **OS-003**: Multi-user features - No shared workspaces, no simultaneous editing, no operator collaboration
+- **OS-004**: Authentication and authorization - No user login, no role-based access control
+- **OS-005**: Admin interface - No system configuration, no FAQ management, no user management
+- **OS-006**: Response templates editing - Operators can edit copies but cannot modify original templates in database
+- **OS-007**: Inquiry history and search - No ability to view or search past inquiries
+- **OS-008**: Mobile or tablet interfaces - Desktop-only for MVP
+- **OS-009**: Internationalization - Russian language only for MVP
+- **OS-010**: Advanced editing features - No rich text formatting, no attachments, basic text editing only
+
+### Future Enhancements
+- Response effectiveness tracking (which templates were most helpful)
+- Operator feedback mechanism (thumbs up/down on classifications)
+- Batch processing of multiple inquiries
+- Saved/favorite templates for quick access
+- Integration with ticketing systems (Zendesk, Jira Service Desk, etc.)
diff --git a/specs/004-smart-support-operator/tasks.md b/specs/004-smart-support-operator/tasks.md
new file mode 100644
index 0000000..d9ca3ed
--- /dev/null
+++ b/specs/004-smart-support-operator/tasks.md
@@ -0,0 +1,603 @@
+# Tasks: Operator Web Interface
+
+**Input**: Design documents from `/specs/004-smart-support-operator/`
+**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/
+
+**Tests**: Tests are included for integration validation and E2E scenarios as specified in the constitution (Principle III: Data-Driven Validation).
+
+**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
+
+## Format: `[ID] [P?] [Story] Description`
+- **[P]**: Can run in parallel (different files, no dependencies)
+- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3, US4, US5)
+- Include exact file paths in descriptions
+
+## Path Conventions
+- **Web app structure**: `backend/src/`, `frontend/src/`
+- Backend: `backend/src/api/` for FastAPI endpoints
+- Frontend: `frontend/src/components/` for React components
+- Tests: `backend/tests/integration/`, `tests/e2e/` for integration and E2E tests
+
+---
+
+## Phase 1: Setup (Shared Infrastructure)
+
+**Purpose**: Project initialization and basic structure
+
+- [ ] T001 Create backend directory structure: `backend/src/api/`, `backend/src/api/routes/`, `backend/tests/integration/`, `backend/tests/unit/`
+- [ ] T002 Create frontend directory structure using Vite: `npm create vite@latest frontend -- --template react-ts`
+- [ ] T003 [P] Install backend dependencies in `backend/requirements.txt` (FastAPI 0.104+, Uvicorn, Pydantic 2.x, pytest, httpx, testcontainers)
+- [ ] T004 [P] Install frontend dependencies in `frontend/`: axios, react-query, @headlessui/react, tailwindcss
+- [ ] T005 [P] Configure Tailwind CSS in `frontend/tailwind.config.js` and `frontend/src/index.css`
+- [ ] T006 [P] Create Python package files: `backend/src/__init__.py`, `backend/src/api/__init__.py`, `backend/src/api/routes/__init__.py`, `backend/tests/__init__.py`
+- [ ] T007 Configure git to ignore frontend node_modules and backend __pycache__ in `.gitignore`
+
+**Checkpoint**: Project structure initialized, dependencies installed
+
+---
+
+## Phase 2: Foundational (Blocking Prerequisites)
+
+**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
+
+**⚠️ CRITICAL**: No user story work can begin until this phase is complete
+
+- [ ] T008 Implement FastAPI application factory in `backend/src/api/main.py` with CORS middleware, error handlers, and app lifecycle
+- [ ] T009 [P] Create Pydantic request/response models in `backend/src/api/models.py` mirroring `ClassificationRequest`, `ClassificationResult`, `RetrievalRequest`, `RetrievalResponse`, `ErrorResponse`
+- [ ] T010 [P] Implement CORS and error handling middleware in `backend/src/api/middleware.py`
+- [ ] T011 [P] Create TypeScript type definitions in `frontend/src/types/classification.ts` and `frontend/src/types/retrieval.ts` matching backend Pydantic models
+- [ ] T012 [P] Setup Axios instance with base URL and interceptors in `frontend/src/services/api.ts`
+- [ ] T013 [P] Configure React Query provider in `frontend/src/main.tsx`
+- [ ] T014 [P] Create Vite proxy configuration in `frontend/vite.config.ts` to proxy `/api` to `http://localhost:8000`
+- [ ] T015 Implement health check endpoint GET `/api/health` in `backend/src/api/routes/health.py` checking classification and retrieval module availability
+
+**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
+
+---
+
+## Phase 3: User Story 1 - Inquiry Analysis and Template Retrieval (Priority: P1) 🎯 MVP
+
+**Goal**: Enable operators to submit Russian inquiries, receive classification with confidence scores, view top 5 ranked template responses, and copy answers to clipboard - delivering the complete core workflow in under 10 seconds.
+
+**Independent Test**: Submit inquiry "Как открыть накопительный счет?" → See classification "Счета и вклады / Открытие счета" with confidence → See 5 ranked templates → Click copy on top template → Verify text copied to clipboard. Full workflow completes in <10s.
+
+### Tests for User Story 1 (TDD Approach)
+
+**NOTE: Write these tests FIRST, ensure they FAIL before implementation**
+
+- [ ] T016 [P] [US1] Integration test for classification endpoint in `backend/tests/integration/test_classification_api.py`:
+ - Test POST `/api/classify` returns valid `ClassificationResult`
+ - Test validation rejects empty inquiry (<5 chars)
+ - Test validation rejects non-Russian text
+ - Test performance <2s (FR-015)
+ - Test service unavailable error handling (FR-019)
+
+- [ ] T017 [P] [US1] Integration test for retrieval endpoint in `backend/tests/integration/test_retrieval_api.py`:
+ - Test POST `/api/retrieve` returns ranked templates
+ - Test top_k=5 returns exactly 5 results (FR-005)
+ - Test results sorted by similarity_score descending (FR-004)
+ - Test performance <1s (FR-016)
+ - Test no templates found returns empty array with warning
+
+- [ ] T018 [P] [US1] Full workflow integration test in `backend/tests/integration/test_full_workflow.py`:
+ - Test full pipeline: classify → retrieve
+ - Test total time <3s for backend operations
+ - Test classification auto-triggers retrieval (FR-003)
+
+- [ ] T019 [P] [US1] E2E test for complete user story in `tests/e2e/test_user_story_1.py`:
+ - Test operator enters inquiry → sees classification within 2s
+ - Test templates display within 1s after classification
+ - Test 5 templates shown with question, answer, similarity score
+ - Test copy button copies answer text to clipboard
+ - Test full workflow <10s (SC-001)
+
+### Implementation for User Story 1
+
+**Backend API Endpoints**
+
+- [ ] T020 [US1] Implement POST `/api/classify` endpoint in `backend/src/api/routes/classification.py`:
+ - Accept `ClassificationRequest` with inquiry text
+ - Import and call existing `src.classification.classifier.get_classifier().classify()`
+ - Return `ClassificationResult` with category, subcategory, confidence, processing_time_ms
+ - Handle validation errors (400), service errors (503), timeouts (504)
+ - Add FR-018 validation: min 5 chars, Cyrillic required
+
+- [ ] T021 [US1] Implement POST `/api/retrieve` endpoint in `backend/src/api/routes/retrieval.py`:
+ - Accept `RetrievalRequest` with query, category, subcategory, top_k
+ - Import existing `src.retrieval.retriever.TemplateRetriever` and `src.retrieval.integration.initialize_retrieval_module()`
+ - Call `retriever.retrieve()` to get ranked templates
+ - Return `RetrievalResponse` with top-K results, total_candidates, processing_time_ms, warnings
+ - Handle no templates found (empty results with warning)
+ - Handle service errors (503), timeouts (504)
+
+**Frontend Components**
+
+- [ ] T022 [P] [US1] Create `InquiryInput.tsx` component in `frontend/src/components/InquiryInput.tsx`:
+ - Textarea input for inquiry text with 5-5000 character limits (FR-001)
+ - Submit button (disabled if <5 chars or no Cyrillic)
+ - Client-side validation with error display (FR-018)
+ - Loading state during classification (FR-012)
+ - Maintain inquiry text after submission (FR-026)
+
+- [ ] T023 [P] [US1] Create `ClassificationDisplay.tsx` component in `frontend/src/components/ClassificationDisplay.tsx`:
+ - Display category and subcategory
+ - Display confidence score as percentage
+ - Display processing time (FR-013)
+ - Visual layout with clear labels
+
+- [ ] T024 [P] [US1] Create `TemplateList.tsx` component in `frontend/src/components/TemplateList.tsx`:
+ - Display list of TemplateResult items
+ - Pass each template to TemplateCard component
+ - Show "No templates found" message if empty
+ - Display total_candidates and retrieval processing time (FR-014)
+
+- [ ] T025 [P] [US1] Create `TemplateCard.tsx` component in `frontend/src/components/TemplateCard.tsx`:
+ - Display template question, answer, similarity score
+ - Show rank number (1-5)
+ - Copy button with clipboard functionality (FR-006)
+ - Visual highlight for rank #1 template (FR-025)
+
+- [ ] T026 [P] [US1] Create `LoadingSpinner.tsx` component in `frontend/src/components/LoadingSpinner.tsx`:
+ - Animated spinner for classification and retrieval loading states (FR-012)
+ - Text indicator "Classifying..." or "Retrieving templates..."
+
+**Frontend Services**
+
+- [ ] T027 [P] [US1] Implement classification API client in `frontend/src/services/classification.ts`:
+ - `classify(inquiry: string): Promise`
+ - POST to `/api/classify` with axios
+ - Handle errors (400, 503, 504) and convert to user messages (FR-019, FR-021, FR-022)
+ - Set 5s timeout for classification API
+
+- [ ] T028 [P] [US1] Implement retrieval API client in `frontend/src/services/retrieval.ts`:
+ - `retrieve(request: RetrievalRequest): Promise`
+ - POST to `/api/retrieve` with axios
+ - Handle errors (400, 503, 504) and convert to user messages (FR-020, FR-021)
+ - Set 3s timeout for retrieval API
+
+- [ ] T029 [US1] Create `useClipboard` hook in `frontend/src/hooks/useClipboard.ts`:
+ - `copyToClipboard(text: string)` function using Clipboard API with fallback
+ - `copied` state with 2s auto-reset for visual feedback
+ - Cross-browser compatibility (Chrome, Firefox, Safari, Edge - SC-015)
+
+**Frontend Integration**
+
+- [ ] T030 [US1] Implement main App component in `frontend/src/App.tsx`:
+ - Import InquiryInput, ClassificationDisplay, TemplateList, LoadingSpinner
+ - Wire up state: `currentInquiry`, `classificationResult`, `retrievalResponse`, `isClassifying`, `isRetrieving`
+ - On inquiry submit: call classification API
+ - On classification success: auto-trigger retrieval API (FR-003)
+ - Handle loading states and errors
+ - Clear previous results when new inquiry submitted (FR-008)
+ - Tailwind CSS layout for desktop (FR-023)
+
+**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently. Operators can submit inquiries, see classifications, view ranked templates, and copy answers.
+
+---
+
+## Phase 4: User Story 2 - Response Customization (Priority: P2)
+
+**Goal**: Enable operators to edit template answer text before copying and restore original text if needed, allowing personalization without losing the original template.
+
+**Independent Test**: Complete US1 workflow → Click "Edit" on a template → Modify answer text → Click "Copy" → Verify edited version copied → Click "Restore" → Verify original text restored.
+
+### Tests for User Story 2
+
+- [ ] T031 [P] [US2] E2E test for response editing in `tests/e2e/test_user_story_2.py`:
+ - Test edit button makes answer editable
+ - Test edited text is saved when exiting edit mode
+ - Test copy button uses edited text
+ - Test restore button reverts to original
+ - Test edited state indicator shows modification
+
+### Implementation for User Story 2
+
+- [ ] T032 [US2] Enhance `TemplateCard.tsx` with editing functionality:
+ - Add `isEditing` local state
+ - Add `editedAnswer` state (initialized from template_answer)
+ - Add `original_answer` stored on mount (immutable - VR-020)
+ - Add "Edit" button to toggle edit mode
+ - Replace answer display with textarea when `isEditing=true`
+ - Add "Save" and "Cancel" buttons in edit mode
+ - Add "Restore Original" button (FR-009)
+ - Update copy button to use `editedAnswer` instead of `template_answer` (FR-007)
+ - Visual indicator when answer is modified (is_modified computed field)
+
+**Checkpoint**: User Stories 1 AND 2 should both work independently. Operators can now edit responses before copying while US1 core workflow remains intact.
+
+---
+
+## Phase 5: User Story 3 - Classification Confidence Assessment (Priority: P2)
+
+**Goal**: Display visual confidence indicators (high/medium/low) for classification scores and template similarity scores to help operators make informed decisions.
+
+**Independent Test**: Submit various inquiries → Verify classification confidence shows green (>80%), yellow (60-80%), or red (<60%) indicator → Verify each template shows color-coded similarity score → Verify low confidence (<60%) shows warning message.
+
+### Tests for User Story 3
+
+- [ ] T033 [P] [US3] E2E test for confidence indicators in `tests/e2e/test_user_story_3.py`:
+ - Test high confidence (>80%) shows green indicator
+ - Test medium confidence (60-80%) shows yellow indicator
+ - Test low confidence (<60%) shows red indicator
+ - Test template similarity scores have matching color coding
+ - Test low confidence displays warning message
+
+### Implementation for User Story 3
+
+- [ ] T034 [P] [US3] Create `ConfidenceBadge.tsx` component in `frontend/src/components/ConfidenceBadge.tsx`:
+ - Accept `score: number` (0.0-1.0) and `type: 'classification' | 'similarity'` props
+ - Compute confidence_level: "high" (≥0.8), "medium" (0.6-0.8), "low" (<0.6)
+ - Render colored badge with percentage text
+ - Colors: green (high), yellow (medium), red (low) - FR-010, FR-011, FR-024
+ - Tailwind CSS classes for visual distinction
+
+- [ ] T035 [US3] Integrate ConfidenceBadge into `ClassificationDisplay.tsx`:
+ - Import and render ConfidenceBadge with classification confidence score
+ - Display confidence as percentage with visual indicator (FR-010)
+
+- [ ] T036 [US3] Integrate ConfidenceBadge into `TemplateCard.tsx`:
+ - Import and render ConfidenceBadge with similarity_score
+ - Show similarity score for each template (FR-011)
+
+- [ ] T037 [US3] Add low confidence warning to `ClassificationDisplay.tsx`:
+ - If confidence <0.6, display warning message "Low confidence - manual review suggested" (FR-010)
+ - Use yellow/red alert styling
+
+- [ ] T038 [US3] Add low similarity warning to `TemplateList.tsx`:
+ - If all templates have combined_score <0.5, display warning from RetrievalResponse.warnings array
+ - Use alert styling for visibility
+
+**Checkpoint**: User Stories 1, 2, AND 3 should all work independently. Operators now have visual confidence indicators to aid decision-making while core workflows remain functional.
+
+---
+
+## Phase 6: User Story 4 - Error Recovery and System Feedback (Priority: P3)
+
+**Goal**: Provide clear, actionable error messages when classification or retrieval services fail, with loading indicators during processing and validation feedback for invalid inputs.
+
+**Independent Test**: Simulate classification service unavailable (stop backend) → Submit inquiry → Verify user-friendly error message "Unable to connect to classification service..." → Simulate invalid input (English text) → Verify validation error "Please enter inquiry in Russian" → Test loading spinner shows during processing.
+
+### Tests for User Story 4
+
+- [ ] T039 [P] [US4] E2E test for error handling in `tests/e2e/test_error_handling.py`:
+ - Test classification service unavailable shows user-friendly message (FR-019)
+ - Test retrieval service unavailable shows user-friendly message (FR-020)
+ - Test network timeout shows timeout message (FR-021)
+ - Test validation errors show actionable guidance (FR-022)
+ - Test loading spinner displays during processing (FR-012)
+
+### Implementation for User Story 4
+
+- [ ] T040 [P] [US4] Create `ErrorMessage.tsx` component in `frontend/src/components/ErrorMessage.tsx`:
+ - Accept `error: ErrorResponse | null` prop
+ - Display error message with red/orange alert styling
+ - Show error_type icon (validation: info, api_error: warning, timeout: clock, unknown: error)
+ - Display actionable message without technical details (VR-017)
+ - Dismiss button to clear error
+
+- [ ] T041 [US4] Enhance error handling in `frontend/src/services/classification.ts`:
+ - Map HTTP 400 → "Please enter inquiry in Russian (at least 5 characters)" (FR-018, FR-022)
+ - Map HTTP 503 → "Unable to connect to classification service. Please check your connection and try again." (FR-019)
+ - Map HTTP 504 → "The classification service is taking longer than expected. Please try again." (FR-021)
+ - Map other errors → "An unexpected error occurred. Please try again or contact support."
+ - Return ErrorResponse with error_type
+
+- [ ] T042 [US4] Enhance error handling in `frontend/src/services/retrieval.ts`:
+ - Map HTTP 503 → "Unable to connect to retrieval service. Please check your connection and try again." (FR-020)
+ - Map HTTP 504 → "The retrieval service is taking longer than expected. Please try again." (FR-021)
+ - Map other errors → "An unexpected error occurred. Please try again or contact support."
+ - Return ErrorResponse with error_type
+
+- [ ] T043 [US4] Integrate ErrorMessage into `App.tsx`:
+ - Add `classificationError` and `retrievalError` state
+ - Display ErrorMessage component when errors occur
+ - Clear errors on new inquiry submission
+ - Ensure errors don't block UI (FR-017 - UI remains responsive)
+
+- [ ] T044 [US4] Enhance LoadingSpinner integration in `App.tsx`:
+ - Show spinner with "Classifying inquiry..." during classification (FR-012)
+ - Show spinner with "Retrieving templates..." during retrieval (FR-012)
+ - Ensure UI remains responsive (no freezing - FR-017, SC-004)
+
+**Checkpoint**: User Stories 1-4 should all work independently. Operators now receive clear error messages and loading feedback while all previous functionality remains intact.
+
+---
+
+## Phase 7: User Story 5 - Performance Monitoring (Priority: P3)
+
+**Goal**: Display processing time metrics for classification and retrieval operations, highlighting slow responses (>2s classification, >1s retrieval) to help operators understand system performance.
+
+**Independent Test**: Submit inquiry → Verify classification time shown (e.g., "Classified in 1.2s") → Verify retrieval time shown (e.g., "Retrieved in 0.5s") → Simulate slow response → Verify time highlighted as slow.
+
+### Tests for User Story 5
+
+- [ ] T045 [P] [US5] E2E test for performance monitoring in `tests/e2e/test_user_story_5.py`:
+ - Test classification processing time displayed (FR-013)
+ - Test retrieval processing time displayed (FR-014)
+ - Test slow classification (>2s) is highlighted
+ - Test slow retrieval (>1s) is highlighted
+
+### Implementation for User Story 5
+
+- [ ] T046 [US5] Enhance `ClassificationDisplay.tsx` with processing time:
+ - Display `processing_time_ms` from ClassificationResult (FR-013)
+ - Format as "Classified in X.Xs"
+ - If >2000ms, highlight in yellow/red with warning icon
+ - Tailwind CSS for highlighting
+
+- [ ] T047 [US5] Enhance `TemplateList.tsx` with retrieval time:
+ - Display `processing_time_ms` from RetrievalResponse (FR-014)
+ - Format as "Retrieved in X.Xs"
+ - If >1000ms, highlight in yellow/red with warning icon
+ - Tailwind CSS for highlighting
+
+**Checkpoint**: All 5 user stories should now be independently functional. Operators have complete workflow (US1), editing (US2), confidence indicators (US3), error handling (US4), and performance monitoring (US5).
+
+---
+
+## Phase 8: Polish & Cross-Cutting Concerns
+
+**Purpose**: Improvements that affect multiple user stories and final quality assurance
+
+- [ ] T048 [P] Add frontend unit tests for components in `frontend/src/components/__tests__/`:
+ - Test InquiryInput validation logic
+ - Test ConfidenceBadge color logic
+ - Test useClipboard hook functionality
+ - Test ErrorMessage display logic
+ - Run: `npm test`
+
+- [ ] T049 [P] Add backend unit tests in `backend/tests/unit/test_api_models.py`:
+ - Test Pydantic validation for ClassificationRequest
+ - Test Pydantic validation for RetrievalRequest
+ - Test ErrorResponse formatting
+ - Run: `pytest backend/tests/unit/ -v`
+
+- [ ] T050 Create Dockerfile for operator UI in `Dockerfile.ui`:
+ - Multi-stage build: frontend (npm build) + backend (Python)
+ - Serve frontend static files through FastAPI
+ - Expose port 8000
+ - Health check endpoint
+
+- [ ] T051 Update `docker-compose.yml` with operator-ui service:
+ - Build from Dockerfile.ui
+ - Mount data/embeddings.db
+ - Environment: SCIBOX_API_KEY, FAQ_PATH
+ - Port mapping: 8080:8000
+ - Depends on classification and retrieval modules
+ - Health check: GET /api/health
+
+- [ ] T052 [P] Code cleanup and optimization:
+ - Remove console.log statements from frontend
+ - Add JSDoc comments to key functions
+ - Run `prettier` on frontend code
+ - Run `black` on backend code
+ - Verify no linting errors
+
+- [ ] T053 [P] Performance validation per `quickstart.md`:
+ - Test classification <2s: `time curl POST /api/classify`
+ - Test retrieval <1s: `time curl POST /api/retrieve`
+ - Test full workflow <10s via E2E test
+ - Document results in validation report
+
+- [ ] T054 Run complete test suite:
+ - Backend unit tests: `pytest backend/tests/unit/ -v`
+ - Backend integration tests: `pytest backend/tests/integration/ -v`
+ - Frontend unit tests: `npm test`
+ - E2E tests: `pytest tests/e2e/ -v -m e2e`
+ - All tests must pass
+
+- [ ] T055 Validate against constitution principles:
+ - ✅ Principle I: Backend doesn't modify `src/classification/` or `src/retrieval/`
+ - ✅ Principle II: All error messages user-actionable (no technical jargon)
+ - ✅ Principle III: Integration tests use testcontainers, E2E use Chrome DevTools MCP
+ - ✅ Principle IV: API matches OpenAPI specs in `contracts/`
+ - ✅ Principle V: Docker works with `docker-compose up operator-ui`
+ - ✅ Principle VI: No changes to FAQ Excel file
+
+- [ ] T056 Create demo video and presentation materials:
+ - Record 2-3 minute demo showing full workflow
+ - Highlight: <10s end-to-end, visual confidence, editing, error handling
+ - Prepare slides explaining architecture and business value
+
+**Checkpoint**: All tasks complete, ready for hackathon submission
+
+---
+
+## Dependencies & Execution Order
+
+### Phase Dependencies
+
+- **Setup (Phase 1)**: No dependencies - can start immediately
+- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
+- **User Stories (Phases 3-7)**: All depend on Foundational phase completion
+ - User Stories 1-5 can proceed in parallel (if staffed)
+ - Or sequentially in priority order: US1 (P1) → US2 (P2) → US3 (P2) → US4 (P3) → US5 (P3)
+- **Polish (Phase 8)**: Depends on all user stories being complete
+
+### User Story Dependencies
+
+- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
+- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - Extends TemplateCard from US1 but independently testable
+- **User Story 3 (P2)**: Can start after Foundational (Phase 2) - Adds ConfidenceBadge to US1 components but independently testable
+- **User Story 4 (P3)**: Can start after Foundational (Phase 2) - Adds ErrorMessage to US1 App.tsx but independently testable
+- **User Story 5 (P3)**: Can start after Foundational (Phase 2) - Enhances US1 display components but independently testable
+
+**Key Insight**: All user stories are designed to be independently testable. Each adds functionality without breaking previous stories.
+
+### Within Each User Story
+
+- **Tests → Implementation**: Write tests FIRST, verify they FAIL, then implement
+- **Backend before Frontend**: API endpoints functional before UI components
+- **Models → Services → Endpoints**: Data models before business logic before HTTP routes
+- **Components → Integration**: Individual React components before App.tsx integration
+
+### Parallel Opportunities
+
+**Setup Phase (Phase 1)**:
+- T003 (backend deps) + T004 (frontend deps) + T005 (Tailwind) + T006 (package files) = 4 parallel tasks
+
+**Foundational Phase (Phase 2)**:
+- T009 (Pydantic models) + T010 (middleware) + T011 (TypeScript types) + T012 (Axios) + T013 (React Query) + T014 (Vite proxy) = 6 parallel tasks after T008 completes
+
+**User Story 1 Tests** (Phase 3):
+- T016 (classification test) + T017 (retrieval test) + T018 (workflow test) + T019 (E2E test) = 4 parallel tasks
+
+**User Story 1 Components** (Phase 3):
+- T022 (InquiryInput) + T023 (ClassificationDisplay) + T024 (TemplateList) + T025 (TemplateCard) + T026 (LoadingSpinner) = 5 parallel tasks after T020-T021 complete
+- T027 (classification service) + T028 (retrieval service) = 2 parallel tasks
+
+**Polish Phase (Phase 8)**:
+- T048 (frontend unit tests) + T049 (backend unit tests) + T052 (code cleanup) + T053 (performance validation) = 4 parallel tasks
+
+**Parallel Example for MVP (User Story 1)**:
+```bash
+# After T008-T015 (Foundational) complete:
+
+# Launch all US1 tests together:
+Task: "Integration test for classification endpoint" (T016)
+Task: "Integration test for retrieval endpoint" (T017)
+Task: "Full workflow integration test" (T018)
+Task: "E2E test for complete user story" (T019)
+
+# After T020-T021 (API endpoints) complete, launch all components:
+Task: "Create InquiryInput.tsx" (T022)
+Task: "Create ClassificationDisplay.tsx" (T023)
+Task: "Create TemplateList.tsx" (T024)
+Task: "Create TemplateCard.tsx" (T025)
+Task: "Create LoadingSpinner.tsx" (T026)
+```
+
+---
+
+## Implementation Strategy
+
+### MVP First (User Story 1 Only)
+
+**Goal**: Deliver complete core workflow as fast as possible
+
+1. **Complete Phase 1: Setup** (T001-T007) → ~30 minutes
+2. **Complete Phase 2: Foundational** (T008-T015) → ~1-2 hours
+ - **CRITICAL**: This blocks all user stories
+3. **Complete Phase 3: User Story 1** (T016-T030) → ~2-3 hours
+ - Tests first (T016-T019): Write, verify FAIL
+ - Backend APIs (T020-T021): Implement, verify tests PASS
+ - Frontend components (T022-T026): Parallel implementation
+ - Frontend services (T027-T029): API clients + clipboard
+ - Integration (T030): Wire everything together
+4. **STOP and VALIDATE**: Run all US1 tests (T016-T019), test manually in browser
+5. **Deploy/Demo**: `docker-compose up operator-ui` and demonstrate <10s workflow
+
+**Estimated Time**: ~4-6 hours for complete MVP (US1 only)
+
+**MVP Validation Criteria**:
+- ✅ Operator enters Russian inquiry
+- ✅ Classification displays within 2s with confidence
+- ✅ Top 5 templates display within 1s
+- ✅ Operator can copy any template answer
+- ✅ Full workflow <10s (SC-001)
+
+---
+
+### Incremental Delivery (Add US2-US5 Sequentially)
+
+**After MVP deployed, add features incrementally**:
+
+1. **Add User Story 2 (Response Customization)** → ~30-45 minutes
+ - T031 (test), T032 (enhance TemplateCard with editing)
+ - Test independently: Edit → Copy → Restore
+ - Deploy: US1 + US2 functional
+
+2. **Add User Story 3 (Confidence Indicators)** → ~45-60 minutes
+ - T033 (test), T034-T038 (ConfidenceBadge + integration)
+ - Test independently: Visual indicators for all confidence levels
+ - Deploy: US1 + US2 + US3 functional
+
+3. **Add User Story 4 (Error Handling)** → ~1 hour
+ - T039 (test), T040-T044 (ErrorMessage + error handling)
+ - Test independently: Service failures, validation errors, loading states
+ - Deploy: US1 + US2 + US3 + US4 functional
+
+4. **Add User Story 5 (Performance Monitoring)** → ~30 minutes
+ - T045 (test), T046-T047 (processing time display)
+ - Test independently: Time metrics and slow response highlighting
+ - Deploy: All user stories functional
+
+5. **Complete Phase 8: Polish** (T048-T056) → ~1-2 hours
+ - Unit tests, Docker, validation, demo materials
+ - Final submission ready
+
+**Total Estimated Time**: ~10-14 hours for complete feature (all 5 user stories + polish)
+
+---
+
+### Parallel Team Strategy
+
+**With 3 developers working simultaneously**:
+
+1. **All devs: Setup + Foundational together** (T001-T015) → ~1-2 hours
+ - **CRITICAL**: Everyone waits for T008-T015 to complete before proceeding
+
+2. **After Foundational complete, split by user story**:
+ - **Developer A: User Story 1 (MVP)** (T016-T030) → ~2-3 hours
+ - **Developer B: User Story 2 + User Story 3** (T031-T038) → ~2 hours
+ - **Developer C: User Story 4 + User Story 5** (T039-T047) → ~2 hours
+
+3. **All devs: Polish together** (T048-T056) → ~1 hour
+ - Parallel: T048 (frontend tests), T049 (backend tests), T052 (cleanup)
+ - Sequential: T050-T051 (Docker), T053-T056 (validation, demo)
+
+**Total Team Time**: ~5-7 hours with 3 developers (vs ~10-14 hours solo)
+
+**Integration Points**: Developers working on US2-US5 may need to wait for Developer A to complete US1 components (TemplateCard, ClassificationDisplay, etc.) before enhancing them. To avoid blocking, Developer A can create component stubs early.
+
+---
+
+## Task Summary
+
+**Total Tasks**: 56
+
+**Task Count by Phase**:
+- Phase 1 (Setup): 7 tasks
+- Phase 2 (Foundational): 8 tasks
+- Phase 3 (US1 - MVP): 15 tasks
+- Phase 4 (US2): 2 tasks
+- Phase 5 (US3): 5 tasks
+- Phase 6 (US4): 5 tasks
+- Phase 7 (US5): 2 tasks
+- Phase 8 (Polish): 12 tasks
+
+**Task Count by User Story**:
+- US1 (Inquiry Analysis and Template Retrieval - P1): 15 tasks (27% of total)
+- US2 (Response Customization - P2): 2 tasks (4% of total)
+- US3 (Confidence Assessment - P2): 5 tasks (9% of total)
+- US4 (Error Recovery - P3): 5 tasks (9% of total)
+- US5 (Performance Monitoring - P3): 2 tasks (4% of total)
+- Shared/Infrastructure (Setup + Foundational + Polish): 27 tasks (48% of total)
+
+**Parallelization**:
+- 28 tasks marked [P] can run in parallel within their phase
+- 5 user stories can run in parallel after Foundational phase
+- Estimated 30-40% time savings with parallel execution
+
+**Independent Test Criteria**:
+- US1: Full workflow <10s (inquiry → classification → retrieval → copy)
+- US2: Edit → Copy → Restore cycle
+- US3: Visual confidence indicators for all score ranges
+- US4: Error messages for all failure modes + loading states
+- US5: Processing time display with slow response highlighting
+
+**Suggested MVP Scope**: User Story 1 only (T001-T030) = 30 tasks, ~4-6 hours solo, ~2-3 hours with team
+
+---
+
+## Notes
+
+- [P] tasks = different files, no dependencies - run in parallel
+- [Story] label (US1-US5) maps task to specific user story for traceability
+- Each user story is independently completable and testable
+- Tests written FIRST (TDD approach), verified to FAIL before implementation
+- Commit after each task or logical group of [P] tasks
+- Stop at any checkpoint to validate story independently before proceeding
+- Avoid vague tasks, same-file conflicts, or cross-story dependencies that break independence
+- Performance requirements (FR-015: <2s classification, FR-016: <1s retrieval, SC-001: <10s workflow) validated in T053
+- Constitution compliance validated in T055 before final submission