diff --git a/CLAUDE.md b/CLAUDE.md index 9cc8d22..5d318bf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,7 +31,7 @@ routes/ operations.js scale, recode, clone, undo — the core forecast ops log.js GET /api/versions/:id/log, DELETE /api/log/:logid layouts.js Named pivot layouts — list per version, create, patch, delete - plans.js Planned load segments — list, create, patch, delete, shift-years + plans.js Planned load segments — list, create, patch, delete lib/ sql_generator.js buildFilterClause, token substitution helpers auth.js scrypt hash/verify, requireAuth, sessionUser; `node lib/auth.js hash` CLI @@ -376,14 +376,6 @@ from its `baseline`/`reference` log entries when it does not — which is every version built before plans existed. `pf.log.params` already stores `filters`, `raw_where` and `date_offset`, so this is a read, not a reconstruction. -**`POST /versions/:id/plans/shift-years`** adds a whole number of years to every -*unrun* plan's `date_offset` and to any bare four-digit `20xx` in its filter -**values**. Values only, and only a bare year: a season reads `2027`, a month -reads `04 - Sep`, and a part number that happens to look like a year would be -corrupted by anything looser. Cloning a definition forward a year is the reason -plans exist, and doing it by hand across eight segments is where the mistakes -come from. - **Run all stops at the first failure.** The segments of a definition are not independent — a baseline followed by references that date-shift off it — so carrying on past a broken one builds something nobody described. diff --git a/routes/plans.js b/routes/plans.js index 24384ba..5a03614 100644 --- a/routes/plans.js +++ b/routes/plans.js @@ -121,64 +121,5 @@ module.exports = function(pool) { } }); - // Shift every unrun plan's date_offset and any four-digit year in its filter - // values by a whole number of years. Cloning a definition forward is the - // reason plans exist, and doing it by hand across eight segments is where - // the mistakes come from. - // - // Years in filter *values* only, and only a bare four-digit 20xx: a season - // reads "2027", a month reads "04 - Sep", and a part number that happens to - // look like a year would be corrupted by anything looser. - router.post('/versions/:id/plans/shift-years', async (req, res) => { - const versionId = parseInt(req.params.id); - const years = parseInt(req.body?.years); - if (!Number.isInteger(years) || years === 0) { - return res.status(400).json({ error: 'years must be a non-zero whole number' }); - } - const bumpYear = (v) => (typeof v === 'string' && /^20\d{2}$/.test(v.trim())) - ? String(parseInt(v, 10) + years) - : v; - const bumpOffset = (off) => { - // '0 days' stays put: an offset of nothing is not a date to shift. - const m = /^\s*(-?\d+)\s+year/.exec(off || ''); - if (!m) return off; - return `${parseInt(m[1], 10) + years} years`; - }; - const client = await pool.connect(); - try { - await client.query('BEGIN'); - const { rows } = await client.query( - `SELECT * FROM pf.load_plan - WHERE version_id = $1 AND run_logid IS NULL - ORDER BY seq, id`, - [versionId] - ); - const out = []; - for (const p of rows) { - const filters = Array.isArray(p.filters) - ? p.filters.map(group => group.map(f => ({ - ...f, - values: Array.isArray(f.values) ? f.values.map(bumpYear) : f.values - }))) - : p.filters; - const upd = await client.query( - `UPDATE pf.load_plan - SET filters = $2::jsonb, date_offset = $3, updated_at = now() - WHERE id = $1 - RETURNING *`, - [p.id, filters ? JSON.stringify(filters) : null, bumpOffset(p.date_offset)] - ); - out.push(upd.rows[0]); - } - await client.query('COMMIT'); - res.json({ shifted: out.length, plans: out }); - } catch (err) { - await client.query('ROLLBACK').catch(() => {}); - res.status(500).json({ error: err.message }); - } finally { - client.release(); - } - }); - return router; }; diff --git a/ui/src/views/Baseline.jsx b/ui/src/views/Baseline.jsx index bf07df8..570a51d 100644 --- a/ui/src/views/Baseline.jsx +++ b/ui/src/views/Baseline.jsx @@ -102,7 +102,6 @@ export default function Baseline({ sources = [], sourceId, versions = [], versio const [plans, setPlans] = useState([]) const [editingPlanId, setEditingPlanId] = useState(null) const [runningAll, setRunningAll] = useState(false) - const [shiftYears, setShiftYears] = useState('1') // segment form const [segType, setSegType] = useState('baseline') @@ -277,22 +276,6 @@ export default function Baseline({ sources = [], sourceId, versions = [], versio flash(`Loaded ${done} segment${done === 1 ? '' : 's'}`) } - async function shiftPlanYears() { - const years = parseInt(shiftYears) - if (!Number.isInteger(years) || years === 0) { flash('Enter a whole number of years', 'error'); return } - try { - const res = await fetch(`/api/versions/${versionId}/plans/shift-years`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ years }), - }) - const data = await res.json() - if (!res.ok) { flash(data.error, 'error'); return } - loadPlans() - flash(`Shifted ${data.shifted} plan${data.shifted === 1 ? '' : 's'} by ${years > 0 ? '+' : ''}${years} year${Math.abs(years) === 1 ? '' : 's'}`) - } catch (err) { flash(err.message, 'error') } - } - async function deletePlan(plan) { await fetch(`/api/plans/${plan.id}`, { method: 'DELETE' }) if (editingPlanId === plan.id) cancelEdit() @@ -687,14 +670,6 @@ export default function Baseline({ sources = [], sourceId, versions = [], versio {plans.filter(p => !p.run_logid).length} of {plans.length} not yet loaded
-
- Shift years - setShiftYears(e.target.value)} - className="border border-gray-200 rounded px-1 py-0.5 w-12 text-center" /> - -