refactor: simplify pricing tables to USD-only

- Remove KRW pricing calculations from all pricing tables
- Simplify pricing table to store only wholesale USD prices
- Simplify anvil_pricing to store only retail USD prices
- Remove KRW environment variables (KRW_EXCHANGE_RATE, KRW_MARGIN_RATE)
- Remove KRW functions from constants.ts
- Update GPU/G8/VPU pricing repositories to match
- Add Anvil tables and repositories for branded product support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-25 21:16:25 +09:00
parent a2133ae5c9
commit 9f3d3a245a
21 changed files with 1952 additions and 197 deletions

View File

@@ -0,0 +1,60 @@
-- Migration 003: Add Retail Pricing Fields
-- Description: Add hourly_price_retail and monthly_price_retail to all pricing tables
-- Date: 2026-01-23
-- Author: Claude Code
-- ============================================================
-- Add retail pricing columns to pricing table
-- ============================================================
ALTER TABLE pricing ADD COLUMN hourly_price_retail REAL;
ALTER TABLE pricing ADD COLUMN monthly_price_retail REAL;
-- Backfill existing data with retail prices (wholesale × 1.21)
UPDATE pricing
SET
hourly_price_retail = ROUND(hourly_price * 1.21 * 10000) / 10000,
monthly_price_retail = ROUND(monthly_price * 1.21 * 100) / 100
WHERE hourly_price_retail IS NULL;
-- ============================================================
-- Add retail pricing columns to gpu_pricing table
-- ============================================================
ALTER TABLE gpu_pricing ADD COLUMN hourly_price_retail REAL;
ALTER TABLE gpu_pricing ADD COLUMN monthly_price_retail REAL;
-- Backfill existing data with retail prices (wholesale × 1.21)
UPDATE gpu_pricing
SET
hourly_price_retail = ROUND(hourly_price * 1.21 * 10000) / 10000,
monthly_price_retail = ROUND(monthly_price * 1.21 * 100) / 100
WHERE hourly_price_retail IS NULL;
-- ============================================================
-- Add retail pricing columns to g8_pricing table
-- ============================================================
ALTER TABLE g8_pricing ADD COLUMN hourly_price_retail REAL;
ALTER TABLE g8_pricing ADD COLUMN monthly_price_retail REAL;
-- Backfill existing data with retail prices (wholesale × 1.21)
UPDATE g8_pricing
SET
hourly_price_retail = ROUND(hourly_price * 1.21 * 10000) / 10000,
monthly_price_retail = ROUND(monthly_price * 1.21 * 100) / 100
WHERE hourly_price_retail IS NULL;
-- ============================================================
-- Add retail pricing columns to vpu_pricing table
-- ============================================================
ALTER TABLE vpu_pricing ADD COLUMN hourly_price_retail REAL;
ALTER TABLE vpu_pricing ADD COLUMN monthly_price_retail REAL;
-- Backfill existing data with retail prices (wholesale × 1.21)
UPDATE vpu_pricing
SET
hourly_price_retail = ROUND(hourly_price * 1.21 * 10000) / 10000,
monthly_price_retail = ROUND(monthly_price * 1.21 * 100) / 100
WHERE hourly_price_retail IS NULL;

View File

