Browse Source

content quality analytics page

VISHAL BHANUSHALI 3 months ago
parent
commit
eb8445d1e3

+ 2 - 0
content_quality_tool_public/templates/get-data.html

@@ -521,6 +521,7 @@
 
                     // responseDiv.innerHTML = `<div class="alert alert-success">✅ ${data.message}</div>`;
                 } else {
+                    $('#full-page-loader').hide();
                     // responseDiv.innerHTML = `<div class="alert alert-danger">❌ ${data.error}</div>`;
                 }
 
@@ -530,6 +531,7 @@
                 // }, 5000);
             })
             .catch(error => {
+                $('#full-page-loader').hide();
                 // responseDiv.innerHTML = `<div class="alert alert-danger">❌ API call failed: ${error}</div>`;
                 // setTimeout(() => {
                 //     responseDiv.innerHTML = '';

+ 2 - 1
content_quality_tool_public/templates/index.html

@@ -236,7 +236,8 @@
                 // Remove message after 5 seconds
                 setTimeout(() => {
                     responseDiv.innerHTML = '';
-                    window.location.href = "/content-scorecard";
+                    window.location.href = "/content-quality-analytics";
+                    // window.location.href = "/content-scorecard";
                 }, 3000);
             });
         });

+ 391 - 0
content_quality_tool_public/templates/product-performance-analysis.html

