diff --git a/README.md b/README.md index e69de29..0e16a00 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,212 @@ +# Dropout Enrollments Command + +## **Overview** + +This console command automates the process of marking enrollments as **DROPOUT** based on specific conditions. + +--- + +## Setup Instructions + +### Prerequisites + +* Composer +* PHP 8.x +* Laravel 10+ +* MySQL + +### Clone the Repository + +```bash +git clone https://github.com/ashshidiq23/technical-excercise +cd technical-excercise +``` + +### Install Dependencies + +```bash +composer install +``` + +### Create a `.env` file + +Copy the .env.example file to .env + +```bash +cp .env.example .env +``` + +Set up your database connection in .env: + +```dotenv +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=your_database +DB_USERNAME=your_username +DB_PASSWORD=your_password +``` + +### Run Migration & Seeder + +```bash +php artisan migrate --seed +``` + +--- + +## API Documentation + +### Dropout Enrollments Command + +This command drops out any enrollments that meet the following criteria: + +* The enrollment deadline has passed. +* The student has no active exams (`IN_PROGRESS` status). +* The student has no pending submissions (`WAITING_REVIEW` status). + +After that, the command will execute: + +1. The enrollment status is updated to `DROPOUT`. +2. An activity log entry is created for tracking. + +#### Usage + +To execute the command, run: + +```bash +php artisan enrollments:dropout +``` + +example output: + +```text +Enrollments to be dropped out: 500000 +Excluded from drop out: 39401 +Final dropped out enrollments: 460599 +default/App\Console\Commands\DropOutEnrollments: 30.00 MiB - 68719 ms +``` + +## Testing Instructions + +#### Verify the dropout process manually: + +* Before Running the Command, query enrollments that should be dropped based on the latest deadline: + +```sql +SELECT * +FROM enrollments +WHERE deadline_at <= (SELECT deadline_at FROM enrollments ORDER BY id DESC LIMIT 1) + AND status != 'DROPOUT'; +``` + +* Run the command: + +```bash +php artisan enrollments:dropout +``` + +* Check the enrollments table to ensure that the status of the relevant enrollments has been updated to `DROPOUT`: + +```sql +SELECT * +FROM enrollments +WHERE status = 'DROPOUT'; +``` + +--- + +## Product Analysis + +### Pain Points + +1. **No Learning Progress at a Glance** + - Users cannot quickly see their course progress, making it difficult to track learning milestones. + - Proposed Solution: Add a progress bar, completion percentage, and estimated time left for each course. + +2. **Blank Page Before Each Question in Quizzes** + - When loading the next question, a blank page is showing, causing a distracting experience. + - Proposed Solution: Implement smooth transitions using skeleton loading or preloading the next question in the + background or make it client-side entirely. + +3. **Price Point is Too Steep** + - The pricing model may not be affordable to all learners. + - Proposed Solution: Introduce tiered pricing (e.g., Basic, Pro, Premium) and differentiate the feature between them + e.g. no certificate in Basic Tier, etc. + +### Feature Recommendations, Justification, and Solution Design + +1. **AI Implementation** + - **Recommended Learning Path:** AI-driven personalization can help learners choose the most suitable courses based + on their existing skills, learning pace, and career goals. This helps beginner to choose their preferred learning + path. + - **AI Chat in Course Pages:** Many learners struggle with understanding concepts in real-time. An AI-powered + assistant can provide instant explanations, reducing frustration and increasing retention. + + **Solution Design:** + - Use LLM to power AI chat. + - Implement a recommendation engine using machine learning to analyze user progress and suggest courses. + - Display AI suggestions on the dashboard in an easy-to-read format. + +2. **Gamification for Engagement** + - **Daily Learning Goals:** Encourages consistency, as learners are more likely to stay committed if they have + short-term, achievable goals. Gamification elements have been proven to increase motivation and user engagement ( + like Duolingo). + ![img.png](public/duo-streak.png) + - **Badges and Achievements:** Visual indicators of progress provide intrinsic motivation, making learners more + likely to complete courses (like badge on google developers or microsoft learn). + - **Skillset Display:** A character sheet-style display helps learners track their growing expertise, reinforcing a + sense of accomplishment and making their progress more tangible. + ![img.png](public/skill-card.png) + + **Solution Design:** + - Add a user profile section displaying earned badges and skill levels. + - Implement a streak-tracking system that rewards users for consistent learning. + - Use progress bars and experience points to make progress more engaging. + +3. **Minor UI Enhancements and Quality of Life Updates** + - Learning Path Menu: Convert the learning path menu into a sidebar format to display all options at once, improving + navigation. + - Learning Path Testimonials: Display testimonials in a carousel format to showcase more reviews efficiently. + - Dicoding Mentoring: Add a filter option to sort mentors based on their expertise, making it easier for learners to + find relevant guidance. + - Rewards Menu: Show accumulated points directly in the Rewards menu, rather than only in the profile hover section. + + **Solution Design:** + - Redesign UI components to improve visibility and accessibility. + - Ensure enhancements are lightweight and do not impact site performance. + +### Implementation Strategy + +To ensure a smooth rollout and faster iteration, features will be released one by one based on priority and user impact. +This allows for incremental improvements, continuous user feedback, and reduced risk. + +#### Phase 1: Progress Tracking UI & Performance Optimization + +- Develop and test a dashboard update with progress indicators. +- Implement skeleton loaders for quiz questions to reduce flicker. + +#### Phase 2: AI Features + +- Train an LLM model for AI chat support. +- Develop a recommendation engine for personalized learning paths. + +#### Phase 3: Gamification System + +- Implement streak tracking, badges, and skillset visualization. +- Develop a user profile section displaying achievements. + +#### Phase 4: Full Deployment & Continuous Improvement + +- Gradually roll out finalized features platform-wide with proper onboarding. +- Monitor key success metrics and user satisfaction. + +*Do A/B testing or beta release on each phase to test the impact of the changes on user engagement and satisfaction.* + +--- + +### Success Metrics + +- **User Engagement:** Increase in average session duration and interaction with the platform. +- **Course Completion Rate:** Measure improvement before and after progress tracking implementation. +- **User Satisfaction:** Collect feedback through surveys and track Net Promoter Score (NPS). diff --git a/app/Console/Commands/DropOutEnrollments.php b/app/Console/Commands/DropOutEnrollments.php index f759d35..8d8068a 100644 --- a/app/Console/Commands/DropOutEnrollments.php +++ b/app/Console/Commands/DropOutEnrollments.php @@ -67,41 +67,56 @@ public function handle() */ private function dropOutEnrollmentsBefore(Carbon $deadline) { - $enrollmentsToBeDroppedOut = Enrollment::where('deadline_at', '<=', $deadline)->get(); - - $this->info('Enrollments to be dropped out: ' . count($enrollmentsToBeDroppedOut)); - $droppedOutEnrollments = 0; - - foreach ($enrollmentsToBeDroppedOut as $enrollment) { - $hasActiveExam = Exam::where('course_id', $enrollment->course_id) - ->where('student_id', $enrollment->student_id) - ->where('status', 'IN_PROGRESS') - ->exists(); - - $hasWaitingReviewSubmission = Submission::where('course_id', $enrollment->course_id) - ->where('student_id', $enrollment->student_id) - ->where('status', 'WAITING_REVIEW') - ->exists(); - - if ($hasActiveExam || $hasWaitingReviewSubmission) { - continue; - } - - $enrollment->update([ - 'status' => 'DROPOUT', - 'updated_at' => now(), - ]); - - Activity::create([ - 'resource_id' => $enrollment->id, - 'user_id' => $enrollment->student_id, - 'description' => 'COURSE_DROPOUT', - ]); - - $droppedOutEnrollments++; - } - - $this->info('Excluded from drop out: ' . count($enrollmentsToBeDroppedOut) - $droppedOutEnrollments); - $this->info('Final dropped out enrollments: ' . $droppedOutEnrollments); + $initialEnrollmentsToBeDroppedOut = Enrollment::where('enrollments.deadline_at', '<=', $deadline); + $initialEnrollmentsToBeDroppedOutCount = $initialEnrollmentsToBeDroppedOut->count(); + + $this->info('Enrollments to be dropped out: ' . $initialEnrollmentsToBeDroppedOutCount); + + $batchSize = 500; + $enrollmentsToBeDroppedOutCount = 0; + $now = now(); + $initialEnrollmentsToBeDroppedOut + ->leftJoin('exams', function ($join) { + $join->on('enrollments.course_id', '=', 'exams.course_id') + ->on('enrollments.student_id', '=', 'exams.student_id') + ->where('exams.status', 'IN_PROGRESS'); + }) + ->leftJoin('submissions', function ($join) { + $join->on('enrollments.course_id', '=', 'submissions.course_id') + ->on('enrollments.student_id', '=', 'submissions.student_id') + ->where('submissions.status', 'WAITING_REVIEW'); + }) + ->whereNull('exams.id') + ->whereNull('submissions.id') + ->selectRaw('DISTINCT enrollments.id, enrollments.student_id') + ->orderBy('enrollments.id') + ->chunkById($batchSize, function ($enrollments) use (&$enrollmentsToBeDroppedOutCount, $now) { + $ids = []; + $activities = []; + + foreach ($enrollments as $enrollment) { + $ids[] = $enrollment->id; + $activities[] = [ + 'resource_id' => $enrollment->id, + 'user_id' => $enrollment->student_id, + 'description' => 'COURSE_DROPOUT', + ]; + } + + Enrollment::whereIn('id', $ids)->update([ + 'status' => 'DROPOUT', + 'updated_at' => $now, + ]); + + Activity::insert($activities); + $enrollmentsToBeDroppedOutCount += count($activities); + + unset($ids, $activities); + gc_collect_cycles(); + }, 'enrollments.id', 'id'); + + $this->info('Excluded from drop out: ' . $initialEnrollmentsToBeDroppedOutCount - $enrollmentsToBeDroppedOutCount); + $this->info('Final dropped out enrollments: ' . $enrollmentsToBeDroppedOutCount); } + } diff --git a/public/duo-streak.png b/public/duo-streak.png new file mode 100644 index 0000000..d198106 Binary files /dev/null and b/public/duo-streak.png differ diff --git a/public/skill-card.png b/public/skill-card.png new file mode 100644 index 0000000..9eea08c Binary files /dev/null and b/public/skill-card.png differ