[프로젝트] 안면 인식 기반 추천 프로그램 - (8)

julian·2025년 7월 15일

python

목록 보기
67/74

이제 마지막 최종적으로 관리자용 페이지를 좀 더 다듬기만 하면 된다.

현재 로그인하면 그냥 로그인이 완료되었고 네비게이션을 통해 주문 내역으로 들어가도록 되어있으나, 딱 로그인 하여 들어가면 한눈에 볼 수 있는 대시보드를 만드는 것이 좋아보인다.

1. 대시보드

구현하고자 하는 UI 예시는 위와 같다.

1.1. sales.py

총 세 군대로 나눠서 보면 된다.
금액 매출액, 막대그래프로 전날과 금일 매출금액을 비교, 금일 판매기록을 기록된 테이블

  • 금액 매출액
def get_today_sales_amount(db_path):
    conn=sqlite3.connect(db_path)
    cursor=conn.cursor()
    today=datetime.now().strftime("%Y-%m-%d")
    cursor.execute("""
        SELECT SUM(m.price)
        FROM sales s
        JOIN menu m ON s.menu_id=m.id
        WHERE DATE(s.timestamp)=?
    """, (today,))
    total=cursor.fetchone()[0]
    conn.close()
    return total or 0
  • 전날/금일 매출 비교
def get_sales_amount_comparison(db_path):
    conn=sqlite3.connect(db_path)
    cursor=conn.cursor()

    # 오늘 날짜
    today=datetime.now().date()
    # 어제 날짜
    yesterday=today - timedelta(days=1)

    # 오늘 매출
    cursor.execute(
        """
        SELECT COALESCE(SUM(m.price), 0)
        FROM sales s
        JOIN menu m ON s.menu_id=m.id
        WHERE DATE(s.timestamp)=?
        """,
        (today.strftime("%Y-%m-%d"),)
    )
    today_amount=cursor.fetchone()[0]

    # 어제 매출
    cursor.execute(
        """
        SELECT COALESCE(SUM(m.price), 0)
        FROM sales s
        JOIN menu m ON s.menu_id=m.id
        WHERE DATE(s.timestamp)=?
        """,
        (yesterday.strftime("%Y-%m-%d"),)
    )
    yesterday_amount=cursor.fetchone()[0]

    conn.close()

    return {"today": today_amount, "yesterday": yesterday_amount}
  • 판매기록 테이블
def get_today_sales_records(db_path):
    conn=sqlite3.connect(db_path)
    cursor=conn.cursor()
    today=datetime.now().strftime("%Y-%m-%d")
    cursor.execute("""
        SELECT 
            s.order_id,
            s.timestamp,
            s.sex,
            s.age_group,
            m.name,
            COUNT(*) AS quantity,
            m.price * COUNT(*) AS total_price
        FROM sales s
        JOIN menu m ON s.menu_id=m.id
        WHERE DATE(s.timestamp)=?
        GROUP BY s.order_id, m.id
        ORDER BY s.timestamp ASC
    """, (today,))
    raw_rows=cursor.fetchall()
    conn.close()

    # order_id -> 순번 매핑
    order_map={}
    seq=1
    processed=[]
    for order_id, timestamp, sex, age_group, menu_name, qty, total_price in raw_rows:
        if order_id not in order_map:
            order_map[order_id]=seq
            seq+=1
        processed.append((
            order_map[order_id],
            timestamp,
            sex,
            age_group,
            menu_name,
            qty,
            total_price
        ))
    return processed

1.2. routes.py

from src.db_utils.sales import insert_sales, get_sales_analysis, get_today_sales_amount, get_today_sales_records, get_sales_amount_comparison

# 관리자 대시보드
    @app.route("/admin/dashboard")
    def admin_dashboard():
        if not session.get("admin_logged_in"):
            return redirect(url_for("admin_login"))

        today_amount = get_today_sales_amount(DB_PATH)
        sales_compare = get_sales_amount_comparison(DB_PATH)
        today_records = get_today_sales_records(DB_PATH)

        return render_template(
            "admin/dashboard.html",
            today_amount=today_amount,
            sales_compare=sales_compare,
            today_records=today_records
        )

2. 분석 결과

분석 결과 부분에서 다양하게 볼 수 있도록 구현하느라 시간이 좀 많이 들었다.

