sajith-0701 commited on
Commit
ed1123c
·
1 Parent(s): e39cad1

readme updated

Files changed (1) hide show
  1. README.md +610 -1
README.md CHANGED
@@ -1 +1,610 @@
1
- # interview-bot
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Interview Bot - AI Mock Interview Trainer
2
+
3
+ An end-to-end AI-powered mock interview platform for students and job seekers.
4
+
5
+ Interview Bot combines resume intelligence, job-description alignment, adaptive questioning, speech interaction, and detailed post-interview evaluation in a single full-stack application.
6
+
7
+ The project includes:
8
+ - A FastAPI backend for auth, interview orchestration, speech services, analytics, and admin operations
9
+ - A Next.js frontend for student and admin workflows
10
+ - MongoDB Atlas for persistent records
11
+ - Redis for low-latency in-progress interview session state
12
+ - Gemini for resume parsing, follow-up generation, and final evaluation
13
+
14
+ ---
15
+
16
+ ## Table of Contents
17
+
18
+ 1. [Problem Statement](#problem-statement)
19
+ 2. [Project Overview](#project-overview)
20
+ 3. [Key Features](#key-features)
21
+ 4. [System Architecture](#system-architecture)
22
+ 5. [Interview Engine Design](#interview-engine-design)
23
+ 6. [Speech Pipeline](#speech-pipeline)
24
+ 7. [Tech Stack](#tech-stack)
25
+ 8. [Repository Structure](#repository-structure)
26
+ 9. [Environment Configuration](#environment-configuration)
27
+ 10. [Setup and Installation](#setup-and-installation)
28
+ 11. [How to Run](#how-to-run)
29
+ 12. [API Endpoints](#api-endpoints)
30
+ 13. [Data Model](#data-model)
31
+ 14. [Reliability and Resilience](#reliability-and-resilience)
32
+ 15. [Troubleshooting](#troubleshooting)
33
+ 16. [Deployment Notes](#deployment-notes)
34
+ 17. [License](#license)
35
+
36
+ ---
37
+
38
+ ## Problem Statement
39
+
40
+ Most interview preparation tools are static and generic. They do not adapt to:
41
+ - A candidate's actual resume
42
+ - A specific target job description
43
+ - The quality and depth of previous answers
44
+
45
+ Interview Bot addresses this by generating dynamic, role-targeted interviews that adapt in real time and produce actionable feedback after completion.
46
+
47
+ ---
48
+
49
+ ## Project Overview
50
+
51
+ Interview Bot supports two interview modes:
52
+
53
+ 1. Resume Interview
54
+ - Uses resume data + selected job description
55
+ - Creates personalized role-specific questions
56
+ - Applies adaptive follow-up logic per answer
57
+
58
+ 2. Topic Interview
59
+ - Uses admin-published topics
60
+ - Supports optional timed interviews per topic
61
+ - Mixes topic bank questions with AI-generated follow-ups
62
+
63
+ The platform provides:
64
+ - Secure user authentication with JWT
65
+ - Resume upload and AI parsing
66
+ - AI-recommended role suggestions
67
+ - Resume-vs-job-description alignment checks
68
+ - Real-time interview state management via Redis queues
69
+ - Voice-enabled interviewing (TTS + STT)
70
+ - Detailed interview reports with per-question scoring
71
+ - Admin dashboards for analytics, content management, and user oversight
72
+
73
+ ---
74
+
75
+ ## Key Features
76
+
77
+ ### Student Features
78
+
79
+ - Signup and login flow
80
+ - Resume upload (PDF/DOCX/TXT) and structured parsing
81
+ - Skill extraction and manual skill editing
82
+ - AI recommended role list from resume
83
+ - Personal job description management (create/update/delete)
84
+ - Pre-interview JD compatibility check
85
+ - Resume interview and topic interview modes
86
+ - Optional speech interaction:
87
+ - Backend TTS question playback
88
+ - Backend STT answer transcription
89
+ - Editable transcript before final submit
90
+ - Report history and detailed report views
91
+
92
+ ### Admin Features
93
+
94
+ - Role CRUD and requirement management
95
+ - Topic CRUD and publish/hide controls
96
+ - Optional per-topic timer configuration
97
+ - Resume/topic question CRUD
98
+ - Bulk question import from PDF using Gemini extraction
99
+ - Job description management across users
100
+ - User management (student deletion with related data cleanup)
101
+ - Analytics dashboard:
102
+ - Total students
103
+ - Live users
104
+ - New users today
105
+ - Average scores
106
+ - Top performers
107
+ - Common weak areas
108
+ - Quit interview monitoring and report auditing
109
+
110
+ ---
111
+
112
+ ## System Architecture
113
+
114
+ The platform is split into frontend and backend layers with AI and data services:
115
+
116
+ ```mermaid
117
+ flowchart LR
118
+ A[Next.js Frontend] --> B[FastAPI Backend]
119
+ B --> C[MongoDB Atlas]
120
+ B --> D[Redis]
121
+ B --> E[Gemini API]
122
+ B --> F[Speech Models: XTTS + Whisper]
123
+ ```
124
+
125
+ ### High-Level Runtime Flow
126
+
127
+ ```mermaid
128
+ flowchart TD
129
+ U[User Login] --> R[Upload Resume]
130
+ R --> P[Resume Parsing via Gemini]
131
+ P --> S[Skills + Recommended Roles Saved]
132
+ S --> J[Select Job Description]
133
+ J --> V[Optional Resume-JD Verification]
134
+ V --> I[Start Interview]
135
+ I --> Q[Redis Queue Orchestration]
136
+ Q --> A1[Answer Submission]
137
+ A1 --> F1[Adaptive Follow-up Generation]
138
+ F1 --> Q
139
+ Q --> C{Interview Complete?}
140
+ C -- No --> A1
141
+ C -- Yes --> E1[Final Evaluation via Gemini]
142
+ E1 --> M[Report Stored in MongoDB]
143
+ M --> UI[Report UI and Analytics]
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Interview Engine Design
149
+
150
+ Core orchestration lives in backend services and uses:
151
+ - Redis question queues and backlog lists
152
+ - LangGraph state flow for question generation
153
+ - Gemini for AI follow-up generation and final scoring
154
+ - Deterministic fallback questions when AI output is unavailable
155
+
156
+ ### Resume Interview Flow
157
+
158
+ - Requires a resume and selected job description
159
+ - Starts with a personalized intro question
160
+ - Seeds an initial AI question batch in background
161
+ - Maintains max 10 questions by default
162
+ - Stores answers immediately and processes follow-up generation asynchronously
163
+
164
+ Queue-first strategy:
165
+ - `question_queue` keeps the next questions ready for low-latency response
166
+ - `question_backlog` stores overflow to avoid generation stalls
167
+ - Deduplication prevents repeated or near-duplicate questions
168
+
169
+ ### Topic Interview Flow
170
+
171
+ - Requires a published topic
172
+ - Starts with DB topic question bank
173
+ - After initial stage, generates additional follow-ups
174
+ - Maintains max 10 questions
175
+ - Supports optional timer (configured per topic by admin)
176
+
177
+ ### Follow-up Diversity Policy
178
+
179
+ Resume mode prevents repetitive drilling on the same skill/topic:
180
+ - Up to 2 consecutive same-topic follow-ups are allowed
181
+ - A third consecutive same-topic follow-up is allowed only when follow-up need score >= 95
182
+ - Otherwise, the system switches to an alternate focus skill
183
+
184
+ ### Evaluation and Completion
185
+
186
+ - Per-answer evaluation can run in background while interview continues
187
+ - Final report is generated at completion (or partial report on quit if answers exist)
188
+ - Redis session data is cleaned after final report generation
189
+
190
+ ---
191
+
192
+ ## Speech Pipeline
193
+
194
+ Speech features are backend-powered for consistency across browsers:
195
+
196
+ ### Text-to-Speech (TTS)
197
+
198
+ - Uses Coqui XTTS (`xtts_v2`) with warmup on startup
199
+ - Uses voice presets by gender (`female`, `male`, `auto`)
200
+ - Includes fallback synthesis models if XTTS fails transiently
201
+ - Audio caching improves repeat playback latency
202
+
203
+ ### Speech-to-Text (STT)
204
+
205
+ - Uses `faster-whisper`
206
+ - Handles CUDA runtime issues and falls back to CPU automatically
207
+ - Returns transcription text + latency metrics
208
+
209
+ ### UX Safeguards
210
+
211
+ - Spoken text normalization strips prefixes like `Question 3:` before playback
212
+ - Candidate can edit transcript before submission
213
+ - Frontend prefetches upcoming question audio where possible
214
+
215
+ ---
216
+
217
+ ## Tech Stack
218
+
219
+ ### Backend
220
+
221
+ - FastAPI
222
+ - Uvicorn
223
+ - Motor (MongoDB async)
224
+ - redis-py asyncio client
225
+ - Pydantic + pydantic-settings
226
+ - python-jose + passlib + bcrypt
227
+ - Google Gemini (`google-genai`)
228
+ - LangGraph + LangChain Core
229
+ - Coqui TTS + faster-whisper
230
+ - pypdf + python-docx
231
+
232
+ ### Frontend
233
+
234
+ - Next.js 16 (App Router)
235
+ - React 19 + TypeScript
236
+ - Tailwind CSS v4
237
+ - Axios
238
+ - React Query
239
+ - Framer Motion
240
+ - Sonner notifications
241
+ - Lucide icons
242
+
243
+ ---
244
+
245
+ ## Repository Structure
246
+
247
+ ```text
248
+ interview-bot/
249
+ |- backend/
250
+ | |- main.py
251
+ | |- config.py
252
+ | |- database.py
253
+ | |- auth/
254
+ | |- routers/
255
+ | |- schemas/
256
+ | |- services/
257
+ | |- utils/
258
+ | |- models/
259
+ | |- uploads/
260
+ | |- requirements.txt
261
+ | |- Dockerfile
262
+ |- frontend/
263
+ | |- src/
264
+ | | |- app/
265
+ | | |- components/
266
+ | | |- lib/
267
+ | | |- types/
268
+ | |- public/
269
+ | |- package.json
270
+ |- README.md
271
+ |- LICENSE
272
+ |- WORKFLOW.md
273
+ |- LANGGRAPH_AND_TOOLS.md
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Environment Configuration
279
+
280
+ Create and configure environment files before running.
281
+
282
+ ### Backend: `backend/.env`
283
+
284
+ ```env
285
+ # App
286
+ APP_ENV=development
287
+ APP_HOST=0.0.0.0
288
+ APP_PORT=8000
289
+
290
+ # Gemini
291
+ GEMINI_API_KEY=your_gemini_api_key
292
+ GEMINI_MODEL=gemini-2.5-flash
293
+ GEMINI_FALLBACK_MODELS=gemini-2.0-flash,gemini-2.0-flash-lite,gemini-flash-latest
294
+
295
+ # MongoDB (cloud only)
296
+ MONGO_URI=mongodb+srv://<user>:<password>@<cluster>/<db>?retryWrites=true&w=majority
297
+ MONGO_DB_NAME=interview_bot
298
+
299
+ # Redis (cloud URL)
300
+ REDIS_URL=rediss://:<password>@<host>:<port>
301
+
302
+ # JWT
303
+ JWT_SECRET=replace_with_strong_secret
304
+ JWT_ALGORITHM=HS256
305
+ JWT_EXPIRY=3600
306
+
307
+ # File storage
308
+ UPLOAD_DIR=./uploads
309
+
310
+ # Speech
311
+ COQUI_TOS_AGREED=1
312
+ XTTS_USE_GPU=auto
313
+ WHISPER_DEVICE=auto
314
+ ```
315
+
316
+ Important validation rules in backend config:
317
+ - `MONGO_URI` must use `mongodb+srv://` and must not be localhost
318
+ - `REDIS_URL` must use `redis://` or `rediss://` and must not be localhost
319
+
320
+ ### Frontend: `frontend/.env.local`
321
+
322
+ ```env
323
+ NEXT_PUBLIC_API_URL=http://127.0.0.1:8000
324
+ ```
325
+
326
+ ---
327
+
328
+ ## Setup and Installation
329
+
330
+ ### Prerequisites
331
+
332
+ - Python 3.10+
333
+ - Node.js 18+
334
+ - npm
335
+ - MongoDB Atlas instance
336
+ - Cloud Redis instance
337
+ - Gemini API key
338
+
339
+ ### 1) Clone Repository
340
+
341
+ ```bash
342
+ git clone <your-repo-url>
343
+ cd interview-bot
344
+ ```
345
+
346
+ ### 2) Backend Setup
347
+
348
+ ```bash
349
+ cd backend
350
+ python -m venv ../inter
351
+ ..\inter\Scripts\activate
352
+ pip install -r requirements.txt
353
+ ```
354
+
355
+ Linux/macOS equivalent:
356
+
357
+ ```bash
358
+ python -m venv inter
359
+ source inter/bin/activate
360
+ pip install -r backend/requirements.txt
361
+ ```
362
+
363
+ ### 3) Frontend Setup
364
+
365
+ ```bash
366
+ cd ../frontend
367
+ npm install
368
+ ```
369
+
370
+ ---
371
+
372
+ ## How to Run
373
+
374
+ ### Start Backend API
375
+
376
+ From `backend/` directory:
377
+
378
+ ```bash
379
+ uvicorn main:app --host 0.0.0.0 --port 8000 --reload
380
+ ```
381
+
382
+ Backend endpoints:
383
+ - API root health: `http://localhost:8000/health`
384
+ - Swagger docs: `http://localhost:8000/docs`
385
+
386
+ ### Start Frontend
387
+
388
+ From `frontend/` directory:
389
+
390
+ ```bash
391
+ npm run dev
392
+ ```
393
+
394
+ Frontend app: `http://localhost:3000`
395
+
396
+ ### First Run Checklist
397
+
398
+ 1. Register a user account
399
+ 2. Upload resume in Settings
400
+ 3. Add at least one Job Description
401
+ 4. Start Resume Interview from Dashboard or Bot's Help
402
+ 5. Complete interview and open Reports
403
+
404
+ ---
405
+
406
+ ## API Endpoints
407
+
408
+ All secured endpoints require `Authorization: Bearer <token>`.
409
+
410
+ ### Health
411
+
412
+ | Method | Path | Description |
413
+ |---|---|---|
414
+ | GET | `/health` | Service health check |
415
+
416
+ ### Authentication
417
+
418
+ | Method | Path | Description |
419
+ |---|---|---|
420
+ | POST | `/auth/signup` | Register user |
421
+ | POST | `/auth/login` | Login and receive JWT |
422
+
423
+ Note: By default, emails ending with `@admin.com` are created with admin role.
424
+
425
+ ### Resume
426
+
427
+ | Method | Path | Description |
428
+ |---|---|---|
429
+ | POST | `/resume/upload` | Upload and parse resume |
430
+
431
+ ### Profile
432
+
433
+ | Method | Path | Description |
434
+ |---|---|---|
435
+ | GET | `/profile` | Get profile, resume, skills |
436
+ | PUT | `/profile/speech-settings` | Update voice preference |
437
+ | PUT | `/profile/skills` | Update user skill list |
438
+ | PUT | `/profile/resume-data` | Update structured resume fields |
439
+ | GET | `/profile/job-descriptions` | List user's JDs |
440
+ | POST | `/profile/job-descriptions` | Create JD |
441
+ | PUT | `/profile/job-descriptions/{jd_id}` | Update JD |
442
+ | DELETE | `/profile/job-descriptions/{jd_id}` | Delete JD |
443
+
444
+ ### Interview
445
+
446
+ | Method | Path | Description |
447
+ |---|---|---|
448
+ | POST | `/interview/start` | Start interview session |
449
+ | POST | `/interview/start_interview` | Compatibility alias for start |
450
+ | POST | `/interview/verify` | Resume-vs-JD verification |
451
+ | POST | `/interview/answer` | Submit answer and get next question |
452
+ | POST | `/interview/submit_answer` | Compatibility alias for answer |
453
+ | GET | `/interview/next_question` | Peek next queued question |
454
+ | POST | `/interview/quit` | Quit interview; optional partial report |
455
+ | GET | `/interview/report` | Generate/get report |
456
+ | GET | `/interview/latency` | Latency summary (p50/p95) |
457
+ | POST | `/interview/latency/reset` | Reset latency metrics |
458
+
459
+ ### Reports
460
+
461
+ | Method | Path | Description |
462
+ |---|---|---|
463
+ | GET | `/reports/history` | Student report history |
464
+
465
+ ### Speech
466
+
467
+ | Method | Path | Description |
468
+ |---|---|---|
469
+ | GET | `/speech/health` | Speech service health |
470
+ | POST | `/speech/warmup` | Warm TTS/STT models |
471
+ | POST | `/speech/synthesize` | Convert text to WAV |
472
+ | POST | `/speech/transcribe` | Transcribe uploaded audio |
473
+
474
+ ### Admin (selected)
475
+
476
+ | Method | Path | Description |
477
+ |---|---|---|
478
+ | GET/POST/PUT/DELETE | `/admin/roles` (+ `/{id}`) | Role management |
479
+ | GET/POST/PUT/DELETE | `/admin/questions` (+ `/{id}`) | Question management |
480
+ | POST | `/admin/questions/upload` | Import questions from PDF |
481
+ | GET/POST/PUT/DELETE | `/admin/topics` (+ `/{id}`) | Topic management |
482
+ | PUT | `/admin/topics/{topic_id}/publish` | Publish/hide topic + timer |
483
+ | GET/POST/DELETE | `/admin/requirements` | Role requirement management |
484
+ | GET | `/admin/analytics` | Admin dashboard analytics |
485
+ | GET | `/admin/quit-interviews` | Quit interview details |
486
+ | GET | `/admin/reports` | Report summaries |
487
+ | GET | `/admin/reports/{session_id}` | Report detail |
488
+ | GET | `/admin/users` | Student list |
489
+ | DELETE | `/admin/users/{user_id}` | Delete student and linked data |
490
+ | GET/POST/PUT/DELETE | `/admin/job-descriptions` | Admin JD management |
491
+
492
+ ---
493
+
494
+ ## Data Model
495
+
496
+ Primary MongoDB collections:
497
+
498
+ - `users`
499
+ - `resumes`
500
+ - `skills`
501
+ - `job_roles`
502
+ - `job_descriptions`
503
+ - `jd_verifications`
504
+ - `role_requirements`
505
+ - `questions`
506
+ - `topics`
507
+ - `topic_questions`
508
+ - `sessions`
509
+ - `answers`
510
+ - `results`
511
+
512
+ Redis stores in-progress interview state with TTL, including:
513
+ - Session metadata
514
+ - Question queue/backlog
515
+ - Asked question fingerprints
516
+ - Q/A hashes
517
+ - Context cache
518
+
519
+ ---
520
+
521
+ ## Reliability and Resilience
522
+
523
+ This project includes multiple runtime safeguards:
524
+
525
+ - Gemini model fallback chain for transient provider failures
526
+ - Retry logic for 503/high-demand conditions
527
+ - Loose JSON extraction/parsing to recover from malformed model output
528
+ - Deterministic fallback question templates when AI output is empty/duplicate
529
+ - Question deduplication using normalized fingerprinting
530
+ - Queue/backlog buffering to avoid blocking next question delivery
531
+ - Placeholder report detection and regeneration logic
532
+ - Report generation fallback from MongoDB answers when Redis session data is unavailable
533
+ - TTS and STT warmup with graceful fallback paths
534
+ - STT automatic CPU fallback on CUDA runtime issues
535
+
536
+ ---
537
+
538
+ ## Troubleshooting
539
+
540
+ ### 1) Login fails despite valid credentials
541
+
542
+ Symptom:
543
+ - Frontend shows auth error even though credentials are correct.
544
+
545
+ Likely cause:
546
+ - Frontend cannot reach backend API.
547
+
548
+ Fix:
549
+ 1. Ensure backend is running on the configured host/port.
550
+ 2. Verify `frontend/.env.local`:
551
+ - `NEXT_PUBLIC_API_URL=http://127.0.0.1:8000`
552
+ 3. Restart frontend after changing env.
553
+
554
+ ### 2) Resume interview start fails
555
+
556
+ Check:
557
+ - Resume is uploaded
558
+ - Job Description is selected
559
+ - Job Description includes `required_skills`
560
+
561
+ ### 3) No recommended roles in Start Interview dropdown
562
+
563
+ Check:
564
+ - Resume parsing succeeded
565
+ - `recommended_roles` exists in profile resume parsed data
566
+
567
+ If missing, re-upload resume from Settings.
568
+
569
+ ### 4) Speech is slow on first request
570
+
571
+ Use:
572
+ - `POST /speech/warmup` after login
573
+
574
+ The frontend also warms speech automatically, but manual warmup helps during diagnostics.
575
+
576
+ ### 5) Mongo/Redis config validation errors at startup
577
+
578
+ Ensure:
579
+ - Mongo uses `mongodb+srv://`
580
+ - Redis uses `redis://` or `rediss://`
581
+ - Neither uses localhost in current backend validation rules
582
+
583
+ ### 6) CUDA errors during transcription
584
+
585
+ The service automatically falls back to CPU. Transcription will continue but can be slower.
586
+
587
+ ---
588
+
589
+ ## Deployment Notes
590
+
591
+ - Backend includes a Dockerfile (`backend/Dockerfile`)
592
+ - Configure production secrets via environment variables
593
+ - Use strong JWT secret in production
594
+ - Restrict CORS origins for production deployments
595
+ - Ensure cloud MongoDB/Redis endpoints are reachable from deployment network
596
+
597
+ Example backend container run:
598
+
599
+ ```bash
600
+ cd backend
601
+ docker build -t interview-bot-backend .
602
+ docker run -p 8000:8000 --env-file .env interview-bot-backend
603
+ ```
604
+
605
+ ---
606
+
607
+ ## License
608
+
609
+ This project is licensed under the MIT License.
610
+ See [LICENSE](LICENSE) for details.