phase 1: foundation — next.js 16 scaffold, prisma schema, docker compose, seed data
This commit is contained in:
parent
1e9b287036
commit
6d7d2d966b
51
.gitignore
vendored
Normal file
51
.gitignore
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# database
|
||||||
|
pgdata/
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# prisma
|
||||||
|
src/generated/
|
||||||
|
|
||||||
|
# bun
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage/
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
@ -7,7 +7,7 @@ export default {
|
|||||||
arrowParens: 'avoid',
|
arrowParens: 'avoid',
|
||||||
semi: true,
|
semi: true,
|
||||||
plugins: ['prettier-plugin-organize-imports', 'prettier-plugin-tailwindcss'],
|
plugins: ['prettier-plugin-organize-imports', 'prettier-plugin-tailwindcss'],
|
||||||
tailwindStylesheet: './app/globals.css',
|
tailwindStylesheet: './src/app/globals.css',
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['*.ts', '*.js', '*.tsx', '*.jsx', '*.cjs', '*.mjs'],
|
files: ['*.ts', '*.js', '*.tsx', '*.jsx', '*.cjs', '*.mjs'],
|
||||||
|
|||||||
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# bonus4
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.3.8. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
||||||
21
components.json
Normal file
21
components.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "",
|
||||||
|
"css": "src/app/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
||||||
122
data/ai-milestones.json
Normal file
122
data/ai-milestones.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"date": "2020-06-11",
|
||||||
|
"title": "GPT-3 API Launch",
|
||||||
|
"description": "OpenAI opens GPT-3 API access, demonstrating that scaling language models yields emergent capabilities. Signals the start of the large-model era.",
|
||||||
|
"category": "model_launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2021-06-29",
|
||||||
|
"title": "GitHub Copilot Preview",
|
||||||
|
"description": "GitHub launches Copilot technical preview, the first mass-market AI coding assistant. Powered by OpenAI Codex, it demonstrates commercial viability of large language models.",
|
||||||
|
"category": "model_launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2022-04-06",
|
||||||
|
"title": "DALL-E 2 Launch",
|
||||||
|
"description": "OpenAI unveils DALL-E 2, a breakthrough in AI image generation. Kicks off the generative AI media wave and accelerates GPU demand for inference workloads.",
|
||||||
|
"category": "model_launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2022-08-22",
|
||||||
|
"title": "Stable Diffusion Open-Source Release",
|
||||||
|
"description": "Stability AI releases Stable Diffusion publicly under an open-source license, democratizing AI image generation and driving massive consumer GPU demand.",
|
||||||
|
"category": "model_launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2022-11-30",
|
||||||
|
"title": "ChatGPT Launch",
|
||||||
|
"description": "OpenAI releases ChatGPT, reaching 1 million users in 5 days and 100 million in 2 months. Sparks the generative AI boom and triggers an unprecedented race for GPU compute.",
|
||||||
|
"category": "model_launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2023-01-23",
|
||||||
|
"title": "Microsoft Invests $10B in OpenAI",
|
||||||
|
"description": "Microsoft announces a $10 billion investment in OpenAI, the largest single AI investment at the time. Signals hyperscaler conviction that AI will drive massive infrastructure spend.",
|
||||||
|
"category": "market"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2023-03-14",
|
||||||
|
"title": "GPT-4 Launch",
|
||||||
|
"description": "OpenAI releases GPT-4, a multimodal model accepting text and image inputs. Demonstrates a step-change in AI capability, intensifying the arms race for training compute.",
|
||||||
|
"category": "model_launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2023-05-30",
|
||||||
|
"title": "NVIDIA Hits $1 Trillion Market Cap",
|
||||||
|
"description": "NVIDIA becomes the first chipmaker to reach a $1 trillion market capitalization, driven by explosive AI GPU demand. Stock price reflects the market's bet on sustained datacenter buildout.",
|
||||||
|
"category": "market"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2023-07-18",
|
||||||
|
"title": "Meta Releases Llama 2 Open-Source",
|
||||||
|
"description": "Meta releases Llama 2 for research and commercial use, open-sourcing 7B, 13B, and 70B parameter models. Accelerates AI adoption by lowering barriers and increasing total compute demand.",
|
||||||
|
"category": "model_launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2023-08-17",
|
||||||
|
"title": "NVIDIA H100 GPU Shortage Peak",
|
||||||
|
"description": "Reports confirm H100 GPUs are sold out through Q1 2024 with 6+ month lead times. CoWoS packaging bottleneck limits supply to ~550K units for the year, driving datacenter expansion plans.",
|
||||||
|
"category": "infrastructure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2023-10-30",
|
||||||
|
"title": "Biden Executive Order on AI Safety",
|
||||||
|
"description": "President Biden signs Executive Order 14110, the most comprehensive US AI governance action. Establishes safety standards, reporting requirements for large model training runs, and energy impact assessments.",
|
||||||
|
"category": "policy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2023-12-06",
|
||||||
|
"title": "Google Gemini Launch",
|
||||||
|
"description": "Google announces Gemini 1.0 with Ultra, Pro, and Nano variants. Google's entry into the frontier model race further escalates industry-wide datacenter investment.",
|
||||||
|
"category": "model_launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2024-03-04",
|
||||||
|
"title": "Claude 3 Family Launch",
|
||||||
|
"description": "Anthropic releases Claude 3 Haiku, Sonnet, and Opus, the first AI model family with clear speed/cost/capability tradeoffs. Broadens enterprise AI adoption and inference compute demand.",
|
||||||
|
"category": "model_launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2024-03-18",
|
||||||
|
"title": "NVIDIA Blackwell GPU Architecture Announced",
|
||||||
|
"description": "NVIDIA unveils Blackwell B200 and GB200 GPUs at GTC 2024. Each GB200 rack draws up to 120 kW, signaling that next-generation AI hardware will dramatically increase datacenter power density.",
|
||||||
|
"category": "infrastructure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2024-05-13",
|
||||||
|
"title": "GPT-4o Launch",
|
||||||
|
"description": "OpenAI releases GPT-4o (omni), a natively multimodal model processing text, image, and audio. Made free in ChatGPT, massively expanding inference volume and datacenter load.",
|
||||||
|
"category": "model_launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2024-07-15",
|
||||||
|
"title": "PJM Capacity Auction Sets Record Prices",
|
||||||
|
"description": "PJM Interconnection's capacity auction clears at $16.1 billion, up 9.5% year-over-year. Data center load growth identified as the primary driver, with 30 GW of new demand projected by 2030.",
|
||||||
|
"category": "market"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2024-12-18",
|
||||||
|
"title": "NERC Warns of Nationwide Capacity Shortfalls",
|
||||||
|
"description": "NERC's Long-Term Reliability Assessment warns that all US regions except MRO-Manitoba face elevated or high risk of supply shortfalls by 2028, driven primarily by datacenter load growth.",
|
||||||
|
"category": "policy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2025-01-03",
|
||||||
|
"title": "Microsoft Commits $80B to AI Datacenters",
|
||||||
|
"description": "Microsoft announces plans to spend $80 billion on AI-enabled datacenters in fiscal year 2025, more than half in the US. Largest single-company infrastructure commitment in history.",
|
||||||
|
"category": "infrastructure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2025-01-21",
|
||||||
|
"title": "Stargate Project Announced ($500B)",
|
||||||
|
"description": "OpenAI, SoftBank, and Oracle announce the Stargate Project: $500 billion over four years for AI infrastructure, with $100 billion deployed immediately. Represents a new scale of AI investment.",
|
||||||
|
"category": "infrastructure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2025-11-12",
|
||||||
|
"title": "Anthropic Announces $50B Infrastructure Buildout",
|
||||||
|
"description": "Anthropic announces plans to spend $50 billion on US AI infrastructure, starting with custom datacenters in Texas and New York. Signals that AI labs themselves are becoming major power consumers.",
|
||||||
|
"category": "infrastructure"
|
||||||
|
}
|
||||||
|
]
|
||||||
485
data/datacenters.geojson
Normal file
485
data/datacenters.geojson
Normal file
@ -0,0 +1,485 @@
|
|||||||
|
{
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-77.4875, 39.0438] },
|
||||||
|
"properties": {
|
||||||
|
"name": "AWS US-East Ashburn Campus",
|
||||||
|
"operator": "AWS",
|
||||||
|
"capacity_mw": 350,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2006,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-77.5105, 38.7515] },
|
||||||
|
"properties": {
|
||||||
|
"name": "AWS Manassas Data Center",
|
||||||
|
"operator": "AWS",
|
||||||
|
"capacity_mw": 200,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2016,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-82.9071, 40.0798] },
|
||||||
|
"properties": {
|
||||||
|
"name": "AWS US-East-2 Columbus Campus",
|
||||||
|
"operator": "AWS",
|
||||||
|
"capacity_mw": 250,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2016,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-119.8526, 45.6946] },
|
||||||
|
"properties": {
|
||||||
|
"name": "AWS US-West-2 Oregon Campus",
|
||||||
|
"operator": "AWS",
|
||||||
|
"capacity_mw": 300,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2011,
|
||||||
|
"region": "BPA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-86.2748, 40.4822] },
|
||||||
|
"properties": {
|
||||||
|
"name": "AWS Indiana Data Center Campus",
|
||||||
|
"operator": "AWS",
|
||||||
|
"capacity_mw": 525,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2023,
|
||||||
|
"region": "MISO"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-121.1787, 45.5946] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Google The Dalles Data Center",
|
||||||
|
"operator": "Google",
|
||||||
|
"capacity_mw": 250,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2006,
|
||||||
|
"region": "BPA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-95.8608, 41.2619] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Google Council Bluffs Data Center",
|
||||||
|
"operator": "Google",
|
||||||
|
"capacity_mw": 400,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2009,
|
||||||
|
"region": "MISO"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-95.2400, 36.3000] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Google Mayes County Data Center",
|
||||||
|
"operator": "Google",
|
||||||
|
"capacity_mw": 300,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2011,
|
||||||
|
"region": "SPP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-77.4207, 36.6713] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Google Loudoun County Data Center",
|
||||||
|
"operator": "Google",
|
||||||
|
"capacity_mw": 200,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2019,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-86.5861, 33.3684] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Google Bridgeport Data Center",
|
||||||
|
"operator": "Google",
|
||||||
|
"capacity_mw": 300,
|
||||||
|
"status": "under_construction",
|
||||||
|
"year_opened": 2025,
|
||||||
|
"region": "SERC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-98.4936, 29.4241] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Microsoft San Antonio Data Center",
|
||||||
|
"operator": "Microsoft",
|
||||||
|
"capacity_mw": 250,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2010,
|
||||||
|
"region": "ERCOT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-78.3877, 36.6559] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Microsoft Boydton Data Center",
|
||||||
|
"operator": "Microsoft",
|
||||||
|
"capacity_mw": 400,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2010,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-119.8530, 47.2343] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Microsoft Quincy Data Center",
|
||||||
|
"operator": "Microsoft",
|
||||||
|
"capacity_mw": 300,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2007,
|
||||||
|
"region": "BPA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-93.7910, 41.5770] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Microsoft West Des Moines Data Center",
|
||||||
|
"operator": "Microsoft",
|
||||||
|
"capacity_mw": 350,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2014,
|
||||||
|
"region": "MISO"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-87.8135, 41.7375] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Microsoft Chicago Data Center",
|
||||||
|
"operator": "Microsoft",
|
||||||
|
"capacity_mw": 150,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2009,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-111.9749, 33.3784] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Microsoft Phoenix (Goodyear) Data Center",
|
||||||
|
"operator": "Microsoft",
|
||||||
|
"capacity_mw": 250,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2021,
|
||||||
|
"region": "SPP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-120.9224, 44.2993] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Meta Prineville Data Center",
|
||||||
|
"operator": "Meta",
|
||||||
|
"capacity_mw": 350,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2011,
|
||||||
|
"region": "BPA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-93.4747, 41.6432] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Meta Altoona Data Center",
|
||||||
|
"operator": "Meta",
|
||||||
|
"capacity_mw": 400,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2014,
|
||||||
|
"region": "MISO"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-97.2767, 32.8140] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Meta Fort Worth Data Center",
|
||||||
|
"operator": "Meta",
|
||||||
|
"capacity_mw": 250,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2016,
|
||||||
|
"region": "ERCOT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-82.8085, 40.0747] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Meta New Albany Data Center",
|
||||||
|
"operator": "Meta",
|
||||||
|
"capacity_mw": 300,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2020,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-111.6780, 33.4484] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Meta Mesa Data Center",
|
||||||
|
"operator": "Meta",
|
||||||
|
"capacity_mw": 200,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2022,
|
||||||
|
"region": "SPP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-77.4591, 39.0204] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Digital Realty Ashburn ACC3 Campus",
|
||||||
|
"operator": "Digital Realty",
|
||||||
|
"capacity_mw": 150,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2014,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-96.8560, 32.8970] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Digital Realty Dallas Campus",
|
||||||
|
"operator": "Digital Realty",
|
||||||
|
"capacity_mw": 120,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2005,
|
||||||
|
"region": "ERCOT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-87.6503, 41.8495] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Digital Realty Chicago Campus",
|
||||||
|
"operator": "Digital Realty",
|
||||||
|
"capacity_mw": 100,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2007,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-121.7830, 37.2418] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Equinix SV1 San Jose Campus",
|
||||||
|
"operator": "Equinix",
|
||||||
|
"capacity_mw": 80,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2002,
|
||||||
|
"region": "CAISO"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-77.4877, 39.0296] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Equinix DC11 Ashburn Campus",
|
||||||
|
"operator": "Equinix",
|
||||||
|
"capacity_mw": 100,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2015,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-74.0566, 40.7831] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Equinix NY5 Secaucus Campus",
|
||||||
|
"operator": "Equinix",
|
||||||
|
"capacity_mw": 60,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2010,
|
||||||
|
"region": "NYISO"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-96.9365, 32.9290] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Equinix DA1 Dallas Campus",
|
||||||
|
"operator": "Equinix",
|
||||||
|
"capacity_mw": 50,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2000,
|
||||||
|
"region": "ERCOT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-87.6298, 41.8535] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Equinix CH1 Chicago Campus",
|
||||||
|
"operator": "Equinix",
|
||||||
|
"capacity_mw": 50,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2001,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-96.8419, 32.9339] },
|
||||||
|
"properties": {
|
||||||
|
"name": "QTS Irving (Dallas) Data Center",
|
||||||
|
"operator": "QTS",
|
||||||
|
"capacity_mw": 110,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2010,
|
||||||
|
"region": "ERCOT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-77.4700, 39.0451] },
|
||||||
|
"properties": {
|
||||||
|
"name": "QTS Ashburn Data Center",
|
||||||
|
"operator": "QTS",
|
||||||
|
"capacity_mw": 130,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2017,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-87.6579, 41.8458] },
|
||||||
|
"properties": {
|
||||||
|
"name": "QTS Chicago Data Center",
|
||||||
|
"operator": "QTS",
|
||||||
|
"capacity_mw": 80,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2012,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-122.9907, 45.5412] },
|
||||||
|
"properties": {
|
||||||
|
"name": "QTS Hillsboro Data Center",
|
||||||
|
"operator": "QTS",
|
||||||
|
"capacity_mw": 70,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2015,
|
||||||
|
"region": "BPA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-96.8929, 32.9501] },
|
||||||
|
"properties": {
|
||||||
|
"name": "CyrusOne Carrollton Data Center",
|
||||||
|
"operator": "CyrusOne",
|
||||||
|
"capacity_mw": 100,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2011,
|
||||||
|
"region": "ERCOT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-97.7194, 30.2655] },
|
||||||
|
"properties": {
|
||||||
|
"name": "CyrusOne Austin Data Center",
|
||||||
|
"operator": "CyrusOne",
|
||||||
|
"capacity_mw": 60,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2015,
|
||||||
|
"region": "ERCOT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-84.3880, 33.7554] },
|
||||||
|
"properties": {
|
||||||
|
"name": "QTS Atlanta Metro Data Center",
|
||||||
|
"operator": "QTS",
|
||||||
|
"capacity_mw": 100,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2013,
|
||||||
|
"region": "SERC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-112.1161, 33.5605] },
|
||||||
|
"properties": {
|
||||||
|
"name": "CyrusOne Chandler Data Center",
|
||||||
|
"operator": "CyrusOne",
|
||||||
|
"capacity_mw": 80,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2018,
|
||||||
|
"region": "SPP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-77.4510, 38.9275] },
|
||||||
|
"properties": {
|
||||||
|
"name": "CoreSite Reston Data Center",
|
||||||
|
"operator": "CoreSite",
|
||||||
|
"capacity_mw": 50,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2013,
|
||||||
|
"region": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-104.8563, 39.5731] },
|
||||||
|
"properties": {
|
||||||
|
"name": "CoreSite Denver Data Center",
|
||||||
|
"operator": "CoreSite",
|
||||||
|
"capacity_mw": 40,
|
||||||
|
"status": "operational",
|
||||||
|
"year_opened": 2010,
|
||||||
|
"region": "SPP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": { "type": "Point", "coordinates": [-97.5028, 35.4810] },
|
||||||
|
"properties": {
|
||||||
|
"name": "Oracle Stargate Abilene Campus",
|
||||||
|
"operator": "Oracle",
|
||||||
|
"capacity_mw": 200,
|
||||||
|
"status": "under_construction",
|
||||||
|
"year_opened": 2025,
|
||||||
|
"region": "ERCOT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
345
data/grid-regions.geojson
Normal file
345
data/grid-regions.geojson
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
{
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "MultiPolygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[-83.67, 41.73],
|
||||||
|
[-84.82, 41.76],
|
||||||
|
[-84.82, 39.10],
|
||||||
|
[-84.43, 38.45],
|
||||||
|
[-83.65, 38.63],
|
||||||
|
[-82.60, 38.17],
|
||||||
|
[-81.95, 37.54],
|
||||||
|
[-81.23, 37.27],
|
||||||
|
[-80.52, 37.48],
|
||||||
|
[-80.30, 37.10],
|
||||||
|
[-79.51, 36.54],
|
||||||
|
[-78.45, 35.69],
|
||||||
|
[-77.75, 36.00],
|
||||||
|
[-75.87, 36.55],
|
||||||
|
[-75.24, 37.77],
|
||||||
|
[-75.62, 38.46],
|
||||||
|
[-74.98, 38.93],
|
||||||
|
[-74.70, 39.30],
|
||||||
|
[-74.18, 39.62],
|
||||||
|
[-74.01, 40.07],
|
||||||
|
[-74.72, 40.15],
|
||||||
|
[-75.06, 39.99],
|
||||||
|
[-75.13, 39.88],
|
||||||
|
[-75.53, 39.84],
|
||||||
|
[-76.04, 39.72],
|
||||||
|
[-77.25, 39.32],
|
||||||
|
[-77.49, 39.10],
|
||||||
|
[-77.72, 39.32],
|
||||||
|
[-77.83, 39.64],
|
||||||
|
[-79.48, 39.72],
|
||||||
|
[-80.52, 40.64],
|
||||||
|
[-80.52, 41.98],
|
||||||
|
[-81.28, 42.21],
|
||||||
|
[-82.00, 41.96],
|
||||||
|
[-83.13, 41.96],
|
||||||
|
[-83.67, 41.73]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[-74.72, 40.15],
|
||||||
|
[-74.01, 40.07],
|
||||||
|
[-73.89, 40.57],
|
||||||
|
[-74.25, 40.53],
|
||||||
|
[-75.14, 40.68],
|
||||||
|
[-75.12, 41.85],
|
||||||
|
[-76.11, 42.00],
|
||||||
|
[-79.76, 42.27],
|
||||||
|
[-80.52, 41.98],
|
||||||
|
[-80.52, 40.64],
|
||||||
|
[-79.48, 39.72],
|
||||||
|
[-77.83, 39.64],
|
||||||
|
[-77.72, 39.32],
|
||||||
|
[-77.49, 39.10],
|
||||||
|
[-77.25, 39.32],
|
||||||
|
[-76.04, 39.72],
|
||||||
|
[-75.53, 39.84],
|
||||||
|
[-75.13, 39.88],
|
||||||
|
[-75.06, 39.99],
|
||||||
|
[-74.72, 40.15]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[-87.53, 41.76],
|
||||||
|
[-87.53, 39.35],
|
||||||
|
[-87.53, 38.23],
|
||||||
|
[-87.69, 37.79],
|
||||||
|
[-87.10, 37.79],
|
||||||
|
[-86.52, 36.64],
|
||||||
|
[-85.98, 36.63],
|
||||||
|
[-84.86, 36.63],
|
||||||
|
[-84.43, 38.45],
|
||||||
|
[-84.82, 39.10],
|
||||||
|
[-84.82, 41.76],
|
||||||
|
[-87.53, 41.76]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "PJM Interconnection",
|
||||||
|
"code": "PJM",
|
||||||
|
"iso": "PJM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "MultiPolygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[-106.65, 31.75],
|
||||||
|
[-103.06, 31.97],
|
||||||
|
[-100.00, 31.00],
|
||||||
|
[-99.41, 27.84],
|
||||||
|
[-97.14, 25.97],
|
||||||
|
[-96.36, 28.14],
|
||||||
|
[-93.84, 29.71],
|
||||||
|
[-93.72, 31.08],
|
||||||
|
[-94.04, 33.55],
|
||||||
|
[-96.31, 33.90],
|
||||||
|
[-97.37, 33.97],
|
||||||
|
[-100.00, 34.56],
|
||||||
|
[-103.04, 32.00],
|
||||||
|
[-106.65, 31.75]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "Electric Reliability Council of Texas",
|
||||||
|
"code": "ERCOT",
|
||||||
|
"iso": "ERCOT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "MultiPolygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[-124.41, 42.00],
|
||||||
|
[-123.23, 42.00],
|
||||||
|
[-120.00, 42.00],
|
||||||
|
[-120.00, 39.00],
|
||||||
|
[-117.63, 37.43],
|
||||||
|
[-116.09, 35.98],
|
||||||
|
[-114.63, 34.87],
|
||||||
|
[-114.63, 32.72],
|
||||||
|
[-117.12, 32.54],
|
||||||
|
[-118.60, 33.78],
|
||||||
|
[-120.63, 34.57],
|
||||||
|
[-121.89, 36.60],
|
||||||
|
[-122.39, 37.62],
|
||||||
|
[-122.47, 37.81],
|
||||||
|
[-123.03, 38.31],
|
||||||
|
[-123.73, 39.33],
|
||||||
|
[-124.41, 40.44],
|
||||||
|
[-124.41, 42.00]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "California Independent System Operator",
|
||||||
|
"code": "CAISO",
|
||||||
|
"iso": "CAISO"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "MultiPolygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[-79.76, 42.27],
|
||||||
|
[-79.76, 43.28],
|
||||||
|
[-78.89, 42.95],
|
||||||
|
[-76.80, 43.63],
|
||||||
|
[-76.18, 44.20],
|
||||||
|
[-75.32, 44.81],
|
||||||
|
[-74.87, 45.01],
|
||||||
|
[-73.34, 45.01],
|
||||||
|
[-73.34, 42.05],
|
||||||
|
[-73.73, 41.10],
|
||||||
|
[-74.25, 40.53],
|
||||||
|
[-73.89, 40.57],
|
||||||
|
[-74.01, 40.07],
|
||||||
|
[-73.74, 40.63],
|
||||||
|
[-72.76, 40.75],
|
||||||
|
[-71.85, 40.98],
|
||||||
|
[-73.73, 41.10],
|
||||||
|
[-73.34, 42.05],
|
||||||
|
[-79.76, 42.27]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "New York Independent System Operator",
|
||||||
|
"code": "NYISO",
|
||||||
|
"iso": "NYISO"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "MultiPolygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[-73.73, 41.10],
|
||||||
|
[-71.85, 40.98],
|
||||||
|
[-71.12, 41.49],
|
||||||
|
[-69.93, 41.67],
|
||||||
|
[-70.60, 41.78],
|
||||||
|
[-70.82, 42.67],
|
||||||
|
[-70.70, 43.07],
|
||||||
|
[-69.04, 43.98],
|
||||||
|
[-68.12, 44.38],
|
||||||
|
[-67.79, 44.55],
|
||||||
|
[-67.10, 45.14],
|
||||||
|
[-67.10, 47.27],
|
||||||
|
[-68.57, 47.29],
|
||||||
|
[-70.25, 46.25],
|
||||||
|
[-71.08, 45.30],
|
||||||
|
[-71.50, 45.01],
|
||||||
|
[-73.34, 45.01],
|
||||||
|
[-73.34, 42.05],
|
||||||
|
[-73.73, 41.10]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "ISO New England",
|
||||||
|
"code": "ISONE",
|
||||||
|
"iso": "ISONE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "MultiPolygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[-97.23, 49.00],
|
||||||
|
[-95.15, 49.00],
|
||||||
|
[-95.15, 48.00],
|
||||||
|
[-89.49, 48.01],
|
||||||
|
[-84.72, 46.63],
|
||||||
|
[-83.59, 46.03],
|
||||||
|
[-84.11, 45.18],
|
||||||
|
[-85.61, 44.77],
|
||||||
|
[-86.46, 43.89],
|
||||||
|
[-86.27, 42.40],
|
||||||
|
[-86.80, 41.76],
|
||||||
|
[-87.53, 41.76],
|
||||||
|
[-87.53, 38.23],
|
||||||
|
[-87.69, 37.79],
|
||||||
|
[-88.07, 37.50],
|
||||||
|
[-88.47, 37.07],
|
||||||
|
[-89.10, 36.95],
|
||||||
|
[-90.18, 36.50],
|
||||||
|
[-94.62, 36.50],
|
||||||
|
[-94.62, 37.00],
|
||||||
|
[-95.07, 37.00],
|
||||||
|
[-95.78, 39.99],
|
||||||
|
[-96.00, 40.00],
|
||||||
|
[-96.45, 42.49],
|
||||||
|
[-96.63, 42.52],
|
||||||
|
[-96.44, 43.50],
|
||||||
|
[-96.45, 45.30],
|
||||||
|
[-96.56, 45.94],
|
||||||
|
[-97.23, 49.00]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[-89.10, 36.95],
|
||||||
|
[-89.70, 36.25],
|
||||||
|
[-89.67, 34.96],
|
||||||
|
[-90.31, 34.73],
|
||||||
|
[-90.58, 34.14],
|
||||||
|
[-91.15, 33.01],
|
||||||
|
[-91.17, 31.55],
|
||||||
|
[-91.65, 31.00],
|
||||||
|
[-93.53, 31.18],
|
||||||
|
[-93.72, 31.08],
|
||||||
|
[-93.84, 29.71],
|
||||||
|
[-93.84, 30.25],
|
||||||
|
[-94.04, 31.00],
|
||||||
|
[-94.04, 33.55],
|
||||||
|
[-94.48, 33.64],
|
||||||
|
[-94.43, 35.39],
|
||||||
|
[-94.62, 36.50],
|
||||||
|
[-90.18, 36.50],
|
||||||
|
[-89.10, 36.95]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "Midcontinent Independent System Operator",
|
||||||
|
"code": "MISO",
|
||||||
|
"iso": "MISO"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "MultiPolygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[-104.05, 49.00],
|
||||||
|
[-97.23, 49.00],
|
||||||
|
[-96.56, 45.94],
|
||||||
|
[-96.45, 45.30],
|
||||||
|
[-96.44, 43.50],
|
||||||
|
[-96.63, 42.52],
|
||||||
|
[-96.45, 42.49],
|
||||||
|
[-96.00, 40.00],
|
||||||
|
[-95.78, 39.99],
|
||||||
|
[-95.07, 37.00],
|
||||||
|
[-94.62, 37.00],
|
||||||
|
[-94.62, 36.50],
|
||||||
|
[-94.43, 35.39],
|
||||||
|
[-94.48, 33.64],
|
||||||
|
[-96.31, 33.90],
|
||||||
|
[-100.00, 34.56],
|
||||||
|
[-103.00, 36.50],
|
||||||
|
[-103.00, 37.00],
|
||||||
|
[-104.05, 38.00],
|
||||||
|
[-104.05, 41.00],
|
||||||
|
[-104.05, 43.00],
|
||||||
|
[-104.05, 45.94],
|
||||||
|
[-104.05, 49.00]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "Southwest Power Pool",
|
||||||
|
"code": "SPP",
|
||||||
|
"iso": "SPP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgis/postgis:18-3.6
|
||||||
|
ports:
|
||||||
|
- "5433:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: energy_dashboard
|
||||||
|
POSTGRES_USER: energy
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
@ -1,18 +1,18 @@
|
|||||||
|
import eslint from '@eslint/js';
|
||||||
import nextPlugin from '@next/eslint-plugin-next';
|
import nextPlugin from '@next/eslint-plugin-next';
|
||||||
import pluginQuery from '@tanstack/eslint-plugin-query';
|
import pluginQuery from '@tanstack/eslint-plugin-query';
|
||||||
import parser from '@typescript-eslint/parser';
|
import parser from '@typescript-eslint/parser';
|
||||||
|
import importPlugin from 'eslint-plugin-import';
|
||||||
|
import prettierPlugin from 'eslint-plugin-prettier/recommended';
|
||||||
import pluginReact from 'eslint-plugin-react';
|
import pluginReact from 'eslint-plugin-react';
|
||||||
import reactCompiler from 'eslint-plugin-react-compiler';
|
import reactCompiler from 'eslint-plugin-react-compiler';
|
||||||
import pluginReactHooks from 'eslint-plugin-react-hooks';
|
import pluginReactHooks from 'eslint-plugin-react-hooks';
|
||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
import eslint from '@eslint/js';
|
|
||||||
import importPlugin from 'eslint-plugin-import';
|
|
||||||
import prettierPlugin from 'eslint-plugin-prettier/recommended';
|
|
||||||
import tseslint from 'typescript-eslint';
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
/** @type {import("eslint").Linter.Config} */
|
/** @type {import("eslint").Linter.Config} */
|
||||||
export default [
|
export default [
|
||||||
...eslint.configs.recommended,
|
eslint.configs.recommended,
|
||||||
...tseslint.configs.recommendedTypeChecked,
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
prettierPlugin,
|
prettierPlugin,
|
||||||
{
|
{
|
||||||
@ -149,8 +149,6 @@ export default [
|
|||||||
'warn',
|
'warn',
|
||||||
{ selector: "CallExpression[callee.name='unwrap']", message: 'Handle errors instead of unwrapping!' },
|
{ selector: "CallExpression[callee.name='unwrap']", message: 'Handle errors instead of unwrapping!' },
|
||||||
],
|
],
|
||||||
'meridian/no-direct-cache-tag': 'error',
|
|
||||||
'meridian/no-private-env-unless-server-only': 'error',
|
|
||||||
'@typescript-eslint/no-misused-promises': [
|
'@typescript-eslint/no-misused-promises': [
|
||||||
'error',
|
'error',
|
||||||
{ checksVoidReturn: { arguments: false, attributes: false } },
|
{ checksVoidReturn: { arguments: false, attributes: false } },
|
||||||
@ -160,14 +158,14 @@ export default [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['app/**/{page,layout,not-found,default,loading,global-error,error}.tsx'],
|
files: ['src/app/**/{page,layout,not-found,default,loading,global-error,error}.tsx'],
|
||||||
rules: {
|
rules: {
|
||||||
'import/no-default-export': 'off',
|
'import/no-default-export': 'off',
|
||||||
'@typescript-eslint/require-await': 'off',
|
'@typescript-eslint/require-await': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['app/**/routeType.ts'],
|
files: ['src/app/**/routeType.ts'],
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'warn',
|
'warn',
|
||||||
@ -180,16 +178,13 @@ export default [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
files: ['lib/cache.ts'],
|
|
||||||
rules: {
|
|
||||||
'meridian/no-direct-cache-tag': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
files: ['next.config.ts'],
|
files: ['next.config.ts'],
|
||||||
rules: { '@typescript-eslint/no-unsafe-type-assertion': 'off', 'import/no-default-export': 'off' },
|
rules: { '@typescript-eslint/no-unsafe-type-assertion': 'off', 'import/no-default-export': 'off' },
|
||||||
},
|
},
|
||||||
{ ignores: ['next-env.d.ts', 'gen/**/*'] },
|
{
|
||||||
|
files: ['prisma.config.ts'],
|
||||||
|
rules: { 'import/no-default-export': 'off' },
|
||||||
|
},
|
||||||
|
{ ignores: ['next-env.d.ts', 'gen/**/*', 'src/generated/**'] },
|
||||||
];
|
];
|
||||||
|
|||||||
7
next.config.ts
Normal file
7
next.config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { NextConfig } from 'next';
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
typedRoutes: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
65
package.json
Normal file
65
package.json
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"name": "bonus4",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --turbopack",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint . --max-warnings 0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "9",
|
||||||
|
"@next/eslint-plugin-next": "^16.1.6",
|
||||||
|
"@tanstack/eslint-plugin-query": "^5.91.4",
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/node": "^25.2.3",
|
||||||
|
"@types/react": "^19.2.13",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@typescript-eslint/parser": "^8.55.0",
|
||||||
|
"eslint": "9",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-import": "^2.32.0",
|
||||||
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
"globals": "^17.3.0",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
|
"prettier-plugin-organize-imports": "^4.3.0",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
|
"typescript-eslint": "^8.55.0",
|
||||||
|
"zod-validation-error": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/adapter-pg": "^7.3.0",
|
||||||
|
"@prisma/client": "^7.3.0",
|
||||||
|
"@tailwindcss/postcss": "^4.1.18",
|
||||||
|
"@vis.gl/react-google-maps": "^1.7.1",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"dotenv": "^17.2.4",
|
||||||
|
"framer-motion": "^12.34.0",
|
||||||
|
"lucide-react": "^0.563.0",
|
||||||
|
"next": "^16.1.6",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"prisma": "^7.3.0",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-dom": "^19.2.4",
|
||||||
|
"recharts": "^3.7.0",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
|
"superjson": "^2.2.6",
|
||||||
|
"tailwind-merge": "^3.4.0",
|
||||||
|
"tailwindcss": "^4.1.18",
|
||||||
|
"tslib": "^2.8.1",
|
||||||
|
"tw-animate-css": "^1.4.0",
|
||||||
|
"zod": "^4.3.6"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"zod-validation-error": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
postcss.config.js
Normal file
5
postcss.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
'@tailwindcss/postcss': {},
|
||||||
|
},
|
||||||
|
};
|
||||||
13
prisma.config.ts
Normal file
13
prisma.config.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
|
import { defineConfig, env } from 'prisma/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: 'prisma/schema.prisma',
|
||||||
|
migrations: {
|
||||||
|
path: 'prisma/migrations',
|
||||||
|
seed: 'bun run prisma/seed.ts',
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: env('DATABASE_URL'),
|
||||||
|
},
|
||||||
|
});
|
||||||
85
prisma/migrations/20260211091325_init/migration.sql
Normal file
85
prisma/migrations/20260211091325_init/migration.sql
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
-- CreateExtension
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "postgis";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "grid_regions" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"code" TEXT NOT NULL,
|
||||||
|
"iso" TEXT NOT NULL,
|
||||||
|
"boundary" geography(MultiPolygon, 4326) NOT NULL,
|
||||||
|
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "grid_regions_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "datacenters" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"operator" TEXT NOT NULL,
|
||||||
|
"location" geography(Point, 4326) NOT NULL,
|
||||||
|
"capacity_mw" DOUBLE PRECISION NOT NULL,
|
||||||
|
"status" TEXT NOT NULL,
|
||||||
|
"year_opened" INTEGER NOT NULL,
|
||||||
|
"region_id" UUID NOT NULL,
|
||||||
|
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "datacenters_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "electricity_prices" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"region_id" UUID NOT NULL,
|
||||||
|
"price_mwh" DOUBLE PRECISION NOT NULL,
|
||||||
|
"demand_mw" DOUBLE PRECISION NOT NULL,
|
||||||
|
"timestamp" TIMESTAMPTZ NOT NULL,
|
||||||
|
"source" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "electricity_prices_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "commodity_prices" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"commodity" TEXT NOT NULL,
|
||||||
|
"price" DOUBLE PRECISION NOT NULL,
|
||||||
|
"unit" TEXT NOT NULL,
|
||||||
|
"timestamp" TIMESTAMPTZ NOT NULL,
|
||||||
|
"source" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "commodity_prices_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "generation_mix" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"region_id" UUID NOT NULL,
|
||||||
|
"fuel_type" TEXT NOT NULL,
|
||||||
|
"generation_mw" DOUBLE PRECISION NOT NULL,
|
||||||
|
"timestamp" TIMESTAMPTZ NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "generation_mix_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "grid_regions_code_key" ON "grid_regions"("code");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "electricity_prices_region_id_timestamp_idx" ON "electricity_prices"("region_id", "timestamp");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "commodity_prices_commodity_timestamp_idx" ON "commodity_prices"("commodity", "timestamp");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "generation_mix_region_id_timestamp_idx" ON "generation_mix"("region_id", "timestamp");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "datacenters" ADD CONSTRAINT "datacenters_region_id_fkey" FOREIGN KEY ("region_id") REFERENCES "grid_regions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "electricity_prices" ADD CONSTRAINT "electricity_prices_region_id_fkey" FOREIGN KEY ("region_id") REFERENCES "grid_regions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "generation_mix" ADD CONSTRAINT "generation_mix_region_id_fkey" FOREIGN KEY ("region_id") REFERENCES "grid_regions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "postgresql"
|
||||||
75
prisma/schema.prisma
Normal file
75
prisma/schema.prisma
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client"
|
||||||
|
output = "../src/generated/prisma"
|
||||||
|
previewFeatures = ["typedSql"]
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
}
|
||||||
|
|
||||||
|
model GridRegion {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
name String
|
||||||
|
code String @unique
|
||||||
|
iso String
|
||||||
|
boundary Unsupported("geography(MultiPolygon, 4326)")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||||
|
datacenters Datacenter[]
|
||||||
|
electricityPrices ElectricityPrice[]
|
||||||
|
generationMixes GenerationMix[]
|
||||||
|
|
||||||
|
@@map("grid_regions")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Datacenter {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
name String
|
||||||
|
operator String
|
||||||
|
location Unsupported("geography(Point, 4326)")
|
||||||
|
capacityMw Float @map("capacity_mw")
|
||||||
|
status String
|
||||||
|
yearOpened Int @map("year_opened")
|
||||||
|
regionId String @map("region_id") @db.Uuid
|
||||||
|
region GridRegion @relation(fields: [regionId], references: [id])
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||||
|
|
||||||
|
@@map("datacenters")
|
||||||
|
}
|
||||||
|
|
||||||
|
model ElectricityPrice {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
regionId String @map("region_id") @db.Uuid
|
||||||
|
region GridRegion @relation(fields: [regionId], references: [id])
|
||||||
|
priceMwh Float @map("price_mwh")
|
||||||
|
demandMw Float @map("demand_mw")
|
||||||
|
timestamp DateTime @db.Timestamptz
|
||||||
|
source String
|
||||||
|
|
||||||
|
@@index([regionId, timestamp])
|
||||||
|
@@map("electricity_prices")
|
||||||
|
}
|
||||||
|
|
||||||
|
model CommodityPrice {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
commodity String
|
||||||
|
price Float
|
||||||
|
unit String
|
||||||
|
timestamp DateTime @db.Timestamptz
|
||||||
|
source String
|
||||||
|
|
||||||
|
@@index([commodity, timestamp])
|
||||||
|
@@map("commodity_prices")
|
||||||
|
}
|
||||||
|
|
||||||
|
model GenerationMix {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
regionId String @map("region_id") @db.Uuid
|
||||||
|
region GridRegion @relation(fields: [regionId], references: [id])
|
||||||
|
fuelType String @map("fuel_type")
|
||||||
|
generationMw Float @map("generation_mw")
|
||||||
|
timestamp DateTime @db.Timestamptz
|
||||||
|
|
||||||
|
@@index([regionId, timestamp])
|
||||||
|
@@map("generation_mix")
|
||||||
|
}
|
||||||
229
prisma/seed.ts
Normal file
229
prisma/seed.ts
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { z } from 'zod/v4';
|
||||||
|
import { PrismaClient } from '../src/generated/prisma/client.js';
|
||||||
|
|
||||||
|
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
||||||
|
const prisma = new PrismaClient({ adapter });
|
||||||
|
|
||||||
|
const PointGeometrySchema = z.object({
|
||||||
|
type: z.literal('Point'),
|
||||||
|
coordinates: z.tuple([z.number(), z.number()]),
|
||||||
|
});
|
||||||
|
|
||||||
|
const MultiPolygonGeometrySchema = z.object({
|
||||||
|
type: z.literal('MultiPolygon'),
|
||||||
|
coordinates: z.array(z.array(z.array(z.tuple([z.number(), z.number()])))),
|
||||||
|
});
|
||||||
|
|
||||||
|
const RegionPropertiesSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
code: z.string(),
|
||||||
|
iso: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const DatacenterPropertiesSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
operator: z.string(),
|
||||||
|
capacity_mw: z.number(),
|
||||||
|
status: z.string(),
|
||||||
|
year_opened: z.number(),
|
||||||
|
region: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const RegionFeatureSchema = z.object({
|
||||||
|
type: z.literal('Feature'),
|
||||||
|
geometry: MultiPolygonGeometrySchema,
|
||||||
|
properties: RegionPropertiesSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
const DatacenterFeatureSchema = z.object({
|
||||||
|
type: z.literal('Feature'),
|
||||||
|
geometry: PointGeometrySchema,
|
||||||
|
properties: DatacenterPropertiesSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
const RegionCollectionSchema = z.object({
|
||||||
|
type: z.literal('FeatureCollection'),
|
||||||
|
features: z.array(RegionFeatureSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const DatacenterCollectionSchema = z.object({
|
||||||
|
type: z.literal('FeatureCollection'),
|
||||||
|
features: z.array(DatacenterFeatureSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const AIMilestoneSchema = z.object({
|
||||||
|
date: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
category: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
function readAndParse<T>(relativePath: string, schema: z.ZodType<T>): T {
|
||||||
|
const fullPath = resolve(import.meta.dirname, '..', relativePath);
|
||||||
|
const raw: unknown = JSON.parse(readFileSync(fullPath, 'utf-8'));
|
||||||
|
return schema.parse(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedGridRegions() {
|
||||||
|
console.log('Seeding grid regions...');
|
||||||
|
|
||||||
|
const geojson = readAndParse('data/grid-regions.geojson', RegionCollectionSchema);
|
||||||
|
|
||||||
|
// Delete existing data (order matters for foreign keys)
|
||||||
|
await prisma.$executeRawUnsafe('DELETE FROM datacenters');
|
||||||
|
await prisma.$executeRawUnsafe('DELETE FROM electricity_prices');
|
||||||
|
await prisma.$executeRawUnsafe('DELETE FROM generation_mix');
|
||||||
|
await prisma.$executeRawUnsafe('DELETE FROM grid_regions');
|
||||||
|
|
||||||
|
for (const feature of geojson.features) {
|
||||||
|
const id = randomUUID();
|
||||||
|
const geojsonStr = JSON.stringify(feature.geometry);
|
||||||
|
|
||||||
|
await prisma.$executeRawUnsafe(
|
||||||
|
`INSERT INTO grid_regions (id, name, code, iso, boundary, created_at)
|
||||||
|
VALUES ($1::uuid, $2, $3, $4, ST_GeomFromGeoJSON($5)::geography, NOW())`,
|
||||||
|
id,
|
||||||
|
feature.properties.name,
|
||||||
|
feature.properties.code,
|
||||||
|
feature.properties.iso,
|
||||||
|
geojsonStr,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(` Inserted region: ${feature.properties.code}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedDatacenters() {
|
||||||
|
console.log('Seeding datacenters...');
|
||||||
|
|
||||||
|
const geojson = readAndParse('data/datacenters.geojson', DatacenterCollectionSchema);
|
||||||
|
|
||||||
|
// Get all region codes from DB
|
||||||
|
const regions = await prisma.$queryRawUnsafe<Array<{ id: string; code: string }>>(
|
||||||
|
'SELECT id, code FROM grid_regions',
|
||||||
|
);
|
||||||
|
const regionMap = new Map(regions.map(r => [r.code, r.id]));
|
||||||
|
|
||||||
|
let inserted = 0;
|
||||||
|
let skipped = 0;
|
||||||
|
|
||||||
|
for (const feature of geojson.features) {
|
||||||
|
const props = feature.properties;
|
||||||
|
const regionId = regionMap.get(props.region);
|
||||||
|
|
||||||
|
if (!regionId) {
|
||||||
|
// Try to find the region by spatial containment
|
||||||
|
const [lng, lat] = feature.geometry.coordinates;
|
||||||
|
const spatialMatch = await prisma.$queryRawUnsafe<Array<{ id: string; code: string }>>(
|
||||||
|
`SELECT id, code FROM grid_regions
|
||||||
|
WHERE ST_Contains(boundary::geometry, ST_SetSRID(ST_MakePoint($1, $2), 4326))
|
||||||
|
LIMIT 1`,
|
||||||
|
lng,
|
||||||
|
lat,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (spatialMatch.length > 0) {
|
||||||
|
const match = spatialMatch[0]!;
|
||||||
|
const id = randomUUID();
|
||||||
|
const geojsonStr = JSON.stringify(feature.geometry);
|
||||||
|
|
||||||
|
await prisma.$executeRawUnsafe(
|
||||||
|
`INSERT INTO datacenters (id, name, operator, location, capacity_mw, status, year_opened, region_id, created_at)
|
||||||
|
VALUES ($1::uuid, $2, $3, ST_GeomFromGeoJSON($4)::geography, $5, $6, $7, $8::uuid, NOW())`,
|
||||||
|
id,
|
||||||
|
props.name,
|
||||||
|
props.operator,
|
||||||
|
geojsonStr,
|
||||||
|
props.capacity_mw,
|
||||||
|
props.status,
|
||||||
|
props.year_opened,
|
||||||
|
match.id,
|
||||||
|
);
|
||||||
|
console.log(` Inserted DC: ${props.name} (spatial match -> ${match.code})`);
|
||||||
|
inserted++;
|
||||||
|
} else {
|
||||||
|
console.log(` Skipped DC: ${props.name} (no matching region for "${props.region}")`);
|
||||||
|
skipped++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = randomUUID();
|
||||||
|
const geojsonStr = JSON.stringify(feature.geometry);
|
||||||
|
|
||||||
|
await prisma.$executeRawUnsafe(
|
||||||
|
`INSERT INTO datacenters (id, name, operator, location, capacity_mw, status, year_opened, region_id, created_at)
|
||||||
|
VALUES ($1::uuid, $2, $3, ST_GeomFromGeoJSON($4)::geography, $5, $6, $7, $8::uuid, NOW())`,
|
||||||
|
id,
|
||||||
|
props.name,
|
||||||
|
props.operator,
|
||||||
|
geojsonStr,
|
||||||
|
props.capacity_mw,
|
||||||
|
props.status,
|
||||||
|
props.year_opened,
|
||||||
|
regionId,
|
||||||
|
);
|
||||||
|
console.log(` Inserted DC: ${props.name} (${props.region})`);
|
||||||
|
inserted++;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` Total: ${inserted.toString()} inserted, ${skipped.toString()} skipped`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateAIMilestones() {
|
||||||
|
console.log('Validating AI milestones...');
|
||||||
|
const milestones = readAndParse('data/ai-milestones.json', z.array(AIMilestoneSchema));
|
||||||
|
console.log(` Valid JSON with ${milestones.length.toString()} milestones`);
|
||||||
|
|
||||||
|
for (const m of milestones) {
|
||||||
|
if (isNaN(Date.parse(m.date))) {
|
||||||
|
throw new Error(`Invalid date in milestone: ${m.date}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(' All milestones valid');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Starting seed...\n');
|
||||||
|
|
||||||
|
await seedGridRegions();
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
await seedDatacenters();
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
validateAIMilestones();
|
||||||
|
|
||||||
|
// Print summary
|
||||||
|
console.log('\n--- Seed Summary ---');
|
||||||
|
const regionCount = await prisma.$queryRawUnsafe<Array<{ count: bigint }>>(
|
||||||
|
'SELECT count(*) as count FROM grid_regions',
|
||||||
|
);
|
||||||
|
const dcCount = await prisma.$queryRawUnsafe<Array<{ count: bigint }>>('SELECT count(*) as count FROM datacenters');
|
||||||
|
console.log(`Grid regions: ${regionCount[0]!.count.toString()}`);
|
||||||
|
console.log(`Datacenters: ${dcCount[0]!.count.toString()}`);
|
||||||
|
|
||||||
|
// Show sample spatial data
|
||||||
|
const sample = await prisma.$queryRawUnsafe<Array<{ name: string; location_text: string }>>(
|
||||||
|
'SELECT name, ST_AsText(location) as location_text FROM datacenters LIMIT 3',
|
||||||
|
);
|
||||||
|
console.log('\nSample datacenter locations:');
|
||||||
|
for (const s of sample) {
|
||||||
|
console.log(` ${s.name}: ${s.location_text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\nSeed complete!');
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e: unknown) => {
|
||||||
|
console.error('Seed failed:', e);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
void prisma.$disconnect();
|
||||||
|
});
|
||||||
8
prisma/sql/findDatacentersInRegion.sql
Normal file
8
prisma/sql/findDatacentersInRegion.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
-- @param {String} $1:regionCode
|
||||||
|
SELECT
|
||||||
|
d.id, d.name, d.operator, d.capacity_mw, d.status, d.year_opened,
|
||||||
|
ST_AsGeoJSON(d.location)::TEXT as location_geojson
|
||||||
|
FROM datacenters d
|
||||||
|
JOIN grid_regions r ON d.region_id = r.id
|
||||||
|
WHERE r.code = $1
|
||||||
|
ORDER BY d.capacity_mw DESC
|
||||||
10
prisma/sql/findNearbyDatacenters.sql
Normal file
10
prisma/sql/findNearbyDatacenters.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
-- @param {Float} $1:lat
|
||||||
|
-- @param {Float} $2:lng
|
||||||
|
-- @param {Float} $3:radiusKm
|
||||||
|
SELECT
|
||||||
|
d.id, d.name, d.operator, d.capacity_mw,
|
||||||
|
ST_AsGeoJSON(d.location)::TEXT as location_geojson,
|
||||||
|
ST_Distance(d.location, ST_MakePoint($2, $1)::geography) / 1000 as distance_km
|
||||||
|
FROM datacenters d
|
||||||
|
WHERE ST_DWithin(d.location, ST_MakePoint($2, $1)::geography, $3 * 1000)
|
||||||
|
ORDER BY distance_km
|
||||||
15
prisma/sql/getDemandByRegion.sql
Normal file
15
prisma/sql/getDemandByRegion.sql
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
-- @param {DateTime} $1:startDate
|
||||||
|
-- @param {DateTime} $2:endDate
|
||||||
|
SELECT
|
||||||
|
r.code as region_code, r.name as region_name,
|
||||||
|
date_trunc('day', ep.timestamp) as day,
|
||||||
|
AVG(ep.demand_mw) as avg_demand,
|
||||||
|
MAX(ep.demand_mw) as peak_demand,
|
||||||
|
COUNT(DISTINCT d.id)::INT as datacenter_count,
|
||||||
|
COALESCE(SUM(DISTINCT d.capacity_mw), 0) as total_dc_capacity_mw
|
||||||
|
FROM grid_regions r
|
||||||
|
LEFT JOIN electricity_prices ep ON ep.region_id = r.id
|
||||||
|
AND ep.timestamp BETWEEN $1 AND $2
|
||||||
|
LEFT JOIN datacenters d ON d.region_id = r.id
|
||||||
|
GROUP BY r.id, r.code, r.name, date_trunc('day', ep.timestamp)
|
||||||
|
ORDER BY r.code, day
|
||||||
11
prisma/sql/getGenerationMix.sql
Normal file
11
prisma/sql/getGenerationMix.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
-- @param {String} $1:regionCode
|
||||||
|
-- @param {DateTime} $2:startDate
|
||||||
|
-- @param {DateTime} $3:endDate
|
||||||
|
SELECT
|
||||||
|
gm.fuel_type, gm.timestamp, gm.generation_mw,
|
||||||
|
r.code as region_code, r.name as region_name
|
||||||
|
FROM generation_mix gm
|
||||||
|
JOIN grid_regions r ON gm.region_id = r.id
|
||||||
|
WHERE r.code = $1
|
||||||
|
AND gm.timestamp BETWEEN $2 AND $3
|
||||||
|
ORDER BY gm.timestamp ASC, gm.fuel_type
|
||||||
6
prisma/sql/getLatestPrices.sql
Normal file
6
prisma/sql/getLatestPrices.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
SELECT DISTINCT ON (ep.region_id)
|
||||||
|
ep.id, ep.region_id, ep.price_mwh, ep.demand_mw, ep.timestamp, ep.source,
|
||||||
|
r.code as region_code, r.name as region_name
|
||||||
|
FROM electricity_prices ep
|
||||||
|
JOIN grid_regions r ON ep.region_id = r.id
|
||||||
|
ORDER BY ep.region_id, ep.timestamp DESC
|
||||||
11
prisma/sql/getPriceTrends.sql
Normal file
11
prisma/sql/getPriceTrends.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
-- @param {String} $1:regionCode
|
||||||
|
-- @param {DateTime} $2:startDate
|
||||||
|
-- @param {DateTime} $3:endDate
|
||||||
|
SELECT
|
||||||
|
ep.timestamp, ep.price_mwh, ep.demand_mw,
|
||||||
|
r.code as region_code, r.name as region_name
|
||||||
|
FROM electricity_prices ep
|
||||||
|
JOIN grid_regions r ON ep.region_id = r.id
|
||||||
|
WHERE r.code = $1
|
||||||
|
AND ep.timestamp BETWEEN $2 AND $3
|
||||||
|
ORDER BY ep.timestamp ASC
|
||||||
13
prisma/sql/getRegionPriceHeatmap.sql
Normal file
13
prisma/sql/getRegionPriceHeatmap.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
SELECT
|
||||||
|
r.code, r.name,
|
||||||
|
ST_AsGeoJSON(r.boundary)::TEXT as boundary_geojson,
|
||||||
|
AVG(ep.price_mwh) as avg_price,
|
||||||
|
MAX(ep.price_mwh) as max_price,
|
||||||
|
AVG(ep.demand_mw) as avg_demand,
|
||||||
|
COUNT(DISTINCT d.id)::INT as datacenter_count,
|
||||||
|
COALESCE(SUM(d.capacity_mw), 0) as total_dc_capacity_mw
|
||||||
|
FROM grid_regions r
|
||||||
|
LEFT JOIN electricity_prices ep ON ep.region_id = r.id
|
||||||
|
AND ep.timestamp > NOW() - INTERVAL '24 hours'
|
||||||
|
LEFT JOIN datacenters d ON d.region_id = r.id
|
||||||
|
GROUP BY r.id, r.code, r.name, r.boundary
|
||||||
123
src/app/globals.css
Normal file
123
src/app/globals.css
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
@import 'tailwindcss';
|
||||||
|
@import 'tw-animate-css';
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: oklch(0.205 0 0);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.97 0 0);
|
||||||
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
--muted: oklch(0.97 0 0);
|
||||||
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--input: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: oklch(0.145 0 0);
|
||||||
|
--foreground: oklch(0.985 0 0);
|
||||||
|
--card: oklch(0.145 0 0);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.145 0 0);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.985 0 0);
|
||||||
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
|
--secondary: oklch(0.269 0 0);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.269 0 0);
|
||||||
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
|
--accent: oklch(0.269 0 0);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.396 0.141 25.723);
|
||||||
|
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||||
|
--border: oklch(0.269 0 0);
|
||||||
|
--input: oklch(0.269 0 0);
|
||||||
|
--ring: oklch(0.439 0 0);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.205 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(0.269 0 0);
|
||||||
|
--sidebar-ring: oklch(0.439 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--color-sidebar: var(--sidebar);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/app/layout.tsx
Normal file
23
src/app/layout.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { ThemeProvider } from 'next-themes';
|
||||||
|
import { Toaster } from 'sonner';
|
||||||
|
import './globals.css';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Energy & AI Dashboard',
|
||||||
|
description:
|
||||||
|
'Interactive dashboard visualizing how AI datacenter buildout is driving regional electricity demand and energy prices',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="en" suppressHydrationWarning>
|
||||||
|
<body className="min-h-screen antialiased">
|
||||||
|
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem={false} disableTransitionOnChange>
|
||||||
|
{children}
|
||||||
|
<Toaster theme="dark" richColors position="bottom-right" />
|
||||||
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
src/app/page.tsx
Normal file
8
src/app/page.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default function DashboardHome() {
|
||||||
|
return (
|
||||||
|
<main className="flex min-h-screen flex-col items-center justify-center">
|
||||||
|
<h1 className="text-4xl font-bold tracking-tight">Energy & AI Dashboard</h1>
|
||||||
|
<p className="mt-4 text-muted-foreground">Phase 1 scaffold complete. Dashboard coming soon.</p>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
src/lib/db.ts
Normal file
16
src/lib/db.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { PrismaClient } from '@/generated/prisma/client.js';
|
||||||
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
|
|
||||||
|
const globalPrismaKey = Symbol.for('prisma');
|
||||||
|
|
||||||
|
type GlobalWithPrisma = typeof globalThis & { [globalPrismaKey]?: PrismaClient };
|
||||||
|
|
||||||
|
function createPrismaClient() {
|
||||||
|
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
||||||
|
return new PrismaClient({ adapter });
|
||||||
|
}
|
||||||
|
|
||||||
|
const g = globalThis as GlobalWithPrisma;
|
||||||
|
export const prisma = g[globalPrismaKey] ?? createPrismaClient();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') g[globalPrismaKey] = prisma;
|
||||||
11
src/lib/superjson.ts
Normal file
11
src/lib/superjson.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import SuperJSON from 'superjson';
|
||||||
|
|
||||||
|
export function serialize<T>(data: T) {
|
||||||
|
return SuperJSON.serialize(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deserialize<T>(data: ReturnType<typeof SuperJSON.serialize>): T {
|
||||||
|
return SuperJSON.deserialize<T>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SuperJSON };
|
||||||
27
src/lib/utils.ts
Normal file
27
src/lib/utils.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { type ClassValue, clsx } from 'clsx';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
|
const REGION_TIMEZONES: Record<string, string> = {
|
||||||
|
ERCOT: 'America/Chicago',
|
||||||
|
PJM: 'America/New_York',
|
||||||
|
CAISO: 'America/Los_Angeles',
|
||||||
|
NYISO: 'America/New_York',
|
||||||
|
ISONE: 'America/New_York',
|
||||||
|
MISO: 'America/Chicago',
|
||||||
|
SPP: 'America/Chicago',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function formatMarketTime(utcDate: Date, regionCode: string): string {
|
||||||
|
const timezone = REGION_TIMEZONES[regionCode] ?? 'America/New_York';
|
||||||
|
return utcDate.toLocaleString('en-US', {
|
||||||
|
timeZone: timezone,
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: true,
|
||||||
|
timeZoneName: 'short',
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -24,13 +24,41 @@
|
|||||||
"preserveConstEnums": true,
|
"preserveConstEnums": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"plugins": [{ "name": "next" }],
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"noEmit": true
|
"noEmit": true,
|
||||||
}
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"next.config.ts",
|
||||||
|
"prisma.config.ts",
|
||||||
|
"prisma/seed.ts",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
".next",
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user