v0.1.9: per-company progress reviews + deck-quality guidance

progress.py reads the whole graded ledger and answers two questions the
per-deck scorecard can't: is the company actually progressing (composite/
BDEF-category/KPI trajectories, recurring vs resolved flags), and is the
material good enough to judge them by — a deterministic gap engine spots
what the decks are NOT showing (no profitability visibility, untargeted
KPIs, no forward guidance, broken forecast chain, silently dropped KPIs,
thin-evidence BDEF categories, no board asks) and renders each gap as a
concrete, paste-ready request for the next deck.

Served live from the ledger (no GPU) at /api/companies/{slug}/progress(.md),
written to /data/ledger/<slug>/PROGRESS.md + /data/reports/latest-progress.md
after each graded deck, and viewable/downloadable from the dashboard company
card ("View progress review"). 18 new tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jonathan Kirkwood
2026-07-31 20:50:41 -05:00
co-authored by Claude Fable 5
parent 506e6c79bd
commit 9b9c7e58c1
8 changed files with 792 additions and 14 deletions
+24 -8
View File
@@ -149,6 +149,7 @@
<div id="coDecks"><div class="muted"></div></div>
<div class="row">
<button onclick="toggleScorecard()" id="scBtn">View SCORECARD.md</button>
<button onclick="toggleProgress()" id="prBtn">View progress review</button>
</div>
<div id="coViewer" style="margin-top:10px"></div>
</div>
@@ -419,11 +420,15 @@ async function openCompany(slug,silent){
}catch(e){ if(!silent) alert('load company failed: '+(e.message||e)); }
}
function closeCompany(){
currentSlug=null; scorecardOpen=false;
currentSlug=null; resetViewerToggles();
document.getElementById('coViewer').innerHTML='';
document.getElementById('scBtn').textContent='View SCORECARD.md';
document.getElementById('companyCard').style.display='none';
}
function resetViewerToggles(){
scorecardOpen=false; progressOpen=false;
document.getElementById('scBtn').textContent='View SCORECARD.md';
document.getElementById('prBtn').textContent='View progress review';
}
let currentRecs=[];
function renderDetail(d,changed){
@@ -469,9 +474,8 @@ function renderDetail(d,changed){
// Only reset the viewer when switching companies — periodic re-renders must
// not close whatever report the user has open.
if(changed){
scorecardOpen=false;
resetViewerToggles();
document.getElementById('coViewer').innerHTML='';
document.getElementById('scBtn').textContent='View SCORECARD.md';
}
}
async function deleteCompany(){
@@ -525,11 +529,11 @@ async function viewDeckJson(deckId){
deckBase(deckId), `${currentSlug}-${deckId}.json`);
}catch(e){ alert('load record failed: '+(e.message||e)); }
}
let scorecardOpen=false;
let scorecardOpen=false, progressOpen=false;
async function toggleScorecard(){
if(!currentSlug) return;
const v=document.getElementById('coViewer'), btn=document.getElementById('scBtn');
if(scorecardOpen){ v.innerHTML=''; scorecardOpen=false; btn.textContent='View SCORECARD.md'; return; }
if(scorecardOpen){ v.innerHTML=''; resetViewerToggles(); return; }
try{
const url='/api/companies/'+encodeURIComponent(currentSlug)+'/scorecard';
const r=await fetch(url);
@@ -539,9 +543,21 @@ async function toggleScorecard(){
scorecardOpen=true; btn.textContent='Hide SCORECARD.md';
}catch(e){ alert(e); }
}
async function toggleProgress(){
if(!currentSlug) return;
const v=document.getElementById('coViewer'), btn=document.getElementById('prBtn');
if(progressOpen){ v.innerHTML=''; resetViewerToggles(); return; }
try{
const url='/api/companies/'+encodeURIComponent(currentSlug)+'/progress.md';
const r=await fetch(url);
const t=await r.text();
setViewer('Progress review', r.ok? mdToHtml(t) : `<pre class="tall">${esc('error: '+t)}</pre>`,
url, `${currentSlug}-PROGRESS.md`);
progressOpen=true; btn.textContent='Hide progress review';
}catch(e){ alert(e); }
}
function setViewer(title,html,dlHref,dlName){
scorecardOpen=false;
document.getElementById('scBtn').textContent='View SCORECARD.md';
resetViewerToggles();
const dl=dlHref?`<a class="mini" style="margin-left:auto" href="${dlHref}" `+
`download="${esc(dlName||'report.txt')}" title="Download">⬇ download</a>`:'';
document.getElementById('coViewer').innerHTML=