Spaces:
Sleeping
Sleeping
Commit ·
762ea2d
0
Parent(s):
Initial commit: AI Training Data Deals Dashboard
Browse files- Complete Next.js application with minimalist humanist design
- Database schema with Prisma (SQLite) seeded with 24 baseline deals
- Deals explorer as main view with search, filters, and sorting
- Modal view for deal details with full information
- Pricing normalizations displayed in table and modal
- Analytics dashboard with market structure visualizations
- Pricing normalization tool for per-unit comparisons
- Python ingestion pipeline structure (Exa/Perplexity integration)
- Public Sans font throughout
- All columns sortable (provider, buyer, modality, price, date, etc.)
- Responsive design with clean, accessible UI
- .gitignore +55 -0
- app/analytics/page.tsx +190 -0
- app/api/deals/route.ts +65 -0
- app/deals/DealModal.tsx +301 -0
- app/deals/DealsClient.tsx +504 -0
- app/deals/[id]/page.tsx +205 -0
- app/deals/page.tsx +6 -0
- app/globals.css +236 -0
- app/layout.tsx +27 -0
- app/normalization/page.tsx +166 -0
- app/page.tsx +119 -0
- ingestion/.gitignore +12 -0
- ingestion/config.py +29 -0
- ingestion/db_writer.py +171 -0
- ingestion/deal_parser.py +303 -0
- ingestion/exa_client.py +190 -0
- ingestion/main.py +131 -0
- ingestion/perplexity_client.py +170 -0
- ingestion/requirements.txt +30 -0
- ingestion/source_registry.py +225 -0
- ingestion/validator.py +151 -0
- lib/prisma.ts +14 -0
- next-env.d.ts +5 -0
- next.config.js +7 -0
- package-lock.json +2246 -0
- package.json +33 -0
- postcss.config.js +7 -0
- prisma/schema.prisma +154 -0
- prisma/seed.ts +492 -0
- tailwind.config.ts +36 -0
- tsconfig.json +28 -0
- vercel.json +11 -0
.gitignore
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dependencies
|
| 2 |
+
node_modules/
|
| 3 |
+
.pnp
|
| 4 |
+
.pnp.js
|
| 5 |
+
|
| 6 |
+
# Testing
|
| 7 |
+
coverage/
|
| 8 |
+
|
| 9 |
+
# Next.js
|
| 10 |
+
.next/
|
| 11 |
+
out/
|
| 12 |
+
build/
|
| 13 |
+
dist/
|
| 14 |
+
|
| 15 |
+
# Production
|
| 16 |
+
*.log
|
| 17 |
+
|
| 18 |
+
# Misc
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.pem
|
| 21 |
+
|
| 22 |
+
# Debug
|
| 23 |
+
npm-debug.log*
|
| 24 |
+
yarn-debug.log*
|
| 25 |
+
yarn-error.log*
|
| 26 |
+
|
| 27 |
+
# Local env files
|
| 28 |
+
.env
|
| 29 |
+
.env*.local
|
| 30 |
+
|
| 31 |
+
# Vercel
|
| 32 |
+
.vercel
|
| 33 |
+
|
| 34 |
+
# Prisma
|
| 35 |
+
prisma/dev.db
|
| 36 |
+
prisma/dev.db-journal
|
| 37 |
+
*.db
|
| 38 |
+
*.db-journal
|
| 39 |
+
|
| 40 |
+
# IDE
|
| 41 |
+
.idea/
|
| 42 |
+
.vscode/
|
| 43 |
+
*.swp
|
| 44 |
+
*.swo
|
| 45 |
+
|
| 46 |
+
# Python
|
| 47 |
+
__pycache__/
|
| 48 |
+
*.py[cod]
|
| 49 |
+
*$py.class
|
| 50 |
+
*.so
|
| 51 |
+
.Python
|
| 52 |
+
venv/
|
| 53 |
+
env/
|
| 54 |
+
ENV/
|
| 55 |
+
|
app/analytics/page.tsx
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { prisma } from '@/lib/prisma'
|
| 2 |
+
import Link from 'next/link'
|
| 3 |
+
|
| 4 |
+
async function getAnalytics() {
|
| 5 |
+
const deals = await prisma.deal.findMany({
|
| 6 |
+
include: {
|
| 7 |
+
buyerRelations: {
|
| 8 |
+
include: {
|
| 9 |
+
buyer: true,
|
| 10 |
+
},
|
| 11 |
+
},
|
| 12 |
+
providerRelation: true,
|
| 13 |
+
},
|
| 14 |
+
})
|
| 15 |
+
|
| 16 |
+
// Calculate stats
|
| 17 |
+
const totalDeals = deals.length
|
| 18 |
+
const totalSpend = deals.reduce((sum, deal) => sum + (deal.priceUsd || 0), 0)
|
| 19 |
+
const exclusiveDeals = deals.filter(d => d.exclusive === true).length
|
| 20 |
+
const compensatedDeals = deals.filter(d => d.creatorsCompensated === true).length
|
| 21 |
+
const compensatedPercent = totalDeals > 0 ? Math.round((compensatedDeals / totalDeals) * 100) : 0
|
| 22 |
+
|
| 23 |
+
// Modality breakdown
|
| 24 |
+
const modalityCounts: Record<string, number> = {}
|
| 25 |
+
deals.forEach(deal => {
|
| 26 |
+
modalityCounts[deal.modality] = (modalityCounts[deal.modality] || 0) + 1
|
| 27 |
+
})
|
| 28 |
+
|
| 29 |
+
// Buyer breakdown
|
| 30 |
+
const buyerCounts: Record<string, number> = {}
|
| 31 |
+
const buyerSpend: Record<string, number> = {}
|
| 32 |
+
deals.forEach(deal => {
|
| 33 |
+
const buyers = deal.buyer.split(',').map(b => b.trim())
|
| 34 |
+
buyers.forEach(buyer => {
|
| 35 |
+
buyerCounts[buyer] = (buyerCounts[buyer] || 0) + 1
|
| 36 |
+
buyerSpend[buyer] = (buyerSpend[buyer] || 0) + (deal.priceUsd || 0)
|
| 37 |
+
})
|
| 38 |
+
})
|
| 39 |
+
|
| 40 |
+
// Provider breakdown
|
| 41 |
+
const providerCounts: Record<string, number> = {}
|
| 42 |
+
const providerSpend: Record<string, number> = {}
|
| 43 |
+
deals.forEach(deal => {
|
| 44 |
+
providerCounts[deal.provider] = (providerCounts[deal.provider] || 0) + 1
|
| 45 |
+
providerSpend[deal.provider] = (providerSpend[deal.provider] || 0) + (deal.priceUsd || 0)
|
| 46 |
+
})
|
| 47 |
+
|
| 48 |
+
// Top buyers by spend
|
| 49 |
+
const topBuyers = Object.entries(buyerSpend)
|
| 50 |
+
.sort(([, a], [, b]) => b - a)
|
| 51 |
+
.slice(0, 10)
|
| 52 |
+
.map(([name, spend]) => ({ name, spend, count: buyerCounts[name] }))
|
| 53 |
+
|
| 54 |
+
// Top providers by spend
|
| 55 |
+
const topProviders = Object.entries(providerSpend)
|
| 56 |
+
.sort(([, a], [, b]) => b - a)
|
| 57 |
+
.slice(0, 10)
|
| 58 |
+
.map(([name, spend]) => ({ name, spend, count: providerCounts[name] }))
|
| 59 |
+
|
| 60 |
+
return {
|
| 61 |
+
totalDeals,
|
| 62 |
+
totalSpend,
|
| 63 |
+
exclusiveDeals,
|
| 64 |
+
compensatedDeals,
|
| 65 |
+
compensatedPercent,
|
| 66 |
+
modalityCounts,
|
| 67 |
+
topBuyers,
|
| 68 |
+
topProviders,
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
function formatCurrency(amount: number): string {
|
| 73 |
+
if (amount >= 1000000000) {
|
| 74 |
+
return `$${(amount / 1000000000).toFixed(1)}B`
|
| 75 |
+
}
|
| 76 |
+
if (amount >= 1000000) {
|
| 77 |
+
return `$${(amount / 1000000).toFixed(0)}M`
|
| 78 |
+
}
|
| 79 |
+
if (amount >= 1000) {
|
| 80 |
+
return `$${(amount / 1000).toFixed(0)}K`
|
| 81 |
+
}
|
| 82 |
+
return `$${amount.toFixed(0)}`
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
export default async function AnalyticsPage() {
|
| 86 |
+
const analytics = await getAnalytics()
|
| 87 |
+
|
| 88 |
+
return (
|
| 89 |
+
<main className="min-h-screen bg-background">
|
| 90 |
+
<div className="container-content section-padding">
|
| 91 |
+
<div className="mb-8">
|
| 92 |
+
<h1 className="text-4xl font-semibold mb-4">Market Analytics</h1>
|
| 93 |
+
<p className="text-text-muted text-lg">
|
| 94 |
+
Market structure, concentration, and trends
|
| 95 |
+
</p>
|
| 96 |
+
</div>
|
| 97 |
+
|
| 98 |
+
{/* Key Stats */}
|
| 99 |
+
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-12">
|
| 100 |
+
<div className="stat-card">
|
| 101 |
+
<div className="stat-value">{analytics.totalDeals}</div>
|
| 102 |
+
<div className="stat-label">Total Deals</div>
|
| 103 |
+
</div>
|
| 104 |
+
<div className="stat-card">
|
| 105 |
+
<div className="stat-value">{formatCurrency(analytics.totalSpend)}</div>
|
| 106 |
+
<div className="stat-label">Total Reported Spend</div>
|
| 107 |
+
</div>
|
| 108 |
+
<div className="stat-card">
|
| 109 |
+
<div className="stat-value">{analytics.exclusiveDeals}</div>
|
| 110 |
+
<div className="stat-label">Exclusive Deals</div>
|
| 111 |
+
</div>
|
| 112 |
+
<div className="stat-card">
|
| 113 |
+
<div className="stat-value">{analytics.compensatedPercent}%</div>
|
| 114 |
+
<div className="stat-label">With Creator Compensation</div>
|
| 115 |
+
</div>
|
| 116 |
+
</div>
|
| 117 |
+
|
| 118 |
+
{/* Modality Breakdown */}
|
| 119 |
+
<div className="card mb-8">
|
| 120 |
+
<h2 className="text-2xl font-semibold mb-6">Deals by Modality</h2>
|
| 121 |
+
<div className="space-y-4">
|
| 122 |
+
{Object.entries(analytics.modalityCounts)
|
| 123 |
+
.sort(([, a], [, b]) => b - a)
|
| 124 |
+
.map(([modality, count]) => (
|
| 125 |
+
<div key={modality} className="flex items-center justify-between py-2 border-b border-border-subtle last:border-0">
|
| 126 |
+
<span className="font-medium">{modality}</span>
|
| 127 |
+
<div className="flex items-center gap-4">
|
| 128 |
+
<div className="w-32 bg-border-subtle rounded-full h-2">
|
| 129 |
+
<div
|
| 130 |
+
className="bg-accent h-2 rounded-full"
|
| 131 |
+
style={{ width: `${(count / analytics.totalDeals) * 100}%` }}
|
| 132 |
+
/>
|
| 133 |
+
</div>
|
| 134 |
+
<span className="text-text-muted w-12 text-right">{count}</span>
|
| 135 |
+
</div>
|
| 136 |
+
</div>
|
| 137 |
+
))}
|
| 138 |
+
</div>
|
| 139 |
+
</div>
|
| 140 |
+
|
| 141 |
+
{/* Top Buyers */}
|
| 142 |
+
<div className="grid md:grid-cols-2 gap-8 mb-8">
|
| 143 |
+
<div className="card">
|
| 144 |
+
<h2 className="text-2xl font-semibold mb-6">Top Buyers by Spend</h2>
|
| 145 |
+
<div className="space-y-4">
|
| 146 |
+
{analytics.topBuyers.map((buyer, idx) => (
|
| 147 |
+
<div key={buyer.name} className="flex items-center justify-between py-2">
|
| 148 |
+
<div className="flex items-center gap-3">
|
| 149 |
+
<span className="text-text-muted w-6">#{idx + 1}</span>
|
| 150 |
+
<div>
|
| 151 |
+
<div className="font-medium">{buyer.name}</div>
|
| 152 |
+
<div className="text-sm text-text-muted">{buyer.count} deal{buyer.count !== 1 ? 's' : ''}</div>
|
| 153 |
+
</div>
|
| 154 |
+
</div>
|
| 155 |
+
<span className="font-semibold">{formatCurrency(buyer.spend)}</span>
|
| 156 |
+
</div>
|
| 157 |
+
))}
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
|
| 161 |
+
{/* Top Providers */}
|
| 162 |
+
<div className="card">
|
| 163 |
+
<h2 className="text-2xl font-semibold mb-6">Top Providers by Spend</h2>
|
| 164 |
+
<div className="space-y-4">
|
| 165 |
+
{analytics.topProviders.map((provider, idx) => (
|
| 166 |
+
<div key={provider.name} className="flex items-center justify-between py-2">
|
| 167 |
+
<div className="flex items-center gap-3">
|
| 168 |
+
<span className="text-text-muted w-6">#{idx + 1}</span>
|
| 169 |
+
<div>
|
| 170 |
+
<div className="font-medium">{provider.name}</div>
|
| 171 |
+
<div className="text-sm text-text-muted">{provider.count} deal{provider.count !== 1 ? 's' : ''}</div>
|
| 172 |
+
</div>
|
| 173 |
+
</div>
|
| 174 |
+
<span className="font-semibold">{formatCurrency(provider.spend)}</span>
|
| 175 |
+
</div>
|
| 176 |
+
))}
|
| 177 |
+
</div>
|
| 178 |
+
</div>
|
| 179 |
+
</div>
|
| 180 |
+
|
| 181 |
+
<div className="mt-8">
|
| 182 |
+
<Link href="/" className="text-accent hover:text-accent-hover">
|
| 183 |
+
← Back to Deals
|
| 184 |
+
</Link>
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
+
</main>
|
| 188 |
+
)
|
| 189 |
+
}
|
| 190 |
+
|
app/api/deals/route.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextResponse } from 'next/server'
|
| 2 |
+
import { prisma } from '@/lib/prisma'
|
| 3 |
+
|
| 4 |
+
export async function GET(request: Request) {
|
| 5 |
+
try {
|
| 6 |
+
const { searchParams } = new URL(request.url)
|
| 7 |
+
const modality = searchParams.get('modality')
|
| 8 |
+
const buyer = searchParams.get('buyer')
|
| 9 |
+
const provider = searchParams.get('provider')
|
| 10 |
+
const exclusive = searchParams.get('exclusive')
|
| 11 |
+
const creatorsCompensated = searchParams.get('creatorsCompensated')
|
| 12 |
+
const limit = parseInt(searchParams.get('limit') || '100')
|
| 13 |
+
const offset = parseInt(searchParams.get('offset') || '0')
|
| 14 |
+
|
| 15 |
+
const where: any = {}
|
| 16 |
+
|
| 17 |
+
if (modality) {
|
| 18 |
+
where.modality = modality
|
| 19 |
+
}
|
| 20 |
+
if (buyer) {
|
| 21 |
+
where.buyer = { contains: buyer }
|
| 22 |
+
}
|
| 23 |
+
if (provider) {
|
| 24 |
+
where.provider = { contains: provider }
|
| 25 |
+
}
|
| 26 |
+
if (exclusive !== null) {
|
| 27 |
+
where.exclusive = exclusive === 'true'
|
| 28 |
+
}
|
| 29 |
+
if (creatorsCompensated !== null) {
|
| 30 |
+
where.creatorsCompensated = creatorsCompensated === 'true'
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
const [deals, total] = await Promise.all([
|
| 34 |
+
prisma.deal.findMany({
|
| 35 |
+
where,
|
| 36 |
+
take: limit,
|
| 37 |
+
skip: offset,
|
| 38 |
+
orderBy: { date: 'desc' },
|
| 39 |
+
include: {
|
| 40 |
+
buyerRelations: {
|
| 41 |
+
include: {
|
| 42 |
+
buyer: true,
|
| 43 |
+
},
|
| 44 |
+
},
|
| 45 |
+
providerRelation: true,
|
| 46 |
+
},
|
| 47 |
+
}),
|
| 48 |
+
prisma.deal.count({ where }),
|
| 49 |
+
])
|
| 50 |
+
|
| 51 |
+
return NextResponse.json({
|
| 52 |
+
deals,
|
| 53 |
+
total,
|
| 54 |
+
limit,
|
| 55 |
+
offset,
|
| 56 |
+
})
|
| 57 |
+
} catch (error) {
|
| 58 |
+
console.error('Error fetching deals:', error)
|
| 59 |
+
return NextResponse.json(
|
| 60 |
+
{ error: 'Failed to fetch deals' },
|
| 61 |
+
{ status: 500 }
|
| 62 |
+
)
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
app/deals/DealModal.tsx
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client'
|
| 2 |
+
|
| 3 |
+
import { useEffect } from 'react'
|
| 4 |
+
|
| 5 |
+
interface Deal {
|
| 6 |
+
id: string
|
| 7 |
+
provider: string
|
| 8 |
+
buyer: string
|
| 9 |
+
modality: string
|
| 10 |
+
dataType: string | null
|
| 11 |
+
priceUsd: number | null
|
| 12 |
+
priceRangeMinUsd: number | null
|
| 13 |
+
priceRangeMaxUsd: number | null
|
| 14 |
+
reportedTerms: string | null
|
| 15 |
+
exclusive: boolean | null
|
| 16 |
+
creatorsCompensated: boolean | null
|
| 17 |
+
creatorSplitPercentage: number | null
|
| 18 |
+
revenueShare: boolean | null
|
| 19 |
+
date: string | null
|
| 20 |
+
dealType: string | null
|
| 21 |
+
pricingMechanism: string | null
|
| 22 |
+
sourcePrimary: string | null
|
| 23 |
+
trainingAllowed: boolean | null
|
| 24 |
+
finetuningAllowed: boolean | null
|
| 25 |
+
inferenceAllowed: boolean | null
|
| 26 |
+
redistributionAllowed: boolean | null
|
| 27 |
+
deletionRequired: boolean | null
|
| 28 |
+
notes: string | null
|
| 29 |
+
sources: string | null
|
| 30 |
+
pricingNormalizations?: Array<{
|
| 31 |
+
unitType: string
|
| 32 |
+
normalizedCostPerUnit: number
|
| 33 |
+
normalizationMethod: string
|
| 34 |
+
}>
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
interface DealModalProps {
|
| 38 |
+
deal: Deal | null
|
| 39 |
+
isOpen: boolean
|
| 40 |
+
onClose: () => void
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
function formatPrice(deal: Deal) {
|
| 44 |
+
if (deal.priceUsd) {
|
| 45 |
+
if (deal.priceUsd >= 1000000000) {
|
| 46 |
+
return `$${(deal.priceUsd / 1000000000).toFixed(1)}B`
|
| 47 |
+
}
|
| 48 |
+
if (deal.priceUsd >= 1000000) {
|
| 49 |
+
return `$${(deal.priceUsd / 1000000).toFixed(0)}M`
|
| 50 |
+
}
|
| 51 |
+
if (deal.priceUsd >= 1000) {
|
| 52 |
+
return `$${(deal.priceUsd / 1000).toFixed(0)}K`
|
| 53 |
+
}
|
| 54 |
+
return `$${deal.priceUsd.toFixed(0)}`
|
| 55 |
+
}
|
| 56 |
+
if (deal.priceRangeMinUsd && deal.priceRangeMaxUsd) {
|
| 57 |
+
return `$${deal.priceRangeMinUsd.toFixed(2)}–${deal.priceRangeMaxUsd.toFixed(2)}`
|
| 58 |
+
}
|
| 59 |
+
return deal.reportedTerms || 'Undisclosed'
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
export default function DealModal({ deal, isOpen, onClose }: DealModalProps) {
|
| 63 |
+
useEffect(() => {
|
| 64 |
+
if (isOpen) {
|
| 65 |
+
document.body.style.overflow = 'hidden'
|
| 66 |
+
} else {
|
| 67 |
+
document.body.style.overflow = 'unset'
|
| 68 |
+
}
|
| 69 |
+
return () => {
|
| 70 |
+
document.body.style.overflow = 'unset'
|
| 71 |
+
}
|
| 72 |
+
}, [isOpen])
|
| 73 |
+
|
| 74 |
+
if (!isOpen || !deal) return null
|
| 75 |
+
|
| 76 |
+
return (
|
| 77 |
+
<div
|
| 78 |
+
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm"
|
| 79 |
+
onClick={onClose}
|
| 80 |
+
>
|
| 81 |
+
<div
|
| 82 |
+
className="bg-surface rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto"
|
| 83 |
+
onClick={(e) => e.stopPropagation()}
|
| 84 |
+
>
|
| 85 |
+
{/* Header */}
|
| 86 |
+
<div className="sticky top-0 bg-surface border-b border-border px-6 py-4 flex items-start justify-between">
|
| 87 |
+
<div className="flex-1">
|
| 88 |
+
<h2 className="text-2xl font-semibold mb-1">
|
| 89 |
+
{deal.provider} → {deal.buyer}
|
| 90 |
+
</h2>
|
| 91 |
+
<p className="text-text-muted">{deal.dataType || deal.modality}</p>
|
| 92 |
+
</div>
|
| 93 |
+
<button
|
| 94 |
+
onClick={onClose}
|
| 95 |
+
className="text-text-muted hover:text-text text-2xl leading-none ml-4"
|
| 96 |
+
aria-label="Close"
|
| 97 |
+
>
|
| 98 |
+
×
|
| 99 |
+
</button>
|
| 100 |
+
</div>
|
| 101 |
+
|
| 102 |
+
{/* Content */}
|
| 103 |
+
<div className="p-6 space-y-6">
|
| 104 |
+
{/* Summary Card */}
|
| 105 |
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 pb-6 border-b border-border">
|
| 106 |
+
<div>
|
| 107 |
+
<div className="text-sm text-text-muted mb-1">Price</div>
|
| 108 |
+
<div className="text-xl font-semibold">{formatPrice(deal)}</div>
|
| 109 |
+
</div>
|
| 110 |
+
<div>
|
| 111 |
+
<div className="text-sm text-text-muted mb-1">Deal Type</div>
|
| 112 |
+
<div className="font-medium">{deal.dealType || deal.pricingMechanism || '—'}</div>
|
| 113 |
+
</div>
|
| 114 |
+
<div>
|
| 115 |
+
<div className="text-sm text-text-muted mb-1">Date</div>
|
| 116 |
+
<div className="font-medium">{deal.date || '—'}</div>
|
| 117 |
+
</div>
|
| 118 |
+
<div>
|
| 119 |
+
<div className="text-sm text-text-muted mb-1">Source</div>
|
| 120 |
+
<div className="font-medium">{deal.sourcePrimary || '—'}</div>
|
| 121 |
+
</div>
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
{/* Reported Terms */}
|
| 125 |
+
{deal.reportedTerms && (
|
| 126 |
+
<div>
|
| 127 |
+
<h3 className="text-lg font-semibold mb-2">Reported Terms</h3>
|
| 128 |
+
<p className="text-text-muted leading-relaxed">{deal.reportedTerms}</p>
|
| 129 |
+
</div>
|
| 130 |
+
)}
|
| 131 |
+
|
| 132 |
+
{/* Rights & Compensation */}
|
| 133 |
+
<div className="grid md:grid-cols-2 gap-6">
|
| 134 |
+
<div>
|
| 135 |
+
<h3 className="text-lg font-semibold mb-4">Rights Granted</h3>
|
| 136 |
+
<div className="space-y-2">
|
| 137 |
+
<div className="flex justify-between">
|
| 138 |
+
<span className="text-text-muted">Training</span>
|
| 139 |
+
<span>{deal.trainingAllowed !== null ? (deal.trainingAllowed ? 'Yes' : 'No') : '—'}</span>
|
| 140 |
+
</div>
|
| 141 |
+
<div className="flex justify-between">
|
| 142 |
+
<span className="text-text-muted">Fine-tuning</span>
|
| 143 |
+
<span>{deal.finetuningAllowed !== null ? (deal.finetuningAllowed ? 'Yes' : 'No') : '—'}</span>
|
| 144 |
+
</div>
|
| 145 |
+
<div className="flex justify-between">
|
| 146 |
+
<span className="text-text-muted">Inference</span>
|
| 147 |
+
<span>{deal.inferenceAllowed !== null ? (deal.inferenceAllowed ? 'Yes' : 'No') : '—'}</span>
|
| 148 |
+
</div>
|
| 149 |
+
<div className="flex justify-between">
|
| 150 |
+
<span className="text-text-muted">Redistribution</span>
|
| 151 |
+
<span>{deal.redistributionAllowed !== null ? (deal.redistributionAllowed ? 'Yes' : 'No') : '—'}</span>
|
| 152 |
+
</div>
|
| 153 |
+
{deal.deletionRequired && (
|
| 154 |
+
<div className="flex justify-between">
|
| 155 |
+
<span className="text-text-muted">Deletion Required</span>
|
| 156 |
+
<span>Yes</span>
|
| 157 |
+
</div>
|
| 158 |
+
)}
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
|
| 162 |
+
<div>
|
| 163 |
+
<h3 className="text-lg font-semibold mb-4">Creator Compensation</h3>
|
| 164 |
+
<div className="space-y-2">
|
| 165 |
+
<div className="flex justify-between">
|
| 166 |
+
<span className="text-text-muted">Compensated</span>
|
| 167 |
+
<span>
|
| 168 |
+
{deal.creatorsCompensated === true ? (
|
| 169 |
+
<span className="badge badge-primary">Yes</span>
|
| 170 |
+
) : deal.creatorsCompensated === false ? (
|
| 171 |
+
'No'
|
| 172 |
+
) : (
|
| 173 |
+
'Unclear'
|
| 174 |
+
)}
|
| 175 |
+
</span>
|
| 176 |
+
</div>
|
| 177 |
+
{deal.creatorSplitPercentage && (
|
| 178 |
+
<div className="flex justify-between">
|
| 179 |
+
<span className="text-text-muted">Split</span>
|
| 180 |
+
<span>{deal.creatorSplitPercentage}%</span>
|
| 181 |
+
</div>
|
| 182 |
+
)}
|
| 183 |
+
{deal.revenueShare && (
|
| 184 |
+
<div className="flex justify-between">
|
| 185 |
+
<span className="text-text-muted">Revenue Share</span>
|
| 186 |
+
<span>Yes</span>
|
| 187 |
+
</div>
|
| 188 |
+
)}
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
</div>
|
| 192 |
+
|
| 193 |
+
{/* Pricing Normalizations */}
|
| 194 |
+
{(() => {
|
| 195 |
+
const normalizations = deal.pricingNormalizations || []
|
| 196 |
+
if (normalizations.length > 0) {
|
| 197 |
+
return (
|
| 198 |
+
<div>
|
| 199 |
+
<h3 className="text-lg font-semibold mb-4">Pricing Normalizations</h3>
|
| 200 |
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
| 201 |
+
{normalizations.map((norm, idx) => (
|
| 202 |
+
<div key={idx} className="border border-border-subtle rounded p-3">
|
| 203 |
+
<div className="text-xs text-text-muted mb-1">Per {norm.unitType}</div>
|
| 204 |
+
<div className="font-medium">
|
| 205 |
+
{norm.normalizedCostPerUnit < 0.001
|
| 206 |
+
? `$${(norm.normalizedCostPerUnit * 1000000).toFixed(2)}/1M`
|
| 207 |
+
: norm.normalizedCostPerUnit < 1
|
| 208 |
+
? `$${norm.normalizedCostPerUnit.toFixed(4)}`
|
| 209 |
+
: `$${norm.normalizedCostPerUnit.toFixed(2)}`}
|
| 210 |
+
</div>
|
| 211 |
+
<div className="text-xs text-text-muted mt-1">
|
| 212 |
+
{norm.normalizationMethod}
|
| 213 |
+
</div>
|
| 214 |
+
</div>
|
| 215 |
+
))}
|
| 216 |
+
</div>
|
| 217 |
+
</div>
|
| 218 |
+
)
|
| 219 |
+
}
|
| 220 |
+
return null
|
| 221 |
+
})()}
|
| 222 |
+
|
| 223 |
+
{/* Notes */}
|
| 224 |
+
{deal.notes && (
|
| 225 |
+
<div>
|
| 226 |
+
<h3 className="text-lg font-semibold mb-2">Notes</h3>
|
| 227 |
+
<p className="text-text-muted leading-relaxed">{deal.notes}</p>
|
| 228 |
+
</div>
|
| 229 |
+
)}
|
| 230 |
+
|
| 231 |
+
{/* Sources */}
|
| 232 |
+
<div>
|
| 233 |
+
<h3 className="text-lg font-semibold mb-4">Sources</h3>
|
| 234 |
+
<div className="space-y-2">
|
| 235 |
+
{deal.sourcePrimary && (
|
| 236 |
+
<div className="text-text-muted">
|
| 237 |
+
Primary: <span className="text-text font-medium">{deal.sourcePrimary}</span>
|
| 238 |
+
</div>
|
| 239 |
+
)}
|
| 240 |
+
{deal.sources && (() => {
|
| 241 |
+
try {
|
| 242 |
+
const sourcesArray = JSON.parse(deal.sources)
|
| 243 |
+
if (Array.isArray(sourcesArray) && sourcesArray.length > 0) {
|
| 244 |
+
return (
|
| 245 |
+
<div>
|
| 246 |
+
<div className="text-text-muted mb-2">Additional sources:</div>
|
| 247 |
+
<ul className="list-disc list-inside space-y-1">
|
| 248 |
+
{sourcesArray.map((source: string, idx: number) => (
|
| 249 |
+
<li key={idx}>
|
| 250 |
+
<a
|
| 251 |
+
href={source}
|
| 252 |
+
target="_blank"
|
| 253 |
+
rel="noopener noreferrer"
|
| 254 |
+
className="text-accent hover:text-accent-hover break-all"
|
| 255 |
+
>
|
| 256 |
+
{source}
|
| 257 |
+
</a>
|
| 258 |
+
</li>
|
| 259 |
+
))}
|
| 260 |
+
</ul>
|
| 261 |
+
</div>
|
| 262 |
+
)
|
| 263 |
+
}
|
| 264 |
+
} catch (e) {
|
| 265 |
+
// If not valid JSON, treat as plain string
|
| 266 |
+
if (deal.sources) {
|
| 267 |
+
return (
|
| 268 |
+
<div>
|
| 269 |
+
<a
|
| 270 |
+
href={deal.sources}
|
| 271 |
+
target="_blank"
|
| 272 |
+
rel="noopener noreferrer"
|
| 273 |
+
className="text-accent hover:text-accent-hover break-all"
|
| 274 |
+
>
|
| 275 |
+
{deal.sources}
|
| 276 |
+
</a>
|
| 277 |
+
</div>
|
| 278 |
+
)
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
return null
|
| 282 |
+
})()}
|
| 283 |
+
{!deal.sourcePrimary && (!deal.sources || (deal.sources && JSON.parse(deal.sources || '[]').length === 0)) && (
|
| 284 |
+
<div className="text-text-muted">No sources available</div>
|
| 285 |
+
)}
|
| 286 |
+
</div>
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
{/* Modality Badge */}
|
| 290 |
+
<div className="pt-4 border-t border-border">
|
| 291 |
+
<span className="badge badge-secondary">{deal.modality}</span>
|
| 292 |
+
{deal.exclusive && (
|
| 293 |
+
<span className="badge badge-primary ml-2">Exclusive</span>
|
| 294 |
+
)}
|
| 295 |
+
</div>
|
| 296 |
+
</div>
|
| 297 |
+
</div>
|
| 298 |
+
</div>
|
| 299 |
+
)
|
| 300 |
+
}
|
| 301 |
+
|
app/deals/DealsClient.tsx
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client'
|
| 2 |
+
|
| 3 |
+
import { useState } from 'react'
|
| 4 |
+
import DealModal from './DealModal'
|
| 5 |
+
|
| 6 |
+
interface Deal {
|
| 7 |
+
id: string
|
| 8 |
+
provider: string
|
| 9 |
+
buyer: string
|
| 10 |
+
modality: string
|
| 11 |
+
dataType: string | null
|
| 12 |
+
priceUsd: number | null
|
| 13 |
+
priceRangeMinUsd: number | null
|
| 14 |
+
priceRangeMaxUsd: number | null
|
| 15 |
+
reportedTerms: string | null
|
| 16 |
+
exclusive: boolean | null
|
| 17 |
+
creatorsCompensated: boolean | null
|
| 18 |
+
creatorSplitPercentage: number | null
|
| 19 |
+
revenueShare: boolean | null
|
| 20 |
+
date: string | null
|
| 21 |
+
dealType: string | null
|
| 22 |
+
pricingMechanism: string | null
|
| 23 |
+
sourcePrimary: string | null
|
| 24 |
+
trainingAllowed: boolean | null
|
| 25 |
+
finetuningAllowed: boolean | null
|
| 26 |
+
inferenceAllowed: boolean | null
|
| 27 |
+
redistributionAllowed: boolean | null
|
| 28 |
+
deletionRequired: boolean | null
|
| 29 |
+
notes: string | null
|
| 30 |
+
sources: string | null
|
| 31 |
+
pricingNormalizations?: Array<{
|
| 32 |
+
unitType: string
|
| 33 |
+
normalizedCostPerUnit: number
|
| 34 |
+
normalizationMethod: string
|
| 35 |
+
}>
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
interface DealsClientProps {
|
| 39 |
+
initialDeals: Deal[]
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
export default function DealsClient({ initialDeals }: DealsClientProps) {
|
| 43 |
+
const [deals] = useState<Deal[]>(initialDeals)
|
| 44 |
+
const [filters, setFilters] = useState({
|
| 45 |
+
modality: '',
|
| 46 |
+
buyer: '',
|
| 47 |
+
provider: '',
|
| 48 |
+
exclusive: '',
|
| 49 |
+
creatorsCompensated: '',
|
| 50 |
+
})
|
| 51 |
+
const [searchQuery, setSearchQuery] = useState('')
|
| 52 |
+
const [selectedDeal, setSelectedDeal] = useState<Deal | null>(null)
|
| 53 |
+
const [isModalOpen, setIsModalOpen] = useState(false)
|
| 54 |
+
const [sortBy, setSortBy] = useState<{ column: string; direction: 'asc' | 'desc' }>({
|
| 55 |
+
column: 'date',
|
| 56 |
+
direction: 'desc',
|
| 57 |
+
})
|
| 58 |
+
|
| 59 |
+
function formatPrice(deal: Deal) {
|
| 60 |
+
if (deal.priceUsd) {
|
| 61 |
+
if (deal.priceUsd >= 1000000000) {
|
| 62 |
+
return `$${(deal.priceUsd / 1000000000).toFixed(1)}B`
|
| 63 |
+
}
|
| 64 |
+
if (deal.priceUsd >= 1000000) {
|
| 65 |
+
return `$${(deal.priceUsd / 1000000).toFixed(0)}M`
|
| 66 |
+
}
|
| 67 |
+
if (deal.priceUsd >= 1000) {
|
| 68 |
+
return `$${(deal.priceUsd / 1000).toFixed(0)}K`
|
| 69 |
+
}
|
| 70 |
+
return `$${deal.priceUsd.toFixed(0)}`
|
| 71 |
+
}
|
| 72 |
+
if (deal.priceRangeMinUsd && deal.priceRangeMaxUsd) {
|
| 73 |
+
return `$${deal.priceRangeMinUsd.toFixed(2)}–${deal.priceRangeMaxUsd.toFixed(2)}`
|
| 74 |
+
}
|
| 75 |
+
return deal.reportedTerms || 'Undisclosed'
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
function calculateNormalization(deal: Deal, unitType: string): number | null {
|
| 79 |
+
if (!deal.priceUsd) return null
|
| 80 |
+
|
| 81 |
+
switch (unitType) {
|
| 82 |
+
case 'token':
|
| 83 |
+
if (deal.modality === 'Text') {
|
| 84 |
+
if (deal.dataType?.toLowerCase().includes('book')) {
|
| 85 |
+
return deal.priceUsd / 80000 // per token (80k tokens per book)
|
| 86 |
+
}
|
| 87 |
+
return deal.priceUsd / 1000 // per token (article estimate)
|
| 88 |
+
}
|
| 89 |
+
return null
|
| 90 |
+
|
| 91 |
+
case 'record':
|
| 92 |
+
if (deal.dealType === 'per-unit') {
|
| 93 |
+
return deal.priceUsd / 1000000 // rough estimate
|
| 94 |
+
}
|
| 95 |
+
return null
|
| 96 |
+
|
| 97 |
+
case 'image':
|
| 98 |
+
if (deal.modality === 'Image') {
|
| 99 |
+
if (deal.dataType?.includes('200M')) {
|
| 100 |
+
return deal.priceUsd / 200000000 // Freepik example
|
| 101 |
+
}
|
| 102 |
+
return deal.priceUsd / 1000000 // rough estimate
|
| 103 |
+
}
|
| 104 |
+
return null
|
| 105 |
+
|
| 106 |
+
case 'minute':
|
| 107 |
+
if (deal.modality === 'Video' || deal.modality === 'Audio') {
|
| 108 |
+
return deal.priceUsd / 60 // per minute (1 hour estimate)
|
| 109 |
+
}
|
| 110 |
+
return null
|
| 111 |
+
|
| 112 |
+
default:
|
| 113 |
+
return null
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
function formatNormalizedPrice(price: number | null, unitType: string): string {
|
| 118 |
+
if (price === null) return ''
|
| 119 |
+
|
| 120 |
+
if (price < 0.001) {
|
| 121 |
+
return `$${(price * 1000000).toFixed(2)}/1M ${unitType}s`
|
| 122 |
+
}
|
| 123 |
+
if (price < 1) {
|
| 124 |
+
return `$${price.toFixed(4)}/${unitType}`
|
| 125 |
+
}
|
| 126 |
+
return `$${price.toFixed(2)}/${unitType}`
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
function getNormalizations(deal: Deal) {
|
| 130 |
+
const normalizations: Array<{ unitType: string; price: number; method: 'stored' | 'calculated' }> = []
|
| 131 |
+
|
| 132 |
+
// Add stored normalizations
|
| 133 |
+
if (deal.pricingNormalizations) {
|
| 134 |
+
deal.pricingNormalizations.forEach(norm => {
|
| 135 |
+
normalizations.push({
|
| 136 |
+
unitType: norm.unitType,
|
| 137 |
+
price: norm.normalizedCostPerUnit,
|
| 138 |
+
method: 'stored',
|
| 139 |
+
})
|
| 140 |
+
})
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// Calculate missing normalizations
|
| 144 |
+
const unitTypes = ['token', 'record', 'image', 'minute']
|
| 145 |
+
const storedUnitTypes = new Set(deal.pricingNormalizations?.map(n => n.unitType) || [])
|
| 146 |
+
|
| 147 |
+
unitTypes.forEach(unitType => {
|
| 148 |
+
if (!storedUnitTypes.has(unitType)) {
|
| 149 |
+
const calculated = calculateNormalization(deal, unitType)
|
| 150 |
+
if (calculated !== null) {
|
| 151 |
+
normalizations.push({
|
| 152 |
+
unitType,
|
| 153 |
+
price: calculated,
|
| 154 |
+
method: 'calculated',
|
| 155 |
+
})
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
})
|
| 159 |
+
|
| 160 |
+
return normalizations
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
let filteredDeals = deals.filter(deal => {
|
| 164 |
+
if (filters.modality && deal.modality !== filters.modality) return false
|
| 165 |
+
if (filters.buyer && !deal.buyer.toLowerCase().includes(filters.buyer.toLowerCase())) return false
|
| 166 |
+
if (filters.provider && !deal.provider.toLowerCase().includes(filters.provider.toLowerCase())) return false
|
| 167 |
+
if (filters.exclusive === 'true' && deal.exclusive !== true) return false
|
| 168 |
+
if (filters.exclusive === 'false' && deal.exclusive !== false) return false
|
| 169 |
+
if (filters.creatorsCompensated === 'true' && deal.creatorsCompensated !== true) return false
|
| 170 |
+
if (filters.creatorsCompensated === 'false' && deal.creatorsCompensated !== false) return false
|
| 171 |
+
|
| 172 |
+
if (searchQuery) {
|
| 173 |
+
const query = searchQuery.toLowerCase()
|
| 174 |
+
if (
|
| 175 |
+
!deal.provider.toLowerCase().includes(query) &&
|
| 176 |
+
!deal.buyer.toLowerCase().includes(query) &&
|
| 177 |
+
!deal.modality.toLowerCase().includes(query) &&
|
| 178 |
+
!(deal.reportedTerms && deal.reportedTerms.toLowerCase().includes(query))
|
| 179 |
+
) {
|
| 180 |
+
return false
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
return true
|
| 184 |
+
})
|
| 185 |
+
|
| 186 |
+
// Sort deals
|
| 187 |
+
const sortedDeals = [...filteredDeals].sort((a, b) => {
|
| 188 |
+
const { column, direction } = sortBy
|
| 189 |
+
let comparison = 0
|
| 190 |
+
|
| 191 |
+
switch (column) {
|
| 192 |
+
case 'provider':
|
| 193 |
+
comparison = a.provider.localeCompare(b.provider)
|
| 194 |
+
break
|
| 195 |
+
case 'buyer':
|
| 196 |
+
comparison = a.buyer.localeCompare(b.buyer)
|
| 197 |
+
break
|
| 198 |
+
case 'modality':
|
| 199 |
+
comparison = a.modality.localeCompare(b.modality)
|
| 200 |
+
break
|
| 201 |
+
case 'price':
|
| 202 |
+
const priceA = a.priceUsd || a.priceRangeMinUsd || 0
|
| 203 |
+
const priceB = b.priceUsd || b.priceRangeMinUsd || 0
|
| 204 |
+
comparison = priceA - priceB
|
| 205 |
+
break
|
| 206 |
+
case 'exclusive':
|
| 207 |
+
const exclusiveA = a.exclusive === true ? 1 : a.exclusive === false ? 0 : -1
|
| 208 |
+
const exclusiveB = b.exclusive === true ? 1 : b.exclusive === false ? 0 : -1
|
| 209 |
+
comparison = exclusiveA - exclusiveB
|
| 210 |
+
break
|
| 211 |
+
case 'creatorsCompensated':
|
| 212 |
+
const compA = a.creatorsCompensated === true ? 1 : a.creatorsCompensated === false ? 0 : -1
|
| 213 |
+
const compB = b.creatorsCompensated === true ? 1 : b.creatorsCompensated === false ? 0 : -1
|
| 214 |
+
comparison = compA - compB
|
| 215 |
+
break
|
| 216 |
+
case 'date':
|
| 217 |
+
default:
|
| 218 |
+
if (!a.date && !b.date) comparison = 0
|
| 219 |
+
else if (!a.date) comparison = 1
|
| 220 |
+
else if (!b.date) comparison = -1
|
| 221 |
+
else comparison = a.date.localeCompare(b.date)
|
| 222 |
+
break
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
return direction === 'asc' ? comparison : -comparison
|
| 226 |
+
})
|
| 227 |
+
|
| 228 |
+
const modalities = Array.from(new Set(deals.map(d => d.modality))).sort()
|
| 229 |
+
const buyers = Array.from(new Set(deals.flatMap(d => d.buyer.split(',').map(b => b.trim())))).sort()
|
| 230 |
+
const providers = Array.from(new Set(deals.map(d => d.provider))).sort()
|
| 231 |
+
|
| 232 |
+
const handleDealClick = (deal: Deal) => {
|
| 233 |
+
setSelectedDeal(deal)
|
| 234 |
+
setIsModalOpen(true)
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
const handleSort = (column: string) => {
|
| 238 |
+
setSortBy(prev => ({
|
| 239 |
+
column,
|
| 240 |
+
direction: prev.column === column && prev.direction === 'asc' ? 'desc' : 'asc',
|
| 241 |
+
}))
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
const getSortIndicator = (column: string) => {
|
| 245 |
+
if (sortBy.column !== column) return null
|
| 246 |
+
return sortBy.direction === 'asc' ? '↑' : '↓'
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
return (
|
| 250 |
+
<>
|
| 251 |
+
<DealModal
|
| 252 |
+
deal={selectedDeal}
|
| 253 |
+
isOpen={isModalOpen}
|
| 254 |
+
onClose={() => {
|
| 255 |
+
setIsModalOpen(false)
|
| 256 |
+
setSelectedDeal(null)
|
| 257 |
+
}}
|
| 258 |
+
/>
|
| 259 |
+
{/* Search and Filters */}
|
| 260 |
+
<div className="card mb-6">
|
| 261 |
+
<div className="mb-4">
|
| 262 |
+
<input
|
| 263 |
+
type="text"
|
| 264 |
+
placeholder="Search deals..."
|
| 265 |
+
value={searchQuery}
|
| 266 |
+
onChange={(e) => setSearchQuery(e.target.value)}
|
| 267 |
+
className="input w-full"
|
| 268 |
+
/>
|
| 269 |
+
</div>
|
| 270 |
+
|
| 271 |
+
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
|
| 272 |
+
<div>
|
| 273 |
+
<label className="block text-sm font-medium text-text-muted mb-2">Modality</label>
|
| 274 |
+
<select
|
| 275 |
+
value={filters.modality}
|
| 276 |
+
onChange={(e) => setFilters({ ...filters, modality: e.target.value })}
|
| 277 |
+
className="input"
|
| 278 |
+
>
|
| 279 |
+
<option value="">All</option>
|
| 280 |
+
{modalities.map(m => (
|
| 281 |
+
<option key={m} value={m}>{m}</option>
|
| 282 |
+
))}
|
| 283 |
+
</select>
|
| 284 |
+
</div>
|
| 285 |
+
|
| 286 |
+
<div>
|
| 287 |
+
<label className="block text-sm font-medium text-text-muted mb-2">Buyer</label>
|
| 288 |
+
<select
|
| 289 |
+
value={filters.buyer}
|
| 290 |
+
onChange={(e) => setFilters({ ...filters, buyer: e.target.value })}
|
| 291 |
+
className="input"
|
| 292 |
+
>
|
| 293 |
+
<option value="">All</option>
|
| 294 |
+
{buyers.map(b => (
|
| 295 |
+
<option key={b} value={b}>{b}</option>
|
| 296 |
+
))}
|
| 297 |
+
</select>
|
| 298 |
+
</div>
|
| 299 |
+
|
| 300 |
+
<div>
|
| 301 |
+
<label className="block text-sm font-medium text-text-muted mb-2">Provider</label>
|
| 302 |
+
<select
|
| 303 |
+
value={filters.provider}
|
| 304 |
+
onChange={(e) => setFilters({ ...filters, provider: e.target.value })}
|
| 305 |
+
className="input"
|
| 306 |
+
>
|
| 307 |
+
<option value="">All</option>
|
| 308 |
+
{providers.map(p => (
|
| 309 |
+
<option key={p} value={p}>{p}</option>
|
| 310 |
+
))}
|
| 311 |
+
</select>
|
| 312 |
+
</div>
|
| 313 |
+
|
| 314 |
+
<div>
|
| 315 |
+
<label className="block text-sm font-medium text-text-muted mb-2">Exclusive</label>
|
| 316 |
+
<select
|
| 317 |
+
value={filters.exclusive}
|
| 318 |
+
onChange={(e) => setFilters({ ...filters, exclusive: e.target.value })}
|
| 319 |
+
className="input"
|
| 320 |
+
>
|
| 321 |
+
<option value="">All</option>
|
| 322 |
+
<option value="true">Yes</option>
|
| 323 |
+
<option value="false">No</option>
|
| 324 |
+
</select>
|
| 325 |
+
</div>
|
| 326 |
+
|
| 327 |
+
<div>
|
| 328 |
+
<label className="block text-sm font-medium text-text-muted mb-2">Creators Comp.</label>
|
| 329 |
+
<select
|
| 330 |
+
value={filters.creatorsCompensated}
|
| 331 |
+
onChange={(e) => setFilters({ ...filters, creatorsCompensated: e.target.value })}
|
| 332 |
+
className="input"
|
| 333 |
+
>
|
| 334 |
+
<option value="">All</option>
|
| 335 |
+
<option value="true">Yes</option>
|
| 336 |
+
<option value="false">No</option>
|
| 337 |
+
</select>
|
| 338 |
+
</div>
|
| 339 |
+
</div>
|
| 340 |
+
</div>
|
| 341 |
+
|
| 342 |
+
{/* Results */}
|
| 343 |
+
<div className="card overflow-hidden p-0">
|
| 344 |
+
<div className="overflow-x-auto">
|
| 345 |
+
<table className="table">
|
| 346 |
+
<thead>
|
| 347 |
+
<tr>
|
| 348 |
+
<th
|
| 349 |
+
className="cursor-pointer hover:bg-border-subtle select-none"
|
| 350 |
+
onClick={() => handleSort('provider')}
|
| 351 |
+
title="Click to sort by provider"
|
| 352 |
+
>
|
| 353 |
+
Provider
|
| 354 |
+
{getSortIndicator('provider') && (
|
| 355 |
+
<span className="ml-2 text-text-muted">{getSortIndicator('provider')}</span>
|
| 356 |
+
)}
|
| 357 |
+
</th>
|
| 358 |
+
<th
|
| 359 |
+
className="cursor-pointer hover:bg-border-subtle select-none"
|
| 360 |
+
onClick={() => handleSort('buyer')}
|
| 361 |
+
title="Click to sort by buyer"
|
| 362 |
+
>
|
| 363 |
+
Buyer
|
| 364 |
+
{getSortIndicator('buyer') && (
|
| 365 |
+
<span className="ml-2 text-text-muted">{getSortIndicator('buyer')}</span>
|
| 366 |
+
)}
|
| 367 |
+
</th>
|
| 368 |
+
<th
|
| 369 |
+
className="cursor-pointer hover:bg-border-subtle select-none"
|
| 370 |
+
onClick={() => handleSort('modality')}
|
| 371 |
+
title="Click to sort by modality"
|
| 372 |
+
>
|
| 373 |
+
Modality
|
| 374 |
+
{getSortIndicator('modality') && (
|
| 375 |
+
<span className="ml-2 text-text-muted">{getSortIndicator('modality')}</span>
|
| 376 |
+
)}
|
| 377 |
+
</th>
|
| 378 |
+
<th
|
| 379 |
+
className="cursor-pointer hover:bg-border-subtle select-none"
|
| 380 |
+
onClick={() => handleSort('price')}
|
| 381 |
+
title="Click to sort by price"
|
| 382 |
+
>
|
| 383 |
+
Price
|
| 384 |
+
{getSortIndicator('price') && (
|
| 385 |
+
<span className="ml-2 text-text-muted">{getSortIndicator('price')}</span>
|
| 386 |
+
)}
|
| 387 |
+
</th>
|
| 388 |
+
<th
|
| 389 |
+
className="cursor-pointer hover:bg-border-subtle select-none"
|
| 390 |
+
onClick={() => handleSort('exclusive')}
|
| 391 |
+
title="Click to sort by exclusivity"
|
| 392 |
+
>
|
| 393 |
+
Exclusive
|
| 394 |
+
{getSortIndicator('exclusive') && (
|
| 395 |
+
<span className="ml-2 text-text-muted">{getSortIndicator('exclusive')}</span>
|
| 396 |
+
)}
|
| 397 |
+
</th>
|
| 398 |
+
<th
|
| 399 |
+
className="cursor-pointer hover:bg-border-subtle select-none"
|
| 400 |
+
onClick={() => handleSort('creatorsCompensated')}
|
| 401 |
+
title="Click to sort by creator compensation"
|
| 402 |
+
>
|
| 403 |
+
Creators Comp.
|
| 404 |
+
{getSortIndicator('creatorsCompensated') && (
|
| 405 |
+
<span className="ml-2 text-text-muted">{getSortIndicator('creatorsCompensated')}</span>
|
| 406 |
+
)}
|
| 407 |
+
</th>
|
| 408 |
+
<th
|
| 409 |
+
className="cursor-pointer hover:bg-border-subtle select-none"
|
| 410 |
+
onClick={() => handleSort('date')}
|
| 411 |
+
title="Click to sort by date"
|
| 412 |
+
>
|
| 413 |
+
Date
|
| 414 |
+
{getSortIndicator('date') && (
|
| 415 |
+
<span className="ml-2 text-text-muted">{getSortIndicator('date')}</span>
|
| 416 |
+
)}
|
| 417 |
+
</th>
|
| 418 |
+
</tr>
|
| 419 |
+
</thead>
|
| 420 |
+
<tbody>
|
| 421 |
+
{sortedDeals.length === 0 ? (
|
| 422 |
+
<tr>
|
| 423 |
+
<td colSpan={7} className="text-center py-8 text-text-muted">
|
| 424 |
+
No deals found
|
| 425 |
+
</td>
|
| 426 |
+
</tr>
|
| 427 |
+
) : (
|
| 428 |
+
sortedDeals.map((deal) => (
|
| 429 |
+
<tr
|
| 430 |
+
key={deal.id}
|
| 431 |
+
onClick={() => handleDealClick(deal)}
|
| 432 |
+
className="cursor-pointer hover:bg-border-subtle transition-colors"
|
| 433 |
+
>
|
| 434 |
+
<td className="font-medium text-accent hover:text-accent-hover">
|
| 435 |
+
{deal.provider}
|
| 436 |
+
</td>
|
| 437 |
+
<td>{deal.buyer}</td>
|
| 438 |
+
<td>
|
| 439 |
+
<span className="badge badge-secondary">{deal.modality}</span>
|
| 440 |
+
</td>
|
| 441 |
+
<td>
|
| 442 |
+
<div>
|
| 443 |
+
<div className="font-medium">{formatPrice(deal)}</div>
|
| 444 |
+
{(() => {
|
| 445 |
+
const normalizations = getNormalizations(deal)
|
| 446 |
+
if (normalizations.length > 0) {
|
| 447 |
+
// Show stored normalizations first, then calculated
|
| 448 |
+
const stored = normalizations.filter(n => n.method === 'stored')
|
| 449 |
+
const calculated = normalizations.filter(n => n.method === 'calculated')
|
| 450 |
+
const toShow = [...stored, ...calculated].slice(0, 2)
|
| 451 |
+
|
| 452 |
+
return (
|
| 453 |
+
<div className="mt-1 space-y-0.5">
|
| 454 |
+
{toShow.map((norm, idx) => (
|
| 455 |
+
<div
|
| 456 |
+
key={idx}
|
| 457 |
+
className={`text-xs ${
|
| 458 |
+
norm.method === 'calculated'
|
| 459 |
+
? 'text-text-muted/50 italic'
|
| 460 |
+
: 'text-text-muted/70'
|
| 461 |
+
}`}
|
| 462 |
+
title={norm.method === 'calculated' ? 'Estimated' : 'From database'}
|
| 463 |
+
>
|
| 464 |
+
{formatNormalizedPrice(norm.price, norm.unitType)}
|
| 465 |
+
{norm.method === 'calculated' && ' *'}
|
| 466 |
+
</div>
|
| 467 |
+
))}
|
| 468 |
+
</div>
|
| 469 |
+
)
|
| 470 |
+
}
|
| 471 |
+
return null
|
| 472 |
+
})()}
|
| 473 |
+
</div>
|
| 474 |
+
</td>
|
| 475 |
+
<td>
|
| 476 |
+
{deal.exclusive === true ? (
|
| 477 |
+
<span className="badge badge-primary">Yes</span>
|
| 478 |
+
) : deal.exclusive === false ? (
|
| 479 |
+
<span className="text-text-muted">No</span>
|
| 480 |
+
) : (
|
| 481 |
+
<span className="text-text-muted">—</span>
|
| 482 |
+
)}
|
| 483 |
+
</td>
|
| 484 |
+
<td>
|
| 485 |
+
{deal.creatorsCompensated === true ? (
|
| 486 |
+
<span className="badge badge-primary">Yes</span>
|
| 487 |
+
) : deal.creatorsCompensated === false ? (
|
| 488 |
+
<span className="text-text-muted">No</span>
|
| 489 |
+
) : (
|
| 490 |
+
<span className="text-text-muted">—</span>
|
| 491 |
+
)}
|
| 492 |
+
</td>
|
| 493 |
+
<td className="text-text-muted">{deal.date || '—'}</td>
|
| 494 |
+
</tr>
|
| 495 |
+
))
|
| 496 |
+
)}
|
| 497 |
+
</tbody>
|
| 498 |
+
</table>
|
| 499 |
+
</div>
|
| 500 |
+
</div>
|
| 501 |
+
</>
|
| 502 |
+
)
|
| 503 |
+
}
|
| 504 |
+
|
app/deals/[id]/page.tsx
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { notFound } from 'next/navigation'
|
| 2 |
+
import Link from 'next/link'
|
| 3 |
+
import { prisma } from '@/lib/prisma'
|
| 4 |
+
|
| 5 |
+
async function getDeal(id: string) {
|
| 6 |
+
const deal = await prisma.deal.findUnique({
|
| 7 |
+
where: { id },
|
| 8 |
+
include: {
|
| 9 |
+
buyerRelations: {
|
| 10 |
+
include: {
|
| 11 |
+
buyer: true,
|
| 12 |
+
},
|
| 13 |
+
},
|
| 14 |
+
providerRelation: true,
|
| 15 |
+
pricingNormalizations: true,
|
| 16 |
+
},
|
| 17 |
+
})
|
| 18 |
+
return deal
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
function formatPrice(deal: any) {
|
| 22 |
+
if (deal.priceUsd) {
|
| 23 |
+
if (deal.priceUsd >= 1000000000) {
|
| 24 |
+
return `$${(deal.priceUsd / 1000000000).toFixed(1)}B`
|
| 25 |
+
}
|
| 26 |
+
if (deal.priceUsd >= 1000000) {
|
| 27 |
+
return `$${(deal.priceUsd / 1000000).toFixed(0)}M`
|
| 28 |
+
}
|
| 29 |
+
if (deal.priceUsd >= 1000) {
|
| 30 |
+
return `$${(deal.priceUsd / 1000).toFixed(0)}K`
|
| 31 |
+
}
|
| 32 |
+
return `$${deal.priceUsd.toFixed(0)}`
|
| 33 |
+
}
|
| 34 |
+
if (deal.priceRangeMinUsd && deal.priceRangeMaxUsd) {
|
| 35 |
+
return `$${deal.priceRangeMinUsd.toFixed(2)}–${deal.priceRangeMaxUsd.toFixed(2)}`
|
| 36 |
+
}
|
| 37 |
+
return deal.reportedTerms || 'Undisclosed'
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
export default async function DealDetailPage({
|
| 41 |
+
params,
|
| 42 |
+
}: {
|
| 43 |
+
params: { id: string }
|
| 44 |
+
}) {
|
| 45 |
+
const deal = await getDeal(params.id)
|
| 46 |
+
|
| 47 |
+
if (!deal) {
|
| 48 |
+
notFound()
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
return (
|
| 52 |
+
<main className="min-h-screen bg-background">
|
| 53 |
+
<div className="container-content section-padding">
|
| 54 |
+
<Link
|
| 55 |
+
href="/deals"
|
| 56 |
+
className="text-accent hover:text-accent-hover mb-6 inline-block"
|
| 57 |
+
>
|
| 58 |
+
← Back to Deals
|
| 59 |
+
</Link>
|
| 60 |
+
|
| 61 |
+
<div className="max-w-4xl">
|
| 62 |
+
{/* Header Card */}
|
| 63 |
+
<div className="card mb-8">
|
| 64 |
+
<div className="flex items-start justify-between mb-6">
|
| 65 |
+
<div>
|
| 66 |
+
<h1 className="text-3xl font-semibold mb-2">
|
| 67 |
+
{deal.provider} → {deal.buyer}
|
| 68 |
+
</h1>
|
| 69 |
+
<p className="text-text-muted">{deal.dataType}</p>
|
| 70 |
+
</div>
|
| 71 |
+
<div className="flex gap-2">
|
| 72 |
+
<span className="badge badge-secondary">{deal.modality}</span>
|
| 73 |
+
{deal.exclusive && (
|
| 74 |
+
<span className="badge badge-primary">Exclusive</span>
|
| 75 |
+
)}
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 pt-6 border-t border-border">
|
| 80 |
+
<div>
|
| 81 |
+
<div className="text-sm text-text-muted mb-1">Price</div>
|
| 82 |
+
<div className="text-xl font-semibold">{formatPrice(deal)}</div>
|
| 83 |
+
</div>
|
| 84 |
+
<div>
|
| 85 |
+
<div className="text-sm text-text-muted mb-1">Deal Type</div>
|
| 86 |
+
<div className="font-medium">{deal.dealType || deal.pricingMechanism}</div>
|
| 87 |
+
</div>
|
| 88 |
+
<div>
|
| 89 |
+
<div className="text-sm text-text-muted mb-1">Date</div>
|
| 90 |
+
<div className="font-medium">{deal.date || '—'}</div>
|
| 91 |
+
</div>
|
| 92 |
+
<div>
|
| 93 |
+
<div className="text-sm text-text-muted mb-1">Source</div>
|
| 94 |
+
<div className="font-medium">{deal.sourcePrimary || '—'}</div>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
{/* Reported Terms */}
|
| 100 |
+
{deal.reportedTerms && (
|
| 101 |
+
<div className="card mb-8">
|
| 102 |
+
<h2 className="text-xl font-semibold mb-4">Reported Terms</h2>
|
| 103 |
+
<p className="text-lg leading-relaxed">{deal.reportedTerms}</p>
|
| 104 |
+
</div>
|
| 105 |
+
)}
|
| 106 |
+
|
| 107 |
+
{/* Rights & Compensation */}
|
| 108 |
+
<div className="grid md:grid-cols-2 gap-6 mb-8">
|
| 109 |
+
<div className="card">
|
| 110 |
+
<h3 className="text-lg font-semibold mb-4">Rights Granted</h3>
|
| 111 |
+
<div className="space-y-2">
|
| 112 |
+
<div className="flex justify-between">
|
| 113 |
+
<span className="text-text-muted">Training</span>
|
| 114 |
+
<span>{deal.trainingAllowed !== null ? (deal.trainingAllowed ? 'Yes' : 'No') : '—'}</span>
|
| 115 |
+
</div>
|
| 116 |
+
<div className="flex justify-between">
|
| 117 |
+
<span className="text-text-muted">Fine-tuning</span>
|
| 118 |
+
<span>{deal.finetuningAllowed !== null ? (deal.finetuningAllowed ? 'Yes' : 'No') : '—'}</span>
|
| 119 |
+
</div>
|
| 120 |
+
<div className="flex justify-between">
|
| 121 |
+
<span className="text-text-muted">Inference</span>
|
| 122 |
+
<span>{deal.inferenceAllowed !== null ? (deal.inferenceAllowed ? 'Yes' : 'No') : '—'}</span>
|
| 123 |
+
</div>
|
| 124 |
+
<div className="flex justify-between">
|
| 125 |
+
<span className="text-text-muted">Redistribution</span>
|
| 126 |
+
<span>{deal.redistributionAllowed !== null ? (deal.redistributionAllowed ? 'Yes' : 'No') : '—'}</span>
|
| 127 |
+
</div>
|
| 128 |
+
</div>
|
| 129 |
+
</div>
|
| 130 |
+
|
| 131 |
+
<div className="card">
|
| 132 |
+
<h3 className="text-lg font-semibold mb-4">Creator Compensation</h3>
|
| 133 |
+
<div className="space-y-2">
|
| 134 |
+
<div className="flex justify-between">
|
| 135 |
+
<span className="text-text-muted">Compensated</span>
|
| 136 |
+
<span>
|
| 137 |
+
{deal.creatorsCompensated === true ? (
|
| 138 |
+
<span className="badge badge-primary">Yes</span>
|
| 139 |
+
) : deal.creatorsCompensated === false ? (
|
| 140 |
+
'No'
|
| 141 |
+
) : (
|
| 142 |
+
'Unclear'
|
| 143 |
+
)}
|
| 144 |
+
</span>
|
| 145 |
+
</div>
|
| 146 |
+
{deal.creatorSplitPercentage && (
|
| 147 |
+
<div className="flex justify-between">
|
| 148 |
+
<span className="text-text-muted">Split</span>
|
| 149 |
+
<span>{deal.creatorSplitPercentage}%</span>
|
| 150 |
+
</div>
|
| 151 |
+
)}
|
| 152 |
+
{deal.revenueShare && (
|
| 153 |
+
<div className="flex justify-between">
|
| 154 |
+
<span className="text-text-muted">Revenue Share</span>
|
| 155 |
+
<span>Yes</span>
|
| 156 |
+
</div>
|
| 157 |
+
)}
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
|
| 162 |
+
{/* Notes */}
|
| 163 |
+
{deal.notes && (
|
| 164 |
+
<div className="card mb-8">
|
| 165 |
+
<h3 className="text-lg font-semibold mb-4">Notes</h3>
|
| 166 |
+
<p className="leading-relaxed">{deal.notes}</p>
|
| 167 |
+
</div>
|
| 168 |
+
)}
|
| 169 |
+
|
| 170 |
+
{/* Sources */}
|
| 171 |
+
<div className="card">
|
| 172 |
+
<h3 className="text-lg font-semibold mb-4">Sources</h3>
|
| 173 |
+
<div className="space-y-2">
|
| 174 |
+
{deal.sourcePrimary && (
|
| 175 |
+
<div className="text-text-muted">
|
| 176 |
+
Primary: <span className="text-text">{deal.sourcePrimary}</span>
|
| 177 |
+
</div>
|
| 178 |
+
)}
|
| 179 |
+
{deal.sources && JSON.parse(deal.sources).length > 0 && (
|
| 180 |
+
<div>
|
| 181 |
+
<div className="text-text-muted mb-2">Additional sources:</div>
|
| 182 |
+
<ul className="list-disc list-inside space-y-1">
|
| 183 |
+
{JSON.parse(deal.sources).map((source: string, idx: number) => (
|
| 184 |
+
<li key={idx}>
|
| 185 |
+
<a
|
| 186 |
+
href={source}
|
| 187 |
+
target="_blank"
|
| 188 |
+
rel="noopener noreferrer"
|
| 189 |
+
className="text-accent hover:text-accent-hover"
|
| 190 |
+
>
|
| 191 |
+
{source}
|
| 192 |
+
</a>
|
| 193 |
+
</li>
|
| 194 |
+
))}
|
| 195 |
+
</ul>
|
| 196 |
+
</div>
|
| 197 |
+
)}
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
+
</main>
|
| 203 |
+
)
|
| 204 |
+
}
|
| 205 |
+
|
app/deals/page.tsx
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { redirect } from 'next/navigation'
|
| 2 |
+
|
| 3 |
+
export default function DealsPage() {
|
| 4 |
+
// Redirect to home page since deals explorer is now the main view
|
| 5 |
+
redirect('/')
|
| 6 |
+
}
|
app/globals.css
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@tailwind base;
|
| 2 |
+
@tailwind components;
|
| 3 |
+
@tailwind utilities;
|
| 4 |
+
|
| 5 |
+
/* Humanist Design System - Minimalist Typography */
|
| 6 |
+
|
| 7 |
+
@layer base {
|
| 8 |
+
:root {
|
| 9 |
+
/* Color Palette - Muted, Humanist */
|
| 10 |
+
--color-base: #0A0A0A;
|
| 11 |
+
--color-text: #1A1A1A;
|
| 12 |
+
--color-text-muted: #6B7280;
|
| 13 |
+
--color-accent: #2563EB;
|
| 14 |
+
--color-accent-hover: #1D4ED8;
|
| 15 |
+
--color-background: #FAFAFA;
|
| 16 |
+
--color-surface: #FFFFFF;
|
| 17 |
+
--color-border: #E5E7EB;
|
| 18 |
+
--color-border-subtle: #F3F4F6;
|
| 19 |
+
|
| 20 |
+
/* Typography */
|
| 21 |
+
--font-sans: 'Public Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
| 22 |
+
--font-serif: 'Charter', 'Georgia', serif;
|
| 23 |
+
--font-mono: 'JetBrains Mono', 'Menlo', monospace;
|
| 24 |
+
|
| 25 |
+
/* Spacing - Generous, Humanist */
|
| 26 |
+
--spacing-xs: 0.5rem; /* 8px */
|
| 27 |
+
--spacing-sm: 0.75rem; /* 12px */
|
| 28 |
+
--spacing-md: 1rem; /* 16px */
|
| 29 |
+
--spacing-lg: 1.5rem; /* 24px */
|
| 30 |
+
--spacing-xl: 2rem; /* 32px */
|
| 31 |
+
--spacing-2xl: 3rem; /* 48px */
|
| 32 |
+
--spacing-3xl: 4rem; /* 64px */
|
| 33 |
+
|
| 34 |
+
/* Typography Scale */
|
| 35 |
+
--text-xs: 0.75rem; /* 12px */
|
| 36 |
+
--text-sm: 0.875rem; /* 14px */
|
| 37 |
+
--text-base: 1rem; /* 16px */
|
| 38 |
+
--text-lg: 1.125rem; /* 18px */
|
| 39 |
+
--text-xl: 1.25rem; /* 20px */
|
| 40 |
+
--text-2xl: 1.5rem; /* 24px */
|
| 41 |
+
--text-3xl: 1.875rem; /* 30px */
|
| 42 |
+
--text-4xl: 2.25rem; /* 36px */
|
| 43 |
+
--text-5xl: 3rem; /* 48px */
|
| 44 |
+
|
| 45 |
+
/* Line Heights - Comfortable Reading */
|
| 46 |
+
--leading-tight: 1.25;
|
| 47 |
+
--leading-normal: 1.5;
|
| 48 |
+
--leading-relaxed: 1.75;
|
| 49 |
+
--leading-loose: 2;
|
| 50 |
+
|
| 51 |
+
/* Max Content Width */
|
| 52 |
+
--max-width-content: 1200px;
|
| 53 |
+
--max-width-narrow: 800px;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
* {
|
| 57 |
+
@apply border-border;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
body {
|
| 61 |
+
@apply bg-background text-text;
|
| 62 |
+
font-family: var(--font-sans);
|
| 63 |
+
font-size: var(--text-base);
|
| 64 |
+
line-height: var(--leading-relaxed);
|
| 65 |
+
-webkit-font-smoothing: antialiased;
|
| 66 |
+
-moz-osx-font-smoothing: grayscale;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
/* Typography Hierarchy */
|
| 70 |
+
h1, h2, h3, h4, h5, h6 {
|
| 71 |
+
@apply font-semibold text-base;
|
| 72 |
+
line-height: var(--leading-tight);
|
| 73 |
+
letter-spacing: -0.02em;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
h1 {
|
| 77 |
+
font-size: var(--text-4xl);
|
| 78 |
+
line-height: var(--leading-tight);
|
| 79 |
+
@apply mb-6;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
h2 {
|
| 83 |
+
font-size: var(--text-3xl);
|
| 84 |
+
@apply mb-5;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
h3 {
|
| 88 |
+
font-size: var(--text-2xl);
|
| 89 |
+
@apply mb-4;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
h4 {
|
| 93 |
+
font-size: var(--text-xl);
|
| 94 |
+
@apply mb-3;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
p {
|
| 98 |
+
@apply mb-4;
|
| 99 |
+
line-height: var(--leading-relaxed);
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
/* Links - Subtle, Humanist */
|
| 103 |
+
a {
|
| 104 |
+
@apply text-accent no-underline;
|
| 105 |
+
transition: color 0.2s ease;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
a:hover {
|
| 109 |
+
@apply text-accent-hover;
|
| 110 |
+
text-decoration: underline;
|
| 111 |
+
text-underline-offset: 2px;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
/* Focus States - Accessible */
|
| 115 |
+
*:focus-visible {
|
| 116 |
+
@apply outline-2 outline-offset-2 outline-accent;
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
@layer components {
|
| 121 |
+
/* Container - Max Width with Generous Padding */
|
| 122 |
+
.container-content {
|
| 123 |
+
@apply mx-auto px-6;
|
| 124 |
+
max-width: var(--max-width-content);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.container-narrow {
|
| 128 |
+
@apply mx-auto px-6;
|
| 129 |
+
max-width: var(--max-width-narrow);
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/* Cards - Minimalist, Subtle */
|
| 133 |
+
.card {
|
| 134 |
+
@apply bg-surface border border-border rounded-lg p-6;
|
| 135 |
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05);
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.card-hover {
|
| 139 |
+
@apply card transition-all duration-200;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.card-hover:hover {
|
| 143 |
+
@apply shadow-md;
|
| 144 |
+
transform: translateY(-1px);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
/* Buttons - Minimalist */
|
| 148 |
+
.btn {
|
| 149 |
+
@apply px-4 py-2 rounded-md font-medium transition-all duration-200;
|
| 150 |
+
@apply focus:outline-none focus:ring-2 focus:ring-offset-2;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
.btn-primary {
|
| 154 |
+
@apply btn bg-accent text-white;
|
| 155 |
+
@apply hover:bg-accent-hover;
|
| 156 |
+
@apply focus:ring-accent;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.btn-secondary {
|
| 160 |
+
@apply btn bg-surface border border-border text-text;
|
| 161 |
+
@apply hover:bg-border-subtle;
|
| 162 |
+
@apply focus:ring-border;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.btn-ghost {
|
| 166 |
+
@apply btn bg-transparent text-text-muted;
|
| 167 |
+
@apply hover:bg-border-subtle hover:text-text;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
/* Tables - Clean, Readable */
|
| 171 |
+
.table {
|
| 172 |
+
@apply w-full border-collapse;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
.table th {
|
| 176 |
+
@apply text-left font-semibold text-sm text-text-muted py-3 px-4;
|
| 177 |
+
@apply border-b border-border;
|
| 178 |
+
@apply select-none;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.table td {
|
| 182 |
+
@apply py-3 px-4 border-b border-border-subtle;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
.table tr:hover {
|
| 186 |
+
@apply bg-border-subtle;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
/* Badges/Tags - Subtle */
|
| 190 |
+
.badge {
|
| 191 |
+
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.badge-primary {
|
| 195 |
+
@apply badge text-accent;
|
| 196 |
+
background-color: rgba(37, 99, 235, 0.1); /* accent color with 10% opacity */
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
.badge-secondary {
|
| 200 |
+
@apply badge bg-border text-text-muted;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
/* Inputs - Clean */
|
| 204 |
+
.input {
|
| 205 |
+
@apply w-full px-4 py-2 border border-border rounded-md;
|
| 206 |
+
@apply bg-surface text-text;
|
| 207 |
+
@apply focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent;
|
| 208 |
+
@apply transition-all duration-200;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
/* Stats Cards */
|
| 212 |
+
.stat-card {
|
| 213 |
+
@apply card;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
.stat-value {
|
| 217 |
+
@apply text-3xl font-semibold text-base mb-1;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
.stat-label {
|
| 221 |
+
@apply text-sm text-text-muted;
|
| 222 |
+
}
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
@layer utilities {
|
| 226 |
+
/* Text Utilities */
|
| 227 |
+
.text-balance {
|
| 228 |
+
text-wrap: balance;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
/* Spacing Utilities */
|
| 232 |
+
.section-padding {
|
| 233 |
+
@apply py-12 md:py-16 lg:py-20;
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
|
app/layout.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Metadata } from 'next'
|
| 2 |
+
import { Public_Sans } from 'next/font/google'
|
| 3 |
+
import './globals.css'
|
| 4 |
+
|
| 5 |
+
const publicSans = Public_Sans({
|
| 6 |
+
subsets: ['latin'],
|
| 7 |
+
variable: '--font-sans',
|
| 8 |
+
display: 'swap',
|
| 9 |
+
})
|
| 10 |
+
|
| 11 |
+
export const metadata: Metadata = {
|
| 12 |
+
title: 'AI Training Data Deals Dashboard',
|
| 13 |
+
description: 'Global licensing, acquisition, and commissioning deals for AI training data (2020–2025)',
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
export default function RootLayout({
|
| 17 |
+
children,
|
| 18 |
+
}: {
|
| 19 |
+
children: React.ReactNode
|
| 20 |
+
}) {
|
| 21 |
+
return (
|
| 22 |
+
<html lang="en" className={publicSans.variable}>
|
| 23 |
+
<body>{children}</body>
|
| 24 |
+
</html>
|
| 25 |
+
)
|
| 26 |
+
}
|
| 27 |
+
|
app/normalization/page.tsx
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { prisma } from '@/lib/prisma'
|
| 2 |
+
import Link from 'next/link'
|
| 3 |
+
|
| 4 |
+
async function getDealsForNormalization() {
|
| 5 |
+
const deals = await prisma.deal.findMany({
|
| 6 |
+
where: {
|
| 7 |
+
OR: [
|
| 8 |
+
{ priceUsd: { not: null } },
|
| 9 |
+
{ priceRangeMinUsd: { not: null } },
|
| 10 |
+
],
|
| 11 |
+
},
|
| 12 |
+
orderBy: { priceUsd: 'desc' },
|
| 13 |
+
})
|
| 14 |
+
return deals
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
function normalizePrice(deal: any, unitType: string): number | null {
|
| 18 |
+
// This is a simplified normalization - in production, you'd have
|
| 19 |
+
// more sophisticated logic based on data type and assumptions
|
| 20 |
+
|
| 21 |
+
if (!deal.priceUsd) return null
|
| 22 |
+
|
| 23 |
+
switch (unitType) {
|
| 24 |
+
case 'token':
|
| 25 |
+
// Rough estimate: 1 book ≈ 80k tokens, 1 article ≈ 1k tokens
|
| 26 |
+
if (deal.modality === 'Text') {
|
| 27 |
+
if (deal.dataType?.toLowerCase().includes('book')) {
|
| 28 |
+
return deal.priceUsd / 80000 // per token
|
| 29 |
+
}
|
| 30 |
+
return deal.priceUsd / 1000 // per token (article estimate)
|
| 31 |
+
}
|
| 32 |
+
return null
|
| 33 |
+
|
| 34 |
+
case 'record':
|
| 35 |
+
// For per-unit deals, try to extract from reported terms
|
| 36 |
+
if (deal.dealType === 'per-unit') {
|
| 37 |
+
// This would need more sophisticated parsing
|
| 38 |
+
return deal.priceUsd / 1000000 // rough estimate
|
| 39 |
+
}
|
| 40 |
+
return null
|
| 41 |
+
|
| 42 |
+
case 'image':
|
| 43 |
+
if (deal.modality === 'Image') {
|
| 44 |
+
// Estimate based on deal size
|
| 45 |
+
if (deal.dataType?.includes('200M')) {
|
| 46 |
+
return deal.priceUsd / 200000000 // Freepik example
|
| 47 |
+
}
|
| 48 |
+
return deal.priceUsd / 1000000 // rough estimate
|
| 49 |
+
}
|
| 50 |
+
return null
|
| 51 |
+
|
| 52 |
+
case 'minute':
|
| 53 |
+
if (deal.modality === 'Video' || deal.modality === 'Audio') {
|
| 54 |
+
// Estimate: 1 hour of content
|
| 55 |
+
return deal.priceUsd / 60 // per minute
|
| 56 |
+
}
|
| 57 |
+
return null
|
| 58 |
+
|
| 59 |
+
default:
|
| 60 |
+
return null
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
function formatNormalizedPrice(price: number | null, unitType: string): string {
|
| 65 |
+
if (price === null) return 'N/A'
|
| 66 |
+
|
| 67 |
+
if (price < 0.001) {
|
| 68 |
+
return `$${(price * 1000000).toFixed(2)} per 1M ${unitType}s`
|
| 69 |
+
}
|
| 70 |
+
if (price < 1) {
|
| 71 |
+
return `$${price.toFixed(4)} per ${unitType}`
|
| 72 |
+
}
|
| 73 |
+
return `$${price.toFixed(2)} per ${unitType}`
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
export default async function NormalizationPage() {
|
| 77 |
+
const deals = await getDealsForNormalization()
|
| 78 |
+
|
| 79 |
+
const unitTypes = ['token', 'record', 'image', 'minute']
|
| 80 |
+
|
| 81 |
+
return (
|
| 82 |
+
<main className="min-h-screen bg-background">
|
| 83 |
+
<div className="container-content section-padding">
|
| 84 |
+
<div className="mb-8">
|
| 85 |
+
<h1 className="text-4xl font-semibold mb-4">Pricing Normalization Tool</h1>
|
| 86 |
+
<p className="text-text-muted text-lg">
|
| 87 |
+
Compare deals on an apples-to-apples basis by normalizing to per-unit pricing
|
| 88 |
+
</p>
|
| 89 |
+
</div>
|
| 90 |
+
|
| 91 |
+
<div className="card mb-8">
|
| 92 |
+
<h2 className="text-2xl font-semibold mb-4">How It Works</h2>
|
| 93 |
+
<p className="text-text-muted leading-relaxed mb-4">
|
| 94 |
+
Different deals use different pricing models (per-book, per-track, aggregate licensing, etc.).
|
| 95 |
+
This tool normalizes prices to common units (tokens, records, images, minutes) to enable
|
| 96 |
+
direct comparison.
|
| 97 |
+
</p>
|
| 98 |
+
<p className="text-text-muted text-sm">
|
| 99 |
+
<strong>Note:</strong> Normalizations are estimates based on deal descriptions and assumptions.
|
| 100 |
+
Actual per-unit costs may vary significantly.
|
| 101 |
+
</p>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
<div className="card overflow-hidden p-0">
|
| 105 |
+
<div className="overflow-x-auto">
|
| 106 |
+
<table className="table">
|
| 107 |
+
<thead>
|
| 108 |
+
<tr>
|
| 109 |
+
<th>Deal</th>
|
| 110 |
+
<th>Raw Price</th>
|
| 111 |
+
<th>Per Token</th>
|
| 112 |
+
<th>Per Record</th>
|
| 113 |
+
<th>Per Image</th>
|
| 114 |
+
<th>Per Minute</th>
|
| 115 |
+
</tr>
|
| 116 |
+
</thead>
|
| 117 |
+
<tbody>
|
| 118 |
+
{deals.map((deal) => {
|
| 119 |
+
const rawPrice = deal.priceUsd
|
| 120 |
+
? deal.priceUsd >= 1000000000
|
| 121 |
+
? `$${(deal.priceUsd / 1000000000).toFixed(1)}B`
|
| 122 |
+
: deal.priceUsd >= 1000000
|
| 123 |
+
? `$${(deal.priceUsd / 1000000).toFixed(0)}M`
|
| 124 |
+
: `$${deal.priceUsd.toFixed(0)}`
|
| 125 |
+
: deal.reportedTerms || 'Undisclosed'
|
| 126 |
+
|
| 127 |
+
return (
|
| 128 |
+
<tr key={deal.id}>
|
| 129 |
+
<td>
|
| 130 |
+
<div>
|
| 131 |
+
<div className="font-medium">{deal.provider}</div>
|
| 132 |
+
<div className="text-sm text-text-muted">→ {deal.buyer}</div>
|
| 133 |
+
<div className="text-xs text-text-muted mt-1">{deal.modality}</div>
|
| 134 |
+
</div>
|
| 135 |
+
</td>
|
| 136 |
+
<td>{rawPrice}</td>
|
| 137 |
+
<td className="text-sm">
|
| 138 |
+
{formatNormalizedPrice(normalizePrice(deal, 'token'), 'token')}
|
| 139 |
+
</td>
|
| 140 |
+
<td className="text-sm">
|
| 141 |
+
{formatNormalizedPrice(normalizePrice(deal, 'record'), 'record')}
|
| 142 |
+
</td>
|
| 143 |
+
<td className="text-sm">
|
| 144 |
+
{formatNormalizedPrice(normalizePrice(deal, 'image'), 'image')}
|
| 145 |
+
</td>
|
| 146 |
+
<td className="text-sm">
|
| 147 |
+
{formatNormalizedPrice(normalizePrice(deal, 'minute'), 'minute')}
|
| 148 |
+
</td>
|
| 149 |
+
</tr>
|
| 150 |
+
)
|
| 151 |
+
})}
|
| 152 |
+
</tbody>
|
| 153 |
+
</table>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
|
| 157 |
+
<div className="mt-8">
|
| 158 |
+
<Link href="/" className="text-accent hover:text-accent-hover">
|
| 159 |
+
← Back to Deals
|
| 160 |
+
</Link>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
</main>
|
| 164 |
+
)
|
| 165 |
+
}
|
| 166 |
+
|
app/page.tsx
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { prisma } from '@/lib/prisma'
|
| 2 |
+
import Link from 'next/link'
|
| 3 |
+
import DealsClient from './deals/DealsClient'
|
| 4 |
+
|
| 5 |
+
async function getDeals() {
|
| 6 |
+
const deals = await prisma.deal.findMany({
|
| 7 |
+
orderBy: { date: 'desc' },
|
| 8 |
+
take: 100,
|
| 9 |
+
select: {
|
| 10 |
+
id: true,
|
| 11 |
+
provider: true,
|
| 12 |
+
buyer: true,
|
| 13 |
+
modality: true,
|
| 14 |
+
dataType: true,
|
| 15 |
+
priceUsd: true,
|
| 16 |
+
priceRangeMinUsd: true,
|
| 17 |
+
priceRangeMaxUsd: true,
|
| 18 |
+
reportedTerms: true,
|
| 19 |
+
exclusive: true,
|
| 20 |
+
creatorsCompensated: true,
|
| 21 |
+
creatorSplitPercentage: true,
|
| 22 |
+
revenueShare: true,
|
| 23 |
+
date: true,
|
| 24 |
+
dealType: true,
|
| 25 |
+
pricingMechanism: true,
|
| 26 |
+
sourcePrimary: true,
|
| 27 |
+
trainingAllowed: true,
|
| 28 |
+
finetuningAllowed: true,
|
| 29 |
+
inferenceAllowed: true,
|
| 30 |
+
redistributionAllowed: true,
|
| 31 |
+
deletionRequired: true,
|
| 32 |
+
notes: true,
|
| 33 |
+
sources: true,
|
| 34 |
+
pricingNormalizations: {
|
| 35 |
+
select: {
|
| 36 |
+
unitType: true,
|
| 37 |
+
normalizedCostPerUnit: true,
|
| 38 |
+
normalizationMethod: true,
|
| 39 |
+
},
|
| 40 |
+
},
|
| 41 |
+
},
|
| 42 |
+
})
|
| 43 |
+
return deals
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
async function getStats() {
|
| 47 |
+
const deals = await prisma.deal.findMany()
|
| 48 |
+
|
| 49 |
+
const totalDeals = deals.length
|
| 50 |
+
const totalSpend = deals.reduce((sum, deal) => sum + (deal.priceUsd || 0), 0)
|
| 51 |
+
const compensatedDeals = deals.filter(d => d.creatorsCompensated === true).length
|
| 52 |
+
const compensatedPercent = totalDeals > 0 ? Math.round((compensatedDeals / totalDeals) * 100) : 0
|
| 53 |
+
|
| 54 |
+
return {
|
| 55 |
+
totalDeals,
|
| 56 |
+
totalSpend,
|
| 57 |
+
compensatedPercent,
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
function formatCurrency(amount: number): string {
|
| 62 |
+
if (amount >= 1000000000) {
|
| 63 |
+
return `$${(amount / 1000000000).toFixed(1)}B+`
|
| 64 |
+
}
|
| 65 |
+
if (amount >= 1000000) {
|
| 66 |
+
return `$${(amount / 1000000).toFixed(0)}M+`
|
| 67 |
+
}
|
| 68 |
+
return `$${amount.toFixed(0)}`
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
export default async function Home() {
|
| 72 |
+
const deals = await getDeals()
|
| 73 |
+
const stats = await getStats()
|
| 74 |
+
|
| 75 |
+
return (
|
| 76 |
+
<main className="min-h-screen bg-background">
|
| 77 |
+
<div className="container-content section-padding">
|
| 78 |
+
{/* Header with stats */}
|
| 79 |
+
<div className="mb-8">
|
| 80 |
+
<div className="flex items-center justify-between mb-4">
|
| 81 |
+
<div>
|
| 82 |
+
<h1 className="text-4xl font-semibold mb-2">AI Training Data Deals</h1>
|
| 83 |
+
<p className="text-text-muted text-lg">
|
| 84 |
+
Global licensing, acquisition, and commissioning deals (2020–2025)
|
| 85 |
+
</p>
|
| 86 |
+
</div>
|
| 87 |
+
<div className="flex gap-2">
|
| 88 |
+
<Link href="/analytics" className="btn-secondary text-sm">
|
| 89 |
+
Analytics
|
| 90 |
+
</Link>
|
| 91 |
+
<Link href="/normalization" className="btn-secondary text-sm">
|
| 92 |
+
Normalization
|
| 93 |
+
</Link>
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
|
| 97 |
+
{/* Quick stats */}
|
| 98 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
| 99 |
+
<div className="stat-card">
|
| 100 |
+
<div className="stat-value">{stats.totalDeals}</div>
|
| 101 |
+
<div className="stat-label">Total Deals</div>
|
| 102 |
+
</div>
|
| 103 |
+
<div className="stat-card">
|
| 104 |
+
<div className="stat-value">{formatCurrency(stats.totalSpend)}</div>
|
| 105 |
+
<div className="stat-label">Total Reported Spend</div>
|
| 106 |
+
</div>
|
| 107 |
+
<div className="stat-card">
|
| 108 |
+
<div className="stat-value">{stats.compensatedPercent}%</div>
|
| 109 |
+
<div className="stat-label">With Creator Compensation</div>
|
| 110 |
+
</div>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
|
| 114 |
+
{/* Deals Explorer */}
|
| 115 |
+
<DealsClient initialDeals={deals} />
|
| 116 |
+
</div>
|
| 117 |
+
</main>
|
| 118 |
+
)
|
| 119 |
+
}
|
ingestion/.gitignore
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
*$py.class
|
| 4 |
+
*.so
|
| 5 |
+
.Python
|
| 6 |
+
venv/
|
| 7 |
+
env/
|
| 8 |
+
ENV/
|
| 9 |
+
.venv
|
| 10 |
+
*.db
|
| 11 |
+
*.log
|
| 12 |
+
|
ingestion/config.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Configuration for ingestion pipeline
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
load_dotenv()
|
| 9 |
+
|
| 10 |
+
# API Keys
|
| 11 |
+
EXA_API_KEY = os.getenv("EXA_API_KEY")
|
| 12 |
+
PERPLEXITY_API_KEY = os.getenv("PERPLEXITY_API_KEY")
|
| 13 |
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
| 14 |
+
|
| 15 |
+
# Database
|
| 16 |
+
DATABASE_URL = os.getenv("DATABASE_URL")
|
| 17 |
+
|
| 18 |
+
# Scraping settings
|
| 19 |
+
SCRAPE_TIMEOUT = 30
|
| 20 |
+
SCRAPE_RETRIES = 3
|
| 21 |
+
SCRAPE_DELAY = 1 # seconds between requests
|
| 22 |
+
|
| 23 |
+
# LLM settings
|
| 24 |
+
PERPLEXITY_MODEL = "llama-3.1-sonar-large-128k-online"
|
| 25 |
+
PERPLEXITY_TEMPERATURE = 0.2
|
| 26 |
+
|
| 27 |
+
# Validation
|
| 28 |
+
MIN_CONFIDENCE_SCORE = 0.5 # Minimum confidence to save a deal
|
| 29 |
+
|
ingestion/db_writer.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Database Writer - Write deals to database using Prisma
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import json
|
| 7 |
+
from typing import Optional
|
| 8 |
+
from validator import DealData
|
| 9 |
+
from prisma import Prisma
|
| 10 |
+
from prisma.models import Deal, Provider, Buyer, DealBuyer
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class DBWriter:
|
| 14 |
+
"""Write deals to database"""
|
| 15 |
+
|
| 16 |
+
def __init__(self, database_url: Optional[str] = None):
|
| 17 |
+
self.database_url = database_url or os.getenv("DATABASE_URL")
|
| 18 |
+
if not self.database_url:
|
| 19 |
+
raise ValueError("DATABASE_URL environment variable required")
|
| 20 |
+
self.prisma = Prisma()
|
| 21 |
+
|
| 22 |
+
async def connect(self):
|
| 23 |
+
"""Connect to database"""
|
| 24 |
+
await self.prisma.connect()
|
| 25 |
+
|
| 26 |
+
async def disconnect(self):
|
| 27 |
+
"""Disconnect from database"""
|
| 28 |
+
await self.prisma.disconnect()
|
| 29 |
+
|
| 30 |
+
async def upsert_provider(self, name: str) -> Provider:
|
| 31 |
+
"""Create or get provider"""
|
| 32 |
+
provider = await self.prisma.provider.upsert(
|
| 33 |
+
where={"name": name},
|
| 34 |
+
data={
|
| 35 |
+
"create": {"name": name},
|
| 36 |
+
"update": {},
|
| 37 |
+
},
|
| 38 |
+
)
|
| 39 |
+
return provider
|
| 40 |
+
|
| 41 |
+
async def upsert_buyer(self, name: str) -> Buyer:
|
| 42 |
+
"""Create or get buyer"""
|
| 43 |
+
buyer = await self.prisma.buyer.upsert(
|
| 44 |
+
where={"name": name},
|
| 45 |
+
data={
|
| 46 |
+
"create": {"name": name},
|
| 47 |
+
"update": {},
|
| 48 |
+
},
|
| 49 |
+
)
|
| 50 |
+
return buyer
|
| 51 |
+
|
| 52 |
+
async def upsert_deal(self, deal_data: DealData) -> Deal:
|
| 53 |
+
"""
|
| 54 |
+
Create or update deal
|
| 55 |
+
|
| 56 |
+
Uses provider + buyer + date as unique identifier
|
| 57 |
+
"""
|
| 58 |
+
# Ensure provider and buyer exist
|
| 59 |
+
provider = await self.upsert_provider(deal_data.provider)
|
| 60 |
+
|
| 61 |
+
# Handle multiple buyers (comma-separated)
|
| 62 |
+
buyer_names = [b.strip() for b in deal_data.buyer.split(",")]
|
| 63 |
+
|
| 64 |
+
# Create deal
|
| 65 |
+
deal_dict = {
|
| 66 |
+
"provider": deal_data.provider,
|
| 67 |
+
"buyer": deal_data.buyer,
|
| 68 |
+
"modality": deal_data.modality,
|
| 69 |
+
"dataType": deal_data.data_type,
|
| 70 |
+
"reportedTerms": deal_data.reported_terms,
|
| 71 |
+
"creatorsCompensated": deal_data.creators_compensated,
|
| 72 |
+
"exclusive": deal_data.exclusive,
|
| 73 |
+
"pricingMechanism": deal_data.pricing_mechanism,
|
| 74 |
+
"dealType": deal_data.deal_type,
|
| 75 |
+
"priceUsd": deal_data.price_usd,
|
| 76 |
+
"priceRangeMinUsd": deal_data.price_range_min_usd,
|
| 77 |
+
"priceRangeMaxUsd": deal_data.price_range_max_usd,
|
| 78 |
+
"priceCurrency": deal_data.price_currency,
|
| 79 |
+
"date": deal_data.date,
|
| 80 |
+
"startDate": deal_data.start_date,
|
| 81 |
+
"endDate": deal_data.end_date,
|
| 82 |
+
"durationYears": deal_data.duration_years,
|
| 83 |
+
"creatorSplitPercentage": deal_data.creator_split_percentage,
|
| 84 |
+
"revenueShare": deal_data.revenue_share,
|
| 85 |
+
"trainingAllowed": deal_data.training_allowed,
|
| 86 |
+
"finetuningAllowed": deal_data.finetuning_allowed,
|
| 87 |
+
"inferenceAllowed": deal_data.inference_allowed,
|
| 88 |
+
"redistributionAllowed": deal_data.redistribution_allowed,
|
| 89 |
+
"deletionRequired": deal_data.deletion_required,
|
| 90 |
+
"sources": json.dumps(deal_data.sources),
|
| 91 |
+
"sourcePrimary": deal_data.source_primary,
|
| 92 |
+
"notes": deal_data.notes,
|
| 93 |
+
"dealStage": deal_data.deal_stage,
|
| 94 |
+
"confidenceScore": deal_data.confidence_score,
|
| 95 |
+
"providerId": provider.id,
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
# Try to find existing deal by provider + buyer + date
|
| 99 |
+
existing = await self.prisma.deal.find_first(
|
| 100 |
+
where={
|
| 101 |
+
"provider": deal_data.provider,
|
| 102 |
+
"buyer": {"contains": buyer_names[0]},
|
| 103 |
+
"date": deal_data.date,
|
| 104 |
+
},
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
if existing:
|
| 108 |
+
# Update existing deal
|
| 109 |
+
deal = await self.prisma.deal.update(
|
| 110 |
+
where={"id": existing.id},
|
| 111 |
+
data=deal_dict,
|
| 112 |
+
)
|
| 113 |
+
else:
|
| 114 |
+
# Create new deal
|
| 115 |
+
deal = await self.prisma.deal.create(data=deal_dict)
|
| 116 |
+
|
| 117 |
+
# Link buyers
|
| 118 |
+
for buyer_name in buyer_names:
|
| 119 |
+
buyer = await self.upsert_buyer(buyer_name)
|
| 120 |
+
|
| 121 |
+
# Check if relationship exists
|
| 122 |
+
existing_relation = await self.prisma.dealbuyer.find_first(
|
| 123 |
+
where={
|
| 124 |
+
"dealId": deal.id,
|
| 125 |
+
"buyerId": buyer.id,
|
| 126 |
+
},
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
if not existing_relation:
|
| 130 |
+
await self.prisma.dealbuyer.create(
|
| 131 |
+
data={
|
| 132 |
+
"dealId": deal.id,
|
| 133 |
+
"buyerId": buyer.id,
|
| 134 |
+
},
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
return deal
|
| 138 |
+
|
| 139 |
+
async def save_deals(self, deals: list[DealData]) -> int:
|
| 140 |
+
"""Save multiple deals"""
|
| 141 |
+
saved = 0
|
| 142 |
+
for deal in deals:
|
| 143 |
+
try:
|
| 144 |
+
await self.upsert_deal(deal)
|
| 145 |
+
saved += 1
|
| 146 |
+
except Exception as e:
|
| 147 |
+
print(f"Error saving deal {deal.provider} → {deal.buyer}: {e}")
|
| 148 |
+
return saved
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
# Async context manager for easy usage
|
| 152 |
+
class DBWriterContext:
|
| 153 |
+
"""Context manager for DBWriter"""
|
| 154 |
+
|
| 155 |
+
def __init__(self, database_url: Optional[str] = None):
|
| 156 |
+
self.writer = DBWriter(database_url)
|
| 157 |
+
|
| 158 |
+
async def __aenter__(self):
|
| 159 |
+
await self.writer.connect()
|
| 160 |
+
return self.writer
|
| 161 |
+
|
| 162 |
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
| 163 |
+
await self.writer.disconnect()
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
# Example usage:
|
| 167 |
+
# async def main():
|
| 168 |
+
# async with DBWriterContext() as writer:
|
| 169 |
+
# deal_data = DealData(...)
|
| 170 |
+
# await writer.upsert_deal(deal_data)
|
| 171 |
+
|
ingestion/deal_parser.py
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Deal Parser - Extract structured deal information from text
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from typing import Dict, Any, Optional
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
import re
|
| 8 |
+
from perplexity_client import PerplexityClient
|
| 9 |
+
from validator import DealValidator, DealData
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class DealParser:
|
| 13 |
+
"""Parse deals from text using LLM + regex validation"""
|
| 14 |
+
|
| 15 |
+
def __init__(self, perplexity_client: Optional[PerplexityClient] = None):
|
| 16 |
+
self.perplexity = perplexity_client or PerplexityClient()
|
| 17 |
+
self.validator = DealValidator()
|
| 18 |
+
|
| 19 |
+
def parse(self, text: str, source_url: str) -> Optional[DealData]:
|
| 20 |
+
"""
|
| 21 |
+
Parse deal information from text
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
text: Article or filing text
|
| 25 |
+
source_url: URL where text came from
|
| 26 |
+
|
| 27 |
+
Returns:
|
| 28 |
+
DealData object if valid deal found, None otherwise
|
| 29 |
+
"""
|
| 30 |
+
# Step 1: LLM extraction
|
| 31 |
+
extracted = self.perplexity.extract_deal_info(text)
|
| 32 |
+
|
| 33 |
+
if not extracted:
|
| 34 |
+
return None
|
| 35 |
+
|
| 36 |
+
# Step 2: Regex validation and normalization
|
| 37 |
+
normalized = self._normalize_extracted(extracted)
|
| 38 |
+
|
| 39 |
+
# Step 3: Add metadata
|
| 40 |
+
normalized["sources"] = [source_url]
|
| 41 |
+
normalized["deal_stage"] = "announced" # Default, can be updated
|
| 42 |
+
normalized["confidence_score"] = self._calculate_confidence(normalized)
|
| 43 |
+
|
| 44 |
+
# Step 4: Validate with Pydantic
|
| 45 |
+
try:
|
| 46 |
+
deal_data = DealData(**normalized)
|
| 47 |
+
return deal_data
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"Validation error: {e}")
|
| 50 |
+
return None
|
| 51 |
+
|
| 52 |
+
def _normalize_extracted(self, extracted: Dict[str, Any]) -> Dict[str, Any]:
|
| 53 |
+
"""Normalize and clean extracted data"""
|
| 54 |
+
normalized = {}
|
| 55 |
+
|
| 56 |
+
# Provider and buyer
|
| 57 |
+
normalized["provider"] = self._clean_string(extracted.get("provider"))
|
| 58 |
+
normalized["buyer"] = self._clean_string(extracted.get("buyer"))
|
| 59 |
+
|
| 60 |
+
# Modality - normalize to standard values
|
| 61 |
+
modality = self._normalize_modality(extracted.get("modality"))
|
| 62 |
+
normalized["modality"] = modality
|
| 63 |
+
|
| 64 |
+
# Data type
|
| 65 |
+
normalized["data_type"] = self._clean_string(extracted.get("data_type"))
|
| 66 |
+
|
| 67 |
+
# Pricing
|
| 68 |
+
normalized["price_usd"] = self._parse_price(extracted.get("price_usd"))
|
| 69 |
+
normalized["price_range_min_usd"] = self._parse_price(extracted.get("price_range_min_usd"))
|
| 70 |
+
normalized["price_range_max_usd"] = self._parse_price(extracted.get("price_range_max_usd"))
|
| 71 |
+
normalized["reported_terms"] = self._clean_string(extracted.get("reported_terms"))
|
| 72 |
+
|
| 73 |
+
# Pricing mechanism and deal type
|
| 74 |
+
normalized["pricing_mechanism"] = self._clean_string(extracted.get("pricing_mechanism"))
|
| 75 |
+
normalized["deal_type"] = self._normalize_deal_type(extracted.get("deal_type"))
|
| 76 |
+
|
| 77 |
+
# Dates
|
| 78 |
+
normalized["date"] = self._parse_date(extracted.get("start_date"))
|
| 79 |
+
normalized["start_date"] = self._parse_datetime(extracted.get("start_date"))
|
| 80 |
+
normalized["end_date"] = self._parse_datetime(extracted.get("end_date"))
|
| 81 |
+
normalized["duration_years"] = self._parse_duration(extracted.get("duration_years"))
|
| 82 |
+
|
| 83 |
+
# Boolean fields
|
| 84 |
+
normalized["exclusive"] = self._parse_boolean(extracted.get("exclusivity"))
|
| 85 |
+
normalized["creators_compensated"] = self._parse_boolean(extracted.get("creators_compensated"))
|
| 86 |
+
normalized["revenue_share"] = self._parse_boolean(extracted.get("revenue_share"))
|
| 87 |
+
|
| 88 |
+
# Compensation
|
| 89 |
+
normalized["creator_split_percentage"] = self._parse_float(extracted.get("creator_split_percentage"))
|
| 90 |
+
|
| 91 |
+
# Rights
|
| 92 |
+
rights = extracted.get("rights_granted", "")
|
| 93 |
+
normalized["training_allowed"] = self._parse_rights(rights, ["training", "train"])
|
| 94 |
+
normalized["finetuning_allowed"] = self._parse_rights(rights, ["fine-tuning", "finetuning", "fine-tune"])
|
| 95 |
+
normalized["inference_allowed"] = self._parse_rights(rights, ["inference", "infer"])
|
| 96 |
+
normalized["redistribution_allowed"] = self._parse_rights(rights, ["redistribution", "redistribute"])
|
| 97 |
+
|
| 98 |
+
# Source
|
| 99 |
+
normalized["source_primary"] = self._clean_string(extracted.get("source"))
|
| 100 |
+
|
| 101 |
+
return normalized
|
| 102 |
+
|
| 103 |
+
def _clean_string(self, value: Any) -> Optional[str]:
|
| 104 |
+
"""Clean and normalize string values"""
|
| 105 |
+
if not value:
|
| 106 |
+
return None
|
| 107 |
+
if isinstance(value, str):
|
| 108 |
+
return value.strip() or None
|
| 109 |
+
return str(value).strip() or None
|
| 110 |
+
|
| 111 |
+
def _normalize_modality(self, modality: Any) -> str:
|
| 112 |
+
"""Normalize modality to standard values"""
|
| 113 |
+
if not modality:
|
| 114 |
+
return "Text" # Default
|
| 115 |
+
|
| 116 |
+
modality_lower = str(modality).lower()
|
| 117 |
+
|
| 118 |
+
modality_map = {
|
| 119 |
+
"text": "Text",
|
| 120 |
+
"image": "Image",
|
| 121 |
+
"audio": "Audio",
|
| 122 |
+
"video": "Video",
|
| 123 |
+
"satellite": "Satellite",
|
| 124 |
+
"biotech": "Health / Biotech",
|
| 125 |
+
"health": "Health / Biotech",
|
| 126 |
+
"corporate": "Corporate / data infra",
|
| 127 |
+
"infra": "Corporate / data infra",
|
| 128 |
+
"legal": "Legal / Books",
|
| 129 |
+
"books": "Legal / Books",
|
| 130 |
+
"commissioning": "Commissioning",
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
for key, value in modality_map.items():
|
| 134 |
+
if key in modality_lower:
|
| 135 |
+
return value
|
| 136 |
+
|
| 137 |
+
return "Text" # Default
|
| 138 |
+
|
| 139 |
+
def _normalize_deal_type(self, deal_type: Any) -> Optional[str]:
|
| 140 |
+
"""Normalize deal type"""
|
| 141 |
+
if not deal_type:
|
| 142 |
+
return None
|
| 143 |
+
|
| 144 |
+
deal_type_lower = str(deal_type).lower()
|
| 145 |
+
|
| 146 |
+
type_map = {
|
| 147 |
+
"aggregate": "aggregate",
|
| 148 |
+
"per-unit": "per-unit",
|
| 149 |
+
"per unit": "per-unit",
|
| 150 |
+
"commissioning": "commissioning",
|
| 151 |
+
"settlement": "settlement",
|
| 152 |
+
"acquisition": "acquisition",
|
| 153 |
+
"commons": "commons",
|
| 154 |
+
"open": "commons",
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
for key, value in type_map.items():
|
| 158 |
+
if key in deal_type_lower:
|
| 159 |
+
return value
|
| 160 |
+
|
| 161 |
+
return None
|
| 162 |
+
|
| 163 |
+
def _parse_price(self, value: Any) -> Optional[float]:
|
| 164 |
+
"""Parse price from various formats"""
|
| 165 |
+
if value is None:
|
| 166 |
+
return None
|
| 167 |
+
|
| 168 |
+
if isinstance(value, (int, float)):
|
| 169 |
+
return float(value)
|
| 170 |
+
|
| 171 |
+
if isinstance(value, str):
|
| 172 |
+
# Remove currency symbols and commas
|
| 173 |
+
cleaned = re.sub(r'[$,€£¥]', '', value.replace(',', ''))
|
| 174 |
+
# Extract number
|
| 175 |
+
match = re.search(r'(\d+\.?\d*)', cleaned)
|
| 176 |
+
if match:
|
| 177 |
+
return float(match.group(1))
|
| 178 |
+
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
def _parse_date(self, value: Any) -> Optional[str]:
|
| 182 |
+
"""Parse date string to YYYY-MM-DD format"""
|
| 183 |
+
if not value:
|
| 184 |
+
return None
|
| 185 |
+
|
| 186 |
+
# Try common date formats
|
| 187 |
+
formats = [
|
| 188 |
+
"%Y-%m-%d",
|
| 189 |
+
"%Y/%m/%d",
|
| 190 |
+
"%m/%d/%Y",
|
| 191 |
+
"%d/%m/%Y",
|
| 192 |
+
"%B %d, %Y",
|
| 193 |
+
"%b %d, %Y",
|
| 194 |
+
]
|
| 195 |
+
|
| 196 |
+
for fmt in formats:
|
| 197 |
+
try:
|
| 198 |
+
dt = datetime.strptime(str(value), fmt)
|
| 199 |
+
return dt.strftime("%Y-%m-%d")
|
| 200 |
+
except ValueError:
|
| 201 |
+
continue
|
| 202 |
+
|
| 203 |
+
# Try to extract year if full date fails
|
| 204 |
+
year_match = re.search(r'20\d{2}', str(value))
|
| 205 |
+
if year_match:
|
| 206 |
+
return f"{year_match.group()}-01-01"
|
| 207 |
+
|
| 208 |
+
return None
|
| 209 |
+
|
| 210 |
+
def _parse_datetime(self, value: Any) -> Optional[datetime]:
|
| 211 |
+
"""Parse datetime"""
|
| 212 |
+
date_str = self._parse_date(value)
|
| 213 |
+
if date_str:
|
| 214 |
+
try:
|
| 215 |
+
return datetime.strptime(date_str, "%Y-%m-%d")
|
| 216 |
+
except ValueError:
|
| 217 |
+
pass
|
| 218 |
+
return None
|
| 219 |
+
|
| 220 |
+
def _parse_duration(self, value: Any) -> Optional[float]:
|
| 221 |
+
"""Parse duration in years"""
|
| 222 |
+
if value is None:
|
| 223 |
+
return None
|
| 224 |
+
|
| 225 |
+
if isinstance(value, (int, float)):
|
| 226 |
+
return float(value)
|
| 227 |
+
|
| 228 |
+
if isinstance(value, str):
|
| 229 |
+
# Extract number
|
| 230 |
+
match = re.search(r'(\d+\.?\d*)', str(value))
|
| 231 |
+
if match:
|
| 232 |
+
return float(match.group(1))
|
| 233 |
+
|
| 234 |
+
return None
|
| 235 |
+
|
| 236 |
+
def _parse_boolean(self, value: Any) -> Optional[bool]:
|
| 237 |
+
"""Parse boolean from various formats"""
|
| 238 |
+
if value is None:
|
| 239 |
+
return None
|
| 240 |
+
|
| 241 |
+
if isinstance(value, bool):
|
| 242 |
+
return value
|
| 243 |
+
|
| 244 |
+
if isinstance(value, str):
|
| 245 |
+
lower = value.lower()
|
| 246 |
+
if lower in ["true", "yes", "1"]:
|
| 247 |
+
return True
|
| 248 |
+
if lower in ["false", "no", "0"]:
|
| 249 |
+
return False
|
| 250 |
+
|
| 251 |
+
return None
|
| 252 |
+
|
| 253 |
+
def _parse_float(self, value: Any) -> Optional[float]:
|
| 254 |
+
"""Parse float"""
|
| 255 |
+
if value is None:
|
| 256 |
+
return None
|
| 257 |
+
|
| 258 |
+
try:
|
| 259 |
+
return float(value)
|
| 260 |
+
except (ValueError, TypeError):
|
| 261 |
+
return None
|
| 262 |
+
|
| 263 |
+
def _parse_rights(self, rights_text: Any, keywords: list) -> Optional[bool]:
|
| 264 |
+
"""Parse rights from text"""
|
| 265 |
+
if not rights_text:
|
| 266 |
+
return None
|
| 267 |
+
|
| 268 |
+
text_lower = str(rights_text).lower()
|
| 269 |
+
for keyword in keywords:
|
| 270 |
+
if keyword in text_lower:
|
| 271 |
+
return True
|
| 272 |
+
return None
|
| 273 |
+
|
| 274 |
+
def _calculate_confidence(self, data: Dict[str, Any]) -> float:
|
| 275 |
+
"""Calculate confidence score (0-1)"""
|
| 276 |
+
score = 0.0
|
| 277 |
+
|
| 278 |
+
# Required fields
|
| 279 |
+
if data.get("provider"):
|
| 280 |
+
score += 0.2
|
| 281 |
+
if data.get("buyer"):
|
| 282 |
+
score += 0.2
|
| 283 |
+
if data.get("modality"):
|
| 284 |
+
score += 0.1
|
| 285 |
+
|
| 286 |
+
# Price information
|
| 287 |
+
if data.get("price_usd") or data.get("price_range_min_usd"):
|
| 288 |
+
score += 0.2
|
| 289 |
+
|
| 290 |
+
# Dates
|
| 291 |
+
if data.get("date") or data.get("start_date"):
|
| 292 |
+
score += 0.1
|
| 293 |
+
|
| 294 |
+
# Terms
|
| 295 |
+
if data.get("reported_terms"):
|
| 296 |
+
score += 0.1
|
| 297 |
+
|
| 298 |
+
# Rights/compensation info
|
| 299 |
+
if data.get("exclusive") is not None or data.get("creators_compensated") is not None:
|
| 300 |
+
score += 0.1
|
| 301 |
+
|
| 302 |
+
return min(score, 1.0)
|
| 303 |
+
|
ingestion/exa_client.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Exa API Client - Intelligent URL retrieval for deal discovery
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from typing import List, Dict, Optional
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
import requests
|
| 9 |
+
from source_registry import EXA_SEARCH_QUERIES
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass
|
| 13 |
+
class ExaResult:
|
| 14 |
+
"""Represents a result from Exa search"""
|
| 15 |
+
url: str
|
| 16 |
+
title: str
|
| 17 |
+
summary: str
|
| 18 |
+
published_date: Optional[str] = None
|
| 19 |
+
author: Optional[str] = None
|
| 20 |
+
score: float = 0.0 # Relevance score
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class ExaClient:
|
| 24 |
+
"""Client for Exa API"""
|
| 25 |
+
|
| 26 |
+
def __init__(self, api_key: Optional[str] = None):
|
| 27 |
+
self.api_key = api_key or os.getenv("EXA_API_KEY")
|
| 28 |
+
if not self.api_key:
|
| 29 |
+
raise ValueError("EXA_API_KEY environment variable required")
|
| 30 |
+
self.base_url = "https://api.exa.ai"
|
| 31 |
+
self.headers = {
|
| 32 |
+
"x-api-key": self.api_key,
|
| 33 |
+
"Content-Type": "application/json",
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
def search(
|
| 37 |
+
self,
|
| 38 |
+
query: str,
|
| 39 |
+
num_results: int = 10,
|
| 40 |
+
start_published_date: Optional[str] = None,
|
| 41 |
+
end_published_date: Optional[str] = None,
|
| 42 |
+
category: Optional[str] = None,
|
| 43 |
+
) -> List[ExaResult]:
|
| 44 |
+
"""
|
| 45 |
+
Search for URLs using Exa
|
| 46 |
+
|
| 47 |
+
Args:
|
| 48 |
+
query: Search query
|
| 49 |
+
num_results: Number of results to return
|
| 50 |
+
start_published_date: Start date (YYYY-MM-DD)
|
| 51 |
+
end_published_date: End date (YYYY-MM-DD)
|
| 52 |
+
category: Content category filter
|
| 53 |
+
|
| 54 |
+
Returns:
|
| 55 |
+
List of ExaResult objects
|
| 56 |
+
"""
|
| 57 |
+
payload = {
|
| 58 |
+
"query": query,
|
| 59 |
+
"num_results": num_results,
|
| 60 |
+
"use_autoprompt": True, # Let Exa optimize the query
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
if start_published_date:
|
| 64 |
+
payload["start_published_date"] = start_published_date
|
| 65 |
+
if end_published_date:
|
| 66 |
+
payload["end_published_date"] = end_published_date
|
| 67 |
+
if category:
|
| 68 |
+
payload["category"] = category
|
| 69 |
+
|
| 70 |
+
try:
|
| 71 |
+
response = requests.post(
|
| 72 |
+
f"{self.base_url}/search",
|
| 73 |
+
json=payload,
|
| 74 |
+
headers=self.headers,
|
| 75 |
+
timeout=30,
|
| 76 |
+
)
|
| 77 |
+
response.raise_for_status()
|
| 78 |
+
data = response.json()
|
| 79 |
+
|
| 80 |
+
results = []
|
| 81 |
+
for item in data.get("results", []):
|
| 82 |
+
results.append(ExaResult(
|
| 83 |
+
url=item.get("url", ""),
|
| 84 |
+
title=item.get("title", ""),
|
| 85 |
+
summary=item.get("text", ""), # Exa provides summary text
|
| 86 |
+
published_date=item.get("published_date"),
|
| 87 |
+
author=item.get("author"),
|
| 88 |
+
score=item.get("score", 0.0),
|
| 89 |
+
))
|
| 90 |
+
|
| 91 |
+
return results
|
| 92 |
+
|
| 93 |
+
except requests.exceptions.RequestException as e:
|
| 94 |
+
print(f"Exa API error for query '{query}': {e}")
|
| 95 |
+
return []
|
| 96 |
+
|
| 97 |
+
def get_contents(self, urls: List[str]) -> Dict[str, str]:
|
| 98 |
+
"""
|
| 99 |
+
Get full content for URLs (bypasses paywalls via Exa)
|
| 100 |
+
|
| 101 |
+
Args:
|
| 102 |
+
urls: List of URLs to fetch
|
| 103 |
+
|
| 104 |
+
Returns:
|
| 105 |
+
Dict mapping URL to content text
|
| 106 |
+
"""
|
| 107 |
+
payload = {
|
| 108 |
+
"urls": urls,
|
| 109 |
+
"text": {"max_characters": 10000}, # Limit content size
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
try:
|
| 113 |
+
response = requests.post(
|
| 114 |
+
f"{self.base_url}/contents",
|
| 115 |
+
json=payload,
|
| 116 |
+
headers=self.headers,
|
| 117 |
+
timeout=60,
|
| 118 |
+
)
|
| 119 |
+
response.raise_for_status()
|
| 120 |
+
data = response.json()
|
| 121 |
+
|
| 122 |
+
contents = {}
|
| 123 |
+
for item in data.get("results", []):
|
| 124 |
+
url = item.get("url", "")
|
| 125 |
+
text = item.get("text", "")
|
| 126 |
+
contents[url] = text
|
| 127 |
+
|
| 128 |
+
return contents
|
| 129 |
+
|
| 130 |
+
except requests.exceptions.RequestException as e:
|
| 131 |
+
print(f"Exa contents API error: {e}")
|
| 132 |
+
return {}
|
| 133 |
+
|
| 134 |
+
def search_deal_candidates(
|
| 135 |
+
self,
|
| 136 |
+
days_back: int = 7,
|
| 137 |
+
num_results_per_query: int = 5,
|
| 138 |
+
) -> List[ExaResult]:
|
| 139 |
+
"""
|
| 140 |
+
Search for deal candidates using all predefined queries
|
| 141 |
+
|
| 142 |
+
Args:
|
| 143 |
+
days_back: How many days back to search
|
| 144 |
+
num_results_per_query: Results per query
|
| 145 |
+
|
| 146 |
+
Returns:
|
| 147 |
+
Deduplicated list of results
|
| 148 |
+
"""
|
| 149 |
+
from datetime import datetime, timedelta
|
| 150 |
+
|
| 151 |
+
end_date = datetime.now()
|
| 152 |
+
start_date = end_date - timedelta(days=days_back)
|
| 153 |
+
|
| 154 |
+
start_str = start_date.strftime("%Y-%m-%d")
|
| 155 |
+
end_str = end_date.strftime("%Y-%m-%d")
|
| 156 |
+
|
| 157 |
+
all_results = []
|
| 158 |
+
seen_urls = set()
|
| 159 |
+
|
| 160 |
+
for query in EXA_SEARCH_QUERIES:
|
| 161 |
+
print(f"Searching Exa for: {query}")
|
| 162 |
+
results = self.search(
|
| 163 |
+
query=query,
|
| 164 |
+
num_results=num_results_per_query,
|
| 165 |
+
start_published_date=start_str,
|
| 166 |
+
end_published_date=end_str,
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
for result in results:
|
| 170 |
+
if result.url not in seen_urls:
|
| 171 |
+
seen_urls.add(result.url)
|
| 172 |
+
all_results.append(result)
|
| 173 |
+
|
| 174 |
+
# Sort by score (relevance)
|
| 175 |
+
all_results.sort(key=lambda x: x.score, reverse=True)
|
| 176 |
+
|
| 177 |
+
return all_results
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
if __name__ == "__main__":
|
| 181 |
+
# Example usage
|
| 182 |
+
client = ExaClient()
|
| 183 |
+
results = client.search_deal_candidates(days_back=30)
|
| 184 |
+
print(f"Found {len(results)} unique URLs")
|
| 185 |
+
for result in results[:5]:
|
| 186 |
+
print(f"\n{result.title}")
|
| 187 |
+
print(f" URL: {result.url}")
|
| 188 |
+
print(f" Score: {result.score:.2f}")
|
| 189 |
+
print(f" Summary: {result.summary[:200]}...")
|
| 190 |
+
|
ingestion/main.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Main Ingestion Pipeline - Orchestrates the full scraping and ingestion process
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
from datetime import datetime
|
| 8 |
+
from typing import List
|
| 9 |
+
from dotenv import load_dotenv
|
| 10 |
+
|
| 11 |
+
# Load environment variables
|
| 12 |
+
load_dotenv()
|
| 13 |
+
|
| 14 |
+
# Import clients and modules
|
| 15 |
+
try:
|
| 16 |
+
from exa_client import ExaClient, ExaResult
|
| 17 |
+
from perplexity_client import PerplexityClient
|
| 18 |
+
from deal_parser import DealParser
|
| 19 |
+
from validator import DealValidator, DealData
|
| 20 |
+
from source_registry import get_all_sources, EXA_SEARCH_QUERIES
|
| 21 |
+
except ImportError as e:
|
| 22 |
+
print(f"Import error: {e}")
|
| 23 |
+
print("Make sure all dependencies are installed: pip install -r requirements.txt")
|
| 24 |
+
sys.exit(1)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class IngestionPipeline:
|
| 28 |
+
"""Main pipeline orchestrator"""
|
| 29 |
+
|
| 30 |
+
def __init__(self):
|
| 31 |
+
self.exa = ExaClient() if os.getenv("EXA_API_KEY") else None
|
| 32 |
+
self.perplexity = PerplexityClient() if os.getenv("PERPLEXITY_API_KEY") else None
|
| 33 |
+
self.parser = DealParser(self.perplexity) if self.perplexity else None
|
| 34 |
+
self.validator = DealValidator()
|
| 35 |
+
|
| 36 |
+
def run_exa_search(self, days_back: int = 7) -> List[ExaResult]:
|
| 37 |
+
"""Run Exa search for deal candidates"""
|
| 38 |
+
if not self.exa:
|
| 39 |
+
print("Exa API key not configured, skipping Exa search")
|
| 40 |
+
return []
|
| 41 |
+
|
| 42 |
+
print(f"🔍 Searching Exa for deals from last {days_back} days...")
|
| 43 |
+
results = self.exa.search_deal_candidates(
|
| 44 |
+
days_back=days_back,
|
| 45 |
+
num_results_per_query=5,
|
| 46 |
+
)
|
| 47 |
+
print(f"✅ Found {len(results)} unique URLs from Exa")
|
| 48 |
+
return results
|
| 49 |
+
|
| 50 |
+
def process_urls(self, urls: List[str]) -> List[DealData]:
|
| 51 |
+
"""Process URLs and extract deals"""
|
| 52 |
+
if not self.parser:
|
| 53 |
+
print("Perplexity API key not configured, cannot parse deals")
|
| 54 |
+
return []
|
| 55 |
+
|
| 56 |
+
deals = []
|
| 57 |
+
|
| 58 |
+
for url in urls:
|
| 59 |
+
print(f"\n📄 Processing: {url}")
|
| 60 |
+
|
| 61 |
+
# Get content (simplified - in production, use html_scraper.py)
|
| 62 |
+
try:
|
| 63 |
+
import requests
|
| 64 |
+
response = requests.get(url, timeout=30, headers={
|
| 65 |
+
"User-Agent": "Mozilla/5.0 (compatible; DealTracker/1.0)"
|
| 66 |
+
})
|
| 67 |
+
text = response.text
|
| 68 |
+
|
| 69 |
+
# Extract deal
|
| 70 |
+
deal = self.parser.parse(text, url)
|
| 71 |
+
|
| 72 |
+
if deal:
|
| 73 |
+
print(f" ✅ Extracted deal: {deal.provider} → {deal.buyer}")
|
| 74 |
+
deals.append(deal)
|
| 75 |
+
else:
|
| 76 |
+
print(f" ⚠️ No deal found in this URL")
|
| 77 |
+
|
| 78 |
+
except Exception as e:
|
| 79 |
+
print(f" ❌ Error processing URL: {e}")
|
| 80 |
+
continue
|
| 81 |
+
|
| 82 |
+
return deals
|
| 83 |
+
|
| 84 |
+
def save_deals(self, deals: List[DealData]):
|
| 85 |
+
"""Save deals to database"""
|
| 86 |
+
# This would connect to your database
|
| 87 |
+
# For now, just print
|
| 88 |
+
print(f"\n💾 Saving {len(deals)} deals to database...")
|
| 89 |
+
|
| 90 |
+
# TODO: Implement database writer
|
| 91 |
+
# from db_writer import DBWriter
|
| 92 |
+
# writer = DBWriter()
|
| 93 |
+
# for deal in deals:
|
| 94 |
+
# writer.upsert_deal(deal)
|
| 95 |
+
|
| 96 |
+
print("✅ Deals saved (database writer not yet implemented)")
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def main():
|
| 100 |
+
"""Main entry point"""
|
| 101 |
+
print("=" * 60)
|
| 102 |
+
print("AI Training Data Deals - Ingestion Pipeline")
|
| 103 |
+
print("=" * 60)
|
| 104 |
+
print(f"Started at: {datetime.now().isoformat()}\n")
|
| 105 |
+
|
| 106 |
+
pipeline = IngestionPipeline()
|
| 107 |
+
|
| 108 |
+
# Step 1: Search for deal candidates
|
| 109 |
+
exa_results = pipeline.run_exa_search(days_back=7)
|
| 110 |
+
|
| 111 |
+
if not exa_results:
|
| 112 |
+
print("\n⚠️ No URLs found. Exiting.")
|
| 113 |
+
return
|
| 114 |
+
|
| 115 |
+
# Step 2: Process URLs
|
| 116 |
+
urls = [r.url for r in exa_results]
|
| 117 |
+
deals = pipeline.process_urls(urls)
|
| 118 |
+
|
| 119 |
+
# Step 3: Save to database
|
| 120 |
+
if deals:
|
| 121 |
+
pipeline.save_deals(deals)
|
| 122 |
+
print(f"\n✅ Pipeline complete: {len(deals)} deals extracted")
|
| 123 |
+
else:
|
| 124 |
+
print("\n⚠️ No deals extracted")
|
| 125 |
+
|
| 126 |
+
print(f"\nFinished at: {datetime.now().isoformat()}")
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
if __name__ == "__main__":
|
| 130 |
+
main()
|
| 131 |
+
|
ingestion/perplexity_client.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Perplexity API Client - For content summarization and structured extraction
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from typing import Dict, Optional, Any
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class PerplexityClient:
|
| 12 |
+
"""Client for Perplexity API"""
|
| 13 |
+
|
| 14 |
+
def __init__(self, api_key: Optional[str] = None):
|
| 15 |
+
self.api_key = api_key or os.getenv("PERPLEXITY_API_KEY")
|
| 16 |
+
if not self.api_key:
|
| 17 |
+
raise ValueError("PERPLEXITY_API_KEY environment variable required")
|
| 18 |
+
self.base_url = "https://api.perplexity.ai"
|
| 19 |
+
self.headers = {
|
| 20 |
+
"Authorization": f"Bearer {self.api_key}",
|
| 21 |
+
"Content-Type": "application/json",
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
def chat(
|
| 25 |
+
self,
|
| 26 |
+
messages: list,
|
| 27 |
+
model: str = "llama-3.1-sonar-large-128k-online",
|
| 28 |
+
temperature: float = 0.2,
|
| 29 |
+
) -> Dict[str, Any]:
|
| 30 |
+
"""
|
| 31 |
+
Send chat request to Perplexity
|
| 32 |
+
|
| 33 |
+
Args:
|
| 34 |
+
messages: List of message dicts (role, content)
|
| 35 |
+
model: Model to use
|
| 36 |
+
temperature: Sampling temperature
|
| 37 |
+
|
| 38 |
+
Returns:
|
| 39 |
+
Response dict with content and citations
|
| 40 |
+
"""
|
| 41 |
+
payload = {
|
| 42 |
+
"model": model,
|
| 43 |
+
"messages": messages,
|
| 44 |
+
"temperature": temperature,
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
try:
|
| 48 |
+
response = requests.post(
|
| 49 |
+
f"{self.base_url}/chat/completions",
|
| 50 |
+
json=payload,
|
| 51 |
+
headers=self.headers,
|
| 52 |
+
timeout=60,
|
| 53 |
+
)
|
| 54 |
+
response.raise_for_status()
|
| 55 |
+
return response.json()
|
| 56 |
+
|
| 57 |
+
except requests.exceptions.RequestException as e:
|
| 58 |
+
print(f"Perplexity API error: {e}")
|
| 59 |
+
return {}
|
| 60 |
+
|
| 61 |
+
def extract_deal_info(self, text: str) -> Dict[str, Any]:
|
| 62 |
+
"""
|
| 63 |
+
Extract structured deal information from text
|
| 64 |
+
|
| 65 |
+
Args:
|
| 66 |
+
text: Article or filing text
|
| 67 |
+
|
| 68 |
+
Returns:
|
| 69 |
+
Dict with extracted fields
|
| 70 |
+
"""
|
| 71 |
+
prompt = """Please extract structured information about an AI training data deal from the following text.
|
| 72 |
+
|
| 73 |
+
Extract:
|
| 74 |
+
- provider (company/organization providing data)
|
| 75 |
+
- buyer (company/organization buying/licensing data)
|
| 76 |
+
- modality (text, image, audio, video, satellite, biotech, etc.)
|
| 77 |
+
- data_type (brief description of the data)
|
| 78 |
+
- pricing_mechanism (e.g., "Access / aggregate licensing", "Per-unit licensing", etc.)
|
| 79 |
+
- price_usd (numeric value if mentioned, or null)
|
| 80 |
+
- price_range_min_usd (if range given)
|
| 81 |
+
- price_range_max_usd (if range given)
|
| 82 |
+
- reported_terms (headline terms as mentioned)
|
| 83 |
+
- exclusivity (true/false/null if mentioned)
|
| 84 |
+
- creators_compensated (true/false/null if mentioned)
|
| 85 |
+
- creator_split_percentage (if mentioned)
|
| 86 |
+
- revenue_share (true/false if mentioned)
|
| 87 |
+
- start_date (if mentioned)
|
| 88 |
+
- end_date (if mentioned)
|
| 89 |
+
- duration_years (if mentioned)
|
| 90 |
+
- rights_granted (training, fine-tuning, inference, etc.)
|
| 91 |
+
- deal_type (aggregate, per-unit, commissioning, settlement, etc.)
|
| 92 |
+
|
| 93 |
+
Return ONLY valid JSON, no markdown formatting. If a field cannot be determined, use null."""
|
| 94 |
+
|
| 95 |
+
messages = [
|
| 96 |
+
{
|
| 97 |
+
"role": "system",
|
| 98 |
+
"content": "You are a data extraction assistant. Extract structured information from text and return only valid JSON.",
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"role": "user",
|
| 102 |
+
"content": f"{prompt}\n\nText:\n{text[:8000]}", # Limit text size
|
| 103 |
+
},
|
| 104 |
+
]
|
| 105 |
+
|
| 106 |
+
response = self.chat(messages, temperature=0.1)
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
content = response.get("choices", [{}])[0].get("message", {}).get("content", "{}")
|
| 110 |
+
# Remove markdown code blocks if present
|
| 111 |
+
content = content.strip()
|
| 112 |
+
if content.startswith("```json"):
|
| 113 |
+
content = content[7:]
|
| 114 |
+
if content.startswith("```"):
|
| 115 |
+
content = content[3:]
|
| 116 |
+
if content.endswith("```"):
|
| 117 |
+
content = content[:-3]
|
| 118 |
+
content = content.strip()
|
| 119 |
+
|
| 120 |
+
extracted = json.loads(content)
|
| 121 |
+
return extracted
|
| 122 |
+
|
| 123 |
+
except (json.JSONDecodeError, KeyError, IndexError) as e:
|
| 124 |
+
print(f"Error parsing Perplexity response: {e}")
|
| 125 |
+
print(f"Response: {response}")
|
| 126 |
+
return {}
|
| 127 |
+
|
| 128 |
+
def summarize_article(self, text: str) -> str:
|
| 129 |
+
"""
|
| 130 |
+
Generate a summary of an article
|
| 131 |
+
|
| 132 |
+
Args:
|
| 133 |
+
text: Article text
|
| 134 |
+
|
| 135 |
+
Returns:
|
| 136 |
+
Summary string
|
| 137 |
+
"""
|
| 138 |
+
messages = [
|
| 139 |
+
{
|
| 140 |
+
"role": "system",
|
| 141 |
+
"content": "You are a summarization assistant. Provide concise summaries.",
|
| 142 |
+
},
|
| 143 |
+
{
|
| 144 |
+
"role": "user",
|
| 145 |
+
"content": f"Summarize the following article in 2-3 sentences:\n\n{text[:6000]}",
|
| 146 |
+
},
|
| 147 |
+
]
|
| 148 |
+
|
| 149 |
+
response = self.chat(messages)
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
return response.get("choices", [{}])[0].get("message", {}).get("content", "")
|
| 153 |
+
except (KeyError, IndexError):
|
| 154 |
+
return ""
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
if __name__ == "__main__":
|
| 158 |
+
# Example usage
|
| 159 |
+
client = PerplexityClient()
|
| 160 |
+
|
| 161 |
+
sample_text = """
|
| 162 |
+
OpenAI announced a multi-year licensing agreement with News Corp worth over $250 million.
|
| 163 |
+
The deal grants OpenAI access to content from The Wall Street Journal, The Times, and NY Post
|
| 164 |
+
for training AI models. The agreement is exclusive and spans 5 years.
|
| 165 |
+
"""
|
| 166 |
+
|
| 167 |
+
extracted = client.extract_deal_info(sample_text)
|
| 168 |
+
print("Extracted deal info:")
|
| 169 |
+
print(json.dumps(extracted, indent=2))
|
| 170 |
+
|
ingestion/requirements.txt
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python Ingestion Pipeline Dependencies
|
| 2 |
+
|
| 3 |
+
# Core
|
| 4 |
+
requests>=2.31.0
|
| 5 |
+
beautifulsoup4>=4.12.0
|
| 6 |
+
newspaper3k>=0.2.8
|
| 7 |
+
python-dotenv>=1.0.0
|
| 8 |
+
|
| 9 |
+
# Database
|
| 10 |
+
psycopg2-binary>=2.9.9
|
| 11 |
+
sqlalchemy>=2.0.23
|
| 12 |
+
prisma>=0.11.0
|
| 13 |
+
|
| 14 |
+
# LLM APIs
|
| 15 |
+
openai>=1.12.0
|
| 16 |
+
anthropic>=0.18.0
|
| 17 |
+
|
| 18 |
+
# Validation
|
| 19 |
+
pydantic>=2.6.0
|
| 20 |
+
|
| 21 |
+
# Utilities
|
| 22 |
+
python-dateutil>=2.8.2
|
| 23 |
+
tqdm>=4.66.0
|
| 24 |
+
|
| 25 |
+
# Exa API (when available)
|
| 26 |
+
# exa-py>=1.0.0
|
| 27 |
+
|
| 28 |
+
# Perplexity API (when available)
|
| 29 |
+
# perplexity>=1.0.0
|
| 30 |
+
|
ingestion/source_registry.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Source Registry - Central registry of data sources to scrape
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Optional, List
|
| 7 |
+
from enum import Enum
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class SourceType(Enum):
|
| 11 |
+
NEWS = "news"
|
| 12 |
+
FILING = "filing"
|
| 13 |
+
PRESS_RELEASE = "press_release"
|
| 14 |
+
RSS = "rss"
|
| 15 |
+
TRACKER = "tracker"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class Source:
|
| 20 |
+
"""Represents a data source configuration"""
|
| 21 |
+
name: str
|
| 22 |
+
base_url: str
|
| 23 |
+
source_type: SourceType
|
| 24 |
+
rss_feed: Optional[str] = None
|
| 25 |
+
css_selectors: Optional[dict] = None # e.g., {"title": ".article-title", "content": ".article-body"}
|
| 26 |
+
api_endpoint: Optional[str] = None
|
| 27 |
+
headers: Optional[dict] = None
|
| 28 |
+
enabled: bool = True
|
| 29 |
+
priority: int = 5 # 1-10, higher = more important
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
# News Outlets
|
| 33 |
+
NEWS_SOURCES = [
|
| 34 |
+
Source(
|
| 35 |
+
name="Reuters",
|
| 36 |
+
base_url="https://www.reuters.com",
|
| 37 |
+
source_type=SourceType.NEWS,
|
| 38 |
+
rss_feed="https://www.reuters.com/tools/rss",
|
| 39 |
+
priority=9,
|
| 40 |
+
),
|
| 41 |
+
Source(
|
| 42 |
+
name="Bloomberg",
|
| 43 |
+
base_url="https://www.bloomberg.com",
|
| 44 |
+
source_type=SourceType.NEWS,
|
| 45 |
+
priority=9,
|
| 46 |
+
),
|
| 47 |
+
Source(
|
| 48 |
+
name="Financial Times",
|
| 49 |
+
base_url="https://www.ft.com",
|
| 50 |
+
source_type=SourceType.NEWS,
|
| 51 |
+
priority=8,
|
| 52 |
+
),
|
| 53 |
+
Source(
|
| 54 |
+
name="Axios",
|
| 55 |
+
base_url="https://www.axios.com",
|
| 56 |
+
source_type=SourceType.NEWS,
|
| 57 |
+
rss_feed="https://api.axios.com/feed/",
|
| 58 |
+
priority=7,
|
| 59 |
+
),
|
| 60 |
+
Source(
|
| 61 |
+
name="The Verge",
|
| 62 |
+
base_url="https://www.theverge.com",
|
| 63 |
+
source_type=SourceType.NEWS,
|
| 64 |
+
rss_feed="https://www.theverge.com/rss/index.xml",
|
| 65 |
+
priority=7,
|
| 66 |
+
),
|
| 67 |
+
Source(
|
| 68 |
+
name="WIRED",
|
| 69 |
+
base_url="https://www.wired.com",
|
| 70 |
+
source_type=SourceType.NEWS,
|
| 71 |
+
rss_feed="https://www.wired.com/feed/rss",
|
| 72 |
+
priority=7,
|
| 73 |
+
),
|
| 74 |
+
Source(
|
| 75 |
+
name="TechCrunch",
|
| 76 |
+
base_url="https://techcrunch.com",
|
| 77 |
+
source_type=SourceType.NEWS,
|
| 78 |
+
rss_feed="https://techcrunch.com/feed/",
|
| 79 |
+
priority=8,
|
| 80 |
+
),
|
| 81 |
+
Source(
|
| 82 |
+
name="Music Business Worldwide",
|
| 83 |
+
base_url="https://www.musicbusinessworldwide.com",
|
| 84 |
+
source_type=SourceType.NEWS,
|
| 85 |
+
priority=6,
|
| 86 |
+
),
|
| 87 |
+
Source(
|
| 88 |
+
name="PetaPixel",
|
| 89 |
+
base_url="https://petapixel.com",
|
| 90 |
+
source_type=SourceType.NEWS,
|
| 91 |
+
rss_feed="https://petapixel.com/feed/",
|
| 92 |
+
priority=5,
|
| 93 |
+
),
|
| 94 |
+
]
|
| 95 |
+
|
| 96 |
+
# Press Release Sources
|
| 97 |
+
PRESS_RELEASE_SOURCES = [
|
| 98 |
+
Source(
|
| 99 |
+
name="OpenAI Blog",
|
| 100 |
+
base_url="https://openai.com/blog",
|
| 101 |
+
source_type=SourceType.PRESS_RELEASE,
|
| 102 |
+
rss_feed="https://openai.com/blog/rss.xml",
|
| 103 |
+
priority=9,
|
| 104 |
+
),
|
| 105 |
+
Source(
|
| 106 |
+
name="Google AI Blog",
|
| 107 |
+
base_url="https://ai.googleblog.com",
|
| 108 |
+
source_type=SourceType.PRESS_RELEASE,
|
| 109 |
+
priority=8,
|
| 110 |
+
),
|
| 111 |
+
Source(
|
| 112 |
+
name="Meta AI",
|
| 113 |
+
base_url="https://ai.meta.com/blog",
|
| 114 |
+
source_type=SourceType.PRESS_RELEASE,
|
| 115 |
+
priority=8,
|
| 116 |
+
),
|
| 117 |
+
Source(
|
| 118 |
+
name="Anthropic",
|
| 119 |
+
base_url="https://www.anthropic.com/news",
|
| 120 |
+
source_type=SourceType.PRESS_RELEASE,
|
| 121 |
+
priority=8,
|
| 122 |
+
),
|
| 123 |
+
Source(
|
| 124 |
+
name="Microsoft AI",
|
| 125 |
+
base_url="https://www.microsoft.com/en-us/research/blog",
|
| 126 |
+
source_type=SourceType.PRESS_RELEASE,
|
| 127 |
+
priority=7,
|
| 128 |
+
),
|
| 129 |
+
Source(
|
| 130 |
+
name="HarperCollins",
|
| 131 |
+
base_url="https://www.harpercollins.com/news",
|
| 132 |
+
source_type=SourceType.PRESS_RELEASE,
|
| 133 |
+
priority=6,
|
| 134 |
+
),
|
| 135 |
+
Source(
|
| 136 |
+
name="Wiley",
|
| 137 |
+
base_url="https://www.wiley.com/en-us/news",
|
| 138 |
+
source_type=SourceType.PRESS_RELEASE,
|
| 139 |
+
priority=6,
|
| 140 |
+
),
|
| 141 |
+
Source(
|
| 142 |
+
name="News Corp",
|
| 143 |
+
base_url="https://newscorp.com/news",
|
| 144 |
+
source_type=SourceType.PRESS_RELEASE,
|
| 145 |
+
priority=7,
|
| 146 |
+
),
|
| 147 |
+
]
|
| 148 |
+
|
| 149 |
+
# Filing Sources
|
| 150 |
+
FILING_SOURCES = [
|
| 151 |
+
Source(
|
| 152 |
+
name="SEC EDGAR",
|
| 153 |
+
base_url="https://www.sec.gov/edgar",
|
| 154 |
+
source_type=SourceType.FILING,
|
| 155 |
+
api_endpoint="https://data.sec.gov/api/xbrl",
|
| 156 |
+
priority=9,
|
| 157 |
+
),
|
| 158 |
+
Source(
|
| 159 |
+
name="SEDAR",
|
| 160 |
+
base_url="https://www.sedar.com",
|
| 161 |
+
source_type=SourceType.FILING,
|
| 162 |
+
priority=7,
|
| 163 |
+
),
|
| 164 |
+
]
|
| 165 |
+
|
| 166 |
+
# Industry Trackers
|
| 167 |
+
TRACKER_SOURCES = [
|
| 168 |
+
Source(
|
| 169 |
+
name="CB Insights",
|
| 170 |
+
base_url="https://www.cbinsights.com",
|
| 171 |
+
source_type=SourceType.TRACKER,
|
| 172 |
+
priority=8,
|
| 173 |
+
),
|
| 174 |
+
Source(
|
| 175 |
+
name="Appen",
|
| 176 |
+
base_url="https://appen.com",
|
| 177 |
+
source_type=SourceType.TRACKER,
|
| 178 |
+
priority=6,
|
| 179 |
+
),
|
| 180 |
+
]
|
| 181 |
+
|
| 182 |
+
# Exa Search Queries (for intelligent retrieval)
|
| 183 |
+
EXA_SEARCH_QUERIES = [
|
| 184 |
+
"OpenAI licensing",
|
| 185 |
+
"Anthropic licensing",
|
| 186 |
+
"training data deal",
|
| 187 |
+
"content licensing AI",
|
| 188 |
+
"publisher AI model agreement",
|
| 189 |
+
"data partnership",
|
| 190 |
+
"AI dataset licensing",
|
| 191 |
+
"media archive deal OpenAI",
|
| 192 |
+
"exclusive AI rights",
|
| 193 |
+
"API access licensing",
|
| 194 |
+
"settlement AI training",
|
| 195 |
+
"Microsoft AI data licensing",
|
| 196 |
+
"Google AI content deal",
|
| 197 |
+
"Meta AI training data",
|
| 198 |
+
"music AI licensing",
|
| 199 |
+
"image AI training data",
|
| 200 |
+
"video AI dataset",
|
| 201 |
+
"satellite AI data",
|
| 202 |
+
"biotech AI training",
|
| 203 |
+
]
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def get_all_sources() -> List[Source]:
|
| 207 |
+
"""Get all enabled sources"""
|
| 208 |
+
all_sources = (
|
| 209 |
+
NEWS_SOURCES +
|
| 210 |
+
PRESS_RELEASE_SOURCES +
|
| 211 |
+
FILING_SOURCES +
|
| 212 |
+
TRACKER_SOURCES
|
| 213 |
+
)
|
| 214 |
+
return [s for s in all_sources if s.enabled]
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def get_sources_by_type(source_type: SourceType) -> List[Source]:
|
| 218 |
+
"""Get sources filtered by type"""
|
| 219 |
+
return [s for s in get_all_sources() if s.source_type == source_type]
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def get_high_priority_sources(threshold: int = 7) -> List[Source]:
|
| 223 |
+
"""Get sources with priority >= threshold"""
|
| 224 |
+
return [s for s in get_all_sources() if s.priority >= threshold]
|
| 225 |
+
|
ingestion/validator.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Deal Validator - Pydantic models for validation
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from pydantic import BaseModel, Field, validator
|
| 6 |
+
from typing import Optional, List
|
| 7 |
+
from datetime import datetime
|
| 8 |
+
from enum import Enum
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Modality(str, Enum):
|
| 12 |
+
TEXT = "Text"
|
| 13 |
+
IMAGE = "Image"
|
| 14 |
+
AUDIO = "Audio"
|
| 15 |
+
VIDEO = "Video"
|
| 16 |
+
SATELLITE = "Satellite"
|
| 17 |
+
HEALTH_BIOTECH = "Health / Biotech"
|
| 18 |
+
CORPORATE_INFRA = "Corporate / data infra"
|
| 19 |
+
LEGAL_BOOKS = "Legal / Books"
|
| 20 |
+
COMMISSIONING = "Commissioning"
|
| 21 |
+
MIXED = "Mixed"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class DealType(str, Enum):
|
| 25 |
+
AGGREGATE = "aggregate"
|
| 26 |
+
PER_UNIT = "per-unit"
|
| 27 |
+
COMMISSIONING = "commissioning"
|
| 28 |
+
SETTLEMENT = "settlement"
|
| 29 |
+
ACQUISITION = "acquisition"
|
| 30 |
+
COMMONS = "commons"
|
| 31 |
+
IMPLICIT = "implicit"
|
| 32 |
+
HYBRID = "hybrid"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class DealStage(str, Enum):
|
| 36 |
+
ANNOUNCED = "announced"
|
| 37 |
+
RUMORED = "rumored"
|
| 38 |
+
CONFIRMED = "confirmed"
|
| 39 |
+
SETTLED = "settled"
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class DealData(BaseModel):
|
| 43 |
+
"""Validated deal data structure"""
|
| 44 |
+
|
| 45 |
+
# Core fields (required)
|
| 46 |
+
provider: str = Field(..., min_length=1)
|
| 47 |
+
buyer: str = Field(..., min_length=1)
|
| 48 |
+
modality: str = Field(default="Text")
|
| 49 |
+
data_type: Optional[str] = None
|
| 50 |
+
|
| 51 |
+
# Pricing
|
| 52 |
+
price_usd: Optional[float] = Field(None, ge=0)
|
| 53 |
+
price_range_min_usd: Optional[float] = Field(None, ge=0)
|
| 54 |
+
price_range_max_usd: Optional[float] = Field(None, ge=0)
|
| 55 |
+
price_currency: str = Field(default="USD")
|
| 56 |
+
reported_terms: Optional[str] = None
|
| 57 |
+
pricing_mechanism: Optional[str] = None
|
| 58 |
+
deal_type: Optional[str] = None
|
| 59 |
+
|
| 60 |
+
# Dates
|
| 61 |
+
date: Optional[str] = None # YYYY-MM-DD or YYYY or YYYY-MM
|
| 62 |
+
start_date: Optional[datetime] = None
|
| 63 |
+
end_date: Optional[datetime] = None
|
| 64 |
+
duration_years: Optional[float] = Field(None, ge=0)
|
| 65 |
+
|
| 66 |
+
# Rights
|
| 67 |
+
exclusive: Optional[bool] = None
|
| 68 |
+
creators_compensated: Optional[bool] = None
|
| 69 |
+
creator_split_percentage: Optional[float] = Field(None, ge=0, le=100)
|
| 70 |
+
revenue_share: Optional[bool] = None
|
| 71 |
+
|
| 72 |
+
# Rights granted
|
| 73 |
+
training_allowed: Optional[bool] = None
|
| 74 |
+
finetuning_allowed: Optional[bool] = None
|
| 75 |
+
inference_allowed: Optional[bool] = None
|
| 76 |
+
redistribution_allowed: Optional[bool] = None
|
| 77 |
+
deletion_required: Optional[bool] = None
|
| 78 |
+
|
| 79 |
+
# Provenance
|
| 80 |
+
sources: List[str] = Field(default_factory=list)
|
| 81 |
+
source_primary: Optional[str] = None
|
| 82 |
+
notes: Optional[str] = None
|
| 83 |
+
deal_stage: str = Field(default="announced")
|
| 84 |
+
confidence_score: float = Field(default=0.5, ge=0, le=1)
|
| 85 |
+
|
| 86 |
+
@validator("modality")
|
| 87 |
+
def validate_modality(cls, v):
|
| 88 |
+
"""Normalize modality"""
|
| 89 |
+
if v in [m.value for m in Modality]:
|
| 90 |
+
return v
|
| 91 |
+
return "Text" # Default
|
| 92 |
+
|
| 93 |
+
@validator("deal_type")
|
| 94 |
+
def validate_deal_type(cls, v):
|
| 95 |
+
"""Normalize deal type"""
|
| 96 |
+
if not v:
|
| 97 |
+
return None
|
| 98 |
+
if v in [d.value for d in DealType]:
|
| 99 |
+
return v
|
| 100 |
+
return None
|
| 101 |
+
|
| 102 |
+
@validator("deal_stage")
|
| 103 |
+
def validate_deal_stage(cls, v):
|
| 104 |
+
"""Normalize deal stage"""
|
| 105 |
+
if v in [s.value for s in DealStage]:
|
| 106 |
+
return v
|
| 107 |
+
return "announced"
|
| 108 |
+
|
| 109 |
+
@validator("price_range_max_usd")
|
| 110 |
+
def validate_price_range(cls, v, values):
|
| 111 |
+
"""Ensure max >= min"""
|
| 112 |
+
if v and values.get("price_range_min_usd"):
|
| 113 |
+
if v < values["price_range_min_usd"]:
|
| 114 |
+
raise ValueError("price_range_max_usd must be >= price_range_min_usd")
|
| 115 |
+
return v
|
| 116 |
+
|
| 117 |
+
@validator("end_date")
|
| 118 |
+
def validate_end_date(cls, v, values):
|
| 119 |
+
"""Ensure end_date >= start_date"""
|
| 120 |
+
if v and values.get("start_date"):
|
| 121 |
+
if v < values["start_date"]:
|
| 122 |
+
raise ValueError("end_date must be >= start_date")
|
| 123 |
+
return v
|
| 124 |
+
|
| 125 |
+
class Config:
|
| 126 |
+
use_enum_values = True
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class DealValidator:
|
| 130 |
+
"""Validator for deal data"""
|
| 131 |
+
|
| 132 |
+
@staticmethod
|
| 133 |
+
def validate(deal_dict: dict) -> tuple[bool, Optional[DealData], Optional[str]]:
|
| 134 |
+
"""
|
| 135 |
+
Validate deal data
|
| 136 |
+
|
| 137 |
+
Returns:
|
| 138 |
+
(is_valid, DealData object, error_message)
|
| 139 |
+
"""
|
| 140 |
+
try:
|
| 141 |
+
deal_data = DealData(**deal_dict)
|
| 142 |
+
return True, deal_data, None
|
| 143 |
+
except Exception as e:
|
| 144 |
+
return False, None, str(e)
|
| 145 |
+
|
| 146 |
+
@staticmethod
|
| 147 |
+
def is_valid_deal(deal_dict: dict) -> bool:
|
| 148 |
+
"""Check if deal is valid"""
|
| 149 |
+
is_valid, _, _ = DealValidator.validate(deal_dict)
|
| 150 |
+
return is_valid
|
| 151 |
+
|
lib/prisma.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaClient } from '@prisma/client'
|
| 2 |
+
|
| 3 |
+
const globalForPrisma = globalThis as unknown as {
|
| 4 |
+
prisma: PrismaClient | undefined
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
export const prisma =
|
| 8 |
+
globalForPrisma.prisma ??
|
| 9 |
+
new PrismaClient({
|
| 10 |
+
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
| 11 |
+
})
|
| 12 |
+
|
| 13 |
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
| 14 |
+
|
next-env.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="next" />
|
| 2 |
+
/// <reference types="next/image-types/global" />
|
| 3 |
+
|
| 4 |
+
// NOTE: This file should not be edited
|
| 5 |
+
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
next.config.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('next').NextConfig} */
|
| 2 |
+
const nextConfig = {
|
| 3 |
+
reactStrictMode: true,
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
module.exports = nextConfig
|
| 7 |
+
|
package-lock.json
ADDED
|
@@ -0,0 +1,2246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "odl-training-deals",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "odl-training-deals",
|
| 9 |
+
"version": "0.1.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"@prisma/client": "^5.19.0",
|
| 12 |
+
"next": "14.2.5",
|
| 13 |
+
"react": "^18.3.1",
|
| 14 |
+
"react-dom": "^18.3.1"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@types/node": "^20.14.12",
|
| 18 |
+
"@types/react": "^18.3.3",
|
| 19 |
+
"@types/react-dom": "^18.3.0",
|
| 20 |
+
"autoprefixer": "^10.4.20",
|
| 21 |
+
"postcss": "^8.4.41",
|
| 22 |
+
"prisma": "^5.19.0",
|
| 23 |
+
"tailwindcss": "^3.4.7",
|
| 24 |
+
"tsx": "^4.16.2",
|
| 25 |
+
"typescript": "^5.5.4"
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
"node_modules/@alloc/quick-lru": {
|
| 29 |
+
"version": "5.2.0",
|
| 30 |
+
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
| 31 |
+
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
| 32 |
+
"dev": true,
|
| 33 |
+
"license": "MIT",
|
| 34 |
+
"engines": {
|
| 35 |
+
"node": ">=10"
|
| 36 |
+
},
|
| 37 |
+
"funding": {
|
| 38 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 39 |
+
}
|
| 40 |
+
},
|
| 41 |
+
"node_modules/@esbuild/aix-ppc64": {
|
| 42 |
+
"version": "0.25.12",
|
| 43 |
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
| 44 |
+
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
|
| 45 |
+
"cpu": [
|
| 46 |
+
"ppc64"
|
| 47 |
+
],
|
| 48 |
+
"dev": true,
|
| 49 |
+
"license": "MIT",
|
| 50 |
+
"optional": true,
|
| 51 |
+
"os": [
|
| 52 |
+
"aix"
|
| 53 |
+
],
|
| 54 |
+
"engines": {
|
| 55 |
+
"node": ">=18"
|
| 56 |
+
}
|
| 57 |
+
},
|
| 58 |
+
"node_modules/@esbuild/android-arm": {
|
| 59 |
+
"version": "0.25.12",
|
| 60 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
| 61 |
+
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
| 62 |
+
"cpu": [
|
| 63 |
+
"arm"
|
| 64 |
+
],
|
| 65 |
+
"dev": true,
|
| 66 |
+
"license": "MIT",
|
| 67 |
+
"optional": true,
|
| 68 |
+
"os": [
|
| 69 |
+
"android"
|
| 70 |
+
],
|
| 71 |
+
"engines": {
|
| 72 |
+
"node": ">=18"
|
| 73 |
+
}
|
| 74 |
+
},
|
| 75 |
+
"node_modules/@esbuild/android-arm64": {
|
| 76 |
+
"version": "0.25.12",
|
| 77 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
|
| 78 |
+
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
|
| 79 |
+
"cpu": [
|
| 80 |
+
"arm64"
|
| 81 |
+
],
|
| 82 |
+
"dev": true,
|
| 83 |
+
"license": "MIT",
|
| 84 |
+
"optional": true,
|
| 85 |
+
"os": [
|
| 86 |
+
"android"
|
| 87 |
+
],
|
| 88 |
+
"engines": {
|
| 89 |
+
"node": ">=18"
|
| 90 |
+
}
|
| 91 |
+
},
|
| 92 |
+
"node_modules/@esbuild/android-x64": {
|
| 93 |
+
"version": "0.25.12",
|
| 94 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
|
| 95 |
+
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
|
| 96 |
+
"cpu": [
|
| 97 |
+
"x64"
|
| 98 |
+
],
|
| 99 |
+
"dev": true,
|
| 100 |
+
"license": "MIT",
|
| 101 |
+
"optional": true,
|
| 102 |
+
"os": [
|
| 103 |
+
"android"
|
| 104 |
+
],
|
| 105 |
+
"engines": {
|
| 106 |
+
"node": ">=18"
|
| 107 |
+
}
|
| 108 |
+
},
|
| 109 |
+
"node_modules/@esbuild/darwin-arm64": {
|
| 110 |
+
"version": "0.25.12",
|
| 111 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
| 112 |
+
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
| 113 |
+
"cpu": [
|
| 114 |
+
"arm64"
|
| 115 |
+
],
|
| 116 |
+
"dev": true,
|
| 117 |
+
"license": "MIT",
|
| 118 |
+
"optional": true,
|
| 119 |
+
"os": [
|
| 120 |
+
"darwin"
|
| 121 |
+
],
|
| 122 |
+
"engines": {
|
| 123 |
+
"node": ">=18"
|
| 124 |
+
}
|
| 125 |
+
},
|
| 126 |
+
"node_modules/@esbuild/darwin-x64": {
|
| 127 |
+
"version": "0.25.12",
|
| 128 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
| 129 |
+
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
| 130 |
+
"cpu": [
|
| 131 |
+
"x64"
|
| 132 |
+
],
|
| 133 |
+
"dev": true,
|
| 134 |
+
"license": "MIT",
|
| 135 |
+
"optional": true,
|
| 136 |
+
"os": [
|
| 137 |
+
"darwin"
|
| 138 |
+
],
|
| 139 |
+
"engines": {
|
| 140 |
+
"node": ">=18"
|
| 141 |
+
}
|
| 142 |
+
},
|
| 143 |
+
"node_modules/@esbuild/freebsd-arm64": {
|
| 144 |
+
"version": "0.25.12",
|
| 145 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
| 146 |
+
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
| 147 |
+
"cpu": [
|
| 148 |
+
"arm64"
|
| 149 |
+
],
|
| 150 |
+
"dev": true,
|
| 151 |
+
"license": "MIT",
|
| 152 |
+
"optional": true,
|
| 153 |
+
"os": [
|
| 154 |
+
"freebsd"
|
| 155 |
+
],
|
| 156 |
+
"engines": {
|
| 157 |
+
"node": ">=18"
|
| 158 |
+
}
|
| 159 |
+
},
|
| 160 |
+
"node_modules/@esbuild/freebsd-x64": {
|
| 161 |
+
"version": "0.25.12",
|
| 162 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
| 163 |
+
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
| 164 |
+
"cpu": [
|
| 165 |
+
"x64"
|
| 166 |
+
],
|
| 167 |
+
"dev": true,
|
| 168 |
+
"license": "MIT",
|
| 169 |
+
"optional": true,
|
| 170 |
+
"os": [
|
| 171 |
+
"freebsd"
|
| 172 |
+
],
|
| 173 |
+
"engines": {
|
| 174 |
+
"node": ">=18"
|
| 175 |
+
}
|
| 176 |
+
},
|
| 177 |
+
"node_modules/@esbuild/linux-arm": {
|
| 178 |
+
"version": "0.25.12",
|
| 179 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
| 180 |
+
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
| 181 |
+
"cpu": [
|
| 182 |
+
"arm"
|
| 183 |
+
],
|
| 184 |
+
"dev": true,
|
| 185 |
+
"license": "MIT",
|
| 186 |
+
"optional": true,
|
| 187 |
+
"os": [
|
| 188 |
+
"linux"
|
| 189 |
+
],
|
| 190 |
+
"engines": {
|
| 191 |
+
"node": ">=18"
|
| 192 |
+
}
|
| 193 |
+
},
|
| 194 |
+
"node_modules/@esbuild/linux-arm64": {
|
| 195 |
+
"version": "0.25.12",
|
| 196 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
| 197 |
+
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
| 198 |
+
"cpu": [
|
| 199 |
+
"arm64"
|
| 200 |
+
],
|
| 201 |
+
"dev": true,
|
| 202 |
+
"license": "MIT",
|
| 203 |
+
"optional": true,
|
| 204 |
+
"os": [
|
| 205 |
+
"linux"
|
| 206 |
+
],
|
| 207 |
+
"engines": {
|
| 208 |
+
"node": ">=18"
|
| 209 |
+
}
|
| 210 |
+
},
|
| 211 |
+
"node_modules/@esbuild/linux-ia32": {
|
| 212 |
+
"version": "0.25.12",
|
| 213 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
|
| 214 |
+
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
|
| 215 |
+
"cpu": [
|
| 216 |
+
"ia32"
|
| 217 |
+
],
|
| 218 |
+
"dev": true,
|
| 219 |
+
"license": "MIT",
|
| 220 |
+
"optional": true,
|
| 221 |
+
"os": [
|
| 222 |
+
"linux"
|
| 223 |
+
],
|
| 224 |
+
"engines": {
|
| 225 |
+
"node": ">=18"
|
| 226 |
+
}
|
| 227 |
+
},
|
| 228 |
+
"node_modules/@esbuild/linux-loong64": {
|
| 229 |
+
"version": "0.25.12",
|
| 230 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
|
| 231 |
+
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
|
| 232 |
+
"cpu": [
|
| 233 |
+
"loong64"
|
| 234 |
+
],
|
| 235 |
+
"dev": true,
|
| 236 |
+
"license": "MIT",
|
| 237 |
+
"optional": true,
|
| 238 |
+
"os": [
|
| 239 |
+
"linux"
|
| 240 |
+
],
|
| 241 |
+
"engines": {
|
| 242 |
+
"node": ">=18"
|
| 243 |
+
}
|
| 244 |
+
},
|
| 245 |
+
"node_modules/@esbuild/linux-mips64el": {
|
| 246 |
+
"version": "0.25.12",
|
| 247 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
| 248 |
+
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
| 249 |
+
"cpu": [
|
| 250 |
+
"mips64el"
|
| 251 |
+
],
|
| 252 |
+
"dev": true,
|
| 253 |
+
"license": "MIT",
|
| 254 |
+
"optional": true,
|
| 255 |
+
"os": [
|
| 256 |
+
"linux"
|
| 257 |
+
],
|
| 258 |
+
"engines": {
|
| 259 |
+
"node": ">=18"
|
| 260 |
+
}
|
| 261 |
+
},
|
| 262 |
+
"node_modules/@esbuild/linux-ppc64": {
|
| 263 |
+
"version": "0.25.12",
|
| 264 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
|
| 265 |
+
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
|
| 266 |
+
"cpu": [
|
| 267 |
+
"ppc64"
|
| 268 |
+
],
|
| 269 |
+
"dev": true,
|
| 270 |
+
"license": "MIT",
|
| 271 |
+
"optional": true,
|
| 272 |
+
"os": [
|
| 273 |
+
"linux"
|
| 274 |
+
],
|
| 275 |
+
"engines": {
|
| 276 |
+
"node": ">=18"
|
| 277 |
+
}
|
| 278 |
+
},
|
| 279 |
+
"node_modules/@esbuild/linux-riscv64": {
|
| 280 |
+
"version": "0.25.12",
|
| 281 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
| 282 |
+
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
| 283 |
+
"cpu": [
|
| 284 |
+
"riscv64"
|
| 285 |
+
],
|
| 286 |
+
"dev": true,
|
| 287 |
+
"license": "MIT",
|
| 288 |
+
"optional": true,
|
| 289 |
+
"os": [
|
| 290 |
+
"linux"
|
| 291 |
+
],
|
| 292 |
+
"engines": {
|
| 293 |
+
"node": ">=18"
|
| 294 |
+
}
|
| 295 |
+
},
|
| 296 |
+
"node_modules/@esbuild/linux-s390x": {
|
| 297 |
+
"version": "0.25.12",
|
| 298 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
| 299 |
+
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
| 300 |
+
"cpu": [
|
| 301 |
+
"s390x"
|
| 302 |
+
],
|
| 303 |
+
"dev": true,
|
| 304 |
+
"license": "MIT",
|
| 305 |
+
"optional": true,
|
| 306 |
+
"os": [
|
| 307 |
+
"linux"
|
| 308 |
+
],
|
| 309 |
+
"engines": {
|
| 310 |
+
"node": ">=18"
|
| 311 |
+
}
|
| 312 |
+
},
|
| 313 |
+
"node_modules/@esbuild/linux-x64": {
|
| 314 |
+
"version": "0.25.12",
|
| 315 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
| 316 |
+
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
| 317 |
+
"cpu": [
|
| 318 |
+
"x64"
|
| 319 |
+
],
|
| 320 |
+
"dev": true,
|
| 321 |
+
"license": "MIT",
|
| 322 |
+
"optional": true,
|
| 323 |
+
"os": [
|
| 324 |
+
"linux"
|
| 325 |
+
],
|
| 326 |
+
"engines": {
|
| 327 |
+
"node": ">=18"
|
| 328 |
+
}
|
| 329 |
+
},
|
| 330 |
+
"node_modules/@esbuild/netbsd-arm64": {
|
| 331 |
+
"version": "0.25.12",
|
| 332 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
|
| 333 |
+
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
|
| 334 |
+
"cpu": [
|
| 335 |
+
"arm64"
|
| 336 |
+
],
|
| 337 |
+
"dev": true,
|
| 338 |
+
"license": "MIT",
|
| 339 |
+
"optional": true,
|
| 340 |
+
"os": [
|
| 341 |
+
"netbsd"
|
| 342 |
+
],
|
| 343 |
+
"engines": {
|
| 344 |
+
"node": ">=18"
|
| 345 |
+
}
|
| 346 |
+
},
|
| 347 |
+
"node_modules/@esbuild/netbsd-x64": {
|
| 348 |
+
"version": "0.25.12",
|
| 349 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
| 350 |
+
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
| 351 |
+
"cpu": [
|
| 352 |
+
"x64"
|
| 353 |
+
],
|
| 354 |
+
"dev": true,
|
| 355 |
+
"license": "MIT",
|
| 356 |
+
"optional": true,
|
| 357 |
+
"os": [
|
| 358 |
+
"netbsd"
|
| 359 |
+
],
|
| 360 |
+
"engines": {
|
| 361 |
+
"node": ">=18"
|
| 362 |
+
}
|
| 363 |
+
},
|
| 364 |
+
"node_modules/@esbuild/openbsd-arm64": {
|
| 365 |
+
"version": "0.25.12",
|
| 366 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
| 367 |
+
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
| 368 |
+
"cpu": [
|
| 369 |
+
"arm64"
|
| 370 |
+
],
|
| 371 |
+
"dev": true,
|
| 372 |
+
"license": "MIT",
|
| 373 |
+
"optional": true,
|
| 374 |
+
"os": [
|
| 375 |
+
"openbsd"
|
| 376 |
+
],
|
| 377 |
+
"engines": {
|
| 378 |
+
"node": ">=18"
|
| 379 |
+
}
|
| 380 |
+
},
|
| 381 |
+
"node_modules/@esbuild/openbsd-x64": {
|
| 382 |
+
"version": "0.25.12",
|
| 383 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
| 384 |
+
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
| 385 |
+
"cpu": [
|
| 386 |
+
"x64"
|
| 387 |
+
],
|
| 388 |
+
"dev": true,
|
| 389 |
+
"license": "MIT",
|
| 390 |
+
"optional": true,
|
| 391 |
+
"os": [
|
| 392 |
+
"openbsd"
|
| 393 |
+
],
|
| 394 |
+
"engines": {
|
| 395 |
+
"node": ">=18"
|
| 396 |
+
}
|
| 397 |
+
},
|
| 398 |
+
"node_modules/@esbuild/openharmony-arm64": {
|
| 399 |
+
"version": "0.25.12",
|
| 400 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
|
| 401 |
+
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
|
| 402 |
+
"cpu": [
|
| 403 |
+
"arm64"
|
| 404 |
+
],
|
| 405 |
+
"dev": true,
|
| 406 |
+
"license": "MIT",
|
| 407 |
+
"optional": true,
|
| 408 |
+
"os": [
|
| 409 |
+
"openharmony"
|
| 410 |
+
],
|
| 411 |
+
"engines": {
|
| 412 |
+
"node": ">=18"
|
| 413 |
+
}
|
| 414 |
+
},
|
| 415 |
+
"node_modules/@esbuild/sunos-x64": {
|
| 416 |
+
"version": "0.25.12",
|
| 417 |
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
| 418 |
+
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
| 419 |
+
"cpu": [
|
| 420 |
+
"x64"
|
| 421 |
+
],
|
| 422 |
+
"dev": true,
|
| 423 |
+
"license": "MIT",
|
| 424 |
+
"optional": true,
|
| 425 |
+
"os": [
|
| 426 |
+
"sunos"
|
| 427 |
+
],
|
| 428 |
+
"engines": {
|
| 429 |
+
"node": ">=18"
|
| 430 |
+
}
|
| 431 |
+
},
|
| 432 |
+
"node_modules/@esbuild/win32-arm64": {
|
| 433 |
+
"version": "0.25.12",
|
| 434 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
| 435 |
+
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
| 436 |
+
"cpu": [
|
| 437 |
+
"arm64"
|
| 438 |
+
],
|
| 439 |
+
"dev": true,
|
| 440 |
+
"license": "MIT",
|
| 441 |
+
"optional": true,
|
| 442 |
+
"os": [
|
| 443 |
+
"win32"
|
| 444 |
+
],
|
| 445 |
+
"engines": {
|
| 446 |
+
"node": ">=18"
|
| 447 |
+
}
|
| 448 |
+
},
|
| 449 |
+
"node_modules/@esbuild/win32-ia32": {
|
| 450 |
+
"version": "0.25.12",
|
| 451 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
| 452 |
+
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
| 453 |
+
"cpu": [
|
| 454 |
+
"ia32"
|
| 455 |
+
],
|
| 456 |
+
"dev": true,
|
| 457 |
+
"license": "MIT",
|
| 458 |
+
"optional": true,
|
| 459 |
+
"os": [
|
| 460 |
+
"win32"
|
| 461 |
+
],
|
| 462 |
+
"engines": {
|
| 463 |
+
"node": ">=18"
|
| 464 |
+
}
|
| 465 |
+
},
|
| 466 |
+
"node_modules/@esbuild/win32-x64": {
|
| 467 |
+
"version": "0.25.12",
|
| 468 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
| 469 |
+
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
|
| 470 |
+
"cpu": [
|
| 471 |
+
"x64"
|
| 472 |
+
],
|
| 473 |
+
"dev": true,
|
| 474 |
+
"license": "MIT",
|
| 475 |
+
"optional": true,
|
| 476 |
+
"os": [
|
| 477 |
+
"win32"
|
| 478 |
+
],
|
| 479 |
+
"engines": {
|
| 480 |
+
"node": ">=18"
|
| 481 |
+
}
|
| 482 |
+
},
|
| 483 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 484 |
+
"version": "0.3.13",
|
| 485 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 486 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 487 |
+
"dev": true,
|
| 488 |
+
"license": "MIT",
|
| 489 |
+
"dependencies": {
|
| 490 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 491 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 492 |
+
}
|
| 493 |
+
},
|
| 494 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 495 |
+
"version": "3.1.2",
|
| 496 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 497 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 498 |
+
"dev": true,
|
| 499 |
+
"license": "MIT",
|
| 500 |
+
"engines": {
|
| 501 |
+
"node": ">=6.0.0"
|
| 502 |
+
}
|
| 503 |
+
},
|
| 504 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 505 |
+
"version": "1.5.5",
|
| 506 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 507 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 508 |
+
"dev": true,
|
| 509 |
+
"license": "MIT"
|
| 510 |
+
},
|
| 511 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 512 |
+
"version": "0.3.31",
|
| 513 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 514 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 515 |
+
"dev": true,
|
| 516 |
+
"license": "MIT",
|
| 517 |
+
"dependencies": {
|
| 518 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 519 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 520 |
+
}
|
| 521 |
+
},
|
| 522 |
+
"node_modules/@next/env": {
|
| 523 |
+
"version": "14.2.5",
|
| 524 |
+
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz",
|
| 525 |
+
"integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==",
|
| 526 |
+
"license": "MIT"
|
| 527 |
+
},
|
| 528 |
+
"node_modules/@next/swc-darwin-arm64": {
|
| 529 |
+
"version": "14.2.5",
|
| 530 |
+
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz",
|
| 531 |
+
"integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==",
|
| 532 |
+
"cpu": [
|
| 533 |
+
"arm64"
|
| 534 |
+
],
|
| 535 |
+
"license": "MIT",
|
| 536 |
+
"optional": true,
|
| 537 |
+
"os": [
|
| 538 |
+
"darwin"
|
| 539 |
+
],
|
| 540 |
+
"engines": {
|
| 541 |
+
"node": ">= 10"
|
| 542 |
+
}
|
| 543 |
+
},
|
| 544 |
+
"node_modules/@next/swc-darwin-x64": {
|
| 545 |
+
"version": "14.2.5",
|
| 546 |
+
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz",
|
| 547 |
+
"integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==",
|
| 548 |
+
"cpu": [
|
| 549 |
+
"x64"
|
| 550 |
+
],
|
| 551 |
+
"license": "MIT",
|
| 552 |
+
"optional": true,
|
| 553 |
+
"os": [
|
| 554 |
+
"darwin"
|
| 555 |
+
],
|
| 556 |
+
"engines": {
|
| 557 |
+
"node": ">= 10"
|
| 558 |
+
}
|
| 559 |
+
},
|
| 560 |
+
"node_modules/@next/swc-linux-arm64-gnu": {
|
| 561 |
+
"version": "14.2.5",
|
| 562 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz",
|
| 563 |
+
"integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==",
|
| 564 |
+
"cpu": [
|
| 565 |
+
"arm64"
|
| 566 |
+
],
|
| 567 |
+
"license": "MIT",
|
| 568 |
+
"optional": true,
|
| 569 |
+
"os": [
|
| 570 |
+
"linux"
|
| 571 |
+
],
|
| 572 |
+
"engines": {
|
| 573 |
+
"node": ">= 10"
|
| 574 |
+
}
|
| 575 |
+
},
|
| 576 |
+
"node_modules/@next/swc-linux-arm64-musl": {
|
| 577 |
+
"version": "14.2.5",
|
| 578 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz",
|
| 579 |
+
"integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==",
|
| 580 |
+
"cpu": [
|
| 581 |
+
"arm64"
|
| 582 |
+
],
|
| 583 |
+
"license": "MIT",
|
| 584 |
+
"optional": true,
|
| 585 |
+
"os": [
|
| 586 |
+
"linux"
|
| 587 |
+
],
|
| 588 |
+
"engines": {
|
| 589 |
+
"node": ">= 10"
|
| 590 |
+
}
|
| 591 |
+
},
|
| 592 |
+
"node_modules/@next/swc-linux-x64-gnu": {
|
| 593 |
+
"version": "14.2.5",
|
| 594 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz",
|
| 595 |
+
"integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==",
|
| 596 |
+
"cpu": [
|
| 597 |
+
"x64"
|
| 598 |
+
],
|
| 599 |
+
"license": "MIT",
|
| 600 |
+
"optional": true,
|
| 601 |
+
"os": [
|
| 602 |
+
"linux"
|
| 603 |
+
],
|
| 604 |
+
"engines": {
|
| 605 |
+
"node": ">= 10"
|
| 606 |
+
}
|
| 607 |
+
},
|
| 608 |
+
"node_modules/@next/swc-linux-x64-musl": {
|
| 609 |
+
"version": "14.2.5",
|
| 610 |
+
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz",
|
| 611 |
+
"integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==",
|
| 612 |
+
"cpu": [
|
| 613 |
+
"x64"
|
| 614 |
+
],
|
| 615 |
+
"license": "MIT",
|
| 616 |
+
"optional": true,
|
| 617 |
+
"os": [
|
| 618 |
+
"linux"
|
| 619 |
+
],
|
| 620 |
+
"engines": {
|
| 621 |
+
"node": ">= 10"
|
| 622 |
+
}
|
| 623 |
+
},
|
| 624 |
+
"node_modules/@next/swc-win32-arm64-msvc": {
|
| 625 |
+
"version": "14.2.5",
|
| 626 |
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz",
|
| 627 |
+
"integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==",
|
| 628 |
+
"cpu": [
|
| 629 |
+
"arm64"
|
| 630 |
+
],
|
| 631 |
+
"license": "MIT",
|
| 632 |
+
"optional": true,
|
| 633 |
+
"os": [
|
| 634 |
+
"win32"
|
| 635 |
+
],
|
| 636 |
+
"engines": {
|
| 637 |
+
"node": ">= 10"
|
| 638 |
+
}
|
| 639 |
+
},
|
| 640 |
+
"node_modules/@next/swc-win32-ia32-msvc": {
|
| 641 |
+
"version": "14.2.5",
|
| 642 |
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz",
|
| 643 |
+
"integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==",
|
| 644 |
+
"cpu": [
|
| 645 |
+
"ia32"
|
| 646 |
+
],
|
| 647 |
+
"license": "MIT",
|
| 648 |
+
"optional": true,
|
| 649 |
+
"os": [
|
| 650 |
+
"win32"
|
| 651 |
+
],
|
| 652 |
+
"engines": {
|
| 653 |
+
"node": ">= 10"
|
| 654 |
+
}
|
| 655 |
+
},
|
| 656 |
+
"node_modules/@next/swc-win32-x64-msvc": {
|
| 657 |
+
"version": "14.2.5",
|
| 658 |
+
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz",
|
| 659 |
+
"integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==",
|
| 660 |
+
"cpu": [
|
| 661 |
+
"x64"
|
| 662 |
+
],
|
| 663 |
+
"license": "MIT",
|
| 664 |
+
"optional": true,
|
| 665 |
+
"os": [
|
| 666 |
+
"win32"
|
| 667 |
+
],
|
| 668 |
+
"engines": {
|
| 669 |
+
"node": ">= 10"
|
| 670 |
+
}
|
| 671 |
+
},
|
| 672 |
+
"node_modules/@nodelib/fs.scandir": {
|
| 673 |
+
"version": "2.1.5",
|
| 674 |
+
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
| 675 |
+
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
| 676 |
+
"dev": true,
|
| 677 |
+
"license": "MIT",
|
| 678 |
+
"dependencies": {
|
| 679 |
+
"@nodelib/fs.stat": "2.0.5",
|
| 680 |
+
"run-parallel": "^1.1.9"
|
| 681 |
+
},
|
| 682 |
+
"engines": {
|
| 683 |
+
"node": ">= 8"
|
| 684 |
+
}
|
| 685 |
+
},
|
| 686 |
+
"node_modules/@nodelib/fs.stat": {
|
| 687 |
+
"version": "2.0.5",
|
| 688 |
+
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
| 689 |
+
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
| 690 |
+
"dev": true,
|
| 691 |
+
"license": "MIT",
|
| 692 |
+
"engines": {
|
| 693 |
+
"node": ">= 8"
|
| 694 |
+
}
|
| 695 |
+
},
|
| 696 |
+
"node_modules/@nodelib/fs.walk": {
|
| 697 |
+
"version": "1.2.8",
|
| 698 |
+
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
| 699 |
+
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
| 700 |
+
"dev": true,
|
| 701 |
+
"license": "MIT",
|
| 702 |
+
"dependencies": {
|
| 703 |
+
"@nodelib/fs.scandir": "2.1.5",
|
| 704 |
+
"fastq": "^1.6.0"
|
| 705 |
+
},
|
| 706 |
+
"engines": {
|
| 707 |
+
"node": ">= 8"
|
| 708 |
+
}
|
| 709 |
+
},
|
| 710 |
+
"node_modules/@prisma/client": {
|
| 711 |
+
"version": "5.22.0",
|
| 712 |
+
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz",
|
| 713 |
+
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
|
| 714 |
+
"hasInstallScript": true,
|
| 715 |
+
"license": "Apache-2.0",
|
| 716 |
+
"engines": {
|
| 717 |
+
"node": ">=16.13"
|
| 718 |
+
},
|
| 719 |
+
"peerDependencies": {
|
| 720 |
+
"prisma": "*"
|
| 721 |
+
},
|
| 722 |
+
"peerDependenciesMeta": {
|
| 723 |
+
"prisma": {
|
| 724 |
+
"optional": true
|
| 725 |
+
}
|
| 726 |
+
}
|
| 727 |
+
},
|
| 728 |
+
"node_modules/@prisma/debug": {
|
| 729 |
+
"version": "5.22.0",
|
| 730 |
+
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
|
| 731 |
+
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
|
| 732 |
+
"devOptional": true,
|
| 733 |
+
"license": "Apache-2.0"
|
| 734 |
+
},
|
| 735 |
+
"node_modules/@prisma/engines": {
|
| 736 |
+
"version": "5.22.0",
|
| 737 |
+
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz",
|
| 738 |
+
"integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==",
|
| 739 |
+
"devOptional": true,
|
| 740 |
+
"hasInstallScript": true,
|
| 741 |
+
"license": "Apache-2.0",
|
| 742 |
+
"dependencies": {
|
| 743 |
+
"@prisma/debug": "5.22.0",
|
| 744 |
+
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
| 745 |
+
"@prisma/fetch-engine": "5.22.0",
|
| 746 |
+
"@prisma/get-platform": "5.22.0"
|
| 747 |
+
}
|
| 748 |
+
},
|
| 749 |
+
"node_modules/@prisma/engines-version": {
|
| 750 |
+
"version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
| 751 |
+
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz",
|
| 752 |
+
"integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==",
|
| 753 |
+
"devOptional": true,
|
| 754 |
+
"license": "Apache-2.0"
|
| 755 |
+
},
|
| 756 |
+
"node_modules/@prisma/fetch-engine": {
|
| 757 |
+
"version": "5.22.0",
|
| 758 |
+
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz",
|
| 759 |
+
"integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==",
|
| 760 |
+
"devOptional": true,
|
| 761 |
+
"license": "Apache-2.0",
|
| 762 |
+
"dependencies": {
|
| 763 |
+
"@prisma/debug": "5.22.0",
|
| 764 |
+
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
| 765 |
+
"@prisma/get-platform": "5.22.0"
|
| 766 |
+
}
|
| 767 |
+
},
|
| 768 |
+
"node_modules/@prisma/get-platform": {
|
| 769 |
+
"version": "5.22.0",
|
| 770 |
+
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz",
|
| 771 |
+
"integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==",
|
| 772 |
+
"devOptional": true,
|
| 773 |
+
"license": "Apache-2.0",
|
| 774 |
+
"dependencies": {
|
| 775 |
+
"@prisma/debug": "5.22.0"
|
| 776 |
+
}
|
| 777 |
+
},
|
| 778 |
+
"node_modules/@swc/counter": {
|
| 779 |
+
"version": "0.1.3",
|
| 780 |
+
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
| 781 |
+
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
| 782 |
+
"license": "Apache-2.0"
|
| 783 |
+
},
|
| 784 |
+
"node_modules/@swc/helpers": {
|
| 785 |
+
"version": "0.5.5",
|
| 786 |
+
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
|
| 787 |
+
"integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
|
| 788 |
+
"license": "Apache-2.0",
|
| 789 |
+
"dependencies": {
|
| 790 |
+
"@swc/counter": "^0.1.3",
|
| 791 |
+
"tslib": "^2.4.0"
|
| 792 |
+
}
|
| 793 |
+
},
|
| 794 |
+
"node_modules/@types/node": {
|
| 795 |
+
"version": "20.19.25",
|
| 796 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
|
| 797 |
+
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
|
| 798 |
+
"dev": true,
|
| 799 |
+
"license": "MIT",
|
| 800 |
+
"dependencies": {
|
| 801 |
+
"undici-types": "~6.21.0"
|
| 802 |
+
}
|
| 803 |
+
},
|
| 804 |
+
"node_modules/@types/prop-types": {
|
| 805 |
+
"version": "15.7.15",
|
| 806 |
+
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
| 807 |
+
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
| 808 |
+
"dev": true,
|
| 809 |
+
"license": "MIT"
|
| 810 |
+
},
|
| 811 |
+
"node_modules/@types/react": {
|
| 812 |
+
"version": "18.3.27",
|
| 813 |
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
| 814 |
+
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
| 815 |
+
"dev": true,
|
| 816 |
+
"license": "MIT",
|
| 817 |
+
"dependencies": {
|
| 818 |
+
"@types/prop-types": "*",
|
| 819 |
+
"csstype": "^3.2.2"
|
| 820 |
+
}
|
| 821 |
+
},
|
| 822 |
+
"node_modules/@types/react-dom": {
|
| 823 |
+
"version": "18.3.7",
|
| 824 |
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
| 825 |
+
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
| 826 |
+
"dev": true,
|
| 827 |
+
"license": "MIT",
|
| 828 |
+
"peerDependencies": {
|
| 829 |
+
"@types/react": "^18.0.0"
|
| 830 |
+
}
|
| 831 |
+
},
|
| 832 |
+
"node_modules/any-promise": {
|
| 833 |
+
"version": "1.3.0",
|
| 834 |
+
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
| 835 |
+
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
| 836 |
+
"dev": true,
|
| 837 |
+
"license": "MIT"
|
| 838 |
+
},
|
| 839 |
+
"node_modules/anymatch": {
|
| 840 |
+
"version": "3.1.3",
|
| 841 |
+
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
| 842 |
+
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
| 843 |
+
"dev": true,
|
| 844 |
+
"license": "ISC",
|
| 845 |
+
"dependencies": {
|
| 846 |
+
"normalize-path": "^3.0.0",
|
| 847 |
+
"picomatch": "^2.0.4"
|
| 848 |
+
},
|
| 849 |
+
"engines": {
|
| 850 |
+
"node": ">= 8"
|
| 851 |
+
}
|
| 852 |
+
},
|
| 853 |
+
"node_modules/arg": {
|
| 854 |
+
"version": "5.0.2",
|
| 855 |
+
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
| 856 |
+
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
| 857 |
+
"dev": true,
|
| 858 |
+
"license": "MIT"
|
| 859 |
+
},
|
| 860 |
+
"node_modules/autoprefixer": {
|
| 861 |
+
"version": "10.4.22",
|
| 862 |
+
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz",
|
| 863 |
+
"integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==",
|
| 864 |
+
"dev": true,
|
| 865 |
+
"funding": [
|
| 866 |
+
{
|
| 867 |
+
"type": "opencollective",
|
| 868 |
+
"url": "https://opencollective.com/postcss/"
|
| 869 |
+
},
|
| 870 |
+
{
|
| 871 |
+
"type": "tidelift",
|
| 872 |
+
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
| 873 |
+
},
|
| 874 |
+
{
|
| 875 |
+
"type": "github",
|
| 876 |
+
"url": "https://github.com/sponsors/ai"
|
| 877 |
+
}
|
| 878 |
+
],
|
| 879 |
+
"license": "MIT",
|
| 880 |
+
"dependencies": {
|
| 881 |
+
"browserslist": "^4.27.0",
|
| 882 |
+
"caniuse-lite": "^1.0.30001754",
|
| 883 |
+
"fraction.js": "^5.3.4",
|
| 884 |
+
"normalize-range": "^0.1.2",
|
| 885 |
+
"picocolors": "^1.1.1",
|
| 886 |
+
"postcss-value-parser": "^4.2.0"
|
| 887 |
+
},
|
| 888 |
+
"bin": {
|
| 889 |
+
"autoprefixer": "bin/autoprefixer"
|
| 890 |
+
},
|
| 891 |
+
"engines": {
|
| 892 |
+
"node": "^10 || ^12 || >=14"
|
| 893 |
+
},
|
| 894 |
+
"peerDependencies": {
|
| 895 |
+
"postcss": "^8.1.0"
|
| 896 |
+
}
|
| 897 |
+
},
|
| 898 |
+
"node_modules/baseline-browser-mapping": {
|
| 899 |
+
"version": "2.8.31",
|
| 900 |
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz",
|
| 901 |
+
"integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==",
|
| 902 |
+
"dev": true,
|
| 903 |
+
"license": "Apache-2.0",
|
| 904 |
+
"bin": {
|
| 905 |
+
"baseline-browser-mapping": "dist/cli.js"
|
| 906 |
+
}
|
| 907 |
+
},
|
| 908 |
+
"node_modules/binary-extensions": {
|
| 909 |
+
"version": "2.3.0",
|
| 910 |
+
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
| 911 |
+
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
| 912 |
+
"dev": true,
|
| 913 |
+
"license": "MIT",
|
| 914 |
+
"engines": {
|
| 915 |
+
"node": ">=8"
|
| 916 |
+
},
|
| 917 |
+
"funding": {
|
| 918 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 919 |
+
}
|
| 920 |
+
},
|
| 921 |
+
"node_modules/braces": {
|
| 922 |
+
"version": "3.0.3",
|
| 923 |
+
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
| 924 |
+
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
| 925 |
+
"dev": true,
|
| 926 |
+
"license": "MIT",
|
| 927 |
+
"dependencies": {
|
| 928 |
+
"fill-range": "^7.1.1"
|
| 929 |
+
},
|
| 930 |
+
"engines": {
|
| 931 |
+
"node": ">=8"
|
| 932 |
+
}
|
| 933 |
+
},
|
| 934 |
+
"node_modules/browserslist": {
|
| 935 |
+
"version": "4.28.0",
|
| 936 |
+
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
|
| 937 |
+
"integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
|
| 938 |
+
"dev": true,
|
| 939 |
+
"funding": [
|
| 940 |
+
{
|
| 941 |
+
"type": "opencollective",
|
| 942 |
+
"url": "https://opencollective.com/browserslist"
|
| 943 |
+
},
|
| 944 |
+
{
|
| 945 |
+
"type": "tidelift",
|
| 946 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 947 |
+
},
|
| 948 |
+
{
|
| 949 |
+
"type": "github",
|
| 950 |
+
"url": "https://github.com/sponsors/ai"
|
| 951 |
+
}
|
| 952 |
+
],
|
| 953 |
+
"license": "MIT",
|
| 954 |
+
"dependencies": {
|
| 955 |
+
"baseline-browser-mapping": "^2.8.25",
|
| 956 |
+
"caniuse-lite": "^1.0.30001754",
|
| 957 |
+
"electron-to-chromium": "^1.5.249",
|
| 958 |
+
"node-releases": "^2.0.27",
|
| 959 |
+
"update-browserslist-db": "^1.1.4"
|
| 960 |
+
},
|
| 961 |
+
"bin": {
|
| 962 |
+
"browserslist": "cli.js"
|
| 963 |
+
},
|
| 964 |
+
"engines": {
|
| 965 |
+
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 966 |
+
}
|
| 967 |
+
},
|
| 968 |
+
"node_modules/busboy": {
|
| 969 |
+
"version": "1.6.0",
|
| 970 |
+
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
| 971 |
+
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
| 972 |
+
"dependencies": {
|
| 973 |
+
"streamsearch": "^1.1.0"
|
| 974 |
+
},
|
| 975 |
+
"engines": {
|
| 976 |
+
"node": ">=10.16.0"
|
| 977 |
+
}
|
| 978 |
+
},
|
| 979 |
+
"node_modules/camelcase-css": {
|
| 980 |
+
"version": "2.0.1",
|
| 981 |
+
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
| 982 |
+
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
| 983 |
+
"dev": true,
|
| 984 |
+
"license": "MIT",
|
| 985 |
+
"engines": {
|
| 986 |
+
"node": ">= 6"
|
| 987 |
+
}
|
| 988 |
+
},
|
| 989 |
+
"node_modules/caniuse-lite": {
|
| 990 |
+
"version": "1.0.30001757",
|
| 991 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
|
| 992 |
+
"integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
|
| 993 |
+
"funding": [
|
| 994 |
+
{
|
| 995 |
+
"type": "opencollective",
|
| 996 |
+
"url": "https://opencollective.com/browserslist"
|
| 997 |
+
},
|
| 998 |
+
{
|
| 999 |
+
"type": "tidelift",
|
| 1000 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
| 1001 |
+
},
|
| 1002 |
+
{
|
| 1003 |
+
"type": "github",
|
| 1004 |
+
"url": "https://github.com/sponsors/ai"
|
| 1005 |
+
}
|
| 1006 |
+
],
|
| 1007 |
+
"license": "CC-BY-4.0"
|
| 1008 |
+
},
|
| 1009 |
+
"node_modules/chokidar": {
|
| 1010 |
+
"version": "3.6.0",
|
| 1011 |
+
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
| 1012 |
+
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
| 1013 |
+
"dev": true,
|
| 1014 |
+
"license": "MIT",
|
| 1015 |
+
"dependencies": {
|
| 1016 |
+
"anymatch": "~3.1.2",
|
| 1017 |
+
"braces": "~3.0.2",
|
| 1018 |
+
"glob-parent": "~5.1.2",
|
| 1019 |
+
"is-binary-path": "~2.1.0",
|
| 1020 |
+
"is-glob": "~4.0.1",
|
| 1021 |
+
"normalize-path": "~3.0.0",
|
| 1022 |
+
"readdirp": "~3.6.0"
|
| 1023 |
+
},
|
| 1024 |
+
"engines": {
|
| 1025 |
+
"node": ">= 8.10.0"
|
| 1026 |
+
},
|
| 1027 |
+
"funding": {
|
| 1028 |
+
"url": "https://paulmillr.com/funding/"
|
| 1029 |
+
},
|
| 1030 |
+
"optionalDependencies": {
|
| 1031 |
+
"fsevents": "~2.3.2"
|
| 1032 |
+
}
|
| 1033 |
+
},
|
| 1034 |
+
"node_modules/chokidar/node_modules/glob-parent": {
|
| 1035 |
+
"version": "5.1.2",
|
| 1036 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
| 1037 |
+
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
| 1038 |
+
"dev": true,
|
| 1039 |
+
"license": "ISC",
|
| 1040 |
+
"dependencies": {
|
| 1041 |
+
"is-glob": "^4.0.1"
|
| 1042 |
+
},
|
| 1043 |
+
"engines": {
|
| 1044 |
+
"node": ">= 6"
|
| 1045 |
+
}
|
| 1046 |
+
},
|
| 1047 |
+
"node_modules/client-only": {
|
| 1048 |
+
"version": "0.0.1",
|
| 1049 |
+
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
| 1050 |
+
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
| 1051 |
+
"license": "MIT"
|
| 1052 |
+
},
|
| 1053 |
+
"node_modules/commander": {
|
| 1054 |
+
"version": "4.1.1",
|
| 1055 |
+
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
| 1056 |
+
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
| 1057 |
+
"dev": true,
|
| 1058 |
+
"license": "MIT",
|
| 1059 |
+
"engines": {
|
| 1060 |
+
"node": ">= 6"
|
| 1061 |
+
}
|
| 1062 |
+
},
|
| 1063 |
+
"node_modules/cssesc": {
|
| 1064 |
+
"version": "3.0.0",
|
| 1065 |
+
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
| 1066 |
+
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
| 1067 |
+
"dev": true,
|
| 1068 |
+
"license": "MIT",
|
| 1069 |
+
"bin": {
|
| 1070 |
+
"cssesc": "bin/cssesc"
|
| 1071 |
+
},
|
| 1072 |
+
"engines": {
|
| 1073 |
+
"node": ">=4"
|
| 1074 |
+
}
|
| 1075 |
+
},
|
| 1076 |
+
"node_modules/csstype": {
|
| 1077 |
+
"version": "3.2.3",
|
| 1078 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1079 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1080 |
+
"dev": true,
|
| 1081 |
+
"license": "MIT"
|
| 1082 |
+
},
|
| 1083 |
+
"node_modules/didyoumean": {
|
| 1084 |
+
"version": "1.2.2",
|
| 1085 |
+
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
| 1086 |
+
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
| 1087 |
+
"dev": true,
|
| 1088 |
+
"license": "Apache-2.0"
|
| 1089 |
+
},
|
| 1090 |
+
"node_modules/dlv": {
|
| 1091 |
+
"version": "1.1.3",
|
| 1092 |
+
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
| 1093 |
+
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
| 1094 |
+
"dev": true,
|
| 1095 |
+
"license": "MIT"
|
| 1096 |
+
},
|
| 1097 |
+
"node_modules/electron-to-chromium": {
|
| 1098 |
+
"version": "1.5.262",
|
| 1099 |
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz",
|
| 1100 |
+
"integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==",
|
| 1101 |
+
"dev": true,
|
| 1102 |
+
"license": "ISC"
|
| 1103 |
+
},
|
| 1104 |
+
"node_modules/esbuild": {
|
| 1105 |
+
"version": "0.25.12",
|
| 1106 |
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
| 1107 |
+
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
| 1108 |
+
"dev": true,
|
| 1109 |
+
"hasInstallScript": true,
|
| 1110 |
+
"license": "MIT",
|
| 1111 |
+
"bin": {
|
| 1112 |
+
"esbuild": "bin/esbuild"
|
| 1113 |
+
},
|
| 1114 |
+
"engines": {
|
| 1115 |
+
"node": ">=18"
|
| 1116 |
+
},
|
| 1117 |
+
"optionalDependencies": {
|
| 1118 |
+
"@esbuild/aix-ppc64": "0.25.12",
|
| 1119 |
+
"@esbuild/android-arm": "0.25.12",
|
| 1120 |
+
"@esbuild/android-arm64": "0.25.12",
|
| 1121 |
+
"@esbuild/android-x64": "0.25.12",
|
| 1122 |
+
"@esbuild/darwin-arm64": "0.25.12",
|
| 1123 |
+
"@esbuild/darwin-x64": "0.25.12",
|
| 1124 |
+
"@esbuild/freebsd-arm64": "0.25.12",
|
| 1125 |
+
"@esbuild/freebsd-x64": "0.25.12",
|
| 1126 |
+
"@esbuild/linux-arm": "0.25.12",
|
| 1127 |
+
"@esbuild/linux-arm64": "0.25.12",
|
| 1128 |
+
"@esbuild/linux-ia32": "0.25.12",
|
| 1129 |
+
"@esbuild/linux-loong64": "0.25.12",
|
| 1130 |
+
"@esbuild/linux-mips64el": "0.25.12",
|
| 1131 |
+
"@esbuild/linux-ppc64": "0.25.12",
|
| 1132 |
+
"@esbuild/linux-riscv64": "0.25.12",
|
| 1133 |
+
"@esbuild/linux-s390x": "0.25.12",
|
| 1134 |
+
"@esbuild/linux-x64": "0.25.12",
|
| 1135 |
+
"@esbuild/netbsd-arm64": "0.25.12",
|
| 1136 |
+
"@esbuild/netbsd-x64": "0.25.12",
|
| 1137 |
+
"@esbuild/openbsd-arm64": "0.25.12",
|
| 1138 |
+
"@esbuild/openbsd-x64": "0.25.12",
|
| 1139 |
+
"@esbuild/openharmony-arm64": "0.25.12",
|
| 1140 |
+
"@esbuild/sunos-x64": "0.25.12",
|
| 1141 |
+
"@esbuild/win32-arm64": "0.25.12",
|
| 1142 |
+
"@esbuild/win32-ia32": "0.25.12",
|
| 1143 |
+
"@esbuild/win32-x64": "0.25.12"
|
| 1144 |
+
}
|
| 1145 |
+
},
|
| 1146 |
+
"node_modules/escalade": {
|
| 1147 |
+
"version": "3.2.0",
|
| 1148 |
+
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1149 |
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 1150 |
+
"dev": true,
|
| 1151 |
+
"license": "MIT",
|
| 1152 |
+
"engines": {
|
| 1153 |
+
"node": ">=6"
|
| 1154 |
+
}
|
| 1155 |
+
},
|
| 1156 |
+
"node_modules/fast-glob": {
|
| 1157 |
+
"version": "3.3.3",
|
| 1158 |
+
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
| 1159 |
+
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
| 1160 |
+
"dev": true,
|
| 1161 |
+
"license": "MIT",
|
| 1162 |
+
"dependencies": {
|
| 1163 |
+
"@nodelib/fs.stat": "^2.0.2",
|
| 1164 |
+
"@nodelib/fs.walk": "^1.2.3",
|
| 1165 |
+
"glob-parent": "^5.1.2",
|
| 1166 |
+
"merge2": "^1.3.0",
|
| 1167 |
+
"micromatch": "^4.0.8"
|
| 1168 |
+
},
|
| 1169 |
+
"engines": {
|
| 1170 |
+
"node": ">=8.6.0"
|
| 1171 |
+
}
|
| 1172 |
+
},
|
| 1173 |
+
"node_modules/fast-glob/node_modules/glob-parent": {
|
| 1174 |
+
"version": "5.1.2",
|
| 1175 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
| 1176 |
+
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
| 1177 |
+
"dev": true,
|
| 1178 |
+
"license": "ISC",
|
| 1179 |
+
"dependencies": {
|
| 1180 |
+
"is-glob": "^4.0.1"
|
| 1181 |
+
},
|
| 1182 |
+
"engines": {
|
| 1183 |
+
"node": ">= 6"
|
| 1184 |
+
}
|
| 1185 |
+
},
|
| 1186 |
+
"node_modules/fastq": {
|
| 1187 |
+
"version": "1.19.1",
|
| 1188 |
+
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
| 1189 |
+
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
| 1190 |
+
"dev": true,
|
| 1191 |
+
"license": "ISC",
|
| 1192 |
+
"dependencies": {
|
| 1193 |
+
"reusify": "^1.0.4"
|
| 1194 |
+
}
|
| 1195 |
+
},
|
| 1196 |
+
"node_modules/fill-range": {
|
| 1197 |
+
"version": "7.1.1",
|
| 1198 |
+
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
| 1199 |
+
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
| 1200 |
+
"dev": true,
|
| 1201 |
+
"license": "MIT",
|
| 1202 |
+
"dependencies": {
|
| 1203 |
+
"to-regex-range": "^5.0.1"
|
| 1204 |
+
},
|
| 1205 |
+
"engines": {
|
| 1206 |
+
"node": ">=8"
|
| 1207 |
+
}
|
| 1208 |
+
},
|
| 1209 |
+
"node_modules/fraction.js": {
|
| 1210 |
+
"version": "5.3.4",
|
| 1211 |
+
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
| 1212 |
+
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
|
| 1213 |
+
"dev": true,
|
| 1214 |
+
"license": "MIT",
|
| 1215 |
+
"engines": {
|
| 1216 |
+
"node": "*"
|
| 1217 |
+
},
|
| 1218 |
+
"funding": {
|
| 1219 |
+
"type": "github",
|
| 1220 |
+
"url": "https://github.com/sponsors/rawify"
|
| 1221 |
+
}
|
| 1222 |
+
},
|
| 1223 |
+
"node_modules/fsevents": {
|
| 1224 |
+
"version": "2.3.3",
|
| 1225 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1226 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1227 |
+
"dev": true,
|
| 1228 |
+
"hasInstallScript": true,
|
| 1229 |
+
"license": "MIT",
|
| 1230 |
+
"optional": true,
|
| 1231 |
+
"os": [
|
| 1232 |
+
"darwin"
|
| 1233 |
+
],
|
| 1234 |
+
"engines": {
|
| 1235 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1236 |
+
}
|
| 1237 |
+
},
|
| 1238 |
+
"node_modules/function-bind": {
|
| 1239 |
+
"version": "1.1.2",
|
| 1240 |
+
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 1241 |
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 1242 |
+
"dev": true,
|
| 1243 |
+
"license": "MIT",
|
| 1244 |
+
"funding": {
|
| 1245 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1246 |
+
}
|
| 1247 |
+
},
|
| 1248 |
+
"node_modules/get-tsconfig": {
|
| 1249 |
+
"version": "4.13.0",
|
| 1250 |
+
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
|
| 1251 |
+
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
|
| 1252 |
+
"dev": true,
|
| 1253 |
+
"license": "MIT",
|
| 1254 |
+
"dependencies": {
|
| 1255 |
+
"resolve-pkg-maps": "^1.0.0"
|
| 1256 |
+
},
|
| 1257 |
+
"funding": {
|
| 1258 |
+
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
| 1259 |
+
}
|
| 1260 |
+
},
|
| 1261 |
+
"node_modules/glob-parent": {
|
| 1262 |
+
"version": "6.0.2",
|
| 1263 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
| 1264 |
+
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
| 1265 |
+
"dev": true,
|
| 1266 |
+
"license": "ISC",
|
| 1267 |
+
"dependencies": {
|
| 1268 |
+
"is-glob": "^4.0.3"
|
| 1269 |
+
},
|
| 1270 |
+
"engines": {
|
| 1271 |
+
"node": ">=10.13.0"
|
| 1272 |
+
}
|
| 1273 |
+
},
|
| 1274 |
+
"node_modules/graceful-fs": {
|
| 1275 |
+
"version": "4.2.11",
|
| 1276 |
+
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
| 1277 |
+
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
| 1278 |
+
"license": "ISC"
|
| 1279 |
+
},
|
| 1280 |
+
"node_modules/hasown": {
|
| 1281 |
+
"version": "2.0.2",
|
| 1282 |
+
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
| 1283 |
+
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
| 1284 |
+
"dev": true,
|
| 1285 |
+
"license": "MIT",
|
| 1286 |
+
"dependencies": {
|
| 1287 |
+
"function-bind": "^1.1.2"
|
| 1288 |
+
},
|
| 1289 |
+
"engines": {
|
| 1290 |
+
"node": ">= 0.4"
|
| 1291 |
+
}
|
| 1292 |
+
},
|
| 1293 |
+
"node_modules/is-binary-path": {
|
| 1294 |
+
"version": "2.1.0",
|
| 1295 |
+
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
| 1296 |
+
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
| 1297 |
+
"dev": true,
|
| 1298 |
+
"license": "MIT",
|
| 1299 |
+
"dependencies": {
|
| 1300 |
+
"binary-extensions": "^2.0.0"
|
| 1301 |
+
},
|
| 1302 |
+
"engines": {
|
| 1303 |
+
"node": ">=8"
|
| 1304 |
+
}
|
| 1305 |
+
},
|
| 1306 |
+
"node_modules/is-core-module": {
|
| 1307 |
+
"version": "2.16.1",
|
| 1308 |
+
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
| 1309 |
+
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
| 1310 |
+
"dev": true,
|
| 1311 |
+
"license": "MIT",
|
| 1312 |
+
"dependencies": {
|
| 1313 |
+
"hasown": "^2.0.2"
|
| 1314 |
+
},
|
| 1315 |
+
"engines": {
|
| 1316 |
+
"node": ">= 0.4"
|
| 1317 |
+
},
|
| 1318 |
+
"funding": {
|
| 1319 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1320 |
+
}
|
| 1321 |
+
},
|
| 1322 |
+
"node_modules/is-extglob": {
|
| 1323 |
+
"version": "2.1.1",
|
| 1324 |
+
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
| 1325 |
+
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
| 1326 |
+
"dev": true,
|
| 1327 |
+
"license": "MIT",
|
| 1328 |
+
"engines": {
|
| 1329 |
+
"node": ">=0.10.0"
|
| 1330 |
+
}
|
| 1331 |
+
},
|
| 1332 |
+
"node_modules/is-glob": {
|
| 1333 |
+
"version": "4.0.3",
|
| 1334 |
+
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
| 1335 |
+
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
| 1336 |
+
"dev": true,
|
| 1337 |
+
"license": "MIT",
|
| 1338 |
+
"dependencies": {
|
| 1339 |
+
"is-extglob": "^2.1.1"
|
| 1340 |
+
},
|
| 1341 |
+
"engines": {
|
| 1342 |
+
"node": ">=0.10.0"
|
| 1343 |
+
}
|
| 1344 |
+
},
|
| 1345 |
+
"node_modules/is-number": {
|
| 1346 |
+
"version": "7.0.0",
|
| 1347 |
+
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
| 1348 |
+
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
| 1349 |
+
"dev": true,
|
| 1350 |
+
"license": "MIT",
|
| 1351 |
+
"engines": {
|
| 1352 |
+
"node": ">=0.12.0"
|
| 1353 |
+
}
|
| 1354 |
+
},
|
| 1355 |
+
"node_modules/jiti": {
|
| 1356 |
+
"version": "1.21.7",
|
| 1357 |
+
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
| 1358 |
+
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
| 1359 |
+
"dev": true,
|
| 1360 |
+
"license": "MIT",
|
| 1361 |
+
"bin": {
|
| 1362 |
+
"jiti": "bin/jiti.js"
|
| 1363 |
+
}
|
| 1364 |
+
},
|
| 1365 |
+
"node_modules/js-tokens": {
|
| 1366 |
+
"version": "4.0.0",
|
| 1367 |
+
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
| 1368 |
+
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
| 1369 |
+
"license": "MIT"
|
| 1370 |
+
},
|
| 1371 |
+
"node_modules/lilconfig": {
|
| 1372 |
+
"version": "3.1.3",
|
| 1373 |
+
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
| 1374 |
+
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
| 1375 |
+
"dev": true,
|
| 1376 |
+
"license": "MIT",
|
| 1377 |
+
"engines": {
|
| 1378 |
+
"node": ">=14"
|
| 1379 |
+
},
|
| 1380 |
+
"funding": {
|
| 1381 |
+
"url": "https://github.com/sponsors/antonk52"
|
| 1382 |
+
}
|
| 1383 |
+
},
|
| 1384 |
+
"node_modules/lines-and-columns": {
|
| 1385 |
+
"version": "1.2.4",
|
| 1386 |
+
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
| 1387 |
+
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
| 1388 |
+
"dev": true,
|
| 1389 |
+
"license": "MIT"
|
| 1390 |
+
},
|
| 1391 |
+
"node_modules/loose-envify": {
|
| 1392 |
+
"version": "1.4.0",
|
| 1393 |
+
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
| 1394 |
+
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
| 1395 |
+
"license": "MIT",
|
| 1396 |
+
"dependencies": {
|
| 1397 |
+
"js-tokens": "^3.0.0 || ^4.0.0"
|
| 1398 |
+
},
|
| 1399 |
+
"bin": {
|
| 1400 |
+
"loose-envify": "cli.js"
|
| 1401 |
+
}
|
| 1402 |
+
},
|
| 1403 |
+
"node_modules/merge2": {
|
| 1404 |
+
"version": "1.4.1",
|
| 1405 |
+
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
| 1406 |
+
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
| 1407 |
+
"dev": true,
|
| 1408 |
+
"license": "MIT",
|
| 1409 |
+
"engines": {
|
| 1410 |
+
"node": ">= 8"
|
| 1411 |
+
}
|
| 1412 |
+
},
|
| 1413 |
+
"node_modules/micromatch": {
|
| 1414 |
+
"version": "4.0.8",
|
| 1415 |
+
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
| 1416 |
+
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
| 1417 |
+
"dev": true,
|
| 1418 |
+
"license": "MIT",
|
| 1419 |
+
"dependencies": {
|
| 1420 |
+
"braces": "^3.0.3",
|
| 1421 |
+
"picomatch": "^2.3.1"
|
| 1422 |
+
},
|
| 1423 |
+
"engines": {
|
| 1424 |
+
"node": ">=8.6"
|
| 1425 |
+
}
|
| 1426 |
+
},
|
| 1427 |
+
"node_modules/mz": {
|
| 1428 |
+
"version": "2.7.0",
|
| 1429 |
+
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
| 1430 |
+
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
| 1431 |
+
"dev": true,
|
| 1432 |
+
"license": "MIT",
|
| 1433 |
+
"dependencies": {
|
| 1434 |
+
"any-promise": "^1.0.0",
|
| 1435 |
+
"object-assign": "^4.0.1",
|
| 1436 |
+
"thenify-all": "^1.0.0"
|
| 1437 |
+
}
|
| 1438 |
+
},
|
| 1439 |
+
"node_modules/nanoid": {
|
| 1440 |
+
"version": "3.3.11",
|
| 1441 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 1442 |
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
| 1443 |
+
"funding": [
|
| 1444 |
+
{
|
| 1445 |
+
"type": "github",
|
| 1446 |
+
"url": "https://github.com/sponsors/ai"
|
| 1447 |
+
}
|
| 1448 |
+
],
|
| 1449 |
+
"license": "MIT",
|
| 1450 |
+
"bin": {
|
| 1451 |
+
"nanoid": "bin/nanoid.cjs"
|
| 1452 |
+
},
|
| 1453 |
+
"engines": {
|
| 1454 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 1455 |
+
}
|
| 1456 |
+
},
|
| 1457 |
+
"node_modules/next": {
|
| 1458 |
+
"version": "14.2.5",
|
| 1459 |
+
"resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz",
|
| 1460 |
+
"integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==",
|
| 1461 |
+
"license": "MIT",
|
| 1462 |
+
"dependencies": {
|
| 1463 |
+
"@next/env": "14.2.5",
|
| 1464 |
+
"@swc/helpers": "0.5.5",
|
| 1465 |
+
"busboy": "1.6.0",
|
| 1466 |
+
"caniuse-lite": "^1.0.30001579",
|
| 1467 |
+
"graceful-fs": "^4.2.11",
|
| 1468 |
+
"postcss": "8.4.31",
|
| 1469 |
+
"styled-jsx": "5.1.1"
|
| 1470 |
+
},
|
| 1471 |
+
"bin": {
|
| 1472 |
+
"next": "dist/bin/next"
|
| 1473 |
+
},
|
| 1474 |
+
"engines": {
|
| 1475 |
+
"node": ">=18.17.0"
|
| 1476 |
+
},
|
| 1477 |
+
"optionalDependencies": {
|
| 1478 |
+
"@next/swc-darwin-arm64": "14.2.5",
|
| 1479 |
+
"@next/swc-darwin-x64": "14.2.5",
|
| 1480 |
+
"@next/swc-linux-arm64-gnu": "14.2.5",
|
| 1481 |
+
"@next/swc-linux-arm64-musl": "14.2.5",
|
| 1482 |
+
"@next/swc-linux-x64-gnu": "14.2.5",
|
| 1483 |
+
"@next/swc-linux-x64-musl": "14.2.5",
|
| 1484 |
+
"@next/swc-win32-arm64-msvc": "14.2.5",
|
| 1485 |
+
"@next/swc-win32-ia32-msvc": "14.2.5",
|
| 1486 |
+
"@next/swc-win32-x64-msvc": "14.2.5"
|
| 1487 |
+
},
|
| 1488 |
+
"peerDependencies": {
|
| 1489 |
+
"@opentelemetry/api": "^1.1.0",
|
| 1490 |
+
"@playwright/test": "^1.41.2",
|
| 1491 |
+
"react": "^18.2.0",
|
| 1492 |
+
"react-dom": "^18.2.0",
|
| 1493 |
+
"sass": "^1.3.0"
|
| 1494 |
+
},
|
| 1495 |
+
"peerDependenciesMeta": {
|
| 1496 |
+
"@opentelemetry/api": {
|
| 1497 |
+
"optional": true
|
| 1498 |
+
},
|
| 1499 |
+
"@playwright/test": {
|
| 1500 |
+
"optional": true
|
| 1501 |
+
},
|
| 1502 |
+
"sass": {
|
| 1503 |
+
"optional": true
|
| 1504 |
+
}
|
| 1505 |
+
}
|
| 1506 |
+
},
|
| 1507 |
+
"node_modules/next/node_modules/postcss": {
|
| 1508 |
+
"version": "8.4.31",
|
| 1509 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
| 1510 |
+
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
| 1511 |
+
"funding": [
|
| 1512 |
+
{
|
| 1513 |
+
"type": "opencollective",
|
| 1514 |
+
"url": "https://opencollective.com/postcss/"
|
| 1515 |
+
},
|
| 1516 |
+
{
|
| 1517 |
+
"type": "tidelift",
|
| 1518 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 1519 |
+
},
|
| 1520 |
+
{
|
| 1521 |
+
"type": "github",
|
| 1522 |
+
"url": "https://github.com/sponsors/ai"
|
| 1523 |
+
}
|
| 1524 |
+
],
|
| 1525 |
+
"license": "MIT",
|
| 1526 |
+
"dependencies": {
|
| 1527 |
+
"nanoid": "^3.3.6",
|
| 1528 |
+
"picocolors": "^1.0.0",
|
| 1529 |
+
"source-map-js": "^1.0.2"
|
| 1530 |
+
},
|
| 1531 |
+
"engines": {
|
| 1532 |
+
"node": "^10 || ^12 || >=14"
|
| 1533 |
+
}
|
| 1534 |
+
},
|
| 1535 |
+
"node_modules/node-releases": {
|
| 1536 |
+
"version": "2.0.27",
|
| 1537 |
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
| 1538 |
+
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
|
| 1539 |
+
"dev": true,
|
| 1540 |
+
"license": "MIT"
|
| 1541 |
+
},
|
| 1542 |
+
"node_modules/normalize-path": {
|
| 1543 |
+
"version": "3.0.0",
|
| 1544 |
+
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
| 1545 |
+
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
| 1546 |
+
"dev": true,
|
| 1547 |
+
"license": "MIT",
|
| 1548 |
+
"engines": {
|
| 1549 |
+
"node": ">=0.10.0"
|
| 1550 |
+
}
|
| 1551 |
+
},
|
| 1552 |
+
"node_modules/normalize-range": {
|
| 1553 |
+
"version": "0.1.2",
|
| 1554 |
+
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
|
| 1555 |
+
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
|
| 1556 |
+
"dev": true,
|
| 1557 |
+
"license": "MIT",
|
| 1558 |
+
"engines": {
|
| 1559 |
+
"node": ">=0.10.0"
|
| 1560 |
+
}
|
| 1561 |
+
},
|
| 1562 |
+
"node_modules/object-assign": {
|
| 1563 |
+
"version": "4.1.1",
|
| 1564 |
+
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
| 1565 |
+
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
| 1566 |
+
"dev": true,
|
| 1567 |
+
"license": "MIT",
|
| 1568 |
+
"engines": {
|
| 1569 |
+
"node": ">=0.10.0"
|
| 1570 |
+
}
|
| 1571 |
+
},
|
| 1572 |
+
"node_modules/object-hash": {
|
| 1573 |
+
"version": "3.0.0",
|
| 1574 |
+
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
| 1575 |
+
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
| 1576 |
+
"dev": true,
|
| 1577 |
+
"license": "MIT",
|
| 1578 |
+
"engines": {
|
| 1579 |
+
"node": ">= 6"
|
| 1580 |
+
}
|
| 1581 |
+
},
|
| 1582 |
+
"node_modules/path-parse": {
|
| 1583 |
+
"version": "1.0.7",
|
| 1584 |
+
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
| 1585 |
+
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
| 1586 |
+
"dev": true,
|
| 1587 |
+
"license": "MIT"
|
| 1588 |
+
},
|
| 1589 |
+
"node_modules/picocolors": {
|
| 1590 |
+
"version": "1.1.1",
|
| 1591 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 1592 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 1593 |
+
"license": "ISC"
|
| 1594 |
+
},
|
| 1595 |
+
"node_modules/picomatch": {
|
| 1596 |
+
"version": "2.3.1",
|
| 1597 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
| 1598 |
+
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
| 1599 |
+
"dev": true,
|
| 1600 |
+
"license": "MIT",
|
| 1601 |
+
"engines": {
|
| 1602 |
+
"node": ">=8.6"
|
| 1603 |
+
},
|
| 1604 |
+
"funding": {
|
| 1605 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 1606 |
+
}
|
| 1607 |
+
},
|
| 1608 |
+
"node_modules/pify": {
|
| 1609 |
+
"version": "2.3.0",
|
| 1610 |
+
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
| 1611 |
+
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
| 1612 |
+
"dev": true,
|
| 1613 |
+
"license": "MIT",
|
| 1614 |
+
"engines": {
|
| 1615 |
+
"node": ">=0.10.0"
|
| 1616 |
+
}
|
| 1617 |
+
},
|
| 1618 |
+
"node_modules/pirates": {
|
| 1619 |
+
"version": "4.0.7",
|
| 1620 |
+
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
| 1621 |
+
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
| 1622 |
+
"dev": true,
|
| 1623 |
+
"license": "MIT",
|
| 1624 |
+
"engines": {
|
| 1625 |
+
"node": ">= 6"
|
| 1626 |
+
}
|
| 1627 |
+
},
|
| 1628 |
+
"node_modules/postcss": {
|
| 1629 |
+
"version": "8.5.6",
|
| 1630 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
| 1631 |
+
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
| 1632 |
+
"dev": true,
|
| 1633 |
+
"funding": [
|
| 1634 |
+
{
|
| 1635 |
+
"type": "opencollective",
|
| 1636 |
+
"url": "https://opencollective.com/postcss/"
|
| 1637 |
+
},
|
| 1638 |
+
{
|
| 1639 |
+
"type": "tidelift",
|
| 1640 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 1641 |
+
},
|
| 1642 |
+
{
|
| 1643 |
+
"type": "github",
|
| 1644 |
+
"url": "https://github.com/sponsors/ai"
|
| 1645 |
+
}
|
| 1646 |
+
],
|
| 1647 |
+
"license": "MIT",
|
| 1648 |
+
"dependencies": {
|
| 1649 |
+
"nanoid": "^3.3.11",
|
| 1650 |
+
"picocolors": "^1.1.1",
|
| 1651 |
+
"source-map-js": "^1.2.1"
|
| 1652 |
+
},
|
| 1653 |
+
"engines": {
|
| 1654 |
+
"node": "^10 || ^12 || >=14"
|
| 1655 |
+
}
|
| 1656 |
+
},
|
| 1657 |
+
"node_modules/postcss-import": {
|
| 1658 |
+
"version": "15.1.0",
|
| 1659 |
+
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
| 1660 |
+
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
| 1661 |
+
"dev": true,
|
| 1662 |
+
"license": "MIT",
|
| 1663 |
+
"dependencies": {
|
| 1664 |
+
"postcss-value-parser": "^4.0.0",
|
| 1665 |
+
"read-cache": "^1.0.0",
|
| 1666 |
+
"resolve": "^1.1.7"
|
| 1667 |
+
},
|
| 1668 |
+
"engines": {
|
| 1669 |
+
"node": ">=14.0.0"
|
| 1670 |
+
},
|
| 1671 |
+
"peerDependencies": {
|
| 1672 |
+
"postcss": "^8.0.0"
|
| 1673 |
+
}
|
| 1674 |
+
},
|
| 1675 |
+
"node_modules/postcss-js": {
|
| 1676 |
+
"version": "4.1.0",
|
| 1677 |
+
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
|
| 1678 |
+
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
|
| 1679 |
+
"dev": true,
|
| 1680 |
+
"funding": [
|
| 1681 |
+
{
|
| 1682 |
+
"type": "opencollective",
|
| 1683 |
+
"url": "https://opencollective.com/postcss/"
|
| 1684 |
+
},
|
| 1685 |
+
{
|
| 1686 |
+
"type": "github",
|
| 1687 |
+
"url": "https://github.com/sponsors/ai"
|
| 1688 |
+
}
|
| 1689 |
+
],
|
| 1690 |
+
"license": "MIT",
|
| 1691 |
+
"dependencies": {
|
| 1692 |
+
"camelcase-css": "^2.0.1"
|
| 1693 |
+
},
|
| 1694 |
+
"engines": {
|
| 1695 |
+
"node": "^12 || ^14 || >= 16"
|
| 1696 |
+
},
|
| 1697 |
+
"peerDependencies": {
|
| 1698 |
+
"postcss": "^8.4.21"
|
| 1699 |
+
}
|
| 1700 |
+
},
|
| 1701 |
+
"node_modules/postcss-load-config": {
|
| 1702 |
+
"version": "6.0.1",
|
| 1703 |
+
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
|
| 1704 |
+
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
|
| 1705 |
+
"dev": true,
|
| 1706 |
+
"funding": [
|
| 1707 |
+
{
|
| 1708 |
+
"type": "opencollective",
|
| 1709 |
+
"url": "https://opencollective.com/postcss/"
|
| 1710 |
+
},
|
| 1711 |
+
{
|
| 1712 |
+
"type": "github",
|
| 1713 |
+
"url": "https://github.com/sponsors/ai"
|
| 1714 |
+
}
|
| 1715 |
+
],
|
| 1716 |
+
"license": "MIT",
|
| 1717 |
+
"dependencies": {
|
| 1718 |
+
"lilconfig": "^3.1.1"
|
| 1719 |
+
},
|
| 1720 |
+
"engines": {
|
| 1721 |
+
"node": ">= 18"
|
| 1722 |
+
},
|
| 1723 |
+
"peerDependencies": {
|
| 1724 |
+
"jiti": ">=1.21.0",
|
| 1725 |
+
"postcss": ">=8.0.9",
|
| 1726 |
+
"tsx": "^4.8.1",
|
| 1727 |
+
"yaml": "^2.4.2"
|
| 1728 |
+
},
|
| 1729 |
+
"peerDependenciesMeta": {
|
| 1730 |
+
"jiti": {
|
| 1731 |
+
"optional": true
|
| 1732 |
+
},
|
| 1733 |
+
"postcss": {
|
| 1734 |
+
"optional": true
|
| 1735 |
+
},
|
| 1736 |
+
"tsx": {
|
| 1737 |
+
"optional": true
|
| 1738 |
+
},
|
| 1739 |
+
"yaml": {
|
| 1740 |
+
"optional": true
|
| 1741 |
+
}
|
| 1742 |
+
}
|
| 1743 |
+
},
|
| 1744 |
+
"node_modules/postcss-nested": {
|
| 1745 |
+
"version": "6.2.0",
|
| 1746 |
+
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
| 1747 |
+
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
| 1748 |
+
"dev": true,
|
| 1749 |
+
"funding": [
|
| 1750 |
+
{
|
| 1751 |
+
"type": "opencollective",
|
| 1752 |
+
"url": "https://opencollective.com/postcss/"
|
| 1753 |
+
},
|
| 1754 |
+
{
|
| 1755 |
+
"type": "github",
|
| 1756 |
+
"url": "https://github.com/sponsors/ai"
|
| 1757 |
+
}
|
| 1758 |
+
],
|
| 1759 |
+
"license": "MIT",
|
| 1760 |
+
"dependencies": {
|
| 1761 |
+
"postcss-selector-parser": "^6.1.1"
|
| 1762 |
+
},
|
| 1763 |
+
"engines": {
|
| 1764 |
+
"node": ">=12.0"
|
| 1765 |
+
},
|
| 1766 |
+
"peerDependencies": {
|
| 1767 |
+
"postcss": "^8.2.14"
|
| 1768 |
+
}
|
| 1769 |
+
},
|
| 1770 |
+
"node_modules/postcss-selector-parser": {
|
| 1771 |
+
"version": "6.1.2",
|
| 1772 |
+
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
| 1773 |
+
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
| 1774 |
+
"dev": true,
|
| 1775 |
+
"license": "MIT",
|
| 1776 |
+
"dependencies": {
|
| 1777 |
+
"cssesc": "^3.0.0",
|
| 1778 |
+
"util-deprecate": "^1.0.2"
|
| 1779 |
+
},
|
| 1780 |
+
"engines": {
|
| 1781 |
+
"node": ">=4"
|
| 1782 |
+
}
|
| 1783 |
+
},
|
| 1784 |
+
"node_modules/postcss-value-parser": {
|
| 1785 |
+
"version": "4.2.0",
|
| 1786 |
+
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
| 1787 |
+
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
| 1788 |
+
"dev": true,
|
| 1789 |
+
"license": "MIT"
|
| 1790 |
+
},
|
| 1791 |
+
"node_modules/prisma": {
|
| 1792 |
+
"version": "5.22.0",
|
| 1793 |
+
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
|
| 1794 |
+
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
|
| 1795 |
+
"devOptional": true,
|
| 1796 |
+
"hasInstallScript": true,
|
| 1797 |
+
"license": "Apache-2.0",
|
| 1798 |
+
"dependencies": {
|
| 1799 |
+
"@prisma/engines": "5.22.0"
|
| 1800 |
+
},
|
| 1801 |
+
"bin": {
|
| 1802 |
+
"prisma": "build/index.js"
|
| 1803 |
+
},
|
| 1804 |
+
"engines": {
|
| 1805 |
+
"node": ">=16.13"
|
| 1806 |
+
},
|
| 1807 |
+
"optionalDependencies": {
|
| 1808 |
+
"fsevents": "2.3.3"
|
| 1809 |
+
}
|
| 1810 |
+
},
|
| 1811 |
+
"node_modules/queue-microtask": {
|
| 1812 |
+
"version": "1.2.3",
|
| 1813 |
+
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
| 1814 |
+
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
| 1815 |
+
"dev": true,
|
| 1816 |
+
"funding": [
|
| 1817 |
+
{
|
| 1818 |
+
"type": "github",
|
| 1819 |
+
"url": "https://github.com/sponsors/feross"
|
| 1820 |
+
},
|
| 1821 |
+
{
|
| 1822 |
+
"type": "patreon",
|
| 1823 |
+
"url": "https://www.patreon.com/feross"
|
| 1824 |
+
},
|
| 1825 |
+
{
|
| 1826 |
+
"type": "consulting",
|
| 1827 |
+
"url": "https://feross.org/support"
|
| 1828 |
+
}
|
| 1829 |
+
],
|
| 1830 |
+
"license": "MIT"
|
| 1831 |
+
},
|
| 1832 |
+
"node_modules/react": {
|
| 1833 |
+
"version": "18.3.1",
|
| 1834 |
+
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
| 1835 |
+
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
| 1836 |
+
"license": "MIT",
|
| 1837 |
+
"dependencies": {
|
| 1838 |
+
"loose-envify": "^1.1.0"
|
| 1839 |
+
},
|
| 1840 |
+
"engines": {
|
| 1841 |
+
"node": ">=0.10.0"
|
| 1842 |
+
}
|
| 1843 |
+
},
|
| 1844 |
+
"node_modules/react-dom": {
|
| 1845 |
+
"version": "18.3.1",
|
| 1846 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
| 1847 |
+
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
| 1848 |
+
"license": "MIT",
|
| 1849 |
+
"dependencies": {
|
| 1850 |
+
"loose-envify": "^1.1.0",
|
| 1851 |
+
"scheduler": "^0.23.2"
|
| 1852 |
+
},
|
| 1853 |
+
"peerDependencies": {
|
| 1854 |
+
"react": "^18.3.1"
|
| 1855 |
+
}
|
| 1856 |
+
},
|
| 1857 |
+
"node_modules/read-cache": {
|
| 1858 |
+
"version": "1.0.0",
|
| 1859 |
+
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
| 1860 |
+
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
| 1861 |
+
"dev": true,
|
| 1862 |
+
"license": "MIT",
|
| 1863 |
+
"dependencies": {
|
| 1864 |
+
"pify": "^2.3.0"
|
| 1865 |
+
}
|
| 1866 |
+
},
|
| 1867 |
+
"node_modules/readdirp": {
|
| 1868 |
+
"version": "3.6.0",
|
| 1869 |
+
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
| 1870 |
+
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
| 1871 |
+
"dev": true,
|
| 1872 |
+
"license": "MIT",
|
| 1873 |
+
"dependencies": {
|
| 1874 |
+
"picomatch": "^2.2.1"
|
| 1875 |
+
},
|
| 1876 |
+
"engines": {
|
| 1877 |
+
"node": ">=8.10.0"
|
| 1878 |
+
}
|
| 1879 |
+
},
|
| 1880 |
+
"node_modules/resolve": {
|
| 1881 |
+
"version": "1.22.11",
|
| 1882 |
+
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
| 1883 |
+
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
| 1884 |
+
"dev": true,
|
| 1885 |
+
"license": "MIT",
|
| 1886 |
+
"dependencies": {
|
| 1887 |
+
"is-core-module": "^2.16.1",
|
| 1888 |
+
"path-parse": "^1.0.7",
|
| 1889 |
+
"supports-preserve-symlinks-flag": "^1.0.0"
|
| 1890 |
+
},
|
| 1891 |
+
"bin": {
|
| 1892 |
+
"resolve": "bin/resolve"
|
| 1893 |
+
},
|
| 1894 |
+
"engines": {
|
| 1895 |
+
"node": ">= 0.4"
|
| 1896 |
+
},
|
| 1897 |
+
"funding": {
|
| 1898 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1899 |
+
}
|
| 1900 |
+
},
|
| 1901 |
+
"node_modules/resolve-pkg-maps": {
|
| 1902 |
+
"version": "1.0.0",
|
| 1903 |
+
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
| 1904 |
+
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
| 1905 |
+
"dev": true,
|
| 1906 |
+
"license": "MIT",
|
| 1907 |
+
"funding": {
|
| 1908 |
+
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
| 1909 |
+
}
|
| 1910 |
+
},
|
| 1911 |
+
"node_modules/reusify": {
|
| 1912 |
+
"version": "1.1.0",
|
| 1913 |
+
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
| 1914 |
+
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
| 1915 |
+
"dev": true,
|
| 1916 |
+
"license": "MIT",
|
| 1917 |
+
"engines": {
|
| 1918 |
+
"iojs": ">=1.0.0",
|
| 1919 |
+
"node": ">=0.10.0"
|
| 1920 |
+
}
|
| 1921 |
+
},
|
| 1922 |
+
"node_modules/run-parallel": {
|
| 1923 |
+
"version": "1.2.0",
|
| 1924 |
+
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
| 1925 |
+
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
| 1926 |
+
"dev": true,
|
| 1927 |
+
"funding": [
|
| 1928 |
+
{
|
| 1929 |
+
"type": "github",
|
| 1930 |
+
"url": "https://github.com/sponsors/feross"
|
| 1931 |
+
},
|
| 1932 |
+
{
|
| 1933 |
+
"type": "patreon",
|
| 1934 |
+
"url": "https://www.patreon.com/feross"
|
| 1935 |
+
},
|
| 1936 |
+
{
|
| 1937 |
+
"type": "consulting",
|
| 1938 |
+
"url": "https://feross.org/support"
|
| 1939 |
+
}
|
| 1940 |
+
],
|
| 1941 |
+
"license": "MIT",
|
| 1942 |
+
"dependencies": {
|
| 1943 |
+
"queue-microtask": "^1.2.2"
|
| 1944 |
+
}
|
| 1945 |
+
},
|
| 1946 |
+
"node_modules/scheduler": {
|
| 1947 |
+
"version": "0.23.2",
|
| 1948 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
| 1949 |
+
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
| 1950 |
+
"license": "MIT",
|
| 1951 |
+
"dependencies": {
|
| 1952 |
+
"loose-envify": "^1.1.0"
|
| 1953 |
+
}
|
| 1954 |
+
},
|
| 1955 |
+
"node_modules/source-map-js": {
|
| 1956 |
+
"version": "1.2.1",
|
| 1957 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 1958 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 1959 |
+
"license": "BSD-3-Clause",
|
| 1960 |
+
"engines": {
|
| 1961 |
+
"node": ">=0.10.0"
|
| 1962 |
+
}
|
| 1963 |
+
},
|
| 1964 |
+
"node_modules/streamsearch": {
|
| 1965 |
+
"version": "1.1.0",
|
| 1966 |
+
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
| 1967 |
+
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
| 1968 |
+
"engines": {
|
| 1969 |
+
"node": ">=10.0.0"
|
| 1970 |
+
}
|
| 1971 |
+
},
|
| 1972 |
+
"node_modules/styled-jsx": {
|
| 1973 |
+
"version": "5.1.1",
|
| 1974 |
+
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
|
| 1975 |
+
"integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
|
| 1976 |
+
"license": "MIT",
|
| 1977 |
+
"dependencies": {
|
| 1978 |
+
"client-only": "0.0.1"
|
| 1979 |
+
},
|
| 1980 |
+
"engines": {
|
| 1981 |
+
"node": ">= 12.0.0"
|
| 1982 |
+
},
|
| 1983 |
+
"peerDependencies": {
|
| 1984 |
+
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
|
| 1985 |
+
},
|
| 1986 |
+
"peerDependenciesMeta": {
|
| 1987 |
+
"@babel/core": {
|
| 1988 |
+
"optional": true
|
| 1989 |
+
},
|
| 1990 |
+
"babel-plugin-macros": {
|
| 1991 |
+
"optional": true
|
| 1992 |
+
}
|
| 1993 |
+
}
|
| 1994 |
+
},
|
| 1995 |
+
"node_modules/sucrase": {
|
| 1996 |
+
"version": "3.35.1",
|
| 1997 |
+
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
|
| 1998 |
+
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
|
| 1999 |
+
"dev": true,
|
| 2000 |
+
"license": "MIT",
|
| 2001 |
+
"dependencies": {
|
| 2002 |
+
"@jridgewell/gen-mapping": "^0.3.2",
|
| 2003 |
+
"commander": "^4.0.0",
|
| 2004 |
+
"lines-and-columns": "^1.1.6",
|
| 2005 |
+
"mz": "^2.7.0",
|
| 2006 |
+
"pirates": "^4.0.1",
|
| 2007 |
+
"tinyglobby": "^0.2.11",
|
| 2008 |
+
"ts-interface-checker": "^0.1.9"
|
| 2009 |
+
},
|
| 2010 |
+
"bin": {
|
| 2011 |
+
"sucrase": "bin/sucrase",
|
| 2012 |
+
"sucrase-node": "bin/sucrase-node"
|
| 2013 |
+
},
|
| 2014 |
+
"engines": {
|
| 2015 |
+
"node": ">=16 || 14 >=14.17"
|
| 2016 |
+
}
|
| 2017 |
+
},
|
| 2018 |
+
"node_modules/supports-preserve-symlinks-flag": {
|
| 2019 |
+
"version": "1.0.0",
|
| 2020 |
+
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
| 2021 |
+
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
| 2022 |
+
"dev": true,
|
| 2023 |
+
"license": "MIT",
|
| 2024 |
+
"engines": {
|
| 2025 |
+
"node": ">= 0.4"
|
| 2026 |
+
},
|
| 2027 |
+
"funding": {
|
| 2028 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 2029 |
+
}
|
| 2030 |
+
},
|
| 2031 |
+
"node_modules/tailwindcss": {
|
| 2032 |
+
"version": "3.4.18",
|
| 2033 |
+
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
|
| 2034 |
+
"integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
|
| 2035 |
+
"dev": true,
|
| 2036 |
+
"license": "MIT",
|
| 2037 |
+
"dependencies": {
|
| 2038 |
+
"@alloc/quick-lru": "^5.2.0",
|
| 2039 |
+
"arg": "^5.0.2",
|
| 2040 |
+
"chokidar": "^3.6.0",
|
| 2041 |
+
"didyoumean": "^1.2.2",
|
| 2042 |
+
"dlv": "^1.1.3",
|
| 2043 |
+
"fast-glob": "^3.3.2",
|
| 2044 |
+
"glob-parent": "^6.0.2",
|
| 2045 |
+
"is-glob": "^4.0.3",
|
| 2046 |
+
"jiti": "^1.21.7",
|
| 2047 |
+
"lilconfig": "^3.1.3",
|
| 2048 |
+
"micromatch": "^4.0.8",
|
| 2049 |
+
"normalize-path": "^3.0.0",
|
| 2050 |
+
"object-hash": "^3.0.0",
|
| 2051 |
+
"picocolors": "^1.1.1",
|
| 2052 |
+
"postcss": "^8.4.47",
|
| 2053 |
+
"postcss-import": "^15.1.0",
|
| 2054 |
+
"postcss-js": "^4.0.1",
|
| 2055 |
+
"postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
|
| 2056 |
+
"postcss-nested": "^6.2.0",
|
| 2057 |
+
"postcss-selector-parser": "^6.1.2",
|
| 2058 |
+
"resolve": "^1.22.8",
|
| 2059 |
+
"sucrase": "^3.35.0"
|
| 2060 |
+
},
|
| 2061 |
+
"bin": {
|
| 2062 |
+
"tailwind": "lib/cli.js",
|
| 2063 |
+
"tailwindcss": "lib/cli.js"
|
| 2064 |
+
},
|
| 2065 |
+
"engines": {
|
| 2066 |
+
"node": ">=14.0.0"
|
| 2067 |
+
}
|
| 2068 |
+
},
|
| 2069 |
+
"node_modules/thenify": {
|
| 2070 |
+
"version": "3.3.1",
|
| 2071 |
+
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
| 2072 |
+
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
| 2073 |
+
"dev": true,
|
| 2074 |
+
"license": "MIT",
|
| 2075 |
+
"dependencies": {
|
| 2076 |
+
"any-promise": "^1.0.0"
|
| 2077 |
+
}
|
| 2078 |
+
},
|
| 2079 |
+
"node_modules/thenify-all": {
|
| 2080 |
+
"version": "1.6.0",
|
| 2081 |
+
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
| 2082 |
+
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
| 2083 |
+
"dev": true,
|
| 2084 |
+
"license": "MIT",
|
| 2085 |
+
"dependencies": {
|
| 2086 |
+
"thenify": ">= 3.1.0 < 4"
|
| 2087 |
+
},
|
| 2088 |
+
"engines": {
|
| 2089 |
+
"node": ">=0.8"
|
| 2090 |
+
}
|
| 2091 |
+
},
|
| 2092 |
+
"node_modules/tinyglobby": {
|
| 2093 |
+
"version": "0.2.15",
|
| 2094 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
| 2095 |
+
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
| 2096 |
+
"dev": true,
|
| 2097 |
+
"license": "MIT",
|
| 2098 |
+
"dependencies": {
|
| 2099 |
+
"fdir": "^6.5.0",
|
| 2100 |
+
"picomatch": "^4.0.3"
|
| 2101 |
+
},
|
| 2102 |
+
"engines": {
|
| 2103 |
+
"node": ">=12.0.0"
|
| 2104 |
+
},
|
| 2105 |
+
"funding": {
|
| 2106 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 2107 |
+
}
|
| 2108 |
+
},
|
| 2109 |
+
"node_modules/tinyglobby/node_modules/fdir": {
|
| 2110 |
+
"version": "6.5.0",
|
| 2111 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 2112 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 2113 |
+
"dev": true,
|
| 2114 |
+
"license": "MIT",
|
| 2115 |
+
"engines": {
|
| 2116 |
+
"node": ">=12.0.0"
|
| 2117 |
+
},
|
| 2118 |
+
"peerDependencies": {
|
| 2119 |
+
"picomatch": "^3 || ^4"
|
| 2120 |
+
},
|
| 2121 |
+
"peerDependenciesMeta": {
|
| 2122 |
+
"picomatch": {
|
| 2123 |
+
"optional": true
|
| 2124 |
+
}
|
| 2125 |
+
}
|
| 2126 |
+
},
|
| 2127 |
+
"node_modules/tinyglobby/node_modules/picomatch": {
|
| 2128 |
+
"version": "4.0.3",
|
| 2129 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
| 2130 |
+
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
| 2131 |
+
"dev": true,
|
| 2132 |
+
"license": "MIT",
|
| 2133 |
+
"engines": {
|
| 2134 |
+
"node": ">=12"
|
| 2135 |
+
},
|
| 2136 |
+
"funding": {
|
| 2137 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 2138 |
+
}
|
| 2139 |
+
},
|
| 2140 |
+
"node_modules/to-regex-range": {
|
| 2141 |
+
"version": "5.0.1",
|
| 2142 |
+
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
| 2143 |
+
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
| 2144 |
+
"dev": true,
|
| 2145 |
+
"license": "MIT",
|
| 2146 |
+
"dependencies": {
|
| 2147 |
+
"is-number": "^7.0.0"
|
| 2148 |
+
},
|
| 2149 |
+
"engines": {
|
| 2150 |
+
"node": ">=8.0"
|
| 2151 |
+
}
|
| 2152 |
+
},
|
| 2153 |
+
"node_modules/ts-interface-checker": {
|
| 2154 |
+
"version": "0.1.13",
|
| 2155 |
+
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
| 2156 |
+
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
| 2157 |
+
"dev": true,
|
| 2158 |
+
"license": "Apache-2.0"
|
| 2159 |
+
},
|
| 2160 |
+
"node_modules/tslib": {
|
| 2161 |
+
"version": "2.8.1",
|
| 2162 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 2163 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 2164 |
+
"license": "0BSD"
|
| 2165 |
+
},
|
| 2166 |
+
"node_modules/tsx": {
|
| 2167 |
+
"version": "4.20.6",
|
| 2168 |
+
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz",
|
| 2169 |
+
"integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==",
|
| 2170 |
+
"dev": true,
|
| 2171 |
+
"license": "MIT",
|
| 2172 |
+
"dependencies": {
|
| 2173 |
+
"esbuild": "~0.25.0",
|
| 2174 |
+
"get-tsconfig": "^4.7.5"
|
| 2175 |
+
},
|
| 2176 |
+
"bin": {
|
| 2177 |
+
"tsx": "dist/cli.mjs"
|
| 2178 |
+
},
|
| 2179 |
+
"engines": {
|
| 2180 |
+
"node": ">=18.0.0"
|
| 2181 |
+
},
|
| 2182 |
+
"optionalDependencies": {
|
| 2183 |
+
"fsevents": "~2.3.3"
|
| 2184 |
+
}
|
| 2185 |
+
},
|
| 2186 |
+
"node_modules/typescript": {
|
| 2187 |
+
"version": "5.9.3",
|
| 2188 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
| 2189 |
+
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
| 2190 |
+
"dev": true,
|
| 2191 |
+
"license": "Apache-2.0",
|
| 2192 |
+
"bin": {
|
| 2193 |
+
"tsc": "bin/tsc",
|
| 2194 |
+
"tsserver": "bin/tsserver"
|
| 2195 |
+
},
|
| 2196 |
+
"engines": {
|
| 2197 |
+
"node": ">=14.17"
|
| 2198 |
+
}
|
| 2199 |
+
},
|
| 2200 |
+
"node_modules/undici-types": {
|
| 2201 |
+
"version": "6.21.0",
|
| 2202 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 2203 |
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
| 2204 |
+
"dev": true,
|
| 2205 |
+
"license": "MIT"
|
| 2206 |
+
},
|
| 2207 |
+
"node_modules/update-browserslist-db": {
|
| 2208 |
+
"version": "1.1.4",
|
| 2209 |
+
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
|
| 2210 |
+
"integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
|
| 2211 |
+
"dev": true,
|
| 2212 |
+
"funding": [
|
| 2213 |
+
{
|
| 2214 |
+
"type": "opencollective",
|
| 2215 |
+
"url": "https://opencollective.com/browserslist"
|
| 2216 |
+
},
|
| 2217 |
+
{
|
| 2218 |
+
"type": "tidelift",
|
| 2219 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 2220 |
+
},
|
| 2221 |
+
{
|
| 2222 |
+
"type": "github",
|
| 2223 |
+
"url": "https://github.com/sponsors/ai"
|
| 2224 |
+
}
|
| 2225 |
+
],
|
| 2226 |
+
"license": "MIT",
|
| 2227 |
+
"dependencies": {
|
| 2228 |
+
"escalade": "^3.2.0",
|
| 2229 |
+
"picocolors": "^1.1.1"
|
| 2230 |
+
},
|
| 2231 |
+
"bin": {
|
| 2232 |
+
"update-browserslist-db": "cli.js"
|
| 2233 |
+
},
|
| 2234 |
+
"peerDependencies": {
|
| 2235 |
+
"browserslist": ">= 4.21.0"
|
| 2236 |
+
}
|
| 2237 |
+
},
|
| 2238 |
+
"node_modules/util-deprecate": {
|
| 2239 |
+
"version": "1.0.2",
|
| 2240 |
+
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
| 2241 |
+
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
| 2242 |
+
"dev": true,
|
| 2243 |
+
"license": "MIT"
|
| 2244 |
+
}
|
| 2245 |
+
}
|
| 2246 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "odl-training-deals",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"scripts": {
|
| 6 |
+
"dev": "next dev",
|
| 7 |
+
"build": "prisma generate && next build",
|
| 8 |
+
"start": "next start",
|
| 9 |
+
"lint": "next lint",
|
| 10 |
+
"db:push": "prisma db push",
|
| 11 |
+
"db:seed": "tsx prisma/seed.ts",
|
| 12 |
+
"db:studio": "prisma studio",
|
| 13 |
+
"db:generate": "prisma generate"
|
| 14 |
+
},
|
| 15 |
+
"dependencies": {
|
| 16 |
+
"@prisma/client": "^5.19.0",
|
| 17 |
+
"next": "14.2.5",
|
| 18 |
+
"react": "^18.3.1",
|
| 19 |
+
"react-dom": "^18.3.1"
|
| 20 |
+
},
|
| 21 |
+
"devDependencies": {
|
| 22 |
+
"@types/node": "^20.14.12",
|
| 23 |
+
"@types/react": "^18.3.3",
|
| 24 |
+
"@types/react-dom": "^18.3.0",
|
| 25 |
+
"autoprefixer": "^10.4.20",
|
| 26 |
+
"postcss": "^8.4.41",
|
| 27 |
+
"prisma": "^5.19.0",
|
| 28 |
+
"tailwindcss": "^3.4.7",
|
| 29 |
+
"tsx": "^4.16.2",
|
| 30 |
+
"typescript": "^5.5.4"
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
|
postcss.config.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module.exports = {
|
| 2 |
+
plugins: {
|
| 3 |
+
tailwindcss: {},
|
| 4 |
+
autoprefixer: {},
|
| 5 |
+
},
|
| 6 |
+
}
|
| 7 |
+
|
prisma/schema.prisma
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// This is your Prisma schema file,
|
| 2 |
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
| 3 |
+
|
| 4 |
+
generator client {
|
| 5 |
+
provider = "prisma-client-js"
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
datasource db {
|
| 9 |
+
provider = "sqlite" // Start with SQLite for MVP, easy migration to Postgres later
|
| 10 |
+
url = env("DATABASE_URL")
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
// Core Deals table - matches your provided table structure
|
| 14 |
+
model Deal {
|
| 15 |
+
id String @id @default(uuid()) // UUID for new deals, custom IDs allowed in seed
|
| 16 |
+
date String? // "2024-05-22" or "2024" or "2021-2024"
|
| 17 |
+
period String? // For ranges like "2021-2024"
|
| 18 |
+
modality String // "Text", "Image", "Audio", "Video", "Satellite", etc.
|
| 19 |
+
provider String
|
| 20 |
+
buyer String // Can be comma-separated for multiple buyers
|
| 21 |
+
dataType String @map("data_type") // Short description
|
| 22 |
+
reportedTerms String? @map("reported_terms") // Headline terms
|
| 23 |
+
creatorsCompensated Boolean? @map("creators_compensated") // Yes/No/Unclear
|
| 24 |
+
exclusive Boolean? // Yes/No/N/A
|
| 25 |
+
pricingMechanism String @map("pricing_mechanism")
|
| 26 |
+
|
| 27 |
+
// Extended fields from your schema plan
|
| 28 |
+
dealType String? @map("deal_type") // aggregate, per-unit, commissioning, etc.
|
| 29 |
+
priceUsd Float? @map("price_usd")
|
| 30 |
+
priceRangeMinUsd Float? @map("price_range_min_usd")
|
| 31 |
+
priceRangeMaxUsd Float? @map("price_range_max_usd")
|
| 32 |
+
priceCurrency String? @map("price_currency") @default("USD")
|
| 33 |
+
durationYears Float? @map("duration_years")
|
| 34 |
+
startDate DateTime? @map("start_date")
|
| 35 |
+
endDate DateTime? @map("end_date")
|
| 36 |
+
|
| 37 |
+
// Rights & restrictions
|
| 38 |
+
trainingAllowed Boolean? @map("training_allowed")
|
| 39 |
+
finetuningAllowed Boolean? @map("finetuning_allowed")
|
| 40 |
+
inferenceAllowed Boolean? @map("inference_allowed")
|
| 41 |
+
redistributionAllowed Boolean? @map("redistribution_allowed")
|
| 42 |
+
deletionRequired Boolean? @map("deletion_required")
|
| 43 |
+
|
| 44 |
+
// Compensation details
|
| 45 |
+
creatorSplitPercentage Float? @map("creator_split_percentage")
|
| 46 |
+
revenueShare Boolean? @map("revenue_share")
|
| 47 |
+
|
| 48 |
+
// Provenance
|
| 49 |
+
sources String // JSON array of URLs
|
| 50 |
+
sourcePrimary String? @map("source_primary") // "Reuters", "SEC filing", etc.
|
| 51 |
+
notes String?
|
| 52 |
+
dealStage String? @map("deal_stage") @default("confirmed") // announced, rumored, confirmed, settled
|
| 53 |
+
confidenceScore Float? @map("confidence_score") @default(1.0) // 0-1
|
| 54 |
+
|
| 55 |
+
// Relations
|
| 56 |
+
providerRelation Provider? @relation(fields: [providerId], references: [id])
|
| 57 |
+
providerId String? @map("provider_id")
|
| 58 |
+
|
| 59 |
+
buyerRelations DealBuyer[]
|
| 60 |
+
|
| 61 |
+
// Pricing normalization
|
| 62 |
+
pricingNormalizations PricingNormalization[]
|
| 63 |
+
|
| 64 |
+
// Timestamps
|
| 65 |
+
createdAt DateTime @default(now()) @map("created_at")
|
| 66 |
+
updatedAt DateTime @updatedAt @map("updated_at")
|
| 67 |
+
|
| 68 |
+
@@index([provider])
|
| 69 |
+
@@index([buyer])
|
| 70 |
+
@@index([modality])
|
| 71 |
+
@@index([date])
|
| 72 |
+
@@index([dealStage])
|
| 73 |
+
@@map("deals")
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// Provider table (normalized)
|
| 77 |
+
model Provider {
|
| 78 |
+
id String @id @default(uuid())
|
| 79 |
+
name String @unique
|
| 80 |
+
industry String?
|
| 81 |
+
country String?
|
| 82 |
+
annualRevenue Float? @map("annual_revenue")
|
| 83 |
+
contentType String? @map("content_type")
|
| 84 |
+
ownershipStructure String? @map("ownership_structure") // publisher, creator-owned, platform, commons
|
| 85 |
+
|
| 86 |
+
deals Deal[]
|
| 87 |
+
|
| 88 |
+
createdAt DateTime @default(now()) @map("created_at")
|
| 89 |
+
updatedAt DateTime @updatedAt @map("updated_at")
|
| 90 |
+
|
| 91 |
+
@@map("providers")
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
// Buyer table (normalized)
|
| 95 |
+
model Buyer {
|
| 96 |
+
id String @id @default(uuid())
|
| 97 |
+
name String @unique
|
| 98 |
+
orgType String? @map("org_type") // lab, cloud, aggregator, startup, research
|
| 99 |
+
modelsAffected String? @map("models_affected") // JSON array
|
| 100 |
+
|
| 101 |
+
deals DealBuyer[]
|
| 102 |
+
|
| 103 |
+
createdAt DateTime @default(now()) @map("created_at")
|
| 104 |
+
updatedAt DateTime @updatedAt @map("updated_at")
|
| 105 |
+
|
| 106 |
+
@@map("buyers")
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
// Many-to-many relationship (deals can have multiple buyers)
|
| 110 |
+
model DealBuyer {
|
| 111 |
+
id String @id @default(uuid())
|
| 112 |
+
dealId String @map("deal_id")
|
| 113 |
+
buyerId String @map("buyer_id")
|
| 114 |
+
|
| 115 |
+
deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade)
|
| 116 |
+
buyer Buyer @relation(fields: [buyerId], references: [id], onDelete: Cascade)
|
| 117 |
+
|
| 118 |
+
@@unique([dealId, buyerId])
|
| 119 |
+
@@map("deal_buyers")
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
// Pricing normalization table
|
| 123 |
+
model PricingNormalization {
|
| 124 |
+
id String @id @default(uuid())
|
| 125 |
+
dealId String @map("deal_id")
|
| 126 |
+
unitType String @map("unit_type") // token, record, dataset, corpus, stream, minute, image, track
|
| 127 |
+
normalizedCostPerUnit Float @map("normalized_cost_per_unit")
|
| 128 |
+
normalizationMethod String @map("normalization_method") // manual, inferred, approximate
|
| 129 |
+
assumptions String? // JSON for assumptions used
|
| 130 |
+
|
| 131 |
+
deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade)
|
| 132 |
+
|
| 133 |
+
createdAt DateTime @default(now()) @map("created_at")
|
| 134 |
+
|
| 135 |
+
@@index([dealId])
|
| 136 |
+
@@map("pricing_normalizations")
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
// Source tracking for provenance
|
| 140 |
+
model DealSource {
|
| 141 |
+
id String @id @default(uuid())
|
| 142 |
+
dealId String @map("deal_id")
|
| 143 |
+
url String
|
| 144 |
+
sourceType String @map("source_type") // news, filing, press_release, rss
|
| 145 |
+
scrapedAt DateTime? @map("scraped_at")
|
| 146 |
+
htmlArchive String? @map("html_archive") // Path to stored HTML
|
| 147 |
+
extractedFields String? @map("extracted_fields") // JSON of what was extracted
|
| 148 |
+
|
| 149 |
+
createdAt DateTime @default(now()) @map("created_at")
|
| 150 |
+
|
| 151 |
+
@@index([dealId])
|
| 152 |
+
@@map("deal_sources")
|
| 153 |
+
}
|
| 154 |
+
|
prisma/seed.ts
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PrismaClient } from '@prisma/client'
|
| 2 |
+
|
| 3 |
+
const prisma = new PrismaClient()
|
| 4 |
+
|
| 5 |
+
// Seed data from your provided table (24 deals)
|
| 6 |
+
const dealsData = [
|
| 7 |
+
{
|
| 8 |
+
id: '1',
|
| 9 |
+
date: '2024-05-22',
|
| 10 |
+
modality: 'Text',
|
| 11 |
+
provider: 'News Corp',
|
| 12 |
+
buyer: 'OpenAI',
|
| 13 |
+
dataType: 'News archives & publisher content (WSJ, Times, NY Post)',
|
| 14 |
+
reportedTerms: '>$250M over 5 years',
|
| 15 |
+
creatorsCompensated: false,
|
| 16 |
+
exclusive: true,
|
| 17 |
+
pricingMechanism: 'Access / aggregate licensing',
|
| 18 |
+
dealType: 'aggregate',
|
| 19 |
+
priceUsd: 250000000,
|
| 20 |
+
durationYears: 5,
|
| 21 |
+
sourcePrimary: 'Reuters',
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
id: '2',
|
| 25 |
+
date: '2024-02-22',
|
| 26 |
+
modality: 'Text',
|
| 27 |
+
provider: 'Reddit',
|
| 28 |
+
buyer: 'Google',
|
| 29 |
+
dataType: 'Social-media UGC feed (API)',
|
| 30 |
+
reportedTerms: '≈$60M per year',
|
| 31 |
+
creatorsCompensated: false,
|
| 32 |
+
exclusive: false,
|
| 33 |
+
pricingMechanism: 'Volume-based API / access',
|
| 34 |
+
dealType: 'aggregate',
|
| 35 |
+
priceUsd: 60000000,
|
| 36 |
+
durationYears: 1,
|
| 37 |
+
sourcePrimary: 'Axios',
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
id: '3',
|
| 41 |
+
date: '2024-05',
|
| 42 |
+
modality: 'Text',
|
| 43 |
+
provider: 'Dotdash Meredith',
|
| 44 |
+
buyer: 'OpenAI',
|
| 45 |
+
dataType: 'Magazine & digital-media archives',
|
| 46 |
+
reportedTerms: '≥$16M per year (fixed component)',
|
| 47 |
+
creatorsCompensated: false,
|
| 48 |
+
exclusive: false,
|
| 49 |
+
pricingMechanism: 'Access / aggregate licensing',
|
| 50 |
+
dealType: 'aggregate',
|
| 51 |
+
priceUsd: 16000000,
|
| 52 |
+
durationYears: 1,
|
| 53 |
+
sourcePrimary: 'TechCrunch',
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
id: '4',
|
| 57 |
+
date: '2024-11',
|
| 58 |
+
modality: 'Text',
|
| 59 |
+
provider: 'HarperCollins',
|
| 60 |
+
buyer: 'Microsoft',
|
| 61 |
+
dataType: 'Non-fiction books (AI training rights)',
|
| 62 |
+
reportedTerms: '$5K per title; 50/50 publisher–author split',
|
| 63 |
+
creatorsCompensated: true,
|
| 64 |
+
exclusive: false,
|
| 65 |
+
pricingMechanism: 'Per-unit licensing (per book)',
|
| 66 |
+
dealType: 'per-unit',
|
| 67 |
+
creatorSplitPercentage: 50,
|
| 68 |
+
sourcePrimary: 'Reuters',
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
id: '5',
|
| 72 |
+
date: '2023',
|
| 73 |
+
modality: 'Text',
|
| 74 |
+
provider: 'Taylor & Francis',
|
| 75 |
+
buyer: 'Microsoft',
|
| 76 |
+
dataType: 'Academic journals & textbooks',
|
| 77 |
+
reportedTerms: '≈$10M',
|
| 78 |
+
creatorsCompensated: null, // Unclear
|
| 79 |
+
exclusive: false,
|
| 80 |
+
pricingMechanism: 'Restricted access licensing',
|
| 81 |
+
dealType: 'aggregate',
|
| 82 |
+
priceUsd: 10000000,
|
| 83 |
+
sourcePrimary: 'CB Insights',
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
id: '6',
|
| 87 |
+
date: '2024',
|
| 88 |
+
modality: 'Text',
|
| 89 |
+
provider: 'Wiley',
|
| 90 |
+
buyer: 'Anthropic, AWS, Perplexity',
|
| 91 |
+
dataType: 'Scientific content + enriched metadata',
|
| 92 |
+
reportedTerms: '$23M (per 2025 proxy filing)',
|
| 93 |
+
creatorsCompensated: false,
|
| 94 |
+
exclusive: false,
|
| 95 |
+
pricingMechanism: 'Limited-term structured license',
|
| 96 |
+
dealType: 'aggregate',
|
| 97 |
+
priceUsd: 23000000,
|
| 98 |
+
sourcePrimary: 'SEC Filing',
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
id: '7',
|
| 102 |
+
date: '2021-2024',
|
| 103 |
+
modality: 'Image / Video',
|
| 104 |
+
provider: 'Shutterstock',
|
| 105 |
+
buyer: 'Meta, OpenAI, Google, Apple',
|
| 106 |
+
dataType: 'Stock images & video + metadata',
|
| 107 |
+
reportedTerms: '≈$25–30M per deal (multi-year)',
|
| 108 |
+
creatorsCompensated: true, // Partial (royalty fund)
|
| 109 |
+
exclusive: false,
|
| 110 |
+
pricingMechanism: 'Hybrid per-unit + access',
|
| 111 |
+
dealType: 'hybrid',
|
| 112 |
+
priceRangeMinUsd: 25000000,
|
| 113 |
+
priceRangeMaxUsd: 30000000,
|
| 114 |
+
sourcePrimary: 'CB Insights',
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
id: '8',
|
| 118 |
+
date: '2024',
|
| 119 |
+
modality: 'Image',
|
| 120 |
+
provider: 'Freepik',
|
| 121 |
+
buyer: 'Unnamed AI firms',
|
| 122 |
+
dataType: '≈200M stock images',
|
| 123 |
+
reportedTerms: '≈$6M total (≈$0.02–0.04 per image)',
|
| 124 |
+
creatorsCompensated: false,
|
| 125 |
+
exclusive: false,
|
| 126 |
+
pricingMechanism: 'Per-unit micro-licensing',
|
| 127 |
+
dealType: 'per-unit',
|
| 128 |
+
priceUsd: 6000000,
|
| 129 |
+
sourcePrimary: 'TechCrunch',
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
id: '9',
|
| 133 |
+
date: '2020-2023',
|
| 134 |
+
modality: 'Image + Text',
|
| 135 |
+
provider: 'LAION / Common Crawl',
|
| 136 |
+
buyer: 'Open model builders',
|
| 137 |
+
dataType: 'Image–caption sets; large-scale web crawls',
|
| 138 |
+
reportedTerms: 'Open data; no direct cash',
|
| 139 |
+
creatorsCompensated: null, // N/A
|
| 140 |
+
exclusive: false,
|
| 141 |
+
pricingMechanism: 'Open commons / open-source',
|
| 142 |
+
dealType: 'commons',
|
| 143 |
+
priceUsd: 0,
|
| 144 |
+
sourcePrimary: 'Open Source',
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
id: '10',
|
| 148 |
+
date: '2025-05',
|
| 149 |
+
modality: 'Text',
|
| 150 |
+
provider: 'Le Monde',
|
| 151 |
+
buyer: 'OpenAI, Perplexity',
|
| 152 |
+
dataType: 'News content',
|
| 153 |
+
reportedTerms: 'Undisclosed; 25% AI revenue share to journalists',
|
| 154 |
+
creatorsCompensated: true,
|
| 155 |
+
exclusive: false,
|
| 156 |
+
pricingMechanism: 'Access licensing with rev-share',
|
| 157 |
+
dealType: 'aggregate',
|
| 158 |
+
revenueShare: true,
|
| 159 |
+
creatorSplitPercentage: 25,
|
| 160 |
+
sourcePrimary: 'Reuters',
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
id: '11',
|
| 164 |
+
date: '2025-07',
|
| 165 |
+
modality: 'Audio',
|
| 166 |
+
provider: 'SourceAudio',
|
| 167 |
+
buyer: 'ElevenLabs, Music.AI',
|
| 168 |
+
dataType: 'Pre-cleared music tracks for training',
|
| 169 |
+
reportedTerms: '$10M (multi-year)',
|
| 170 |
+
creatorsCompensated: true,
|
| 171 |
+
exclusive: false,
|
| 172 |
+
pricingMechanism: 'Access / catalog licensing',
|
| 173 |
+
dealType: 'aggregate',
|
| 174 |
+
priceUsd: 10000000,
|
| 175 |
+
sourcePrimary: 'MBW',
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
id: '12',
|
| 179 |
+
date: '2024',
|
| 180 |
+
modality: 'Audio',
|
| 181 |
+
provider: 'UMG, Warner',
|
| 182 |
+
buyer: 'AI music startups (e.g. Suno, Mubert)',
|
| 183 |
+
dataType: 'Major-label music catalogs (audio + lyrics)',
|
| 184 |
+
reportedTerms: 'Undisclosed',
|
| 185 |
+
creatorsCompensated: null, // Unclear
|
| 186 |
+
exclusive: false,
|
| 187 |
+
pricingMechanism: 'Music/audio access licensing',
|
| 188 |
+
dealType: 'aggregate',
|
| 189 |
+
sourcePrimary: 'MBW',
|
| 190 |
+
},
|
| 191 |
+
{
|
| 192 |
+
id: '13',
|
| 193 |
+
date: '2024',
|
| 194 |
+
modality: 'Audio',
|
| 195 |
+
provider: 'Audius + indie labels',
|
| 196 |
+
buyer: 'EU generative-music firms',
|
| 197 |
+
dataType: 'Independent tracks & stems',
|
| 198 |
+
reportedTerms: '~€0.30–€2.00 per track',
|
| 199 |
+
creatorsCompensated: true,
|
| 200 |
+
exclusive: false,
|
| 201 |
+
pricingMechanism: 'Per-unit micro-licensing',
|
| 202 |
+
dealType: 'per-unit',
|
| 203 |
+
priceRangeMinUsd: 0.30,
|
| 204 |
+
priceRangeMaxUsd: 2.00,
|
| 205 |
+
priceCurrency: 'EUR',
|
| 206 |
+
sourcePrimary: 'TechCrunch',
|
| 207 |
+
},
|
| 208 |
+
{
|
| 209 |
+
id: '14',
|
| 210 |
+
date: '2025-01',
|
| 211 |
+
modality: 'Video',
|
| 212 |
+
provider: 'YouTube creators (individuals)',
|
| 213 |
+
buyer: 'OpenAI, Meta',
|
| 214 |
+
dataType: 'Unpublished creator videos',
|
| 215 |
+
reportedTerms: '≈$5M total; ≈$1–4 per minute of footage',
|
| 216 |
+
creatorsCompensated: true,
|
| 217 |
+
exclusive: false,
|
| 218 |
+
pricingMechanism: 'Per-unit licensing (per video minute)',
|
| 219 |
+
dealType: 'per-unit',
|
| 220 |
+
priceUsd: 5000000,
|
| 221 |
+
priceRangeMinUsd: 1,
|
| 222 |
+
priceRangeMaxUsd: 4,
|
| 223 |
+
sourcePrimary: 'The Verge',
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
id: '15',
|
| 227 |
+
date: '2023-2024',
|
| 228 |
+
modality: 'Video',
|
| 229 |
+
provider: 'Independent creators',
|
| 230 |
+
buyer: 'Runway, Pika Labs',
|
| 231 |
+
dataType: 'Professional / unpublished video footage',
|
| 232 |
+
reportedTerms: '≈$1–4 per minute (estimated)',
|
| 233 |
+
creatorsCompensated: true,
|
| 234 |
+
exclusive: false,
|
| 235 |
+
pricingMechanism: 'Per-unit licensing',
|
| 236 |
+
dealType: 'per-unit',
|
| 237 |
+
priceRangeMinUsd: 1,
|
| 238 |
+
priceRangeMaxUsd: 4,
|
| 239 |
+
sourcePrimary: 'TechCrunch',
|
| 240 |
+
},
|
| 241 |
+
{
|
| 242 |
+
id: '16',
|
| 243 |
+
date: '2020-2024',
|
| 244 |
+
modality: 'Satellite',
|
| 245 |
+
provider: 'Planet Labs',
|
| 246 |
+
buyer: 'Agriculture / gov / AI firms',
|
| 247 |
+
dataType: 'High-frequency Earth observation imagery',
|
| 248 |
+
reportedTerms: '≈$180M annual revenue (licensing)',
|
| 249 |
+
creatorsCompensated: false,
|
| 250 |
+
exclusive: false,
|
| 251 |
+
pricingMechanism: 'Subscription-style access licensing',
|
| 252 |
+
dealType: 'aggregate',
|
| 253 |
+
priceUsd: 180000000,
|
| 254 |
+
durationYears: 1,
|
| 255 |
+
sourcePrimary: 'Company Filings',
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
id: '17',
|
| 259 |
+
date: '2024',
|
| 260 |
+
modality: 'Health / Biotech',
|
| 261 |
+
provider: 'Tempus',
|
| 262 |
+
buyer: 'Pharma & AI firms',
|
| 263 |
+
dataType: 'Anonymized patient + genomic data',
|
| 264 |
+
reportedTerms: '$200M over 3 years',
|
| 265 |
+
creatorsCompensated: false,
|
| 266 |
+
exclusive: false,
|
| 267 |
+
pricingMechanism: 'Access licensing for medical LLMs',
|
| 268 |
+
dealType: 'aggregate',
|
| 269 |
+
priceUsd: 200000000,
|
| 270 |
+
durationYears: 3,
|
| 271 |
+
sourcePrimary: 'SEC Filing',
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
id: '18',
|
| 275 |
+
date: '2025-06',
|
| 276 |
+
modality: 'Corporate / data infra',
|
| 277 |
+
provider: 'Scale AI',
|
| 278 |
+
buyer: 'Meta',
|
| 279 |
+
dataType: 'Data services + infrastructure (equity-linked)',
|
| 280 |
+
reportedTerms: '$14.3B for 49% stake (corporate deal)',
|
| 281 |
+
creatorsCompensated: null, // Variable
|
| 282 |
+
exclusive: true,
|
| 283 |
+
pricingMechanism: 'Strategic acquisition (data services)',
|
| 284 |
+
dealType: 'acquisition',
|
| 285 |
+
priceUsd: 14300000000,
|
| 286 |
+
sourcePrimary: 'Reuters',
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
id: '19',
|
| 290 |
+
date: '2025-04',
|
| 291 |
+
modality: 'Corporate / data infra',
|
| 292 |
+
provider: 'Informatica',
|
| 293 |
+
buyer: 'Salesforce',
|
| 294 |
+
dataType: 'Cloud data integration platform',
|
| 295 |
+
reportedTerms: '≈$8B acquisition',
|
| 296 |
+
creatorsCompensated: null, // N/A
|
| 297 |
+
exclusive: true,
|
| 298 |
+
pricingMechanism: 'Full acquisition (data pipeline assets)',
|
| 299 |
+
dealType: 'acquisition',
|
| 300 |
+
priceUsd: 8000000000,
|
| 301 |
+
sourcePrimary: 'Reuters',
|
| 302 |
+
},
|
| 303 |
+
{
|
| 304 |
+
id: '20',
|
| 305 |
+
date: '2025-09-05',
|
| 306 |
+
modality: 'Legal / Books',
|
| 307 |
+
provider: 'Authors & publishers (class action)',
|
| 308 |
+
buyer: 'Anthropic',
|
| 309 |
+
dataType: 'Books previously ingested without license',
|
| 310 |
+
reportedTerms: '$1.5B settlement; ≈$3K per book',
|
| 311 |
+
creatorsCompensated: true,
|
| 312 |
+
exclusive: null, // N/A
|
| 313 |
+
pricingMechanism: 'Legal settlement (with deletion & limits)',
|
| 314 |
+
dealType: 'settlement',
|
| 315 |
+
priceUsd: 1500000000,
|
| 316 |
+
deletionRequired: true,
|
| 317 |
+
sourcePrimary: 'Court Filing',
|
| 318 |
+
},
|
| 319 |
+
{
|
| 320 |
+
id: '21',
|
| 321 |
+
date: '2025-08',
|
| 322 |
+
modality: 'Text',
|
| 323 |
+
provider: 'CuriosityStream',
|
| 324 |
+
buyer: 'AI partners',
|
| 325 |
+
dataType: 'Factual & documentary video library (as text/video data)',
|
| 326 |
+
reportedTerms: '≈$20–30M per year; ≈25% of 2025 revenue',
|
| 327 |
+
creatorsCompensated: false,
|
| 328 |
+
exclusive: false,
|
| 329 |
+
pricingMechanism: 'Access licensing (subscription/API feed)',
|
| 330 |
+
dealType: 'aggregate',
|
| 331 |
+
priceRangeMinUsd: 20000000,
|
| 332 |
+
priceRangeMaxUsd: 30000000,
|
| 333 |
+
durationYears: 1,
|
| 334 |
+
sourcePrimary: 'Company Filings',
|
| 335 |
+
},
|
| 336 |
+
{
|
| 337 |
+
id: '22',
|
| 338 |
+
date: '2025',
|
| 339 |
+
modality: 'Text',
|
| 340 |
+
provider: 'New York Times',
|
| 341 |
+
buyer: 'Amazon',
|
| 342 |
+
dataType: 'Editorial news (incl. NYT Cooking, The Athletic)',
|
| 343 |
+
reportedTerms: '≈$20–25M per year',
|
| 344 |
+
creatorsCompensated: false,
|
| 345 |
+
exclusive: false,
|
| 346 |
+
pricingMechanism: 'Access licensing for Alexa / AI uses',
|
| 347 |
+
dealType: 'aggregate',
|
| 348 |
+
priceRangeMinUsd: 20000000,
|
| 349 |
+
priceRangeMaxUsd: 25000000,
|
| 350 |
+
durationYears: 1,
|
| 351 |
+
sourcePrimary: 'Axios',
|
| 352 |
+
},
|
| 353 |
+
{
|
| 354 |
+
id: '23',
|
| 355 |
+
date: '2025 H1',
|
| 356 |
+
modality: 'Text / Q&A',
|
| 357 |
+
provider: 'Chegg',
|
| 358 |
+
buyer: 'AI partners',
|
| 359 |
+
dataType: 'Expert-written Q&A pairs (homework help DB)',
|
| 360 |
+
reportedTerms: '$11M in H1 2025 (data licensing revenue)',
|
| 361 |
+
creatorsCompensated: null, // Unclear
|
| 362 |
+
exclusive: false,
|
| 363 |
+
pricingMechanism: 'Access licensing (subset of library)',
|
| 364 |
+
dealType: 'aggregate',
|
| 365 |
+
priceUsd: 11000000,
|
| 366 |
+
durationYears: 0.5,
|
| 367 |
+
sourcePrimary: 'SEC Filing',
|
| 368 |
+
},
|
| 369 |
+
{
|
| 370 |
+
id: '24',
|
| 371 |
+
date: '2022-2025',
|
| 372 |
+
modality: 'Commissioning',
|
| 373 |
+
provider: 'Mercor',
|
| 374 |
+
buyer: 'Multiple AI labs',
|
| 375 |
+
dataType: 'Domain-expert data for RLHF & fine-tuning',
|
| 376 |
+
reportedTerms: '≈$450M ARR (services tied to data creation)',
|
| 377 |
+
creatorsCompensated: true,
|
| 378 |
+
exclusive: false,
|
| 379 |
+
pricingMechanism: 'Commissioning / service-based',
|
| 380 |
+
dealType: 'commissioning',
|
| 381 |
+
priceUsd: 450000000,
|
| 382 |
+
durationYears: 1,
|
| 383 |
+
sourcePrimary: 'CB Insights',
|
| 384 |
+
},
|
| 385 |
+
]
|
| 386 |
+
|
| 387 |
+
async function main() {
|
| 388 |
+
console.log('🌱 Seeding database...')
|
| 389 |
+
|
| 390 |
+
// Create providers and buyers first
|
| 391 |
+
const providers = new Set<string>()
|
| 392 |
+
const buyers = new Set<string>()
|
| 393 |
+
|
| 394 |
+
dealsData.forEach(deal => {
|
| 395 |
+
providers.add(deal.provider)
|
| 396 |
+
deal.buyer.split(',').forEach(b => buyers.add(b.trim()))
|
| 397 |
+
})
|
| 398 |
+
|
| 399 |
+
// Create providers
|
| 400 |
+
for (const providerName of providers) {
|
| 401 |
+
await prisma.provider.upsert({
|
| 402 |
+
where: { name: providerName },
|
| 403 |
+
update: {},
|
| 404 |
+
create: { name: providerName },
|
| 405 |
+
})
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
// Create buyers
|
| 409 |
+
for (const buyerName of buyers) {
|
| 410 |
+
await prisma.buyer.upsert({
|
| 411 |
+
where: { name: buyerName },
|
| 412 |
+
update: {},
|
| 413 |
+
create: { name: buyerName },
|
| 414 |
+
})
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
// Create deals
|
| 418 |
+
for (const dealData of dealsData) {
|
| 419 |
+
const provider = await prisma.provider.findUnique({
|
| 420 |
+
where: { name: dealData.provider },
|
| 421 |
+
})
|
| 422 |
+
|
| 423 |
+
const buyerNames = dealData.buyer.split(',').map(b => b.trim())
|
| 424 |
+
|
| 425 |
+
const deal = await prisma.deal.upsert({
|
| 426 |
+
where: { id: dealData.id },
|
| 427 |
+
update: {},
|
| 428 |
+
create: {
|
| 429 |
+
id: dealData.id,
|
| 430 |
+
date: dealData.date,
|
| 431 |
+
modality: dealData.modality,
|
| 432 |
+
provider: dealData.provider,
|
| 433 |
+
buyer: dealData.buyer,
|
| 434 |
+
dataType: dealData.dataType,
|
| 435 |
+
reportedTerms: dealData.reportedTerms,
|
| 436 |
+
creatorsCompensated: dealData.creatorsCompensated,
|
| 437 |
+
exclusive: dealData.exclusive,
|
| 438 |
+
pricingMechanism: dealData.pricingMechanism,
|
| 439 |
+
dealType: dealData.dealType,
|
| 440 |
+
priceUsd: dealData.priceUsd,
|
| 441 |
+
priceRangeMinUsd: dealData.priceRangeMinUsd,
|
| 442 |
+
priceRangeMaxUsd: dealData.priceRangeMaxUsd,
|
| 443 |
+
priceCurrency: dealData.priceCurrency || 'USD',
|
| 444 |
+
durationYears: dealData.durationYears,
|
| 445 |
+
creatorSplitPercentage: dealData.creatorSplitPercentage,
|
| 446 |
+
revenueShare: dealData.revenueShare,
|
| 447 |
+
deletionRequired: dealData.deletionRequired,
|
| 448 |
+
sourcePrimary: dealData.sourcePrimary,
|
| 449 |
+
sources: JSON.stringify([]),
|
| 450 |
+
dealStage: 'confirmed',
|
| 451 |
+
confidenceScore: 1.0,
|
| 452 |
+
providerId: provider?.id,
|
| 453 |
+
},
|
| 454 |
+
})
|
| 455 |
+
|
| 456 |
+
// Link buyers
|
| 457 |
+
for (const buyerName of buyerNames) {
|
| 458 |
+
const buyer = await prisma.buyer.findUnique({
|
| 459 |
+
where: { name: buyerName },
|
| 460 |
+
})
|
| 461 |
+
if (buyer) {
|
| 462 |
+
await prisma.dealBuyer.upsert({
|
| 463 |
+
where: {
|
| 464 |
+
dealId_buyerId: {
|
| 465 |
+
dealId: deal.id,
|
| 466 |
+
buyerId: buyer.id,
|
| 467 |
+
},
|
| 468 |
+
},
|
| 469 |
+
update: {},
|
| 470 |
+
create: {
|
| 471 |
+
dealId: deal.id,
|
| 472 |
+
buyerId: buyer.id,
|
| 473 |
+
},
|
| 474 |
+
})
|
| 475 |
+
}
|
| 476 |
+
}
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
console.log(`✅ Seeded ${dealsData.length} deals`)
|
| 480 |
+
console.log(`✅ Seeded ${providers.size} providers`)
|
| 481 |
+
console.log(`✅ Seeded ${buyers.size} buyers`)
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
main()
|
| 485 |
+
.catch((e) => {
|
| 486 |
+
console.error(e)
|
| 487 |
+
process.exit(1)
|
| 488 |
+
})
|
| 489 |
+
.finally(async () => {
|
| 490 |
+
await prisma.$disconnect()
|
| 491 |
+
})
|
| 492 |
+
|
tailwind.config.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Config } from 'tailwindcss'
|
| 2 |
+
|
| 3 |
+
const config: Config = {
|
| 4 |
+
content: [
|
| 5 |
+
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
| 6 |
+
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
| 7 |
+
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
| 8 |
+
],
|
| 9 |
+
theme: {
|
| 10 |
+
extend: {
|
| 11 |
+
colors: {
|
| 12 |
+
base: 'var(--color-base)',
|
| 13 |
+
text: 'var(--color-text)',
|
| 14 |
+
'text-muted': 'var(--color-text-muted)',
|
| 15 |
+
accent: 'var(--color-accent)',
|
| 16 |
+
'accent-hover': 'var(--color-accent-hover)',
|
| 17 |
+
background: 'var(--color-background)',
|
| 18 |
+
surface: 'var(--color-surface)',
|
| 19 |
+
border: 'var(--color-border)',
|
| 20 |
+
'border-subtle': 'var(--color-border-subtle)',
|
| 21 |
+
},
|
| 22 |
+
fontFamily: {
|
| 23 |
+
sans: ['var(--font-sans)'],
|
| 24 |
+
serif: ['var(--font-serif)'],
|
| 25 |
+
mono: ['var(--font-mono)'],
|
| 26 |
+
},
|
| 27 |
+
maxWidth: {
|
| 28 |
+
content: 'var(--max-width-content)',
|
| 29 |
+
narrow: 'var(--max-width-narrow)',
|
| 30 |
+
},
|
| 31 |
+
},
|
| 32 |
+
},
|
| 33 |
+
plugins: [],
|
| 34 |
+
}
|
| 35 |
+
export default config
|
| 36 |
+
|
tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2020",
|
| 4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
| 5 |
+
"allowJs": true,
|
| 6 |
+
"skipLibCheck": true,
|
| 7 |
+
"strict": true,
|
| 8 |
+
"noEmit": true,
|
| 9 |
+
"esModuleInterop": true,
|
| 10 |
+
"module": "esnext",
|
| 11 |
+
"moduleResolution": "bundler",
|
| 12 |
+
"resolveJsonModule": true,
|
| 13 |
+
"isolatedModules": true,
|
| 14 |
+
"jsx": "preserve",
|
| 15 |
+
"incremental": true,
|
| 16 |
+
"plugins": [
|
| 17 |
+
{
|
| 18 |
+
"name": "next"
|
| 19 |
+
}
|
| 20 |
+
],
|
| 21 |
+
"paths": {
|
| 22 |
+
"@/*": ["./*"]
|
| 23 |
+
}
|
| 24 |
+
},
|
| 25 |
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
| 26 |
+
"exclude": ["node_modules"]
|
| 27 |
+
}
|
| 28 |
+
|
vercel.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"buildCommand": "npm run build",
|
| 3 |
+
"devCommand": "npm run dev",
|
| 4 |
+
"installCommand": "npm install",
|
| 5 |
+
"framework": "nextjs",
|
| 6 |
+
"regions": ["iad1"],
|
| 7 |
+
"env": {
|
| 8 |
+
"DATABASE_URL": "@database_url"
|
| 9 |
+
}
|
| 10 |
+
}
|
| 11 |
+
|