@app.route("/admin/analyze")
    def admin_analyze():
        if not session.get("admin_logged_in"):
            return redirect(url_for("admin_login"))

        # request 파라미터 get
        start=request.args.get("start_date", "")
        end=request.args.get("end_date", "")
        group_by=request.args.get("group_by", "weekday")
        gender=request.args.get("gender", "all")
        age_group=request.args.get("age_group", "all")
        metric=request.args.get("metric", "revenue")

        # 결과 초기화
        summary_rows=[]  # 요약
        detail_rows=[]  # 상세
        chart_data=[]  # -> Chart.js

        # 날짜 선택 해야지만 진행
        if start and end:
            # [WHERE 절 조립과정]
            cond=["1=1"]  # 항상 True 조건
            params=[]  # 파라미터 값 저장용            
            # 날짜 범위 between
            cond.append("DATE(s.timestamp) BETWEEN ? AND ?")
            params.extend([start, end])
            # 성별 필터링
            if gender != "all":
                cond.append("s.sex=?")
                params.append(gender)
            # 연령대 필터링
            if age_group != "all":
                cond.append("s.age_group=?")
                params.append(age_group)
            # 최종 where절 문자열
            where_clause=" AND ".join(cond)

            # [그룹 필드 매핑]
            group_map={
                "year": "strftime('%Y', s.timestamp)",
                "month": "strftime('%Y-%m', s.timestamp)",
                "weekday": "strftime('%w', s.timestamp)",  # 0~6
                "hour": "strftime('%H', s.timestamp)",
                "age_group": "s.age_group"
            }
            group_field=group_map[group_by]

            # 정렬 컬럼
            # revenue: 매출 합계, quantity: 판매 건수
            order_by="revenue" if metric == "revenue" else "quantity"
            # [집계 SQL문]
            # LIMIT 20: 상위 20개 그룹만
            sql=f"""
                SELECT
                    {group_field} AS grp,
                    COUNT(*) AS quantity,
                    SUM(m.price) AS revenue
                FROM sales s
                JOIN menu m ON s.menu_id = m.id
                WHERE {where_clause}
                GROUP BY grp
                ORDER BY {order_by} DESC
                LIMIT 20
            """
            # DB 접속 및 실행
            conn=sqlite3.connect(DB_PATH)
            corsor=conn.cursor()
            corsor.execute(sql, params)
            rows=corsor.fetchall()
            conn.close()

            # Chart.js용 데이터 변환
            chart_data=[
                {"grp": r[0], "quantity": r[1], "revenue": r[2]}
                for r in rows
            ]

            # 요약/상세 테이블
            # 그룹X성별 통합
            for grp, quantity, revenue in rows:
                summary_rows.append((grp, gender, quantity, revenue))
            # [메뉴별 상세 집계 SQL문]
            sql2=f"""
                SELECT
                    {group_field} AS grp,
                    s.sex,
                    m.name,
                    COUNT(*) AS quantity,
                    SUM(m.price) AS revenue
                FROM sales s
                JOIN menu m ON s.menu_id = m.id
                WHERE {where_clause}
                GROUP BY grp, s.sex, m.name
                ORDER BY grp, s.sex, {order_by} DESC
            """
            # DB 접속
            conn=sqlite3.connect(DB_PATH)
            corsor=conn.cursor()
            corsor.execute(sql2, params)
            # 상세 데이터 fetchall()
            for grp, sex, menu, quantity, revenue in corsor.fetchall():
                detail_rows.append((grp, sex, menu, quantity, revenue))
            conn.close()

        # [결과 analyze.html 렌더링]
        return render_template("admin/analyze.html", request_args=request.args, group_by=group_by, metric=metric,
                            summary_rows=summary_rows, detail_rows=detail_rows, chart_data=chart_data)

이렇게해서 그룹핑을 통해 조건 조회를 많이 걸었다.
실제로 /admin/analyze 를 통해 보면 다음과 같다.


이와 같이 기간을 선택해야만 조회가 가능하도록 설정했으며,


그룹핑조건은 이와 같다.
이렇게하여 그룹핑으로 1차로 묶고, 필터링 조건을 추가로 걸었다.

  • 요일별 -> 성별/연령대 전체 -> 매출순 정렬

  • 시간별 -> 남성/20대 -> 판매량순 정렬

본인의 얼굴을 기준으로 넣었기 때문에 여성으로는 현재 확인이 불가능하다.

전체 동작으로 보면 다음과 같다.

3. 정리

이 프로젝트는 이정도에서 마무리 짓고자 한다.
여기서 더 발전될 사항은 모델적인 부분에서 더 많은 샘플들로 일반화 성능을 끌어올리는 것이 중요하다고 생각한다.
진행하다보니 자꾸 모델적인 부분이 아닌 다른 쪽에 치중하게 되는 거 같아서 이쯤에서 마무리 짓고자 한다.

자세한 코드들은 깃허브를 참고하시길.
https://github.com/itsjulianjeong/ADFOU/tree/main

profile
AI Model Developer

0개의 댓글