Switch Directors Forecast to use forecasting API instead of direct DB
Replace queryForecast/forecast-db.js with fetch() calls to the forecasting app's public API (/forecasting/api/public/forecast/revenue and /forecast/rooms). This removes the need for cross-DB grants and the FORECAST_DATABASE_URL — all data comes from FORECASTING_API_KEY + FORECASTING_URL. The DOW-history algorithm is also removed since the covers model in the forecasting API already handles dry/wet future forecasts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
018957123f
commit
bd9ddbeda9
2 changed files with 119 additions and 157 deletions
|
|
@ -1,6 +1,17 @@
|
|||
import { requireAuth, hasCap } from '../auth.js'
|
||||
import { pool } from '../db.js'
|
||||
import { queryForecast } from '../forecast-db.js'
|
||||
|
||||
const FORECASTING_URL = process.env.FORECASTING_URL || 'http://10.10.10.113:3080'
|
||||
|
||||
async function fcFetch(path) {
|
||||
const apiKey = process.env.FORECASTING_API_KEY
|
||||
if (!apiKey) throw new Error('FORECASTING_API_KEY not configured')
|
||||
const res = await fetch(`${FORECASTING_URL}/forecasting/api/public${path}`, {
|
||||
headers: { 'X-API-Key': apiKey },
|
||||
})
|
||||
if (!res.ok) throw new Error(`Forecasting API ${res.status} — ${path}`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
function weekBand(day) {
|
||||
if (day <= 7) return '1–7'
|
||||
|
|
@ -31,39 +42,18 @@ async function getSessionAndOverrides(year, month) {
|
|||
return { session, overrideMap }
|
||||
}
|
||||
|
||||
async function getDowHistory(firstDayOfMonth) {
|
||||
const lookbackDate = new Date(firstDayOfMonth)
|
||||
lookbackDate.setDate(lookbackDate.getDate() - 70)
|
||||
const lookbackStr = lookbackDate.toISOString().split('T')[0]
|
||||
const yesterdayDate = new Date()
|
||||
yesterdayDate.setDate(yesterdayDate.getDate() - 1)
|
||||
const yesterday = yesterdayDate.toISOString().split('T')[0]
|
||||
|
||||
let rows = []
|
||||
try {
|
||||
rows = await queryForecast(
|
||||
`SELECT date::text, dry, wet FROM newbook_net_revenue_data
|
||||
WHERE date >= $1 AND date <= $2 ORDER BY date DESC`,
|
||||
[lookbackStr, yesterday]
|
||||
)
|
||||
} catch {}
|
||||
|
||||
const dowHistory = { dry: {}, wet: {} }
|
||||
for (const row of rows) {
|
||||
const dow = new Date(row.date + 'T00:00:00').getDay()
|
||||
if (!dowHistory.dry[dow]) { dowHistory.dry[dow] = []; dowHistory.wet[dow] = [] }
|
||||
if (dowHistory.dry[dow].length < 7) {
|
||||
dowHistory.dry[dow].push(parseFloat(row.dry || 0))
|
||||
dowHistory.wet[dow].push(parseFloat(row.wet || 0))
|
||||
}
|
||||
}
|
||||
return dowHistory
|
||||
}
|
||||
|
||||
function dowAvg(dowHistory, field, dow) {
|
||||
const vals = dowHistory[field][dow]
|
||||
if (!vals || vals.length === 0) return 0
|
||||
return vals.reduce((a, b) => a + b, 0) / vals.length
|
||||
async function fetchMonthFromApi(year, month, lastDayNum) {
|
||||
const startDate = `${year}-${String(month).padStart(2, '0')}-01`
|
||||
const days = lastDayNum
|
||||
const [revData, roomsData] = await Promise.all([
|
||||
fcFetch(`/forecast/revenue?start_date=${startDate}&days=${days}&type=all`),
|
||||
fcFetch(`/forecast/rooms?start_date=${startDate}&days=${days}`),
|
||||
])
|
||||
const revMap = {}
|
||||
for (const d of revData.data) revMap[d.date] = d
|
||||
const roomsMap = {}
|
||||
for (const d of roomsData.data) roomsMap[d.date] = d
|
||||
return { revMap, roomsMap, available: roomsData.total_rooms }
|
||||
}
|
||||
|
||||
export async function directorsForecastRoutes(fastify) {
|
||||
|
|
@ -77,89 +67,62 @@ export async function directorsForecastRoutes(fastify) {
|
|||
return reply.status(400).send({ error: 'Invalid year/month' })
|
||||
}
|
||||
|
||||
const firstDay = `${year}-${String(month).padStart(2, '0')}-01`
|
||||
const lastDayNum = new Date(year, month, 0).getDate()
|
||||
const lastDay = `${year}-${String(month).padStart(2, '0')}-${String(lastDayNum).padStart(2, '0')}`
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const lastDayNum = new Date(year, month, 0).getDate()
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const sessionRate = 135
|
||||
|
||||
const { session, overrideMap } = await getSessionAndOverrides(year, month)
|
||||
const [{ session, overrideMap }, { revMap, roomsMap, available }] = await Promise.all([
|
||||
getSessionAndOverrides(year, month),
|
||||
fetchMonthFromApi(year, month, lastDayNum),
|
||||
])
|
||||
|
||||
let otbMap = {}
|
||||
try {
|
||||
const rows = await queryForecast(
|
||||
`SELECT date::text, rooms_count, booking_count, net_booking_rev_total
|
||||
FROM newbook_bookings_stats WHERE date >= $1 AND date <= $2`,
|
||||
[firstDay, lastDay]
|
||||
)
|
||||
for (const r of rows) otbMap[r.date] = r
|
||||
} catch (e) { fastify.log.warn('OTB fetch failed:', e.message) }
|
||||
|
||||
let revenueMap = {}
|
||||
try {
|
||||
const rows = await queryForecast(
|
||||
`SELECT date::text, accommodation, dry, wet
|
||||
FROM newbook_net_revenue_data WHERE date >= $1 AND date <= $2`,
|
||||
[firstDay, lastDay]
|
||||
)
|
||||
for (const r of rows) revenueMap[r.date] = r
|
||||
} catch (e) { fastify.log.warn('Revenue fetch failed:', e.message) }
|
||||
|
||||
let mlMap = {}
|
||||
try {
|
||||
const rows = await queryForecast(
|
||||
`SELECT DISTINCT ON (forecast_date) forecast_date::text, predicted_value
|
||||
FROM forecasts
|
||||
WHERE forecast_date >= $1 AND forecast_date <= $2 AND forecast_type = 'hotel_room_nights'
|
||||
ORDER BY forecast_date, generated_at DESC`,
|
||||
[firstDay, lastDay]
|
||||
)
|
||||
for (const r of rows) mlMap[r.forecast_date] = parseFloat(r.predicted_value)
|
||||
} catch {}
|
||||
|
||||
const dowHistory = await getDowHistory(new Date(firstDay))
|
||||
const sessionRate = parseFloat(session.pickup_avg_rate || 135)
|
||||
const rate = parseFloat(session.pickup_avg_rate || sessionRate)
|
||||
|
||||
const days = []
|
||||
for (let d = 1; d <= lastDayNum; d++) {
|
||||
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(d).padStart(2, '0')}`
|
||||
const isPast = dateStr < today
|
||||
const rev = revMap[dateStr] || {}
|
||||
const rm = roomsMap[dateStr] || {}
|
||||
const ovr = overrideMap[dateStr] || {}
|
||||
|
||||
const isPast = rev.is_past ?? (dateStr < today)
|
||||
const isToday = dateStr === today
|
||||
const dow = new Date(dateStr + 'T00:00:00').getDay()
|
||||
|
||||
const otb = otbMap[dateStr] || {}
|
||||
const rev = revenueMap[dateStr] || {}
|
||||
const ovr = overrideMap[dateStr] || {}
|
||||
|
||||
const available = parseInt(otb.rooms_count || 0)
|
||||
const otbRooms = parseInt(otb.booking_count || 0)
|
||||
const otbNetRev = parseFloat(otb.net_booking_rev_total || 0)
|
||||
const otbRooms = rm.otb_rooms ?? 0
|
||||
const otbNetRev = rev.accom?.otb ?? 0
|
||||
const avgRateNet = otbRooms > 0 ? otbNetRev / otbRooms : null
|
||||
|
||||
const actualAccomm = ovr.actual_accomm != null ? parseFloat(ovr.actual_accomm)
|
||||
: (rev.accommodation != null ? parseFloat(rev.accommodation) : null)
|
||||
const actualDry = rev.dry != null ? parseFloat(rev.dry) : null
|
||||
const actualWet = rev.wet != null ? parseFloat(rev.wet) : null
|
||||
// ML pickup suggestion from the forecasting model
|
||||
const mlSuggestion = rm.pickup_rooms ?? null
|
||||
|
||||
const pickupRooms = ovr.pickup_rooms != null ? parseInt(ovr.pickup_rooms) : 0
|
||||
const dayRate = ovr.pickup_avg_rate != null ? parseFloat(ovr.pickup_avg_rate) : sessionRate
|
||||
const totalRooms = otbRooms + pickupRooms
|
||||
// Actuals (from API — otb == actual for past dates)
|
||||
const actualAccomm = isPast ? (rev.accom?.otb ?? null) : null
|
||||
const actualDry = isPast ? (rev.dry?.otb ?? null) : null
|
||||
const actualWet = isPast ? (rev.wet?.otb ?? null) : null
|
||||
|
||||
const mlSuggestion = mlMap[dateStr] != null
|
||||
? Math.max(0, Math.round(mlMap[dateStr] - otbRooms)) : null
|
||||
// User overrides
|
||||
const pickupRooms = ovr.pickup_rooms != null ? parseInt(ovr.pickup_rooms) : 0
|
||||
const dayRate = ovr.pickup_avg_rate != null ? parseFloat(ovr.pickup_avg_rate) : rate
|
||||
|
||||
let forecastAccomm
|
||||
if (isPast && actualAccomm != null) forecastAccomm = actualAccomm
|
||||
else forecastAccomm = otbNetRev + pickupRooms * dayRate
|
||||
// Forecast values
|
||||
const forecastAccomm = isPast && actualAccomm != null
|
||||
? actualAccomm
|
||||
: otbNetRev + pickupRooms * dayRate
|
||||
|
||||
const forecastDry = ovr.dry_override != null ? parseFloat(ovr.dry_override)
|
||||
: (isPast && actualDry != null ? actualDry : dowAvg(dowHistory, 'dry', dow))
|
||||
const forecastDry = ovr.dry_override != null
|
||||
? parseFloat(ovr.dry_override)
|
||||
: (rev.dry?.forecast ?? 0)
|
||||
|
||||
const forecastWet = ovr.wet_override != null ? parseFloat(ovr.wet_override)
|
||||
: (isPast && actualWet != null ? actualWet : dowAvg(dowHistory, 'wet', dow))
|
||||
const forecastWet = ovr.wet_override != null
|
||||
? parseFloat(ovr.wet_override)
|
||||
: (rev.wet?.forecast ?? 0)
|
||||
|
||||
const totalRooms = otbRooms + pickupRooms
|
||||
|
||||
days.push({
|
||||
date: dateStr, dow, available, otb_rooms: otbRooms,
|
||||
otb_net_rev: otbNetRev, avg_rate_net: avgRateNet,
|
||||
date: dateStr, dow, available,
|
||||
otb_rooms: otbRooms, otb_net_rev: otbNetRev, avg_rate_net: avgRateNet,
|
||||
is_past: isPast, is_today: isToday,
|
||||
actual_accomm: actualAccomm, actual_dry: actualDry, actual_wet: actualWet,
|
||||
ml_suggestion: mlSuggestion,
|
||||
|
|
@ -175,7 +138,7 @@ export async function directorsForecastRoutes(fastify) {
|
|||
})
|
||||
}
|
||||
|
||||
return { session: { ...session, pickup_avg_rate: sessionRate }, days }
|
||||
return { session: { ...session, pickup_avg_rate: rate }, days }
|
||||
})
|
||||
|
||||
// PUT /api/directors-forecast/worksheet/:year/:month — save overrides
|
||||
|
|
@ -222,79 +185,77 @@ export async function directorsForecastRoutes(fastify) {
|
|||
fastify.get('/api/directors-forecast/report/:year/:month', async (request, reply) => {
|
||||
const year = parseInt(request.params.year)
|
||||
const month = parseInt(request.params.month)
|
||||
|
||||
const firstDay = `${year}-${String(month).padStart(2, '0')}-01`
|
||||
const lastDayNum = new Date(year, month, 0).getDate()
|
||||
const lastDay = `${year}-${String(month).padStart(2, '0')}-${String(lastDayNum).padStart(2, '0')}`
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
|
||||
const { session, overrideMap } = await getSessionAndOverrides(year, month)
|
||||
const [{ session, overrideMap }, { revMap, roomsMap, available }] = await Promise.all([
|
||||
getSessionAndOverrides(year, month),
|
||||
fetchMonthFromApi(year, month, lastDayNum),
|
||||
])
|
||||
|
||||
let otbRows = [], revRows = []
|
||||
try { otbRows = await queryForecast(`SELECT date::text, rooms_count, booking_count, net_booking_rev_total FROM newbook_bookings_stats WHERE date >= $1 AND date <= $2`, [firstDay, lastDay]) } catch {}
|
||||
try { revRows = await queryForecast(`SELECT date::text, accommodation, dry, wet FROM newbook_net_revenue_data WHERE date >= $1 AND date <= $2`, [firstDay, lastDay]) } catch {}
|
||||
|
||||
let budget = { accomm: 0, dry: 0, wet: 0 }
|
||||
try {
|
||||
const rows = await queryForecast(`SELECT budget_type, budget_value FROM monthly_budgets WHERE year = $1 AND month = $2`, [year, month])
|
||||
for (const b of rows) {
|
||||
if (b.budget_type === 'net_accom') budget.accomm = parseFloat(b.budget_value || 0)
|
||||
if (b.budget_type === 'net_dry') budget.dry = parseFloat(b.budget_value || 0)
|
||||
if (b.budget_type === 'net_wet') budget.wet = parseFloat(b.budget_value || 0)
|
||||
}
|
||||
} catch {}
|
||||
|
||||
let lastYear = { accomm: 0, dry: 0, wet: 0, rooms: 0 }
|
||||
try {
|
||||
const lyFirst = `${year - 1}-${String(month).padStart(2, '0')}-01`
|
||||
const lyLastNum = new Date(year - 1, month, 0).getDate()
|
||||
const lyLast = `${year - 1}-${String(month).padStart(2, '0')}-${String(lyLastNum).padStart(2, '0')}`
|
||||
const [lyRev] = await queryForecast(`SELECT SUM(accommodation) as accomm, SUM(dry) as dry, SUM(wet) as wet FROM newbook_net_revenue_data WHERE date >= $1 AND date <= $2`, [lyFirst, lyLast])
|
||||
if (lyRev) { lastYear.accomm = parseFloat(lyRev.accomm || 0); lastYear.dry = parseFloat(lyRev.dry || 0); lastYear.wet = parseFloat(lyRev.wet || 0) }
|
||||
const [lyOtb] = await queryForecast(`SELECT SUM(booking_count) as rooms FROM newbook_bookings_stats WHERE date >= $1 AND date <= $2`, [lyFirst, lyLast])
|
||||
if (lyOtb) lastYear.rooms = parseInt(lyOtb.rooms || 0)
|
||||
} catch {}
|
||||
|
||||
const dowHistory = await getDowHistory(new Date(firstDay))
|
||||
const sessionRate = parseFloat(session.pickup_avg_rate || 135)
|
||||
|
||||
const otbMap = {}; for (const r of otbRows) otbMap[r.date] = r
|
||||
const revMap = {}; for (const r of revRows) revMap[r.date] = r
|
||||
|
||||
const weekBands = { '1–7': { accomm: 0, dry: 0, wet: 0, rooms: 0 }, '8–14': { accomm: 0, dry: 0, wet: 0, rooms: 0 }, '15–21': { accomm: 0, dry: 0, wet: 0, rooms: 0 }, '22–28': { accomm: 0, dry: 0, wet: 0, rooms: 0 }, '29–end': { accomm: 0, dry: 0, wet: 0, rooms: 0 } }
|
||||
const weekBands = {
|
||||
'1–7': { accomm: 0, dry: 0, wet: 0, rooms: 0 },
|
||||
'8–14': { accomm: 0, dry: 0, wet: 0, rooms: 0 },
|
||||
'15–21': { accomm: 0, dry: 0, wet: 0, rooms: 0 },
|
||||
'22–28': { accomm: 0, dry: 0, wet: 0, rooms: 0 },
|
||||
'29–end': { accomm: 0, dry: 0, wet: 0, rooms: 0 },
|
||||
}
|
||||
|
||||
let totalAvailable = 0, totalOtbRooms = 0, totalOtbNetRev = 0, totalPickup = 0
|
||||
let totalAccomm = 0, totalDry = 0, totalWet = 0
|
||||
let totalBudgetAccomm = 0, totalBudgetDry = 0, totalBudgetWet = 0
|
||||
let totalLyAccomm = 0, totalLyDry = 0, totalLyWet = 0, totalLyRooms = 0
|
||||
|
||||
for (let d = 1; d <= lastDayNum; d++) {
|
||||
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(d).padStart(2, '0')}`
|
||||
const isPast = dateStr < today
|
||||
const dow = new Date(dateStr + 'T00:00:00').getDay()
|
||||
const rev = revMap[dateStr] || {}
|
||||
const rm = roomsMap[dateStr] || {}
|
||||
const ovr = overrideMap[dateStr] || {}
|
||||
|
||||
const otb = otbMap[dateStr] || {}; const rev = revMap[dateStr] || {}; const ovr = overrideMap[dateStr] || {}
|
||||
const available = parseInt(otb.rooms_count || 0)
|
||||
const otbRooms = parseInt(otb.booking_count || 0)
|
||||
const otbNetRev = parseFloat(otb.net_booking_rev_total || 0)
|
||||
const actualAccomm = ovr.actual_accomm != null ? parseFloat(ovr.actual_accomm) : (rev.accommodation != null ? parseFloat(rev.accommodation) : null)
|
||||
const actualDry = rev.dry != null ? parseFloat(rev.dry) : null
|
||||
const actualWet = rev.wet != null ? parseFloat(rev.wet) : null
|
||||
const pickupRooms = ovr.pickup_rooms != null ? parseInt(ovr.pickup_rooms) : 0
|
||||
const isPast = rev.is_past ?? false
|
||||
const otbRooms = rm.otb_rooms ?? 0
|
||||
const otbNetRev = rev.accom?.otb ?? 0
|
||||
const pickupRooms = ovr.pickup_rooms != null ? parseInt(ovr.pickup_rooms) : 0
|
||||
const dayRate = ovr.pickup_avg_rate != null ? parseFloat(ovr.pickup_avg_rate) : sessionRate
|
||||
const totalRooms = otbRooms + pickupRooms
|
||||
|
||||
const fAccomm = (isPast && actualAccomm != null) ? actualAccomm : otbNetRev + pickupRooms * dayRate
|
||||
const fDry = ovr.dry_override != null ? parseFloat(ovr.dry_override) : (isPast && actualDry != null ? actualDry : dowAvg(dowHistory, 'dry', dow))
|
||||
const fWet = ovr.wet_override != null ? parseFloat(ovr.wet_override) : (isPast && actualWet != null ? actualWet : dowAvg(dowHistory, 'wet', dow))
|
||||
const actualAccomm = isPast ? (rev.accom?.otb ?? null) : null
|
||||
const actualDry = isPast ? (rev.dry?.otb ?? null) : null
|
||||
const actualWet = isPast ? (rev.wet?.otb ?? null) : null
|
||||
|
||||
totalAvailable += available; totalOtbRooms += otbRooms; totalOtbNetRev += otbNetRev
|
||||
totalPickup += pickupRooms; totalAccomm += fAccomm; totalDry += fDry; totalWet += fWet
|
||||
const fAccomm = (isPast && actualAccomm != null) ? actualAccomm : otbNetRev + pickupRooms * dayRate
|
||||
const fDry = ovr.dry_override != null ? parseFloat(ovr.dry_override) : (rev.dry?.forecast ?? 0)
|
||||
const fWet = ovr.wet_override != null ? parseFloat(ovr.wet_override) : (rev.wet?.forecast ?? 0)
|
||||
|
||||
totalAvailable += available
|
||||
totalOtbRooms += otbRooms
|
||||
totalOtbNetRev += otbNetRev
|
||||
totalPickup += pickupRooms
|
||||
totalAccomm += fAccomm
|
||||
totalDry += fDry
|
||||
totalWet += fWet
|
||||
|
||||
// Budget from API per day
|
||||
totalBudgetAccomm += rev.accom?.budget ?? 0
|
||||
totalBudgetDry += rev.dry?.budget ?? 0
|
||||
totalBudgetWet += rev.wet?.budget ?? 0
|
||||
|
||||
// Prior year from API
|
||||
totalLyAccomm += rev.accom?.prior_final ?? 0
|
||||
totalLyDry += rev.dry?.prior_final ?? 0
|
||||
totalLyWet += rev.wet?.prior_final ?? 0
|
||||
totalLyRooms += rm.prior_year_final ?? 0
|
||||
|
||||
const band = weekBand(d)
|
||||
weekBands[band].accomm += fAccomm; weekBands[band].dry += fDry
|
||||
weekBands[band].wet += fWet; weekBands[band].rooms += totalRooms
|
||||
weekBands[band].accomm += fAccomm
|
||||
weekBands[band].dry += fDry
|
||||
weekBands[band].wet += fWet
|
||||
weekBands[band].rooms += totalRooms
|
||||
}
|
||||
|
||||
const totalForecastRooms = totalOtbRooms + totalPickup
|
||||
const totalBudget = totalBudgetAccomm + totalBudgetDry + totalBudgetWet
|
||||
|
||||
const snapshotsRes = await pool.query(
|
||||
`SELECT id, snapshot_at, label, forecast_rooms, forecast_accomm, forecast_dry, forecast_wet, forecast_total, otb_rooms, pickup_rooms, occ_pct
|
||||
|
|
@ -312,8 +273,8 @@ export async function directorsForecastRoutes(fastify) {
|
|||
revpar: totalAvailable > 0 ? totalAccomm / totalAvailable : null,
|
||||
available: totalAvailable,
|
||||
},
|
||||
budget: { ...budget, total: budget.accomm + budget.dry + budget.wet },
|
||||
last_year: { ...lastYear, total: lastYear.accomm + lastYear.dry + lastYear.wet },
|
||||
budget: { accomm: totalBudgetAccomm, dry: totalBudgetDry, wet: totalBudgetWet, total: totalBudget },
|
||||
last_year: { accomm: totalLyAccomm, dry: totalLyDry, wet: totalLyWet, rooms: totalLyRooms, total: totalLyAccomm + totalLyDry + totalLyWet },
|
||||
otb: {
|
||||
rooms: totalOtbRooms, accomm: totalOtbNetRev, available: totalAvailable,
|
||||
occ_pct: totalAvailable > 0 ? (totalOtbRooms / totalAvailable) * 100 : null,
|
||||
|
|
@ -326,7 +287,7 @@ export async function directorsForecastRoutes(fastify) {
|
|||
}
|
||||
})
|
||||
|
||||
// POST /api/directors-forecast/report/:year/:month/snapshot
|
||||
// POST snapshot
|
||||
fastify.post('/api/directors-forecast/report/:year/:month/snapshot', async (request, reply) => {
|
||||
if (!hasCap(request, 'edit')) return reply.status(403).send({ error: 'Missing capability: edit' })
|
||||
|
||||
|
|
@ -345,7 +306,7 @@ export async function directorsForecastRoutes(fastify) {
|
|||
return res.rows[0]
|
||||
})
|
||||
|
||||
// DELETE /api/directors-forecast/report/:year/:month/snapshot/:id
|
||||
// DELETE snapshot
|
||||
fastify.delete('/api/directors-forecast/report/:year/:month/snapshot/:id', async (request, reply) => {
|
||||
if (!hasCap(request, 'edit')) return reply.status(403).send({ error: 'Missing capability: edit' })
|
||||
await pool.query(`DELETE FROM forecast_snapshots WHERE id = $1`, [parseInt(request.params.id)])
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ services:
|
|||
- apparmor=unconfined
|
||||
environment:
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- FORECAST_DATABASE_URL=${FORECAST_DATABASE_URL:-}
|
||||
- FORECASTING_URL=${FORECASTING_URL:-http://10.10.10.113:3080}
|
||||
- FORECASTING_API_KEY=${FORECASTING_API_KEY:-}
|
||||
- CENTRAL_AUTH_SECRET=${CENTRAL_AUTH_SECRET}
|
||||
- SETTINGS_URL=${SETTINGS_URL}
|
||||
- SETTINGS_SECRET=${SETTINGS_SECRET}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue