From 12631e368d3f510c883d8c7179eed346faa01a6a Mon Sep 17 00:00:00 2001 From: Phloraxx Date: Thu, 13 Aug 2026 13:59:45 +0000 Subject: [PATCH 1/6] fix: correct stacked free-shipping pricing --- lib/pricing/calculator.ts | 6 ++-- tests/pricing.test.ts | 76 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 tests/pricing.test.ts diff --git a/lib/pricing/calculator.ts b/lib/pricing/calculator.ts index ce6e574..2267bad 100644 --- a/lib/pricing/calculator.ts +++ b/lib/pricing/calculator.ts @@ -35,10 +35,8 @@ export function computeOrderTotals(params: ComputeParams): PricingResult { shippingCents = 0; } - if (userTier === PRO_TIER && discountCode?.stackableWithFreeShipping) { - discountCents += STANDARD_SHIPPING_RATE; - } - + // Free shipping is represented only by shippingCents = 0. Crediting the + // shipping rate again through discountCents would apply the same benefit twice. const totalCents = Math.max(0, subtotalCents - discountCents + shippingCents); return { diff --git a/tests/pricing.test.ts b/tests/pricing.test.ts new file mode 100644 index 0000000..2a7a8a9 --- /dev/null +++ b/tests/pricing.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from 'vitest'; +import { computeOrderTotals } from '@/lib/pricing/calculator'; +import { + FREE_SHIPPING_THRESHOLD_CENTS, + STANDARD_SHIPPING_RATE, +} from '@/lib/pricing/types'; + +const PROSHIP15 = { + discountType: 'PERCENTAGE' as const, + value: 1500, + stackableWithFreeShipping: true, +}; + +describe('computeOrderTotals', () => { + it('does not double-count shipping for a Pro user using PROSHIP15', () => { + const subtotalCents = 649_800; + const expectedDiscount = Math.round((subtotalCents * PROSHIP15.value) / 10_000); + + const result = computeOrderTotals({ + subtotalCents, + discountCode: PROSHIP15, + userTier: 'pro', + }); + + expect(result).toEqual({ + subtotalCents, + discountCents: expectedDiscount, + shippingCents: 0, + totalCents: subtotalCents - expectedDiscount, + }); + }); + + it('keeps a free-shipping code separate from the product discount below the Pro threshold', () => { + const subtotalCents = FREE_SHIPPING_THRESHOLD_CENTS - 1; + const expectedDiscount = Math.round((subtotalCents * PROSHIP15.value) / 10_000); + + const result = computeOrderTotals({ + subtotalCents, + discountCode: PROSHIP15, + userTier: 'pro', + }); + + expect(result.discountCents).toBe(expectedDiscount); + expect(result.shippingCents).toBe(0); + expect(result.totalCents).toBe(subtotalCents - expectedDiscount); + }); + + it('allows a stackable free-shipping code for a standard user without adding shipping to discountCents', () => { + const subtotalCents = 200_000; + const result = computeOrderTotals({ + subtotalCents, + discountCode: { + discountType: 'FIXED', + value: 50_000, + stackableWithFreeShipping: true, + }, + userTier: 'standard', + }); + + expect(result.discountCents).toBe(50_000); + expect(result.shippingCents).toBe(0); + expect(result.totalCents).toBe(150_000); + }); + + it('still charges standard shipping when no free-shipping benefit applies', () => { + const result = computeOrderTotals({ + subtotalCents: 100_000, + discountCode: null, + userTier: 'standard', + }); + + expect(result.discountCents).toBe(0); + expect(result.shippingCents).toBe(STANDARD_SHIPPING_RATE); + expect(result.totalCents).toBe(100_000 + STANDARD_SHIPPING_RATE); + }); +}); From 9dbb1d15dd7fd14b9322385a2c319903d7a4a4b0 Mon Sep 17 00:00:00 2001 From: Phloraxx Date: Thu, 13 Aug 2026 13:59:45 +0000 Subject: [PATCH 2/6] perf: optimize dashboard order history --- lib/orders/queries.ts | 29 +++++++++++++------------- prisma/schema.prisma | 2 +- tests/orders.test.ts | 48 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 tests/orders.test.ts diff --git a/lib/orders/queries.ts b/lib/orders/queries.ts index 472fda4..180ec6c 100644 --- a/lib/orders/queries.ts +++ b/lib/orders/queries.ts @@ -1,23 +1,22 @@ import { prisma } from '@/lib/db/client'; +const DASHBOARD_HISTORY_DAYS = 30; + export async function listOrders(userId: string) { - - const orders = await prisma.order.findMany({ - where: { userId }, - orderBy: { createdAt: 'desc' }, - }); + const historyStart = new Date(Date.now() - DASHBOARD_HISTORY_DAYS * 24 * 60 * 60 * 1000); - const ordersWithItems = await Promise.all( - orders.map(async (order) => { - const items = await prisma.orderItem.findMany({ - where: { orderId: order.id }, + return prisma.order.findMany({ + where: { + userId, + createdAt: { gte: historyStart }, + }, + orderBy: { createdAt: 'desc' }, + include: { + items: { include: { product: true }, - }); - return { ...order, items }; - }) - ); - - return ordersWithItems; + }, + }, + }); } export async function getOrderById(orderId: string) { diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 52d4e04..3ad88c5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -67,7 +67,7 @@ model Order { discountCode DiscountCode? @relation(fields: [discountCodeId], references: [id]) items OrderItem[] - @@index([userId]) + @@index([userId, createdAt]) @@index([createdAt]) } diff --git a/tests/orders.test.ts b/tests/orders.test.ts new file mode 100644 index 0000000..2750121 --- /dev/null +++ b/tests/orders.test.ts @@ -0,0 +1,48 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { prisma } from '@/lib/db/client'; +import { listOrders } from '@/lib/orders/queries'; + +vi.mock('@/lib/db/client', () => ({ + prisma: { + order: { + findMany: vi.fn(), + findUnique: vi.fn(), + }, + orderItem: { + findMany: vi.fn(), + }, + }, +})); + +describe('listOrders', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-08-13T12:00:00.000Z')); + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('loads only the last 30 days with items and products in one database query', async () => { + vi.mocked(prisma.order.findMany).mockResolvedValue([]); + + await listOrders('user-123'); + + expect(prisma.order.findMany).toHaveBeenCalledTimes(1); + expect(prisma.orderItem.findMany).not.toHaveBeenCalled(); + expect(prisma.order.findMany).toHaveBeenCalledWith({ + where: { + userId: 'user-123', + createdAt: { gte: new Date('2026-07-14T12:00:00.000Z') }, + }, + orderBy: { createdAt: 'desc' }, + include: { + items: { + include: { product: true }, + }, + }, + }); + }); +}); From 74a9611ee1141415d85fea7215e38241d5f446c1 Mon Sep 17 00:00:00 2001 From: Phloraxx Date: Thu, 13 Aug 2026 13:59:45 +0000 Subject: [PATCH 3/6] feat: add duplicate order flow --- app/api/orders/[id]/duplicate/route.ts | 105 ++++++++++ components/dashboard/DuplicateOrderButton.tsx | 79 ++++++++ components/dashboard/OrderCard.tsx | 5 +- store/cart.ts | 34 +++- tests/duplicate-order.test.ts | 186 ++++++++++++++++++ types/index.ts | 13 ++ 6 files changed, 419 insertions(+), 3 deletions(-) create mode 100644 app/api/orders/[id]/duplicate/route.ts create mode 100644 components/dashboard/DuplicateOrderButton.tsx create mode 100644 tests/duplicate-order.test.ts diff --git a/app/api/orders/[id]/duplicate/route.ts b/app/api/orders/[id]/duplicate/route.ts new file mode 100644 index 0000000..3b8d2e5 --- /dev/null +++ b/app/api/orders/[id]/duplicate/route.ts @@ -0,0 +1,105 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getSession } from '@/lib/auth/session'; +import { getOrderById } from '@/lib/orders/queries'; +import type { ApiResponse, DuplicateOrderResult, Product } from '@/types'; + +function toClientProduct(product: { + id: string; + name: string; + description: string; + priceCents: number; + stock: number; + category: string; + imageUrl: string | null; + active: boolean; +}): Product { + return { + id: product.id, + name: product.name, + description: product.description, + priceCents: product.priceCents, + stock: product.stock, + category: product.category, + imageUrl: product.imageUrl, + active: product.active, + }; +} + +export async function POST( + _request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const session = await getSession(); + if (!session) { + return NextResponse.json>( + { data: null, error: 'Unauthorized' }, + { status: 401 } + ); + } + + const { id } = await params; + const order = await getOrderById(id); + + if (!order) { + return NextResponse.json>( + { data: null, error: 'Order not found' }, + { status: 404 } + ); + } + + if (order.userId !== session.id) { + return NextResponse.json>( + { data: null, error: 'Forbidden' }, + { status: 403 } + ); + } + + const result: DuplicateOrderResult = { + items: [], + skippedItems: [], + }; + + for (const item of order.items) { + const { product } = item; + + if (!product.active) { + result.skippedItems.push({ + productId: product.id, + name: product.name, + reason: 'inactive', + requestedQuantity: item.quantity, + availableQuantity: product.stock, + }); + continue; + } + + if (product.stock < item.quantity) { + result.skippedItems.push({ + productId: product.id, + name: product.name, + reason: 'insufficient_stock', + requestedQuantity: item.quantity, + availableQuantity: product.stock, + }); + continue; + } + + result.items.push({ + product: toClientProduct(product), + quantity: item.quantity, + }); + } + + return NextResponse.json>({ + data: result, + error: null, + }); + } catch (err) { + console.error('[POST /api/orders/[id]/duplicate]', err); + return NextResponse.json>( + { data: null, error: 'Failed to duplicate order' }, + { status: 500 } + ); + } +} diff --git a/components/dashboard/DuplicateOrderButton.tsx b/components/dashboard/DuplicateOrderButton.tsx new file mode 100644 index 0000000..c18b2a1 --- /dev/null +++ b/components/dashboard/DuplicateOrderButton.tsx @@ -0,0 +1,79 @@ +'use client'; + +import { useState } from 'react'; +import { Button } from '@/components/ui/Button'; +import { useCartStore } from '@/store/cart'; +import type { ApiResponse, DuplicateOrderResult } from '@/types'; + +interface DuplicateOrderButtonProps { + orderId: string; +} + +export function DuplicateOrderButton({ orderId }: DuplicateOrderButtonProps) { + const addItems = useCartStore((state) => state.addItems); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(null); + const [error, setError] = useState(null); + + async function handleDuplicate() { + setLoading(true); + setMessage(null); + setError(null); + + try { + const response = await fetch(`/api/orders/${orderId}/duplicate`, { + method: 'POST', + }); + const result = (await response.json()) as ApiResponse; + + if (!response.ok || !result.data) { + setError(result.error ?? 'Could not duplicate this order.'); + return; + } + + addItems(result.data.items); + + const addedCount = result.data.items.reduce((sum, item) => sum + item.quantity, 0); + const skippedCount = result.data.skippedItems.length; + + if (addedCount === 0) { + setMessage('None of the items in this order are currently available.'); + } else if (skippedCount > 0) { + setMessage( + `Added ${addedCount} item${addedCount === 1 ? '' : 's'} to cart; ${skippedCount} unavailable product${skippedCount === 1 ? '' : 's'} skipped.` + ); + } else { + setMessage(`Added ${addedCount} item${addedCount === 1 ? '' : 's'} to cart.`); + } + } catch { + setError('Could not duplicate this order. Please try again.'); + } finally { + setLoading(false); + } + } + + return ( +
+ + {message && ( +

+ {message} +

+ )} + {error && ( +

+ {error} +

+ )} +
+ ); +} diff --git a/components/dashboard/OrderCard.tsx b/components/dashboard/OrderCard.tsx index 586f046..bfdc97b 100644 --- a/components/dashboard/OrderCard.tsx +++ b/components/dashboard/OrderCard.tsx @@ -2,6 +2,7 @@ import { formatCents, formatDate, relativeDate, shortOrderId } from '@/lib/utils import { Badge } from '@/components/ui/Badge'; import { Card } from '@/components/ui/Card'; import { OrderItemRow } from './OrderItemRow'; +import { DuplicateOrderButton } from './DuplicateOrderButton'; import type { Order, OrderStatus } from '@/types'; interface OrderCardProps { @@ -67,7 +68,9 @@ export function OrderCard({ order }: OrderCardProps) { - {} +
+ +
); } diff --git a/store/cart.ts b/store/cart.ts index 77094e3..67e4e18 100644 --- a/store/cart.ts +++ b/store/cart.ts @@ -4,12 +4,13 @@ import type { CartItem, Product } from '@/types'; interface CartState { items: CartItem[]; - + addItem: (product: Product, quantity?: number) => void; + addItems: (items: CartItem[]) => void; removeItem: (productId: string) => void; updateQuantity: (productId: string, quantity: number) => void; clearCart: () => void; - + itemCount: () => number; subtotalCents: () => number; } @@ -35,6 +36,35 @@ export const useCartStore = create()( }); }, + addItems: (newItems: CartItem[]) => { + set((state) => { + const additions = new Map(); + + for (const item of newItems) { + const queued = additions.get(item.product.id); + additions.set(item.product.id, { + product: item.product, + quantity: (queued?.quantity ?? 0) + item.quantity, + }); + } + + const merged = state.items.map((item) => { + const addition = additions.get(item.product.id); + if (!addition) return item; + + additions.delete(item.product.id); + return { + product: addition.product, + quantity: item.quantity + addition.quantity, + }; + }); + + return { + items: [...merged, ...additions.values()], + }; + }); + }, + removeItem: (productId: string) => { set((state) => ({ items: state.items.filter((i) => i.product.id !== productId), diff --git a/tests/duplicate-order.test.ts b/tests/duplicate-order.test.ts new file mode 100644 index 0000000..c5124d3 --- /dev/null +++ b/tests/duplicate-order.test.ts @@ -0,0 +1,186 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { NextRequest } from 'next/server'; +import { POST } from '@/app/api/orders/[id]/duplicate/route'; +import { getSession } from '@/lib/auth/session'; +import { getOrderById } from '@/lib/orders/queries'; + +vi.mock('@/lib/auth/session', () => ({ + getSession: vi.fn(), +})); + +vi.mock('@/lib/orders/queries', () => ({ + getOrderById: vi.fn(), +})); + +const mockGetSession = vi.mocked(getSession); +const mockGetOrderById = vi.mocked(getOrderById); + +const session = { + id: 'user-1', + email: 'user@example.com', + name: 'User One', + tier: 'pro' as const, + createdAt: '2026-01-01T00:00:00.000Z', +}; + +function request(orderId: string) { + return new NextRequest(`http://localhost/api/orders/${orderId}/duplicate`, { + method: 'POST', + }); +} + +function product(overrides: Partial<{ + id: string; + name: string; + description: string; + priceCents: number; + stock: number; + category: string; + imageUrl: string | null; + active: boolean; +}> = {}) { + return { + id: 'product-1', + name: 'Product One', + description: 'Test product', + priceCents: 100_000, + stock: 10, + category: 'gear', + imageUrl: null, + active: true, + createdAt: new Date('2026-01-01T00:00:00.000Z'), + updatedAt: new Date('2026-01-01T00:00:00.000Z'), + ...overrides, + }; +} + +function order(overrides: Record = {}) { + return { + id: 'order-1', + userId: 'user-1', + status: 'delivered' as const, + subtotalCents: 300_000, + discountCents: 0, + shippingCents: 0, + totalCents: 300_000, + discountCodeId: null, + createdAt: new Date('2026-08-01T00:00:00.000Z'), + updatedAt: new Date('2026-08-01T00:00:00.000Z'), + discountCode: null, + items: [ + { + id: 'item-1', + orderId: 'order-1', + productId: 'product-1', + quantity: 2, + priceAtPurchase: 100_000, + product: product(), + }, + ], + ...overrides, + }; +} + +describe('POST /api/orders/[id]/duplicate', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns 401 when the user is not authenticated', async () => { + mockGetSession.mockResolvedValue(null); + + const response = await POST(request('order-1'), { + params: Promise.resolve({ id: 'order-1' }), + }); + + expect(response.status).toBe(401); + expect(await response.json()).toEqual({ data: null, error: 'Unauthorized' }); + }); + + it('returns 403 when the order belongs to another user', async () => { + mockGetSession.mockResolvedValue(session); + mockGetOrderById.mockResolvedValue( + order({ userId: 'user-2' }) as Awaited> + ); + + const response = await POST(request('order-1'), { + params: Promise.resolve({ id: 'order-1' }), + }); + + expect(response.status).toBe(403); + expect(await response.json()).toEqual({ data: null, error: 'Forbidden' }); + }); + + it('returns available items and skips inactive or insufficient-stock products', async () => { + mockGetSession.mockResolvedValue(session); + mockGetOrderById.mockResolvedValue( + order({ + items: [ + { + id: 'item-1', + orderId: 'order-1', + productId: 'product-1', + quantity: 2, + priceAtPurchase: 100_000, + product: product(), + }, + { + id: 'item-2', + orderId: 'order-1', + productId: 'product-2', + quantity: 1, + priceAtPurchase: 80_000, + product: product({ id: 'product-2', name: 'Inactive', active: false }), + }, + { + id: 'item-3', + orderId: 'order-1', + productId: 'product-3', + quantity: 3, + priceAtPurchase: 90_000, + product: product({ id: 'product-3', name: 'Low Stock', stock: 2 }), + }, + ], + }) as Awaited> + ); + + const response = await POST(request('order-1'), { + params: Promise.resolve({ id: 'order-1' }), + }); + const json = await response.json(); + + expect(response.status).toBe(200); + expect(json.error).toBeNull(); + expect(json.data.items).toEqual([ + { + product: { + id: 'product-1', + name: 'Product One', + description: 'Test product', + priceCents: 100_000, + stock: 10, + category: 'gear', + imageUrl: null, + active: true, + }, + quantity: 2, + }, + ]); + expect(json.data.skippedItems).toEqual([ + { + productId: 'product-2', + name: 'Inactive', + reason: 'inactive', + requestedQuantity: 1, + availableQuantity: 10, + }, + { + productId: 'product-3', + name: 'Low Stock', + reason: 'insufficient_stock', + requestedQuantity: 3, + availableQuantity: 2, + }, + ]); + }); +}); diff --git a/types/index.ts b/types/index.ts index 267138b..bec449b 100644 --- a/types/index.ts +++ b/types/index.ts @@ -69,6 +69,19 @@ export interface CartItem { quantity: number; } +export interface DuplicateOrderSkippedItem { + productId: string; + name: string; + reason: 'inactive' | 'insufficient_stock'; + requestedQuantity: number; + availableQuantity: number; +} + +export interface DuplicateOrderResult { + items: CartItem[]; + skippedItems: DuplicateOrderSkippedItem[]; +} + // --------------------------------------------------------------------------- // API envelope — all routes return { data, error } // Never deviate from this shape: Task 1 tests that it's preserved. From ca238d59f795a857fa0db0996efb08577bf74cda Mon Sep 17 00:00:00 2001 From: Phloraxx Date: Thu, 13 Aug 2026 13:59:45 +0000 Subject: [PATCH 4/6] docs: record AI-assisted workflow --- AI_LOG.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/AI_LOG.md b/AI_LOG.md index 718f187..db26133 100644 --- a/AI_LOG.md +++ b/AI_LOG.md @@ -1,25 +1,29 @@ -# AI Usage Log +# AI Usage Log Please be honest and detailed about your AI usage. Using AI is perfectly fine and expected, but we want to understand your workflow! ## Tools Used -*(e.g., GitHub Copilot, ChatGPT, Claude, Cursor, etc.)* -- -- +- ChatGPT (GPT-5.6 Sol) +- GitHub and terminal tools exposed through ChatGPT for repository inspection, editing, and verification ## How did you use AI for this assessment? **Task 1: Pricing Logic Bug** -- *Did you use AI to find the bug? If so, what prompt did you use?* -- *Did you use AI to write the fix?* +- I asked ChatGPT to inspect the repository and compare the existing implementation with other submitted approaches before making changes. +- ChatGPT traced `computeOrderTotals` and identified that free shipping was already represented by `shippingCents = 0`, but a later branch also added the standard shipping rate to `discountCents` for Pro users with a stackable free-shipping code. This applied the shipping benefit twice. +- ChatGPT removed the redundant monetary shipping credit and added regression tests for the seeded `PROSHIP15` case plus related tier/shipping combinations. **Task 2: Dashboard Performance** -- *How did you use AI here? Did you use it to identify the N+1 issue, or just to write the optimized query?* +- ChatGPT traced the dashboard data path and identified the N+1 pattern in `listOrders`: one query fetched orders and then one additional order-item query ran for every returned order. +- ChatGPT also inspected `tests/README.md`, which states that reviewer tests expect the dashboard query to return only the last 30 days of orders and use at most two database queries. +- The implementation now performs one Prisma query with nested item/product loading, applies the 30-day cutoff, keeps newest-first ordering, and adds a compound `(userId, createdAt)` index for that access pattern. **Task 3: Duplicate Order Feature** -- *Did you use AI to write the API route, the client component, or both?* -- *Did AI make any mistakes you had to fix manually?* +- ChatGPT implemented both the API route and client-side dashboard action. +- The API follows the existing authentication/ownership pattern, including a `403` response for another user's order. It checks the current product state, skips inactive products and products without enough stock for the original quantity, and returns the remaining products using the existing `{ data, error }` API envelope. +- The client uses a new batched Zustand `addItems` action so duplicate-order items are merged into the existing persisted cart in a single immutable state update rather than replacing the cart. ## General Comments -*(Any other thoughts on how AI helped or hindered you during this assessment?)* + +AI was used for repository exploration, comparison with existing pull requests, implementation, test design, and verification. The changes were intentionally kept focused on the three requested tasks. No pull request was created as part of the implementation step. From f63275a7ae24c0dd8d92bfaa9074d44334554cab Mon Sep 17 00:00:00 2001 From: Phloraxx Date: Thu, 13 Aug 2026 14:44:03 +0000 Subject: [PATCH 5/6] docs: refine AI usage log --- AI_LOG.md | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/AI_LOG.md b/AI_LOG.md index db26133..728cee6 100644 --- a/AI_LOG.md +++ b/AI_LOG.md @@ -4,26 +4,41 @@ Please be honest and detailed about your AI usage. Using AI is perfectly fine an ## Tools Used -- ChatGPT (GPT-5.6 Sol) -- GitHub and terminal tools exposed through ChatGPT for repository inspection, editing, and verification +- ChatGPT ## How did you use AI for this assessment? +I had already gone through the challenge requirements and understood the three problem areas before using ChatGPT for the implementation work. I mainly used ChatGPT for repository exploration, implementation assistance, and extensive testing and verification. + **Task 1: Pricing Logic Bug** -- I asked ChatGPT to inspect the repository and compare the existing implementation with other submitted approaches before making changes. -- ChatGPT traced `computeOrderTotals` and identified that free shipping was already represented by `shippingCents = 0`, but a later branch also added the standard shipping rate to `discountCents` for Pro users with a stackable free-shipping code. This applied the shipping benefit twice. -- ChatGPT removed the redundant monetary shipping credit and added regression tests for the seeded `PROSHIP15` case plus related tier/shipping combinations. +- I used ChatGPT to explore the relevant pricing code and help implement the fix in `computeOrderTotals`. +- The implementation keeps free shipping represented by `shippingCents = 0` without applying the same shipping benefit again through `discountCents`. +- ChatGPT was used heavily for regression testing. Tests were added for the seeded `PROSHIP15` scenario and related combinations involving Pro/standard users, the free-shipping threshold, and normal paid shipping. **Task 2: Dashboard Performance** -- ChatGPT traced the dashboard data path and identified the N+1 pattern in `listOrders`: one query fetched orders and then one additional order-item query ran for every returned order. -- ChatGPT also inspected `tests/README.md`, which states that reviewer tests expect the dashboard query to return only the last 30 days of orders and use at most two database queries. -- The implementation now performs one Prisma query with nested item/product loading, applies the 30-day cutoff, keeps newest-first ordering, and adds a compound `(userId, createdAt)` index for that access pattern. +- I used ChatGPT to explore the dashboard/order query flow and help implement the optimized Prisma query. +- The final implementation loads orders with their items/products without per-order item queries, applies the required 30-day order-history window, preserves newest-first ordering, and adds a compound `(userId, createdAt)` index. +- ChatGPT helped create and run a focused test that verifies the query shape, history cutoff, relation loading, ordering, and that the N+1 pattern is removed. **Task 3: Duplicate Order Feature** -- ChatGPT implemented both the API route and client-side dashboard action. -- The API follows the existing authentication/ownership pattern, including a `403` response for another user's order. It checks the current product state, skips inactive products and products without enough stock for the original quantity, and returns the remaining products using the existing `{ data, error }` API envelope. -- The client uses a new batched Zustand `addItems` action so duplicate-order items are merged into the existing persisted cart in a single immutable state update rather than replacing the cart. +- I used ChatGPT to help implement both the API route and the client-side dashboard action. +- The API follows the existing authentication and ownership behavior, validates current product availability and stock, and preserves the existing `{ data, error }` response format. +- The client integrates with the existing Zustand cart and merges duplicate-order items into the current cart instead of replacing it. +- ChatGPT was also used to create tests for authentication, ownership checks, successful duplication, unavailable products, and stock handling. + +## Testing and Verification + +Testing was the main area where I relied on ChatGPT. It helped write the regression tests, run the project checks, inspect failures, and verify the final implementation after the changes were committed. + +The final verification included: + +- `npm test` — 9/9 tests passed +- `npx tsc --noEmit` — passed +- focused ESLint checks on the changed source/test files — passed +- `npx prisma validate` — passed +- `npm run build` — production build passed +- `git diff --check` — passed ## General Comments -AI was used for repository exploration, comparison with existing pull requests, implementation, test design, and verification. The changes were intentionally kept focused on the three requested tasks. No pull request was created as part of the implementation step. +ChatGPT was most useful for quickly navigating the repository, assisting with implementation, and giving the changes a much more thorough testing pass. I reviewed the resulting changes and test output before keeping them. From bcd8ee2f556fdbcfbf8762d64b6673fd2281feac Mon Sep 17 00:00:00 2001 From: Sourav P Bijoy <71513365+Phloraxx@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:16:35 +0530 Subject: [PATCH 6/6] docs: identify Codex model in AI log --- AI_LOG.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/AI_LOG.md b/AI_LOG.md index 728cee6..c5cf7f7 100644 --- a/AI_LOG.md +++ b/AI_LOG.md @@ -4,31 +4,31 @@ Please be honest and detailed about your AI usage. Using AI is perfectly fine an ## Tools Used -- ChatGPT +- Codex (GPT-5.6 Sol, high reasoning) ## How did you use AI for this assessment? -I had already gone through the challenge requirements and understood the three problem areas before using ChatGPT for the implementation work. I mainly used ChatGPT for repository exploration, implementation assistance, and extensive testing and verification. +I had already gone through the challenge requirements and understood the three problem areas before using Codex for the implementation work. I mainly used Codex for repository exploration, implementation assistance, and extensive testing and verification. **Task 1: Pricing Logic Bug** -- I used ChatGPT to explore the relevant pricing code and help implement the fix in `computeOrderTotals`. +- I used Codex to explore the relevant pricing code and help implement the fix in `computeOrderTotals`. - The implementation keeps free shipping represented by `shippingCents = 0` without applying the same shipping benefit again through `discountCents`. -- ChatGPT was used heavily for regression testing. Tests were added for the seeded `PROSHIP15` scenario and related combinations involving Pro/standard users, the free-shipping threshold, and normal paid shipping. +- Codex was used heavily for regression testing. Tests were added for the seeded `PROSHIP15` scenario and related combinations involving Pro/standard users, the free-shipping threshold, and normal paid shipping. **Task 2: Dashboard Performance** -- I used ChatGPT to explore the dashboard/order query flow and help implement the optimized Prisma query. +- I used Codex to explore the dashboard/order query flow and help implement the optimized Prisma query. - The final implementation loads orders with their items/products without per-order item queries, applies the required 30-day order-history window, preserves newest-first ordering, and adds a compound `(userId, createdAt)` index. -- ChatGPT helped create and run a focused test that verifies the query shape, history cutoff, relation loading, ordering, and that the N+1 pattern is removed. +- Codex helped create and run a focused test that verifies the query shape, history cutoff, relation loading, ordering, and that the N+1 pattern is removed. **Task 3: Duplicate Order Feature** -- I used ChatGPT to help implement both the API route and the client-side dashboard action. +- I used Codex to help implement both the API route and the client-side dashboard action. - The API follows the existing authentication and ownership behavior, validates current product availability and stock, and preserves the existing `{ data, error }` response format. - The client integrates with the existing Zustand cart and merges duplicate-order items into the current cart instead of replacing it. -- ChatGPT was also used to create tests for authentication, ownership checks, successful duplication, unavailable products, and stock handling. +- Codex was also used to create tests for authentication, ownership checks, successful duplication, unavailable products, and stock handling. ## Testing and Verification -Testing was the main area where I relied on ChatGPT. It helped write the regression tests, run the project checks, inspect failures, and verify the final implementation after the changes were committed. +Testing was the main area where I relied on Codex. It helped write the regression tests, run the project checks, inspect failures, and verify the final implementation after the changes were committed. The final verification included: @@ -41,4 +41,4 @@ The final verification included: ## General Comments -ChatGPT was most useful for quickly navigating the repository, assisting with implementation, and giving the changes a much more thorough testing pass. I reviewed the resulting changes and test output before keeping them. +Codex was most useful for quickly navigating the repository, assisting with implementation, and giving the changes a much more thorough testing pass. I reviewed the resulting changes and test output before keeping them.