v0.1.7: delete companies for re-evaluation
- DELETE /api/companies/{slug}: wipes the ledger (deck records, scorecard,
report md), sweeps per-job report and processed-deck copies; optional
restore_decks moves the graded originals from /data/processed back into
the inbox for a fresh Grade Decks run; 409 while a job is running;
config-registered companies reappear as empty rows (aliases + pinned
targets kept)
- Dashboard: "delete company…" danger button on the company card with a
two-step confirm (delete, then restore-decks choice)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
32d5d7f373
commit
7ec5222834
@@ -120,7 +120,7 @@ Canonical repo: `https://gitea.ten31.ai/Ten31AI/boardroom-map`.
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
**v0.1.6 — live in production.** Deployed on a StartOS box driving a DGX Spark
|
**v0.1.7 — live in production.** Deployed on a StartOS box driving a DGX Spark
|
||||||
in single-spark air-gapped mode (gemma-4-31B panel: munger-lens /
|
in single-spark air-gapped mode (gemma-4-31B panel: munger-lens /
|
||||||
girdley-operator / buffett-owner). First full grading run completed
|
girdley-operator / buffett-owner). First full grading run completed
|
||||||
2026-07-29: a three-deck company history graded end-to-end into a running
|
2026-07-29: a three-deck company history graded end-to-end into a running
|
||||||
@@ -135,7 +135,10 @@ timeouts for ~3.6 tok/s local generation; v0.1.5 fixed the dashboard viewer
|
|||||||
(stays open across background refresh) and added report/JSON/scorecard
|
(stays open across background refresh) and added report/JSON/scorecard
|
||||||
downloads; v0.1.6 made reports human-friendly — rendered markdown in the
|
downloads; v0.1.6 made reports human-friendly — rendered markdown in the
|
||||||
dashboard, an at-a-glance strip per deck report, and a concise "At a glance"
|
dashboard, an at-a-glance strip per deck report, and a concise "At a glance"
|
||||||
summary table atop every generated DECK_REPORT.md.
|
summary table atop every generated DECK_REPORT.md; v0.1.7 added company
|
||||||
|
deletion from the dashboard (wipes the graded history, optionally restores
|
||||||
|
the graded deck files to the inbox) so an evaluation can be re-run from
|
||||||
|
scratch with a different model panel.
|
||||||
|
|
||||||
Known optimization not yet done: the wave is torn down per deck, so the 31B
|
Known optimization not yet done: the wave is torn down per deck, so the 31B
|
||||||
reloads from disk (~6 min) between decks even when the model set is unchanged.
|
reloads from disk (~6 min) between decks even when the model set is unchanged.
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ LEDGER_DIR = os.path.join(DATA_DIR, "ledger")
|
|||||||
runner = jobs.runner
|
runner = jobs.runner
|
||||||
INBOX = getattr(jobs, "INBOX", os.path.join(DATA_DIR, "inbox"))
|
INBOX = getattr(jobs, "INBOX", os.path.join(DATA_DIR, "inbox"))
|
||||||
REPORTS_DIR = getattr(jobs, "REPORTS_DIR", os.path.join(DATA_DIR, "reports"))
|
REPORTS_DIR = getattr(jobs, "REPORTS_DIR", os.path.join(DATA_DIR, "reports"))
|
||||||
|
PROCESSED = getattr(jobs, "PROCESSED", os.path.join(DATA_DIR, "processed"))
|
||||||
|
|
||||||
app = FastAPI(title="Boardroom Map Orchestrator")
|
app = FastAPI(title="Boardroom Map Orchestrator")
|
||||||
templates = Jinja2Templates(directory=os.path.join(os.path.dirname(__file__), "templates"))
|
templates = Jinja2Templates(directory=os.path.join(os.path.dirname(__file__), "templates"))
|
||||||
@@ -348,6 +349,63 @@ def company_detail(slug: str):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete("/api/companies/{slug}")
|
||||||
|
def delete_company(slug: str, restore_decks: bool = False):
|
||||||
|
"""Wipe a company's graded data so it can be re-evaluated from scratch.
|
||||||
|
|
||||||
|
Removes the ledger (deck records, scorecard, report markdown) plus the
|
||||||
|
per-job report and processed-deck copies. With restore_decks, the original
|
||||||
|
deck files graded earlier are moved from /data/processed back into the
|
||||||
|
company's inbox folder first, ready for a fresh Grade Decks run. A
|
||||||
|
config-registered company reappears as an empty row (name, aliases and
|
||||||
|
pinned targets kept); an auto-created one disappears entirely.
|
||||||
|
"""
|
||||||
|
import shutil
|
||||||
|
slug = os.path.basename(slug)
|
||||||
|
if runner.phase in jobs.RUNNING_PHASES:
|
||||||
|
raise HTTPException(409, "A grading job is running — wait for it to finish.")
|
||||||
|
if not os.path.isdir(os.path.join(LEDGER_DIR, slug)) \
|
||||||
|
and not os.path.isdir(os.path.join(INBOX, slug)):
|
||||||
|
raise HTTPException(404, "no such company")
|
||||||
|
|
||||||
|
restored = 0
|
||||||
|
if restore_decks and os.path.isdir(PROCESSED):
|
||||||
|
dest_dir = os.path.join(INBOX, slug)
|
||||||
|
for job_dir in sorted(os.listdir(PROCESSED)):
|
||||||
|
src_dir = os.path.join(PROCESSED, job_dir, slug)
|
||||||
|
if not os.path.isdir(src_dir):
|
||||||
|
continue
|
||||||
|
os.makedirs(dest_dir, exist_ok=True)
|
||||||
|
for fn in sorted(os.listdir(src_dir)):
|
||||||
|
src = os.path.join(src_dir, fn)
|
||||||
|
dest = os.path.join(dest_dir, fn)
|
||||||
|
if os.path.isfile(src) and not os.path.exists(dest):
|
||||||
|
try:
|
||||||
|
shutil.move(src, dest)
|
||||||
|
restored += 1
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
removed = {"ledger": False, "report_dirs": 0, "processed_dirs": 0}
|
||||||
|
led_dir = os.path.join(LEDGER_DIR, slug)
|
||||||
|
if os.path.isdir(led_dir):
|
||||||
|
shutil.rmtree(led_dir, ignore_errors=True)
|
||||||
|
removed["ledger"] = True
|
||||||
|
for base, key in ((REPORTS_DIR, "report_dirs"), (PROCESSED, "processed_dirs")):
|
||||||
|
if not os.path.isdir(base):
|
||||||
|
continue
|
||||||
|
for job_dir in os.listdir(base):
|
||||||
|
d = os.path.join(base, job_dir, slug)
|
||||||
|
if os.path.isdir(d):
|
||||||
|
shutil.rmtree(d, ignore_errors=True)
|
||||||
|
removed[key] += 1
|
||||||
|
runner.log(f"[api] company '{slug}' deleted "
|
||||||
|
f"(ledger={removed['ledger']}, report dirs={removed['report_dirs']}, "
|
||||||
|
f"processed dirs={removed['processed_dirs']}, "
|
||||||
|
f"decks restored to inbox={restored})")
|
||||||
|
return {"ok": True, "removed": removed, "restored_decks": restored}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/companies/{slug}/scorecard", response_class=PlainTextResponse)
|
@app.get("/api/companies/{slug}/scorecard", response_class=PlainTextResponse)
|
||||||
def company_scorecard(slug: str):
|
def company_scorecard(slug: str):
|
||||||
slug = os.path.basename(slug)
|
slug = os.path.basename(slug)
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
padding:8px 12px;font-size:13px;cursor:pointer} button:hover{background:#2c3650}
|
padding:8px 12px;font-size:13px;cursor:pointer} button:hover{background:#2c3650}
|
||||||
button.primary{background:#3a2f12;border-color:#6b551f;color:#ffdf9a}
|
button.primary{background:#3a2f12;border-color:#6b551f;color:#ffdf9a}
|
||||||
button.mini{padding:2px 8px;font-size:11px;border-radius:7px}
|
button.mini{padding:2px 8px;font-size:11px;border-radius:7px}
|
||||||
|
button.danger{background:#2c1416;border-color:#552222;color:var(--bad)}
|
||||||
|
button.danger:hover{background:#3b1414}
|
||||||
a.mini{display:inline-block;background:#222a3a;border:1px solid var(--edge);border-radius:7px;
|
a.mini{display:inline-block;background:#222a3a;border:1px solid var(--edge);border-radius:7px;
|
||||||
padding:2px 8px;font-size:11px;color:var(--ink);text-decoration:none;cursor:pointer}
|
padding:2px 8px;font-size:11px;color:var(--ink);text-decoration:none;cursor:pointer}
|
||||||
a.mini:hover{background:#2c3650}
|
a.mini:hover{background:#2c3650}
|
||||||
@@ -127,7 +129,8 @@
|
|||||||
<h2 style="display:flex;align-items:center;gap:10px">Company —
|
<h2 style="display:flex;align-items:center;gap:10px">Company —
|
||||||
<span id="coName" style="color:var(--ink);letter-spacing:0;text-transform:none;font-size:15px"></span>
|
<span id="coName" style="color:var(--ink);letter-spacing:0;text-transform:none;font-size:15px"></span>
|
||||||
<span id="coBadges"></span>
|
<span id="coBadges"></span>
|
||||||
<button class="mini" style="margin-left:auto" onclick="closeCompany()">close</button>
|
<button class="mini danger" style="margin-left:auto" onclick="deleteCompany()">delete company…</button>
|
||||||
|
<button class="mini" onclick="closeCompany()">close</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="coTrend"></div>
|
<div id="coTrend"></div>
|
||||||
<div class="detail-grid">
|
<div class="detail-grid">
|
||||||
@@ -471,6 +474,25 @@ function renderDetail(d,changed){
|
|||||||
document.getElementById('scBtn').textContent='View SCORECARD.md';
|
document.getElementById('scBtn').textContent='View SCORECARD.md';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function deleteCompany(){
|
||||||
|
if(!currentSlug) return;
|
||||||
|
const name=(companiesCache.find(c=>c.slug===currentSlug)||{}).name||currentSlug;
|
||||||
|
if(!confirm(`Delete "${name}"?\n\nThis wipes its graded history: all deck records, `+
|
||||||
|
`the scorecard, and report copies. A company registered in Configure Companies `+
|
||||||
|
`reappears as an empty row (aliases and pinned targets kept), ready to re-grade.`)) return;
|
||||||
|
const restore=confirm('Move its already-graded deck files back into the inbox '+
|
||||||
|
'so they can be re-graded?\n\nOK = yes, put decks back · Cancel = no, just delete');
|
||||||
|
try{
|
||||||
|
const r=await fetch('/api/companies/'+encodeURIComponent(currentSlug)+
|
||||||
|
(restore?'?restore_decks=true':''),{method:'DELETE'});
|
||||||
|
const t=await r.text();
|
||||||
|
if(!r.ok){ alert('Delete failed: '+t.slice(0,300)); return; }
|
||||||
|
const d=JSON.parse(t);
|
||||||
|
alert(`Deleted ${name}.`+(d.restored_decks?` ${d.restored_decks} deck file(s) `+
|
||||||
|
`restored to the inbox — hit "Grade decks" to re-run.`:''));
|
||||||
|
closeCompany(); refresh(); loadCompanies();
|
||||||
|
}catch(e){ alert('Delete failed: '+(e.message||e)); }
|
||||||
|
}
|
||||||
function deckBase(deckId){
|
function deckBase(deckId){
|
||||||
return '/api/companies/'+encodeURIComponent(currentSlug)+'/decks/'+encodeURIComponent(deckId);
|
return '/api/companies/'+encodeURIComponent(currentSlug)+'/decks/'+encodeURIComponent(deckId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import { v_0_1_3 } from './v_0_1_3'
|
|||||||
import { v_0_1_4 } from './v_0_1_4'
|
import { v_0_1_4 } from './v_0_1_4'
|
||||||
import { v_0_1_5 } from './v_0_1_5'
|
import { v_0_1_5 } from './v_0_1_5'
|
||||||
import { v_0_1_6 } from './v_0_1_6'
|
import { v_0_1_6 } from './v_0_1_6'
|
||||||
|
import { v_0_1_7 } from './v_0_1_7'
|
||||||
|
|
||||||
/** The current version MUST be the first argument (`current`). */
|
/** The current version MUST be the first argument (`current`). */
|
||||||
export const versions = VersionGraph.of({
|
export const versions = VersionGraph.of({
|
||||||
current: v_0_1_6,
|
current: v_0_1_7,
|
||||||
other: [v_0_1_5, v_0_1_4, v_0_1_3, v_0_1_2, v_0_1_1, v_0_1_0],
|
other: [v_0_1_6, v_0_1_5, v_0_1_4, v_0_1_3, v_0_1_2, v_0_1_1, v_0_1_0],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { VersionInfo } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
|
/** Company deletion for re-evaluation. ExVer form `<upstream>:<downstream>`. */
|
||||||
|
export const v_0_1_7 = VersionInfo.of({
|
||||||
|
version: '0.1.7:0',
|
||||||
|
releaseNotes:
|
||||||
|
'Companies can now be deleted from the dashboard (delete button on the ' +
|
||||||
|
'company card) so an evaluation can be re-run from scratch with different ' +
|
||||||
|
'models or graders. Deletion wipes the graded history — deck records, ' +
|
||||||
|
'scorecard, and per-job report copies — and optionally moves the ' +
|
||||||
|
'already-graded deck files from /data/processed back into the inbox, ' +
|
||||||
|
'ready for a fresh Grade Decks run. Companies registered in Configure ' +
|
||||||
|
'Companies reappear as empty rows with their aliases and pinned targets ' +
|
||||||
|
'kept; deletion is blocked while a grading job is running.',
|
||||||
|
migrations: {},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user