diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 00000000..b5e8cfd4 --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,44 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +jobs: + claude-review: + # Optional: Filter by PR author + # if: | + # github.event.pull_request.user.login == 'external-contributor' || + # github.event.pull_request.user.login == 'new-developer' || + # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' + plugins: 'code-review@claude-code-plugins' + prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 00000000..6b15fac7 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,50 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr *)' + diff --git a/.mcp.json b/.mcp.json index 91163715..a2ef6397 100644 --- a/.mcp.json +++ b/.mcp.json @@ -5,7 +5,11 @@ "command": "npx", "args": [ "-y", - "@playwright/mcp@0.0.37" + "@playwright/mcp@0.0.37", + "--browser", + "msedge", + "--executable-path", + "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe" ], "env": {} }, diff --git a/ARCHITECTURE.html b/ARCHITECTURE.html new file mode 100644 index 00000000..d3913c4a --- /dev/null +++ b/ARCHITECTURE.html @@ -0,0 +1,718 @@ + + + + + + Factory Inventory Management System - Architecture + + + +
+
+

📦 Factory Inventory Management System

+

Full-Stack Demo with GitHub Integration

+
+ Vue 3 + Vite + FastAPI + Python + Real-Time +
+
+ + +
+

System Architecture

+
+
+
+ Frontend + Vue 3 + Vite
+ :3000 +
+ HTTP/JSON +
+ Backend + FastAPI
+ :8001 +
+ Read/Filter +
+ Data + JSON Files
+ In-Memory +
+
+
+ +

Architecture Overview

+

A clean three-tier architecture separating concerns between presentation, business logic, and data management.

+ +
+
+

🎨 Frontend Layer

+
    +
  • Vue 3 - Reactive UI framework
  • +
  • Composition API - Logical code organization
  • +
  • Vite - Fast build tool
  • +
  • Vue Router - Client-side navigation
  • +
  • Axios - HTTP client
  • +
+
+ +
+

⚙️ Backend Layer

+
    +
  • FastAPI - Modern Python web framework
  • +
  • Pydantic - Data validation
  • +
  • CORS - Cross-origin requests
  • +
  • Python 3.x - Core runtime
  • +
  • In-Memory - Data storage
  • +
+
+ +
+

💾 Data Layer

+
    +
  • JSON Files - Data persistence
  • +
  • Mock Data - Pre-populated datasets
  • +
  • 7 Collections - Inventory, Orders, etc.
  • +
  • No Database - Demo simplicity
  • +
  • In-Memory - Runtime filtering
  • +
+
+
+
+ + +
+

Data Flow

+ +
+ User Interaction → Filter Selection
+ User selects filters (warehouse, category, status, time period) in the UI +
+ +
+ Filter Propagation → API Call
+ Filters are converted to query parameters: ?warehouse=SF&category=boards +
+ +
+ Backend Processing → Data Filtering
+ FastAPI endpoint receives request, validates with Pydantic, filters in-memory data +
+ +
+ Response → JSON Serialization
+ Filtered results are serialized to JSON and returned via HTTP +
+ +
+ Frontend Update → Reactive Rendering
+ Vue receives JSON, updates reactive refs, computed properties recalculate, UI re-renders +
+ +

Key Design Patterns

+
+

Filter System: 4 universal filters (Time Period, Warehouse, Category, Status) apply consistently across all data views via query parameters

+

Reactivity: Raw data stored in Vue refs, derived data computed via computed properties for automatic UI updates

+

Validation: Pydantic models on backend validate data structure; Composition API composables share filtering logic across components

+
+
+ + +
+

Tech Stack

+ +
+
+

Frontend Dependencies

+
    +
  • vue@^3.4.21
  • +
  • vue-router@^4.3.0
  • +
  • axios@^1.6.7
  • +
  • vite@^5.2.0
  • +
  • @vitejs/plugin-vue@^5.0.4
  • +
+
+ +
+

Backend Dependencies

+
    +
  • FastAPI
  • +
  • Pydantic
  • +
  • CORS Middleware
  • +
  • Python 3.x
  • +
  • uv (package manager)
  • +
+
+ +
+

Development Tools

+
    +
  • Vite - Dev server & bundler
  • +
  • Vue DevTools - Browser extension
  • +
  • FastAPI Docs - Swagger UI
  • +
  • Git - Version control
  • +
  • npm - Frontend package management
  • +