@@ -0,0 +1,391 @@
+{% load static %}
+<!DOCTYPE html>
+<html lang="en"> <!--begin::Head-->
+
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>Content Scorecard</title><!--begin::Primary Meta Tags-->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="title" content="CQT | Upload">
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/source-sans-3@5.0.12/index.css"
+        integrity="sha256-tXJfXfp6Ewt1ilPzLDtQnJV4hclT9XuaZUKyUvmyr+Q=" crossorigin="anonymous">
+    <!--end::Fonts--><!--begin::Third Party Plugin(OverlayScrollbars)-->
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/overlayscrollbars@2.3.0/styles/overlayscrollbars.min.css"
+        integrity="sha256-dSokZseQNT08wYEWiz5iLI8QPlKxG+TswNRD8k35cpg=" crossorigin="anonymous">
+    <!--end::Third Party Plugin(OverlayScrollbars)--><!--begin::Third Party Plugin(Bootstrap Icons)-->
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.min.css"
+        integrity="sha256-Qsx5lrStHZyR9REqhUF8iQt73X06c8LGIUPzpOhwRrI=" crossorigin="anonymous">
+    <!--end::Third Party Plugin(Bootstrap Icons)--><!--begin::Required Plugin(AdminLTE)-->
+    <link rel="stylesheet" href="{% static './css/adminlte.css' %}"><!--end::Required Plugin(AdminLTE)--><!-- apexcharts -->
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/apexcharts@3.37.1/dist/apexcharts.css"
+        integrity="sha256-4MX+61mt9NVvvuPjUWdUdyfZfxSB1/Rf9WtqRHgG5S0=" crossorigin="anonymous">
+    <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
+    <link rel="stylesheet" href="{% static './css/select2-bootstrap4.min.css' %}">
+    <link rel="stylesheet" href="{% static './css/custom.css' %}">
+    <style>
+        .select2-container .select2-search--inline .select2-search__field {
+            position: absolute;
+            top: 3px;
+            font-size: 14px;
+        }
+        #full-page-loader {
+            position: fixed;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            background-color: rgba(0, 0, 0, 0.6); /* semi-transparent black */
+            z-index: 9999;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
+
+        .loader-overlay .spinner-border {
+            width: 3rem;
+            height: 3rem;
+        }
+
+    </style>
+    <style>
+    body {
+        background-color: #f8f9fa;
+        font-family: 'Segoe UI', sans-serif;
+    }
+    .header {
+        background-color: #01445E;
+        color: white;
+        padding: 20px;
+        text-align: center;
+        border-radius: 8px;
+        margin-bottom: 30px;
+    }
+    .score-card {
+        border-radius: 15px;
+        color: white;
+        padding: 20px;
+        box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+        text-align: center;
+    }
+    .excellent { 
+    background: linear-gradient(135deg, #2b9348, #2b9348); /* Green gradient */
+    border-left: 5px solid rgb(16, 34, 16); /* Green */
+}
+
+.good { 
+    background: linear-gradient(135deg, #49B5E0, #49B5E0); /* Blue gradient */
+    border-left: 5px solid rgb(9, 29, 37); /* Green */
+}
+
+.fair { 
+        background: linear-gradient(135deg, #4E4A5A, #4E4A5A); /* Dark Gray gradient */
+        border-left: 5px solid #29272c; /* Green */
+    
+}
+
+.poor { 
+background: linear-gradient(135deg, #F15D5C, #F15D5C); /* Red gradient */
+border-left: 5px solid #e20303; /* Green */
+}
+
+   /* .excellent { background: linear-gradient(135deg, #27B7C1, #49B5E0); }
+    .good { background: linear-gradient(135deg, #49B5E0, #27B7C1); }
+    .fair { background: linear-gradient(135deg, #4E4A5A, #49B5E0); }
+    .poor { background: linear-gradient(135deg, #01445E, #4E4A5A); } */
+    .score-card h2 {
+        font-size: 2.5rem;
+        margin-bottom: 10px;
+        font-weight: bold;
+    }
+    .score-card p {
+        font-size: 1.1rem;
+        margin: 0;
+    }
+     .rating-guide {
+            padding: 15px 0;
+        }
+        .rating-guide h5 {
+            font-weight: 600;
+            color: var(--primary-dark);
+            margin-bottom: 15px;
+        }
+        .rating-guide .badge {
+            font-size: 1rem;
+            padding: 0.8em 4em;
+            font-weight: 400;
+            /* border-radius: 20px; */
+             /* Pill shape */
+        }
+    /* .rating-guide {
+        background-color: white;
+        border-radius: 10px;
+        padding: 20px;
+        box-shadow: 0 2px 6px rgba(0,0,0,0.1);
+        margin-top: 30px;
+    } */
+    .rating-item {
+        display: inline-flex;
+        align-items: center;
+        margin-right: 30px;
+    }
+    .dot {
+        height: 15px;
+        width: 15px;
+        border-radius: 50%;
+        margin-right: 10px;
+    }
+    .dot.excellent { background-color: #27B7C1; }
+    .dot.good { background-color: #49B5E0; }
+    .dot.fair { background-color: #4E4A5A; }
+    .dot.poor { background-color: #01445E; }
+    .table thead {
+        background-color: #49B5E0;
+        color: white;
+    }
+    .category-tag {
+        background-color: #4E4A5A;
+        color: white;
+        padding: 5px 10px;
+        border-radius: 5px;
+    }
+    .btn-view {
+        background-color: #27B7C1;
+        color: white;
+    }
+    .btn-edit {
+        background-color: #01445E;
+        color: white;
+    }
+    .score-card:hover {
+        transform: translateY(-5px);
+        box-shadow: 0 12px 25px rgba(0,0,0,0.15);
+    }
+
+
+    /* Mobile view fix */
+    @media (max-width: 768px) {
+        .rating-guide .badge {
+            font-size: 0.8rem;       /* Slightly smaller text */
+            padding: 0.3rem 0.6rem;  /* Reduce padding for small screens */
+            margin: 0.15rem;         /* Tighter spacing */
+            display: block;           /* Stack badges vertically if needed */
+            width: auto;              /* Ensure badges don’t overflow */
+        }
+    }
+
+
+    /* .score-card {
+        border-left: 5px solid #01445E;
+    } */
+
+</style>
+</head>
+
+<body class="layout-fixed sidebar-expand-lg sidebar-mini app-loaded sidebar-collapse">
+    <!--begin::App Wrapper-->
+    <div class="app-wrapper"> <!--begin::Header-->
+        {% include 'header.html' %}
+        {% include 'sidebar.html' %}
+        <main class="app-main"> <!--begin::App Content Header-->
+            <div class="app-content-header"> <!--begin::Container-->
+                <div class="container-fluid"> <!--begin::Row-->
+                    <div class="row">
+                        <div class="col-sm-6">
+                            <h3 class="mb-0">📶 Content Quality Analytics</h3>
+                        </div>
+                        <div class="col-sm-6">
+                            <ol class="breadcrumb float-sm-end">
+                                <li class="breadcrumb-item"><a href="{% url 'file-upload' %}">Home</a></li>
+                                <li class="breadcrumb-item active" aria-current="page"><a href="{% url 'content-quality-analytics' %}"></a>
+                                   📶 Content Quality Analytics</a>
+                                </li>
+                            </ol>
+                        </div>
+                    </div> <!--end::Row-->
+                </div> <!--end::Container-->
+            </div>
+            <div class="app-content"> <!--begin::Container-->
+                <div class="container-fluid"> <!-- Info boxes -->
+                    <div id="full-page-loader" style="display: none;">
+                    <div class="loader-overlay">
+                        <div class="spinner-border text-light" role="status">
+                            <!-- <span class="sr-only">Loading...</span> -->
+                        </div>
+                    </div>
+                </div>
+
+                    <!-- Header -->
+                    <!-- <div class="header">
+                        <h1>Product Content Quality Analytics</h1>
+                        <p>Analyzing and improving product content to ensure the highest standards.</p>
+                    </div> -->
+
+
+                    <!-- Summary Cards -->
+                    <div class="row g-4 text-center">
+                        <div class="col-md-3">
+                            <div class="score-card excellent">
+                                <h2>3</h2>
+                                <p>Excellent Performers</p>
+                            </div>
+                        </div>
+                        <div class="col-md-3">
+                            <div class="score-card good">
+                                <h2>4</h2>
+                                <p>Good Performers</p>
+                            </div>
+                        </div>
+                        <div class="col-md-3">
+                            <div class="score-card fair">
+                                <h2>2</h2>
+                                <p>Fair Performers</p>
+                            </div>
+                        </div>
+                        <div class="col-md-3">
+                            <div class="score-card poor">
+                                <h2>1</h2>
+                                <p>Poor Performers</p>
+                            </div>
+                        </div>
+                    </div>
+
+                    <!-- Rating Guide -->
+                    <!-- <div class="rating-guide mt-4">
+                        <h5 class="mb-3">Performance Rating Guide</h5>
+                        <div class="rating-item"><div class="dot excellent"></div><span><strong>90-100:</strong> Excellent (Market Ready)</span></div>
+                        <div class="rating-item"><div class="dot good"></div><span><strong>75-89:</strong> Good (Minor Improvements)</span></div>
+                        <div class="rating-item"><div class="dot fair"></div><span><strong>60-74:</strong> Fair (Content Gaps)</span></div>
+                        <div class="rating-item"><div class="dot poor"></div><span><strong><60:</strong> Poor (Major Rework Needed)</span></div>
+                    </div> -->
+                    <br/>
+                    <div class="card">
+                        <div class="rating-guide ">
+                            <h5 style="text-align: center;">Content Performace Insights :</h5>
+                            <div style="text-align: center;">
+                                <span class="badge excellent">Excellent: 85-100%</span>
+                                <span class="badge good" >Good: 70-84%</span>
+                                <span class="badge fair" >Fair: 50-69%</span>
+                                <span class="badge poor" >Poor: Below 50%</span>
+                            </div>
+                        </div>
+                    </div>
+
+                    <!-- Table -->
+                    <div class="table-responsive mt-4">
+                        <table class="table table-bordered align-middle">
+                            <thead>
+                                <tr>
+                                    <th>Product Type</th>
+                                    <th>Products</th>
+                                    <th>Avg Score</th>
+                                    <th>Excellent %</th>
+                                    <th>Good %</th>
+                                    <th>Fair %</th>
+                                    <th>Poor %</th>
+                                    <th>Title</th>
+                                    <th>Description</th>
+                                    <th>Images</th>
+                                    <!-- <th>Attributes</th> -->
+                                    <!-- <th>Trend</th> -->
+                                    <!-- <th>Category</th> -->
+                                    <th>Actions</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <tr>
+                                    <td>CeraVe</td>
+                                    <td>3</td>
+                                    <td>92.3</td>
+                                    <td>100%</td>
+                                    <td>0%</td>
+                                    <td>0%</td>
+                                    <td>0%</td>
+                                    <td>9.2</td>
+                                    <td>18.7</td>
+                                    <td>22.3</td>
+                                    <!-- <td>19.7</td> -->
+                                    <!-- <td>+2.1</td> -->
+                                    <!-- <td><span class="category-tag">Skincare</span></td> -->
+                                    <td>
+                                        <button class="btn btn-view btn-sm">View</button>
+                                        <!-- <button class="btn btn-edit btn-sm">Edit</button> -->
+                                    </td>
+                                </tr>
+                                <!-- Add more rows as needed -->
+                            </tbody>
+                        </table>
+                    </div>
+
+                            </div> <!--end::Container-->
+            </div> <!--end::App Content-->
+        </main> <!--end::App Main--> <!--begin::Footer-->
+        {% include 'footer.html' %}
+
+    </div> 
+    <script src="https://code.jquery.com/jquery-3.7.1.min.js"
+        integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/overlayscrollbars@2.3.0/browser/overlayscrollbars.browser.es6.min.js"
+        integrity="sha256-H2VM7BKda+v2Z4+DRy69uknwxjyDRhszjXFhsL4gD3w=" crossorigin="anonymous"></script>
+    <!--end::Third Party Plugin(OverlayScrollbars)--><!--begin::Required Plugin(popperjs for Bootstrap 5)-->
+    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"
+        integrity="sha256-whL0tQWoY1Ku1iskqPFvmZ+CHsvmRWx/PIoEvIeWh4I=" crossorigin="anonymous"></script>
+    <!--end::Required Plugin(popperjs for Bootstrap 5)--><!--begin::Required Plugin(Bootstrap 5)-->
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js"
+        integrity="sha256-YMa+wAM6QkVyz999odX7lPRxkoYAan8suedu4k2Zur8=" crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
+    <!--end::Required Plugin(Bootstrap 5)--><!--begin::Required Plugin(AdminLTE)-->
+    <script src="{% static './js/adminlte.js' %}"></script>
+    <!--end::Required Plugin(AdminLTE)--><!--begin::OverlayScrollbars Configure-->
+    <script>
+        const SELECTOR_SIDEBAR_WRAPPER = ".sidebar-wrapper";
+        const Default = {
+            scrollbarTheme: "os-theme-light",
+            scrollbarAutoHide: "leave",
+            scrollbarClickScroll: true,
+        };
+        document.addEventListener("DOMContentLoaded", function () {
+            const sidebarWrapper = document.querySelector(SELECTOR_SIDEBAR_WRAPPER);
+            if (
+                sidebarWrapper &&
+                typeof OverlayScrollbarsGlobal?.OverlayScrollbars !== "undefined"
+            ) {
+                OverlayScrollbarsGlobal.OverlayScrollbars(sidebarWrapper, {
+                    scrollbars: {
+                        theme: Default.scrollbarTheme,
+                        autoHide: Default.scrollbarAutoHide,
+                        clickScroll: Default.scrollbarClickScroll,
+                    },
+                });
+            }
+        });
+        $(document).ready(function () {
+            $('.select2').select2({
+                theme: 'bootstrap4',
+                placeholder: 'Select Competitors'
+            });
+        });
+    </script> <!--end::OverlayScrollbars Configure--> <!-- OPTIONAL SCRIPTS --> <!-- apexcharts -->
+    <script src="https://cdn.jsdelivr.net/npm/apexcharts@3.37.1/dist/apexcharts.min.js"
+        integrity="sha256-+vh8GkaU7C9/wbSLIcwq82tQ2wTf44aOHA8HlBMwRI8=" crossorigin="anonymous"></script>
+    <script>
+        document.addEventListener('DOMContentLoaded', function () {
+            $('#full-page-loader').show();
+            fetch('/core/api/quality-metrics/', {
+                method: 'GET', // or 'POST' if your API expects POST
+                headers: {
+                    'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
+                }
+            })
+            .then(response => response.json())
+            .then(data => {
+                console.log("data",data);
+            })
+            .catch(error => {
+                $('#full-page-loader').hide();
+            });
+        });
+    </script>
+
+</body><!--end::Body-->
+
+</html>

+ 1 - 0
content_quality_tool_public/urls.py

@@ -12,6 +12,7 @@ urlpatterns = [
     path('content-scorecard/', views.getData, name='content-scorecard'),
     path('product-attributes/', views.getProductAttributes, name='product-attributes'),
     path('attribute-extraction/', views.getAttributeExtraction, name='attribute-extraction'),
+    path('content-quality-analytics/', views.contentQualityAnalysis, name='content-quality-analytics'),
 ]
 
 if settings.DEBUG:

+ 5 - 1
content_quality_tool_public/views.py

@@ -106,4 +106,8 @@ def getAttributeExtraction(request):
 # add-edit product type attributes page
 @login_required
 def getProductAttributes(request):
-    return render(request, 'product-attributes.html')
+    return render(request, 'product-attributes.html')
+
+@login_required
+def contentQualityAnalysis(request):
+    return render(request, 'product-performance-analysis.html')

+ 56 - 1
core/views.py

@@ -988,7 +988,61 @@ from rest_framework import status
 from django.db.models import Avg, Count
 from .models import Product, AttributeScore  # adjust the import path as needed
 
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from django.db.models import Avg, Count
+
 class ProductTypeQualityMetricsView(APIView):
+    """
+    API endpoint to fetch product type quality metrics.
+    Returns metrics for all products, ignoring query parameters.
+    """
+
+    def get(self, request):
+        print("****************** get called")
+        # Fetch all products, no filtering
+        queryset = Product.objects.all()
+        print("queryset", queryset)
+
+        scored = (
+            AttributeScore.objects.filter(product__in=queryset)
+            .annotate(
+                title_quality=Avg('details__title_quality'),
+                description_quality=Avg('details__description_quality'),
+                image_quality=Avg('details__image_score'),
+                attributes_quality=Avg('details__attributes'),
+            )
+            .values('product__product_type')
+            .annotate(
+                product_count=Count('product', distinct=True),
+                scored_product_count=Count('id'),
+                avg_overall_score=Avg('score'),
+                avg_title_quality=Avg('details__title_quality'),
+                avg_description_quality=Avg('details__description_quality'),
+                avg_image_quality=Avg('details__image_score'),
+                avg_attributes_quality=Avg('details__attributes'),
+            )
+        )
+
+        results = [
+            {
+                "product_type": item['product__product_type'],
+                "product_count": item['product_count'],
+                "scored_product_count": item['scored_product_count'],
+                "avg_overall_score": round(item['avg_overall_score'] or 0, 2),
+                "avg_title_quality_percent": round(item['avg_title_quality'] or 0, 2),
+                "avg_description_quality_percent": round(item['avg_description_quality'] or 0, 2),
+                "avg_image_quality_percent": round(item['avg_image_quality'] or 0, 2),
+                "avg_attributes_quality_percent": round(item['avg_attributes_quality'] or 0, 2),
+            }
+            for item in scored
+        ]
+
+        return Response(results, status=status.HTTP_200_OK)
+
+
+# class ProductTypeQualityMetricsView(APIView):
     """
     API endpoint to fetch product type quality metrics.
     Supports optional ?product_type=<type> query param.
@@ -997,6 +1051,7 @@ class ProductTypeQualityMetricsView(APIView):
     def get(self, request):
         product_type_filter = request.query_params.get('product_type', None)
         queryset = Product.objects.all()
+        print("queryset",queryset)
 
         if product_type_filter:
             queryset = queryset.filter(product_type=product_type_filter)
@@ -1009,7 +1064,7 @@ class ProductTypeQualityMetricsView(APIView):
                 image_quality=Avg('details__image_score'),
                 attributes_quality=Avg('details__attributes'),
             )
-            .values('product__product_type')
+            .values('product__category')
             .annotate(
                 product_count=Count('product', distinct=True),
                 scored_product_count=Count('id'),