@@ -0,0 +1,161 @@
-- Migration 004: Anvil Product Tables
-- Creates tables for Anvil-branded cloud products
-- ============================================================
-- Table: anvil_regions
-- Purpose: Anvil-branded regional datacenters mapped to source providers
-- ============================================================
CREATE TABLE IF NOT EXISTS anvil_regions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE, -- "anvil-tyo1", "anvil-sel1"
display_name TEXT NOT NULL, -- "Tokyo 1", "Seoul 1"
country_code TEXT NOT NULL, -- "JP", "KR"
source_provider TEXT NOT NULL, -- "linode", "vultr"
source_region_code TEXT NOT NULL, -- "jp-tyo-3", "nrt"
source_region_id INTEGER NOT NULL, -- FK to regions.id
active INTEGER NOT NULL DEFAULT 1, -- 0/1 boolean
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (source_region_id) REFERENCES regions(id) ON DELETE RESTRICT
);
-- Index for fast lookup by source region
CREATE INDEX IF NOT EXISTS idx_anvil_regions_source
ON anvil_regions(source_region_id);
-- Index for active regions lookup
CREATE INDEX IF NOT EXISTS idx_anvil_regions_active
ON anvil_regions(active);
-- ============================================================
-- Table: anvil_instances
-- Purpose: Anvil-branded instance specifications
-- ============================================================
CREATE TABLE IF NOT EXISTS anvil_instances (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE, -- "anvil-1g-1c"
display_name TEXT NOT NULL, -- "Basic 1GB"
category TEXT NOT NULL DEFAULT 'vm', -- "vm", "gpu", "g8", "vpu"
vcpus INTEGER NOT NULL,
memory_gb REAL NOT NULL,
disk_gb INTEGER NOT NULL,
transfer_tb REAL,
network_gbps REAL,
gpu_model TEXT, -- GPU-specific
gpu_vram_gb INTEGER, -- GPU-specific
active INTEGER NOT NULL DEFAULT 1, -- 0/1 boolean
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Index for category-based queries
CREATE INDEX IF NOT EXISTS idx_anvil_instances_category
ON anvil_instances(category);
-- Index for active instances lookup
CREATE INDEX IF NOT EXISTS idx_anvil_instances_active
ON anvil_instances(active);
-- ============================================================
-- Table: anvil_pricing
-- Purpose: Anvil retail pricing with cost tracking
-- ============================================================
CREATE TABLE IF NOT EXISTS anvil_pricing (
id INTEGER PRIMARY KEY AUTOINCREMENT,
anvil_instance_id INTEGER NOT NULL,
anvil_region_id INTEGER NOT NULL,
hourly_price REAL NOT NULL, -- Retail price (USD)
monthly_price REAL NOT NULL, -- Retail price (USD)
hourly_price_krw REAL, -- Retail price (KRW)
monthly_price_krw REAL, -- Retail price (KRW)
cost_hourly REAL, -- Wholesale cost (USD)
cost_monthly REAL, -- Wholesale cost (USD)
source_instance_id INTEGER, -- FK to source tables (instance_types, gpu_instances, etc)
active INTEGER NOT NULL DEFAULT 1, -- 0/1 boolean
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (anvil_instance_id) REFERENCES anvil_instances(id) ON DELETE CASCADE,
FOREIGN KEY (anvil_region_id) REFERENCES anvil_regions(id) ON DELETE CASCADE,
UNIQUE(anvil_instance_id, anvil_region_id)
);
-- Index for instance-based queries
CREATE INDEX IF NOT EXISTS idx_anvil_pricing_instance
ON anvil_pricing(anvil_instance_id);
-- Index for region-based queries
CREATE INDEX IF NOT EXISTS idx_anvil_pricing_region
ON anvil_pricing(anvil_region_id);
-- Index for active pricing lookup
CREATE INDEX IF NOT EXISTS idx_anvil_pricing_active
ON anvil_pricing(active);
-- ============================================================
-- Table: anvil_transfer_pricing
-- Purpose: Data transfer pricing per region
-- ============================================================
CREATE TABLE IF NOT EXISTS anvil_transfer_pricing (
id INTEGER PRIMARY KEY AUTOINCREMENT,
anvil_region_id INTEGER NOT NULL,
price_per_gb REAL NOT NULL, -- USD/GB
price_per_gb_krw REAL, -- KRW/GB
included_tb REAL NOT NULL DEFAULT 0, -- Reference only
active INTEGER NOT NULL DEFAULT 1, -- 0/1 boolean
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (anvil_region_id) REFERENCES anvil_regions(id) ON DELETE CASCADE,
UNIQUE(anvil_region_id)
);
-- Index for active transfer pricing lookup
CREATE INDEX IF NOT EXISTS idx_anvil_transfer_pricing_active
ON anvil_transfer_pricing(active);
-- ============================================================
-- Triggers: Auto-update updated_at timestamps
-- ============================================================
CREATE TRIGGER IF NOT EXISTS trigger_anvil_regions_updated_at
AFTER UPDATE ON anvil_regions
FOR EACH ROW
BEGIN
UPDATE anvil_regions SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END;
CREATE TRIGGER IF NOT EXISTS trigger_anvil_instances_updated_at
AFTER UPDATE ON anvil_instances
FOR EACH ROW
BEGIN
UPDATE anvil_instances SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END;
CREATE TRIGGER IF NOT EXISTS trigger_anvil_pricing_updated_at
AFTER UPDATE ON anvil_pricing
FOR EACH ROW
BEGIN
UPDATE anvil_pricing SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END;
CREATE TRIGGER IF NOT EXISTS trigger_anvil_transfer_pricing_updated_at
AFTER UPDATE ON anvil_transfer_pricing
FOR EACH ROW
BEGIN
UPDATE anvil_transfer_pricing SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END;
-- ============================================================
-- Initial Data: anvil_regions
-- ============================================================
INSERT INTO anvil_regions (name, display_name, country_code, source_provider, source_region_code, source_region_id) VALUES
('anvil-tyo1', 'Tokyo 1', 'JP', 'linode', 'ap-northeast', 26),
('anvil-tyo2', 'Tokyo 2', 'JP', 'linode', 'jp-tyo-3', 3),
('anvil-tyo3', 'Tokyo 3', 'JP', 'vultr', 'nrt', 323),
('anvil-osa1', 'Osaka 1', 'JP', 'linode', 'jp-osa', 13),
('anvil-osa2', 'Osaka 2', 'JP', 'vultr', 'itm', 314),
('anvil-sel1', 'Seoul 1', 'KR', 'vultr', 'icn', 313)
ON CONFLICT(name) DO UPDATE SET
display_name = excluded.display_name,
country_code = excluded.country_code,
source_provider = excluded.source_provider,
source_region_code = excluded.source_region_code,
source_region_id = excluded.source_region_id;