Time unification
This commit is contained in:
@@ -140,6 +140,12 @@ console.log('DB Connection Info:', {
|
||||
password: (dbConfig.password || dbConfig.connectionString) ? '****' : 'MISSING' // Only show if credentials exist
|
||||
});
|
||||
|
||||
// Pin the session timezone to business time (see docs/TIME.md) — window
|
||||
// anchors (CURRENT_DATE etc.) in the metrics SQL must mean Chicago dates.
|
||||
if (!dbConfig.options) {
|
||||
dbConfig.options = '-c TimeZone=America/Chicago';
|
||||
}
|
||||
|
||||
const pool = new Pool(dbConfig);
|
||||
|
||||
const getConnection = () => {
|
||||
|
||||
Binary file not shown.
@@ -18,6 +18,18 @@ import json
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime, date, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
BUSINESS_TZ = ZoneInfo('America/Chicago')
|
||||
|
||||
|
||||
def business_today() -> date:
|
||||
"""The business date (Chicago calendar date — see docs/TIME.md).
|
||||
|
||||
Never business_today(): the server runs on Europe/Berlin, which flips to the
|
||||
next day at 5/6pm Central and would shift forecast_date anchors.
|
||||
"""
|
||||
return datetime.now(BUSINESS_TZ).date()
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
@@ -740,7 +752,7 @@ def batch_load_product_data(conn, products):
|
||||
GROUP BY pid
|
||||
"""
|
||||
adf = execute_query(conn, arrival_sql, [preorder_pids])
|
||||
today = date.today()
|
||||
today = business_today()
|
||||
for _, row in adf.iterrows():
|
||||
pid = int(row['pid'])
|
||||
fa = row['future_arrival']
|
||||
@@ -948,7 +960,7 @@ def forecast_mature(product, history_df):
|
||||
hist['snapshot_date'] = pd.to_datetime(hist['snapshot_date'])
|
||||
hist = hist.set_index('snapshot_date')['units_sold']
|
||||
full_index = pd.date_range(
|
||||
end=pd.Timestamp(date.today() - timedelta(days=1)),
|
||||
end=pd.Timestamp(business_today() - timedelta(days=1)),
|
||||
periods=EXP_SMOOTHING_WINDOW, freq='D')
|
||||
series = hist.reindex(full_index, fill_value=0.0).values.astype(float)
|
||||
|
||||
@@ -1070,7 +1082,7 @@ def generate_all_forecasts(conn, curves_df, dow_indices, monthly_indices=None,
|
||||
log.info("Batch loading product data...")
|
||||
batch_data = batch_load_product_data(conn, products)
|
||||
|
||||
today = date.today()
|
||||
today = business_today()
|
||||
forecast_dates = [today + timedelta(days=i) for i in range(FORECAST_HORIZON_DAYS)]
|
||||
|
||||
# Pre-compute DOW and seasonal multipliers for each forecast date.
|
||||
@@ -1676,7 +1688,7 @@ def backfill_accuracy_data(conn, backfill_days=30):
|
||||
# Batch load product data for per-product scaling
|
||||
batch_data = batch_load_product_data(conn, active)
|
||||
|
||||
today = date.today()
|
||||
today = business_today()
|
||||
backfill_start = today - timedelta(days=backfill_days)
|
||||
|
||||
# Create a synthetic run entry
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
* /var/www/inventory/.env (or current process env).
|
||||
*/
|
||||
|
||||
// Run in business time (see docs/TIME.md); the spawned Python engine and any
|
||||
// incidental Date math inherit it.
|
||||
process.env.TZ = process.env.TZ || 'America/Chicago';
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Run the whole update cycle in business time (see docs/TIME.md). The cron
|
||||
// that invokes this script does not set TZ; child scripts inherit it.
|
||||
process.env.TZ = process.env.TZ || 'America/Chicago';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
@@ -23,6 +23,17 @@ const IMPORT_STOCK_SNAPSHOTS = true;
|
||||
const INCREMENTAL_UPDATE = process.env.INCREMENTAL_UPDATE !== 'false'; // Default to true unless explicitly set to false
|
||||
|
||||
// SSH configuration
|
||||
// Current UTC offset of America/Chicago ('-05:00' or '-06:00'), DST-aware.
|
||||
function currentChicagoOffset() {
|
||||
const tzName = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: 'America/Chicago',
|
||||
timeZoneName: 'longOffset',
|
||||
})
|
||||
.formatToParts(new Date())
|
||||
.find((p) => p.type === 'timeZoneName').value; // e.g. 'GMT-05:00'
|
||||
return tzName.replace('GMT', '') || '+00:00';
|
||||
}
|
||||
|
||||
const sshConfig = {
|
||||
ssh: {
|
||||
host: process.env.PROD_SSH_HOST,
|
||||
@@ -40,7 +51,12 @@ const sshConfig = {
|
||||
password: process.env.PROD_DB_PASSWORD,
|
||||
database: process.env.PROD_DB_NAME,
|
||||
port: process.env.PROD_DB_PORT || 3306,
|
||||
timezone: '-05:00', // mysql2 driver timezone — corrected at runtime via adjustDateForMySQL() in utils.js
|
||||
// DATETIMEs on this server store Central wall-clock. Use the CURRENT
|
||||
// Chicago offset (DST-aware) so both inbound parsing and outbound
|
||||
// serialization are correct in winter (CST=-06:00) and summer
|
||||
// (CDT=-05:00). adjustDateForMySQL() in utils.js remains as a runtime
|
||||
// safety net (its correction is 0 when this offset matches the server).
|
||||
timezone: currentChicagoOffset(),
|
||||
},
|
||||
localDbConfig: {
|
||||
// PostgreSQL config for local
|
||||
|
||||
@@ -58,7 +58,12 @@ async function setupConnections(sshConfig) {
|
||||
"SELECT TIMESTAMPDIFF(SECOND, NOW(), UTC_TIMESTAMP()) as utcDiffSec"
|
||||
);
|
||||
const mysqlOffsetMs = -utcDiffSec * 1000; // MySQL UTC offset in ms (e.g., -21600000 for CST)
|
||||
const driverOffsetMs = -5 * 3600 * 1000; // Driver's -05:00 in ms (-18000000)
|
||||
// Driver offset from the actual config (e.g. '-06:00' in winter, '-05:00'
|
||||
// in summer — set dynamically in import-from-prod.js).
|
||||
const tzMatch = /^([+-])(\d{2}):(\d{2})$/.exec(sshConfig.prodDbConfig.timezone || '');
|
||||
const driverOffsetMs = tzMatch
|
||||
? (tzMatch[1] === '-' ? -1 : 1) * (Number(tzMatch[2]) * 3600 + Number(tzMatch[3]) * 60) * 1000
|
||||
: -5 * 3600 * 1000;
|
||||
const tzCorrectionMs = driverOffsetMs - mysqlOffsetMs;
|
||||
// CST (winter): -18000000 - (-21600000) = +3600000 (1 hour correction needed)
|
||||
// CDT (summer): -18000000 - (-18000000) = 0 (no correction needed)
|
||||
@@ -79,8 +84,13 @@ async function setupConnections(sshConfig) {
|
||||
}
|
||||
prodConnection.adjustDateForMySQL = adjustDateForMySQL;
|
||||
|
||||
// Setup PostgreSQL connection pool for local
|
||||
const localPool = new Pool(sshConfig.localDbConfig);
|
||||
// Setup PostgreSQL connection pool for local.
|
||||
// Pin the session timezone to business time (see docs/TIME.md) so import
|
||||
// writes and any CURRENT_DATE/NOW()::date logic mean Chicago dates.
|
||||
const localPool = new Pool({
|
||||
options: '-c TimeZone=America/Chicago',
|
||||
...sshConfig.localDbConfig,
|
||||
});
|
||||
|
||||
// Test the PostgreSQL connection
|
||||
try {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
-- SESSION-TZ ASSUMPTION: this script runs with TimeZone=America/Chicago
|
||||
-- (pinned in the pool config and as the inventory_db database default).
|
||||
-- CURRENT_DATE / NOW()::date / ts::date here mean the BUSINESS date
|
||||
-- (Chicago calendar day = 1am-Eastern day rule). See docs/TIME.md.
|
||||
-- Description: Calculates and updates aggregated metrics per brand.
|
||||
-- Dependencies: product_metrics, products, calculate_status table.
|
||||
-- Frequency: Daily (after product_metrics update).
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
-- SESSION-TZ ASSUMPTION: this script runs with TimeZone=America/Chicago
|
||||
-- (pinned in the pool config and as the inventory_db database default).
|
||||
-- CURRENT_DATE / NOW()::date / ts::date here mean the BUSINESS date
|
||||
-- (Chicago calendar day = 1am-Eastern day rule). See docs/TIME.md.
|
||||
-- Description: Calculates and updates aggregated metrics per category with hierarchy rollups.
|
||||
-- Dependencies: product_metrics, products, categories, product_categories, category_hierarchy, calculate_status table.
|
||||
-- Frequency: Daily (after product_metrics update).
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
-- SESSION-TZ ASSUMPTION: this script runs with TimeZone=America/Chicago
|
||||
-- (pinned in the pool config and as the inventory_db database default).
|
||||
-- CURRENT_DATE / NOW()::date / ts::date here mean the BUSINESS date
|
||||
-- (Chicago calendar day = 1am-Eastern day rule). See docs/TIME.md.
|
||||
-- Description: Calculates and updates aggregated metrics per vendor.
|
||||
-- Dependencies: product_metrics, products, purchase_orders, calculate_status table.
|
||||
-- Frequency: Daily (after product_metrics update).
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
-- SESSION-TZ ASSUMPTION: this script runs with TimeZone=America/Chicago
|
||||
-- (pinned in the pool config and as the inventory_db database default).
|
||||
-- CURRENT_DATE / NOW()::date / ts::date here mean the BUSINESS date
|
||||
-- (Chicago calendar day = 1am-Eastern day rule). See docs/TIME.md.
|
||||
-- Description: Calculates and updates daily aggregated product data.
|
||||
-- Self-healing: detects gaps (missing snapshots), stale data (snapshot
|
||||
-- aggregates that don't match source tables after backfills), and always
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
-- SESSION-TZ ASSUMPTION: this script runs with TimeZone=America/Chicago
|
||||
-- (pinned in the pool config and as the inventory_db database default).
|
||||
-- CURRENT_DATE / NOW()::date / ts::date here mean the BUSINESS date
|
||||
-- (Chicago calendar day = 1am-Eastern day rule). See docs/TIME.md.
|
||||
-- Description: Populates lifecycle forecast columns on product_metrics from product_forecasts.
|
||||
-- Runs AFTER update_product_metrics.sql so that lead time / days of stock settings are available.
|
||||
-- Dependencies: product_metrics (fully populated), product_forecasts, settings tables.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
-- SESSION-TZ ASSUMPTION: this script runs with TimeZone=America/Chicago
|
||||
-- (pinned in the pool config and as the inventory_db database default).
|
||||
-- CURRENT_DATE / NOW()::date / ts::date here mean the BUSINESS date
|
||||
-- (Chicago calendar day = 1am-Eastern day rule). See docs/TIME.md.
|
||||
-- Description: Calculates metrics that don't need hourly updates, like ABC class
|
||||
-- and average lead time.
|
||||
-- Dependencies: product_metrics, purchase_orders, settings_global, calculate_status.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
-- SESSION-TZ ASSUMPTION: this script runs with TimeZone=America/Chicago
|
||||
-- (pinned in the pool config and as the inventory_db database default).
|
||||
-- CURRENT_DATE / NOW()::date / ts::date here mean the BUSINESS date
|
||||
-- (Chicago calendar day = 1am-Eastern day rule). See docs/TIME.md.
|
||||
-- Description: Calculates and updates the main product_metrics table based on current data
|
||||
-- and aggregated daily snapshots. Uses UPSERT for idempotency.
|
||||
-- Dependencies: Core import tables, daily_product_snapshots, configuration tables, calculate_status.
|
||||
|
||||
@@ -10,6 +10,9 @@ const dbConfig = {
|
||||
database: process.env.DB_NAME,
|
||||
port: process.env.DB_PORT || 5432,
|
||||
ssl: process.env.DB_SSL === 'true',
|
||||
// Pin the session timezone to business time (see docs/TIME.md) — the
|
||||
// metrics SQL window anchors (CURRENT_DATE etc.) must mean Chicago dates.
|
||||
options: '-c TimeZone=America/Chicago',
|
||||
// Add performance optimizations
|
||||
max: 10, // connection pool max size
|
||||
idleTimeoutMillis: 30000,
|
||||
|
||||
Reference in New Issue
Block a user