+
+
+
+ + +
+

API Endpoints

+ +

Inventory Management

+
+
+ GET + /api/inventory +
Get all inventory items. Filters: warehouse, category
+
+
+ GET + /api/inventory/{item_id} +
Get specific inventory item details
+
+
+ +

Orders & Fulfillment

+
+
+ GET + /api/orders +
Get all orders. Filters: warehouse, category, status, month
+
+
+ GET + /api/orders/{order_id} +
Get specific order details
+
+
+ GET + /api/backlog +
Get backlog items (unfulfilled orders)
+
+
+ +

Analytics & Forecasting

+
+
+ GET + /api/demand +
Get demand forecasts and trends
+
+
+ GET + /api/dashboard/summary +
Get dashboard KPIs. Filters: warehouse, category, status, month
+
+
+ +

Financial Data

+
+
+ GET + /api/spending/summary +
Get total spending overview
+
+
+ GET + /api/spending/monthly +
Get monthly spending breakdown
+
+
+ GET + /api/spending/categories +
Get spending by category
+
+
+ GET + /api/spending/transactions +
Get detailed transaction log
+
+
+ +

Reporting

+
+
+ GET + /api/reports/quarterly +
Get quarterly performance reports
+
+
+ GET + /api/reports/monthly-trends +
Get monthly trend analysis
+
+
+
+ + +
+

Frontend Components

+ +

Views (Page-Level Components)

+
+
+

📊 Dashboard

+

System KPIs, inventory status, pending orders, and key metrics overview

+
+
+

📦 Inventory

+

Inventory items, stock levels, warehouse locations, and reorder points

+
+
+

📋 Orders

+

Active and completed orders, customer details, and delivery status

+
+
+

💰 Spending

+

Financial overview, monthly spending, categories, and transaction history

+
+
+

📈 Demand Forecast

+

Demand predictions, trends, and forecasting analysis

+
+
+

📄 Reports

+

Quarterly reports, trend analysis, and business intelligence

+
+
+ +

UI Components

+
+
+

FilterBar

+

Global filter controls for warehouse, category, status, and time period

+
+
+

ProfileMenu

+

User profile access and logout functionality

+
+
+

LanguageSwitcher

+

i18n support for English and Japanese locales

+
+
+

Modal Components

+

Detail modals for inventory, orders, backlog, and costs

+
+
+ +

Composables (Shared Logic)

+
+
+

useFilters

+

Manages filter state and current filter values across the app

+
+
+

useAuth

+

Authentication state, current user, and profile information

+
+
+

useI18n

+

Internationalization, translations, and locale switching

+
+
+
+ + +
+

Data Collections

+ +
+
+

📦 Inventory Items

+

SKU, name, category, warehouse location, quantity, reorder point, unit cost

+
+
+

📋 Orders

+

Order number, customer, items, status, dates, total value, warehouse

+
+
+

⏳ Backlog Items

+

Unfulfilled orders, quantities needed/available, days delayed

+
+
+

📈 Demand Forecasts

+

Item SKU, current demand, forecasted demand, trend, period

+
+
+

💵 Spending Data

+

Monthly totals, category breakdowns, transaction details

+
+
+

🛒 Purchase Orders

+

PO tracking, backlog item links, supplier information

+
+
+
+ + +
+

Quick Start

+ +

Backend Setup

+
+ + $ cd server
+ $ uv run python main.py
+ # Backend running at http://localhost:8001 +
+
+ +

Frontend Setup

+
+ + $ cd client
+ $ npm install
+ $ npm run dev
+ # Frontend running at http://localhost:3000 +
+
+ +

API Documentation

+

Interactive API documentation available at:

+
+ http://localhost:8001/docs +
+
+ + +
+

Key Features

+ +
+
+ 🔍 Universal Filtering +

4 filters consistently applied across all data views via query parameters

+
+
+ 🌍 Internationalization +

Full i18n support for English and Japanese with locale-specific formatting

+
+
+ 📊 Analytics Dashboard +

Real-time KPIs, demand forecasts, and spending analysis

+
+
+ 🔄 Reactive Updates +

Vue 3 reactivity ensures UI stays in sync with data changes

+
+
+ ⚡ Fast Performance +

Vite-powered development with instant Hot Module Replacement

+
+
+ 📐 Clean Architecture +

