Index Platform is a shared Next.js/TypeScript platform for Ukrainian commodity market indices. The codebase is intentionally organized as one index engine with tenant-specific brands, content, styling, commodities, respondents, integrations and deployment settings.
The platform now also includes the 1d3x corporate landing site. 1d3x is the umbrella brand for local commodity index launches built with institutional partners and market leaders.
The current live products are:
| Tenant | Public Product | Domain | Runtime Status |
|---|---|---|---|
1d3x |
1d3x | 1d3x.com | Corporate landing site and partnership entry point |
uga-ua |
UGA Index | index.uga.ua | Production-style deployment |
spike-ua |
SPIKE SPOT INDEX | spike.1d3x.com | Production-style deployment, active development |
This is not a mixed "UGA plus Spike" app. It is a multi-brand index platform where UGA and Spike are two clients running on the same calculation, publication, respondent and analytics foundation.
UGA Index is also available on the 1d3x mirror domain
uga.1d3x.com. This mirror stays active and must not
redirect to the UGA-owned domain. Legacy cr0pto.com domains are configured as
permanent redirects:
- index-uga.cr0pto.com redirects to index.uga.ua once the UGA DNS cutover is complete;
- spike-ua.cr0pto.com redirects to spike.1d3x.com.
Shared capabilities:
- English 1d3x landing page with live UGA and Spike embeds;
- Resend-backed 1d3x partnership contact form;
- bilingual public sites with
/ukand/enroutes; - tenant-specific logo, favicon, colors, copy, methodology documents and legal pages;
- public index cards with currency switching;
- NBU FX-based conversion for USD, UAH and EUR display;
- DB-backed respondent directory, submissions, calculations and published values;
- admin daily input matrix, respondent management and publication workflow;
- respondent cabinet and secure survey links;
- embed routes for partner websites;
- public JSON APIs for latest values, history and FX rates;
- analytics pages with historical values, spreads, movers, volatility and scenario views;
- Vercel cron endpoints for scheduled imports, notifications and publication.
UGA-specific features:
- UGA Index brand and content;
- 4 public commodities: corn, wheat 11.5% protein, feed wheat, GMO soybean;
- UGA Black Sea export basis language;
- UGA embed package for the association website;
- email-oriented respondent workflow and UGA member-area/product positioning;
- Neon PostgreSQL production database.
Spike-specific features:
- SPIKE SPOT INDEX brand and visual system;
- 6 public positions: corn, wheat 11.5% protein, feed wheat, corn FCA Chop, GMO soybean, sunflower seed;
- CPT Odesa / CPT parity Odesa / FCA Chop export, processing and border basis language;
- MN7R Monitor import as an automatic respondent;
- Telegram-first respondent workflow through
@spike_spot_bot; - admin and respondent password onboarding with temporary credentials;
- auto-publication if no manual publish happens before the evening cut-off;
- public blog with mixed-language posts and language filtering;
- Supabase PostgreSQL production database.
Both products are live as demo/production-style versions. They are suitable for reviewing real workflows, validating integrations and iterating with users. They are still under active development and should not be treated as final regulated market-data products until legal, security, backup and operational reviews are completed.
Current production-oriented state:
- hosting: Vercel;
- platform domain:
https://1d3x.com; - UGA domain:
https://index.uga.ua; - UGA 1d3x mirror domain:
https://uga.1d3x.com; - Spike domain:
https://spike.1d3x.com; - legacy redirects:
https://index-uga.cr0pto.comandhttps://spike-ua.cr0pto.com; - UGA database: Neon Postgres;
- Spike database: Supabase Postgres;
- runtime mode:
UGA_INDEX_RUNTIME_MODE=productionfor production-style deployments; - demo seed history can be disabled with
SEED_DEMO_HISTORY=0; - demo admin password seeding can be disabled with
SEED_DEMO_ADMIN_PASSWORD=0; - health check:
GET /api/health; - latest public values:
GET /api/public/latest.
Production deployments must have DATABASE_URL configured. Without a database,
local development can fall back to seeded/static demo data, but production
runtime should be DB-backed.
The shared calculation engine lives in:
src/lib/index-calculation.tsCore rules:
- collect respondent prices by tenant, date, commodity and basis;
- ignore invalid, missing, zero and negative prices;
- calculate the median of valid submitted prices;
- exclude outliers where
abs(price - median) / median > 0.02; - calculate the arithmetic average of the cleaned sample;
- require the configured minimum number of included respondents before a value is fully publishable;
- store official values in
USD/t; - preserve raw precision internally and round public display values;
- keep publication locks/audit context after publishing.
Spike MN7R import normalizes monitor values into the same respondent-price
pipeline. If MN7R returns UAH or EUR, the value is converted to USD using the
NBU FX rate for the trade date and the original currency/value is saved in
metadata. Positions with monitorPrice == null or quality == "no_data" are
cleared/skipped rather than published as fake values.
Relevant tests:
src/lib/index-calculation.test.ts
src/lib/admin-calculate.test.ts
src/lib/mn7r-monitor-import.test.tsSpike currently uses a hybrid real/demo model:
- MN7R Monitor is the first automatic respondent.
- Manual/admin-entered respondent prices can be added in the admin matrix.
- Real respondent submissions can be collected through the respondent cabinet or Telegram WebApp links.
- Public values are calculated from whatever valid real submissions exist for the day.
- Demo data remains available only for demo/fallback modes and must stay separated from production calculations.
Scheduled Spike processes:
- MN7R import:
/api/cron/mn7r-monitor-prices; - respondent Telegram notifications:
/api/cron/respondent-telegram; - auto-publish:
/api/cron/spike-auto-publish; - admin invite/onboarding helper:
/api/cron/spike-admin-invites.
Scheduled UGA processes:
- respondent Telegram notifications:
/api/cron/respondent-telegram; - respondent email notifications remain available as a backup channel:
/api/cron/respondent-emails; - temporary Spike-to-UGA demo sync exists at
/api/cron/uga-spike-demo-sync, but is disabled by default and must stay disabled for real UGA respondent testing.
Current vercel.json cron schedule:
[
{ "path": "/api/cron/respondent-emails", "schedule": "30 14 * * 1-5" },
{ "path": "/api/cron/respondent-telegram", "schedule": "0 13 * * 1-5" },
{ "path": "/api/cron/mn7r-monitor-prices", "schedule": "0 14 * * *" },
{ "path": "/api/cron/spike-auto-publish", "schedule": "0 16 * * *" },
{ "path": "/api/cron/uga-spike-demo-sync", "schedule": "30 16 * * 1-5" }
]Times are UTC in Vercel. The application code applies Kyiv-time business rules where needed.
UGA Spike fallback status:
- UGA production should not copy or fall back to Spike Spot Index values.
UGA_SPIKE_DEMO_SYNC_ENABLEDmust bedisabledunless a temporary demo sync is explicitly requested again.- UGA daily inputs and public values are now expected to come from admin-entered values, respondent submissions, and UGA publication workflow only.
Telegram respondent UX:
- weekday request around 16:00 Kyiv;
- reminders around 17:00 and 18:00 Kyiv when no submission exists;
- secure WebApp/survey token opens the daily form;
- respondent sees a success summary and can return to edit the submission;
- UGA uses
@uga_index_bot; Spike uses@spike_spot_bot; - admin/super-admin can inspect submissions in the admin matrix.
Core stack:
- Next.js App Router;
- React;
- TypeScript;
- Tailwind CSS;
- Prisma;
- PostgreSQL;
- Vitest;
- ESLint;
- Vercel.
Important modules:
src/lib/index-platform.ts tenant configuration
src/lib/constants.ts active site config
src/lib/public-index-data.ts public homepage and analytics data
src/lib/admin-daily-inputs.ts admin daily matrix data/actions
src/lib/admin-calculate.ts calculation and publication workflow
src/lib/respondent-directory.ts respondent directory and auth metadata
src/lib/respondent-prices.ts respondent price upsert/clear helpers
src/lib/mn7r-monitor-import.ts MN7R Monitor import
src/lib/respondent-telegram.ts Telegram respondent notifications
src/lib/fx-rates.ts NBU FX data layer
src/lib/demo-auth.ts session/auth helpers
src/lib/demo-allowlist.ts tenant-aware fallback usersTenant configuration controls:
- brand name, public title, logo and favicon;
- public domain;
- commodity list and basis labels;
- respondent pool and baskets;
- contact details;
- methodology PDF path;
- localization/copy;
- whether external indicative comparison is shown;
- public pages, embeds and product positioning.
Install dependencies:
npm installRun UGA locally:
INDEX_TENANT=uga-ua NEXT_PUBLIC_INDEX_TENANT=uga-ua npm run devRun Spike locally:
INDEX_TENANT=spike-ua NEXT_PUBLIC_INDEX_TENANT=spike-ua npm run devRun the 1d3x landing site locally:
INDEX_TENANT=1d3x NEXT_PUBLIC_INDEX_TENANT=1d3x npm run devUse another port if needed:
npm run dev -- --port 3100Common required variables:
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/index_platform?schema=public"
NEXT_PUBLIC_SITE_URL="https://TENANT_DOMAIN"
INDEX_TENANT="1d3x-or-uga-ua-or-spike-ua"
NEXT_PUBLIC_INDEX_TENANT="1d3x-or-uga-ua-or-spike-ua"
ALLOWED_EMBED_ORIGINS="https://TENANT_DOMAIN http://localhost:* http://127.0.0.1:*"
DEMO_AUTH_SECRET="replace-with-a-long-random-secret"
UGA_INDEX_RUNTIME_MODE="production"
CRON_SECRET="replace-with-a-long-random-cron-secret"1d3x production example:
NEXT_PUBLIC_SITE_URL="https://1d3x.com"
INDEX_TENANT="1d3x"
NEXT_PUBLIC_INDEX_TENANT="1d3x"
RESEND_API_KEY="set-in-vercel"
PLATFORM_CONTACT_FROM_EMAIL="1d3x <partnerships@1d3x.com>"
PLATFORM_CONTACT_TO_EMAIL="a.biletskiy@gmail.com"UGA production example:
NEXT_PUBLIC_SITE_URL="https://index.uga.ua"
INDEX_TENANT="uga-ua"
NEXT_PUBLIC_INDEX_TENANT="uga-ua"
ALLOWED_EMBED_ORIGINS="https://uga.ua https://www.uga.ua https://index.uga.ua https://1d3x.com https://www.1d3x.com https://uga.1d3x.com https://index-uga.cr0pto.com"
RESEND_API_KEY="set-in-vercel"
RESPONDENT_EMAIL_CRON_SECRET="set-in-vercel"
RESPONDENT_TELEGRAM_CRON_SECRET="set-in-vercel"
TELEGRAM_CONFIRMATION_CRON_SECRET="set-in-vercel"
UGA_TELEGRAM_BOT_TOKEN="set-in-vercel"
UGA_TELEGRAM_ADMIN_CHAT_ID="set-in-vercel"
UGA_SPIKE_PUBLIC_API_BASE="https://spike.1d3x.com"
UGA_SPIKE_DEMO_SYNC_ENABLED="disabled"
UGA_SPIKE_DEMO_SYNC_CRON_SECRET="set-in-vercel"UGA admin provisioning:
UGA_ADMIN_EMAIL="admin@example.ua" \
UGA_ADMIN_NAME="Імʼя Прізвище" \
UGA_ADMIN_TEMPORARY_PASSWORD="temporary-password" \
npm run provision:uga-adminThe provisioned admin signs in with the temporary password and is redirected to
/setup-password to set a permanent password.
Spike production example:
NEXT_PUBLIC_SITE_URL="https://spike.1d3x.com"
INDEX_TENANT="spike-ua"
NEXT_PUBLIC_INDEX_TENANT="spike-ua"
ALLOWED_EMBED_ORIGINS="https://spike.broker https://www.spike.broker https://1d3x.com https://www.1d3x.com https://spike.1d3x.com https://spike-ua.cr0pto.com"
MN7R_API_URL="https://mn7r.com"
MN7R_INDEX_EXPORT_TOKEN="set-in-vercel"
MN7R_INDEX_RESPONDENT_CODE="MN7R_MONITOR"
MN7R_IMPORT_CRON_SECRET="set-in-vercel"
SPIKE_AUTO_PUBLISH_CRON_SECRET="set-in-vercel"
SPIKE_ADMIN_INVITE_SECRET="set-in-vercel"
SPIKE_ADMIN_INVITE_SENDER="set-in-vercel"
SPIKE_ADMIN_INVITE_REPLY_TO="set-in-vercel"
SPIKE_TELEGRAM_BOT_TOKEN="set-in-vercel"
SPIKE_TELEGRAM_SMOKE_CHAT_ID="optional-smoke-chat-id"
RESPONDENT_TELEGRAM_CRON_SECRET="set-in-vercel"
OPENAI_API_KEY="set-in-vercel-for-ai-market-brief"
SPIKE_AI_BRIEF_MODEL="gpt-4.1-mini"
SPIKE_AI_BRIEF_CRON_SECRET="set-in-vercel-if-ai-api-post-is-used"
SPIKE_AI_TELEGRAM_CHAT_ID="optional-admin-telegram-chat-id"
SPIKE_AI_INPUT_USD_PER_1M="0.4"
SPIKE_AI_OUTPUT_USD_PER_1M="1.6"Do not commit production secrets, connection strings or bot tokens. Use Vercel
Environment Variables or an untracked local .env file for operational
commands.
SPIKE uses a cost-controlled AI layer above published index values. The model is
not called on public page views. A daily brief is generated once per trade date
and locale, stored in AiMarketBrief, and then reused by the public analytics
page, index-card AI notes, the admin publication workflow and the AI API.
- Default model:
SPIKE_AI_BRIEF_MODEL(gpt-4.1-mini). - API:
GET /api/ai/market-brief?locale=uk|enreturns the saved public brief. - Regeneration:
POST /api/ai/market-brief?date=YYYY-MM-DD&force=1withAuthorization: Bearer $SPIKE_AI_BRIEF_CRON_SECRET. - Admin:
/admin/calculateshows saved brief status, model, hash, tokens, estimated cost, fallback reason and errors, with a manual regenerate button. - Auto-publish: when Spike auto-publishes daily values, it also generates and stores the UK/EN daily AI brief.
- Telegram: if
SPIKE_AI_TELEGRAM_CHAT_IDis set, auto-publish sends the UK AI summary to that chat. - Cost logging: token usage is saved from OpenAI Responses API usage. Estimated
cost uses
SPIKE_AI_INPUT_USD_PER_1MandSPIKE_AI_OUTPUT_USD_PER_1M, which can be updated if provider pricing changes.
Generate Prisma client:
npm run db:generateApply migrations:
npx prisma migrate deploySeed UGA:
INDEX_TENANT=uga-ua NEXT_PUBLIC_INDEX_TENANT=uga-ua npm run db:seedSeed Spike in production-style mode:
UGA_INDEX_RUNTIME_MODE=production \
SEED_DEMO_HISTORY=0 \
SEED_DEMO_ADMIN_PASSWORD=0 \
INDEX_TENANT=spike-ua \
NEXT_PUBLIC_INDEX_TENANT=spike-ua \
npm run db:seedThe seed is tenant-aware:
- UGA seed creates UGA commodities, respondents, contacts, baskets, fallback submissions, external indicatives and published values for demo/review.
- Spike seed creates Spike commodities, CPT Odesa/CPT parity Odesa/FCA Chop
bases, MN7R Monitor, Spike respondent directory entries and auth accounts.
Demo history is controlled by
SEED_DEMO_HISTORY.
More detail:
docs/database.mdPublic:
/for the 1d3x landing site, or locale redirect for index tenants/uk,/en/uk/about,/en/about/uk/methodology,/en/methodology/uk/analytics,/en/analytics/uk/subscription,/en/subscription/uk/blog,/en/blog/uk/privacy,/en/privacy/uk/terms,/en/terms/uk/risk-disclosure,/en/risk-disclosure
Internal:
/login/logout/setup-password/admin/admin/daily-inputs/admin/respondents/admin/calculate/admin/embed/respondent/respondent/access/[token]/member
Embeds:
/embed/cards/embed/chart/embed/site/embed/uga-index.js
Public API:
GET /api/healthGET /api/public/latestGET /api/public/historyGET /api/public/fx-rates
Cron/internal API:
GET /api/cron/respondent-emailsGET /api/cron/respondent-telegramGET /api/cron/mn7r-monitor-pricesGET /api/cron/spike-auto-publishGET /api/cron/uga-spike-demo-syncGET /api/cron/spike-admin-invites
The platform supports compact widgets and full-site iframe embeds.
UGA full-site iframe example:
<iframe
src="https://index.uga.ua/embed/site?locale=uk&theme=light&view=index"
title="UGA Index"
loading="lazy"
style="width:100%;height:860px;border:0;"
allowfullscreen
></iframe>UGA JS loader example:
<div
id="uga-index"
data-locale="uk"
data-theme="light"
data-layout="site"
></div>
<script src="https://index.uga.ua/embed/uga-index.js" async></script>For UGA integrations, prefer https://index.uga.ua once DNS is configured.
The https://uga.1d3x.com mirror remains available in parallel and should not
redirect. Legacy cr0pto.com embed URLs remain available through redirects for
compatibility.
Full details:
docs/embed.mdCurrent auth model:
- Spike admins use email/password accounts with temporary password onboarding.
- Spike respondents use email/password accounts with temporary password setup.
- Spike super-admin can access both admin and respondent areas for control.
- Respondent Telegram links use short-lived survey tokens.
- UGA still supports preview/demo users for development and demonstrations.
Production deployments should avoid generic admin/admin or
respondent/respondent access. Temporary passwords should be rotated through
the onboarding flow and never committed to source control.
More detail:
docs/auth.mdRun before committing code changes:
npm run lint
npm run test
npm run buildValidate tenant builds explicitly:
INDEX_TENANT=1d3x NEXT_PUBLIC_INDEX_TENANT=1d3x npm run build
INDEX_TENANT=uga-ua NEXT_PUBLIC_INDEX_TENANT=uga-ua npm run build
INDEX_TENANT=spike-ua NEXT_PUBLIC_INDEX_TENANT=spike-ua npm run buildOptional browser smoke tests:
npx playwright install chromium
npm run test:e2eFor README-only changes, at minimum run:
git diff --checkRecommended setup per tenant:
- Create a separate Vercel project per tenant.
- Set
INDEX_TENANTandNEXT_PUBLIC_INDEX_TENANT. - Set
NEXT_PUBLIC_SITE_URLto the tenant domain. - Use tenant-specific database/environment settings.
- Configure
ALLOWED_EMBED_ORIGINS. - Run
npx prisma migrate deployagainst the target database. - Run the tenant seed with production-safe flags.
- Configure cron secrets and integration secrets.
- Redeploy Vercel.
- Confirm
GET /api/healthand public pages.
Deployment docs:
docs/deployment.mdProject docs:
docs/product-brief.mddocs/implementation-plan.mddocs/database.mddocs/auth.mddocs/deployment.mddocs/embed.mddocs/uga-domain-cutover.mddocs/demo-script.mddocs/known-limitations.mddocs/legal.mddocs/source-analysis.mddocs/variant-design-analysis.md
Source reference materials:
docs/source/- Finish production security hardening for auth, sessions, role checks and secret rotation.
- Keep real and demo data strictly separated in analytics, publication and admin views.
- Add backup/restore runbooks for Neon and Supabase databases.
- Add observability for cron runs, MN7R imports, auto-publication and Telegram delivery failures.
- Finalize legal text, risk disclosure and methodology PDFs with the project owners.
- Add subscription/access-control rules for paid analytics, history exports and API access.
- Expand respondent onboarding beyond the first Spike real respondent.
- Continue polishing the 1d3x landing page, partnership copy and live project presentation.