Separation of concerns with modular, reusable components

+
+
+
+ +
+

Project Structure

+
+ +inventory-management/ +├── client/ # Vue 3 Frontend +│ ├── src/ +│ │ ├── views/ # Page components +│ │ ├── components/ # Reusable UI components +│ │ ├── composables/ # Shared logic +│ │ ├── locales/ # i18n translations +│ │ ├── api.js # API client +│ │ └── main.js # App entry point +│ └── vite.config.js +│ +├── server/ # FastAPI Backend +│ ├── main.py # API routes & endpoints +│ ├── mock_data.py # Data loading +│ ├── generate_data.py # Data generation script +│ └── data/ # JSON data files +│ ├── inventory.json +│ ├── orders.json +│ ├── backlog_items.json +│ ├── demand_forecasts.json +│ ├── spending.json +│ ├── transactions.json +│ └── purchase_orders.json +│ +└── CLAUDE.md # Development guidelines + +
+
+ + +
+ + \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index d2086efa..f6aa2182 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,3 +72,14 @@ npm install && npm run dev - Status: green/blue/yellow/red - Charts: Custom SVG, CSS Grid for layouts - No emojis in UI + +## Code Documentation + +**Always document non-obvious logic changes with comments.** Examples of non-obvious changes that need comments: +- Workarounds for specific bugs or framework limitations +- Counterintuitive design decisions +- Performance optimizations that sacrifice readability +- Complex filtering or calculation logic +- State mutations that don't follow typical patterns + +Do NOT comment obvious code — well-named variables and functions are self-documenting. diff --git a/client/index.html b/client/index.html index 1b6ad0a9..7e9cb84b 100644 --- a/client/index.html +++ b/client/index.html @@ -4,6 +4,7 @@ Factory Inventory Management System +
diff --git a/client/src/App.vue b/client/src/App.vue index c2da05a5..cc94332e 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -22,11 +22,15 @@ {{ t('nav.demandForecast') }} + + Restocking + Reports + { - return [...currentUser.value.tasks, ...apiTasks.value] - }) - - const loadTasks = async () => { - try { - apiTasks.value = await api.getTasks() - } catch (err) { - console.error('Failed to load tasks:', err) - } - } - const addTask = async (taskData) => { - try { - const newTask = await api.createTask(taskData) - // Add new task to the beginning of the array - apiTasks.value.unshift(newTask) - } catch (err) { - console.error('Failed to add task:', err) - } + // Use mock tasks from currentUser only — no /api/tasks endpoint exists yet + const tasks = computed(() => currentUser.value.tasks) + + const addTask = (taskData) => { + currentUser.value.tasks.push({ + id: Date.now().toString(), + ...taskData, + status: 'pending' + }) } - const deleteTask = async (taskId) => { - try { - // Check if it's a mock task (from currentUser) - const isMockTask = currentUser.value.tasks.some(t => t.id === taskId) - - if (isMockTask) { - // Remove from mock tasks - const index = currentUser.value.tasks.findIndex(t => t.id === taskId) - if (index !== -1) { - currentUser.value.tasks.splice(index, 1) - } - } else { - // Remove from API tasks - await api.deleteTask(taskId) - apiTasks.value = apiTasks.value.filter(t => t.id !== taskId) - } - } catch (err) { - console.error('Failed to delete task:', err) + const deleteTask = (taskId) => { + const index = currentUser.value.tasks.findIndex(t => t.id === taskId) + if (index !== -1) { + currentUser.value.tasks.splice(index, 1) } } - const toggleTask = async (taskId) => { - try { - // Check if it's a mock task (from currentUser) - const mockTask = currentUser.value.tasks.find(t => t.id === taskId) - - if (mockTask) { - // Toggle mock task status - mockTask.status = mockTask.status === 'pending' ? 'completed' : 'pending' - } else { - // Toggle API task - const updatedTask = await api.toggleTask(taskId) - const index = apiTasks.value.findIndex(t => t.id === taskId) - if (index !== -1) { - apiTasks.value[index] = updatedTask - } - } - } catch (err) { - console.error('Failed to toggle task:', err) + const toggleTask = (taskId) => { + const task = currentUser.value.tasks.find(t => t.id === taskId) + if (task) { + task.status = task.status === 'pending' ? 'completed' : 'pending' } } - onMounted(loadTasks) - return { t, showProfileDetails, @@ -162,6 +127,26 @@ export default { diff --git a/client/src/api.js b/client/src/api.js index 11cb9db7..8ade5ecc 100644 --- a/client/src/api.js +++ b/client/src/api.js @@ -102,5 +102,20 @@ export const api = { async getPurchaseOrderByBacklogItem(backlogItemId) { const response = await axios.get(`${API_BASE_URL}/purchase-orders/${backlogItemId}`) return response.data + }, + + async getRestockingRecommendations() { + const response = await axios.get(`${API_BASE_URL}/restocking/recommendations`) + return response.data + }, + + async submitRestockingOrder(orderData) { + const response = await axios.post(`${API_BASE_URL}/restocking-orders`, orderData) + return response.data + }, + + async getRestockingOrders() { + const response = await axios.get(`${API_BASE_URL}/restocking-orders`) + return response.data } } diff --git a/client/src/components/DarkModeToggle.vue b/client/src/components/DarkModeToggle.vue new file mode 100644 index 00000000..a33c7113 --- /dev/null +++ b/client/src/components/DarkModeToggle.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/client/src/composables/useDarkMode.js b/client/src/composables/useDarkMode.js new file mode 100644 index 00000000..869e0197 --- /dev/null +++ b/client/src/composables/useDarkMode.js @@ -0,0 +1,34 @@ +import { ref } from 'vue' + +const STORAGE_KEY = 'darkMode' + +function getInitialDark() { + const stored = localStorage.getItem(STORAGE_KEY) + if (stored !== null) return stored === 'true' + return window.matchMedia('(prefers-color-scheme: dark)').matches +} + +const isDark = ref(getInitialDark()) + +function applyDark(value) { + if (value) { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } + localStorage.setItem(STORAGE_KEY, String(value)) +} + +applyDark(isDark.value) + +export function useDarkMode() { + const toggleDark = () => { + isDark.value = !isDark.value + applyDark(isDark.value) + } + + return { + isDark, + toggleDark + } +} diff --git a/client/src/main.js b/client/src/main.js index 477c2d96..8613da7f 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -7,6 +7,7 @@ import Orders from './views/Orders.vue' import Demand from './views/Demand.vue' import Spending from './views/Spending.vue' import Reports from './views/Reports.vue' +import Restocking from './views/Restocking.vue' const router = createRouter({ history: createWebHistory(), @@ -16,10 +17,16 @@ const router = createRouter({ { path: '/orders', component: Orders }, { path: '/demand', component: Demand }, { path: '/spending', component: Spending }, - { path: '/reports', component: Reports } + { path: '/reports', component: Reports }, + { path: '/restocking', component: Restocking } ] }) const app = createApp(App) + +app.config.errorHandler = (err, instance, info) => { + console.error('Vue error:', err, info) +} + app.use(router) app.mount('#app') diff --git a/client/src/views/Dashboard.vue b/client/src/views/Dashboard.vue index 437da9c2..f14fa0b0 100644 --- a/client/src/views/Dashboard.vue +++ b/client/src/views/Dashboard.vue @@ -286,13 +286,7 @@ @close="showBacklogModal = false" /> - + diff --git a/client/src/views/Orders.vue b/client/src/views/Orders.vue index 7413f6e6..97e07ebe 100644 --- a/client/src/views/Orders.vue +++ b/client/src/views/Orders.vue @@ -8,6 +8,52 @@
{{ t('common.loading') }}
{{ error }}
+ +
+
+

Submitted Restocking Orders

+ {{ restockingOrders.length }} order{{ restockingOrders.length !== 1 ? 's' : '' }} +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Order IDItemsSubmittedLead TimeEst. DeliveryTotal CostStatus
{{ rso.id }} +
+ + {{ rso.items.length }} item{{ rso.items.length !== 1 ? 's' : '' }} + +
+
+ {{ item.name }} + {{ item.sku }} — Qty: {{ item.quantity }} @ ${{ item.unit_cost }} — Lead time: {{ item.lead_time_days }}d +
+
+
+
{{ formatDate(rso.submitted_date) }}{{ Math.max(...rso.items.map(i => i.lead_time_days)) }} days{{ formatDate(rso.estimated_delivery) }}${{ rso.total_cost.toLocaleString() }}{{ rso.status }}
+
+
+
{{ t('status.delivered') }}
@@ -95,6 +141,7 @@ export default { const loading = ref(true) const error = ref(null) const orders = ref([]) + const restockingOrders = ref([]) // Use shared filters const { @@ -153,13 +200,25 @@ export default { }) } - onMounted(loadOrders) + const loadRestockingOrders = async () => { + try { + restockingOrders.value = await api.getRestockingOrders() + } catch (err) { + console.error('Failed to load restocking orders:', err) + } + } + + onMounted(() => { + loadOrders() + loadRestockingOrders() + }) return { t, loading, error, orders, + restockingOrders, getOrdersByStatus, getOrderStatusClass, formatDate, @@ -201,6 +260,7 @@ export default { .col-value { width: 120px; + text-align: right; } /* Items details styling */ @@ -276,4 +336,14 @@ export default { font-size: 0.813rem; color: #64748b; } + +.restocking-section { + border-left: 3px solid #2563eb; + margin-bottom: 1.5rem; +} + +.restocking-table { + table-layout: auto; + width: 100%; +} diff --git a/client/src/views/Restocking.vue b/client/src/views/Restocking.vue new file mode 100644 index 00000000..fba7d7a7 --- /dev/null +++ b/client/src/views/Restocking.vue @@ -0,0 +1,387 @@ + + + + + diff --git a/client/vite.config.js b/client/vite.config.js index 09a47173..4c26a980 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -4,6 +4,11 @@ import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { - port: 3000 + port: 3000, + // fs.allow is omitted — X: is a mapped drive and path resolution + // conflicts prevent Vite from serving files when strict mode is on. + fs: { + strict: false + } } }) diff --git a/news-20260624.md b/news-20260624.md new file mode 100644 index 00000000..ff1daf0a --- /dev/null +++ b/news-20260624.md @@ -0,0 +1,12 @@ +# AI News — June 24, 2026 + +| Name | Short Summary | Source URL | +|------|--------------|------------| +| Noam Shazeer Leaves Google for OpenAI | Google Gemini co-lead and "Attention Is All You Need" co-author Noam Shazeer joins OpenAI as Lead for Architecture Research after a reported $2.7B recruitment deal brought him back to Google from Character.AI less than two years ago. | https://www.cnbc.com/2026/06/18/google-gemini-co-lead-noam-shazeer-leaves-for-openai.html | +| OpenAI Previews GPT-5.6 | OpenAI's Chief Scientist described GPT-5.6 as a meaningful improvement over GPT-5.5 (which scores 58.6 on SWE-bench Pro), targeting a late-June 2026 release — no confirmed date yet. | https://llm-stats.com/llm-updates | +| Google Releases Android 17 with Gemini Omni | Android 17 launched for Pixel devices on June 16 with Gemini Omni (multimodal text/image/audio/video), Lyria 3 music generation, and AudioLM speech-to-translation, laying groundwork for Google's broader "Gemini Intelligence" agentic OS initiative. | https://techcrunch.com/2026/06/16/android-17-launches-with-new-multitasking-tools-as-google-expands-gemini-features/ | +| White House Executive Order on Advanced AI Innovation and Security | Signed June 2, 2026, the order directs a voluntary AI cybersecurity clearinghouse led by Treasury/NSA/CISA and creates a 30-day pre-release review window for "covered frontier models" — explicitly prohibiting mandatory government licensing or permitting for AI models. | https://www.whitehouse.gov/presidential-actions/2026/06/promoting-advanced-artificial-intelligence-innovation-and-security/ | +| Colorado AI Act Revised and Delayed | Governor Polis signed SB 189 on May 14, 2026, pushing the Colorado Consumer Protections for Artificial Intelligence Act effective date to January 1, 2027 and substantially scaling back requirements — eliminating algorithmic-discrimination duty of care and replacing it with narrower disclosure and transparency rules. | https://www.troutmanprivacy.com/2026/05/colorado-legislature-passes-bill-to-repeal-and-replace-colorado-ai-act/ | +| ChatGPT Market Share Drops Below 50% | ChatGPT's share of worldwide AI chatbot web traffic fell to 46.4% — the first time below 50% — as Google Gemini rose to 27.7% and Claude reached 10.3%, reflecting rapid competitive fragmentation since ChatGPT held 76.5% in February 2025. | https://aistartupedge.com/latest-ai-news-june-2026/ | +| Morgan Stanley: Global AI-Linked Debt to Hit $570B in 2026 | Morgan Stanley projects AI-linked debt will nearly double to $570 billion this year, driven by a $36B Apollo-Blackstone infrastructure deal and Microsoft's $190B capex commitment. | https://theaitrack.com/ai-news-june-2026-in-depth-and-concise/ | +| Claude Fable 5 Restored After Government Shutdown | Anthropic restored access to Claude Fable 5 following a six-day government-forced shutdown that required negotiations between senior Anthropic technical staff and White House officials; the model had also exited complimentary access on Pro/Max/Team/Enterprise plans on June 23. | https://aiweekly.co/alerts/google-launches-android-17-with-gemini-omni-and-lyria-3 | diff --git a/server/data/demand_forecasts.json b/server/data/demand_forecasts.json index e1b38838..5f0c2e97 100644 --- a/server/data/demand_forecasts.json +++ b/server/data/demand_forecasts.json @@ -1,83 +1,83 @@ [ { "id": "1", - "item_sku": "WDG-001", - "item_name": "Industrial Widget Type A", - "current_demand": 300, - "forecasted_demand": 450, + "item_sku": "SRV-302", + "item_name": "Standard Servo Motor", + "current_demand": 28, + "forecasted_demand": 200, "trend": "increasing", "period": "Next 30 days" }, { "id": "2", - "item_sku": "BRG-102", - "item_name": "Steel Bearing Assembly", - "current_demand": 150, - "forecasted_demand": 152, - "trend": "stable", + "item_sku": "SRV-301", + "item_name": "Micro Servo Motor", + "current_demand": 45, + "forecasted_demand": 150, + "trend": "increasing", "period": "Next 30 days" }, { "id": "3", - "item_sku": "GSK-203", - "item_name": "High-Temperature Gasket", - "current_demand": 500, - "forecasted_demand": 600, + "item_sku": "TMP-201", + "item_name": "Temperature Sensor Module", + "current_demand": 125, + "forecasted_demand": 350, "trend": "increasing", "period": "Next 30 days" }, { "id": "4", - "item_sku": "MTR-304", - "item_name": "Electric Motor 5HP", - "current_demand": 50, - "forecasted_demand": 35, - "trend": "decreasing", + "item_sku": "PSU-508", + "item_name": "Battery Backup Power Supply", + "current_demand": 75, + "forecasted_demand": 250, + "trend": "increasing", "period": "Next 30 days" }, { "id": "5", - "item_sku": "FLT-405", - "item_name": "Oil Filter Cartridge", - "current_demand": 800, - "forecasted_demand": 950, + "item_sku": "HMD-202", + "item_name": "Humidity Sensor Module", + "current_demand": 95, + "forecasted_demand": 300, "trend": "increasing", "period": "Next 30 days" }, { "id": "6", - "item_sku": "VLV-506", - "item_name": "Pressure Relief Valve", - "current_demand": 120, - "forecasted_demand": 121, + "item_sku": "STP-303", + "item_name": "Stepper Motor NEMA 17", + "current_demand": 62, + "forecasted_demand": 150, "trend": "stable", "period": "Next 30 days" }, { "id": "7", - "item_sku": "PSU-501", - "item_name": "5V 10A Switching Power Supply", - "current_demand": 250, - "forecasted_demand": 252, + "item_sku": "PSU-507", + "item_name": "Adjustable Bench Power Supply", + "current_demand": 95, + "forecasted_demand": 200, "trend": "stable", "period": "Next 30 days" }, { "id": "8", - "item_sku": "SNR-420", - "item_name": "Temperature Sensor Module", - "current_demand": 180, - "forecasted_demand": 182, + "item_sku": "PCB-003", + "item_name": "Multi Layer PCB Assembly", + "current_demand": 275, + "forecasted_demand": 500, "trend": "stable", "period": "Next 30 days" }, { "id": "9", - "item_sku": "CTL-330", - "item_name": "Logic Controller Board", - "current_demand": 95, - "forecasted_demand": 96, - "trend": "stable", + "item_sku": "STP-304", + "item_name": "Stepper Motor NEMA 23", + "current_demand": 55, + "forecasted_demand": 80, + "trend": "decreasing", "period": "Next 30 days" } ] diff --git a/server/main.py b/server/main.py index a0c2d8c5..8bc0e26e 100644 --- a/server/main.py +++ b/server/main.py @@ -2,6 +2,7 @@ from fastapi.middleware.cors import CORSMiddleware from typing import List, Optional from pydantic import BaseModel +from datetime import datetime, timedelta from mock_data import inventory_items, orders, demand_forecasts, backlog_items, spending_summary, monthly_spending, category_spending, recent_transactions, purchase_orders app = FastAPI(title="Factory Inventory Management System") @@ -120,6 +121,58 @@ class CreatePurchaseOrderRequest(BaseModel): expected_delivery_date: str notes: Optional[str] = None +class RestockingRecommendation(BaseModel): + item_sku: str + item_name: str + category: str + warehouse: str + trend: str + forecasted_demand: int + quantity_on_hand: int + recommended_quantity: int + unit_cost: float + estimated_cost: float + lead_time_days: int + priority: int + +class RestockingOrderItem(BaseModel): + sku: str + name: str + category: str + quantity: int + unit_cost: float + lead_time_days: int + +class RestockingOrder(BaseModel): + id: str + submitted_date: str + items: List[RestockingOrderItem] + total_cost: float + status: str + estimated_delivery: str + +class CreateRestockingOrderRequest(BaseModel): + items: List[RestockingOrderItem] + total_cost: float + +# In-memory storage for submitted restocking orders +restocking_orders: List[dict] = [] +restocking_order_counter = 0 + +LEAD_TIME_BY_CATEGORY = { + 'Circuit Boards': 21, + 'Controllers': 21, + 'Actuators': 14, + 'Sensors': 14, + 'Power Supplies': 7, +} + +TREND_PRIORITY = { + 'increasing': 1, + 'stable': 2, + 'decreasing': 3, +} + # API endpoints @app.get("/") def root(): @@ -304,6 +357,74 @@ def get_monthly_trends(): result.sort(key=lambda x: x['month']) return result +@app.get("/api/restocking/recommendations", response_model=List[RestockingRecommendation]) +def get_restocking_recommendations(): + """Get restocking recommendations by joining demand forecasts with inventory data.""" + inventory_by_sku = {item['sku']: item for item in inventory_items} + results = [] + + for forecast in demand_forecasts: + sku = forecast.get('item_sku') + inv = inventory_by_sku.get(sku) + if not inv: + continue + + recommended_quantity = max(forecast['forecasted_demand'] - inv['quantity_on_hand'], 0) + if recommended_quantity == 0: + continue + + category = inv.get('category', '') + lead_time_days = LEAD_TIME_BY_CATEGORY.get(category, 14) + trend = forecast.get('trend', 'stable') + priority = TREND_PRIORITY.get(trend, 2) + + results.append({ + 'item_sku': sku, + 'item_name': forecast['item_name'], + 'category': category, + 'warehouse': inv.get('warehouse', ''), + 'trend': trend, + 'forecasted_demand': forecast['forecasted_demand'], + 'quantity_on_hand': inv['quantity_on_hand'], + 'recommended_quantity': recommended_quantity, + 'unit_cost': inv['unit_cost'], + 'estimated_cost': round(recommended_quantity * inv['unit_cost'], 2), + 'lead_time_days': lead_time_days, + 'priority': priority, + }) + + results.sort(key=lambda x: (x['priority'], -x['estimated_cost'])) + return results + + +@app.post("/api/restocking-orders", response_model=RestockingOrder) +def create_restocking_order(request: CreateRestockingOrderRequest): + """Submit a restocking order.""" + global restocking_order_counter + restocking_order_counter += 1 + + now = datetime.utcnow() + max_lead_days = max((item.lead_time_days for item in request.items), default=14) + estimated_delivery = now + timedelta(days=max_lead_days) + + order = { + 'id': f'RSO-{restocking_order_counter:03d}', + 'submitted_date': now.isoformat(), + 'items': [item.model_dump() for item in request.items], + 'total_cost': round(request.total_cost, 2), + 'status': 'Processing', + 'estimated_delivery': estimated_delivery.isoformat(), + } + restocking_orders.insert(0, order) + return order + + +@app.get("/api/restocking-orders", response_model=List[RestockingOrder]) +def get_restocking_orders(): + """Get all submitted restocking orders (newest first).""" + return restocking_orders + + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8001)