shak3008 commited on
Commit
8f7eb4f
·
1 Parent(s): 57812c2

feat: auto-approve on CONTINUE path + improved knowledge cards

Browse files

1. Auto-commit when no review needed:
- When decision is CONTINUE (all rules pass), the complete node
auto-approves all PENDING proposals for that document version
- Creates commit records (audit trail preserved)
- Promotes knowledge items from PENDING → ACTIVE
- Items only stay PENDING if human review is required

2. Knowledge page visual overhaul:
- Title is now small/muted (context label)
- Value is the hero (larger, bold, primary color)
- Confidence shown as a colored bar (green/yellow/red)
- Source and date pushed to subtle footer line
- Type and status badges stay compact in header

backend/app/workflow/nodes/complete.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  from app.workflow.state import WorkflowState
2
 
3
 
@@ -6,10 +8,82 @@ def complete(
6
  ) -> WorkflowState:
7
  """
8
  Marks the workflow complete.
 
 
 
 
 
 
 
9
  """
 
 
 
 
 
 
10
 
11
  state.current_node = "COMPLETED"
12
-
13
  state.completed = True
14
 
15
- return state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
  from app.workflow.state import WorkflowState
4
 
5
 
 
8
  ) -> WorkflowState:
9
  """
10
  Marks the workflow complete.
11
+
12
+ If the decision was CONTINUE (all validation passed, no human review
13
+ needed), auto-approves all PENDING proposals for this document version
14
+ and promotes the associated knowledge items to ACTIVE.
15
+
16
+ This ensures that documents which pass all rules get their knowledge
17
+ committed without requiring manual approval.
18
  """
19
+ from sqlalchemy.orm import Session
20
+ from app.database.database import SessionLocal
21
+ from app.models.proposal import Proposal, ProposalStatus
22
+ from app.models.knowledge_item import KnowledgeItem, KnowledgeStatus
23
+ from app.models.commit import Commit
24
+ from datetime import datetime, timezone
25
 
26
  state.current_node = "COMPLETED"
 
27
  state.completed = True
28
 
29
+ # Only auto-commit if decision was CONTINUE (not human-reviewed)
30
+ decision = state.decision.get("decision") if state.decision else None
31
+ if decision != "CONTINUE":
32
+ return state
33
+
34
+ db: Session = SessionLocal()
35
+ try:
36
+ # Find all PENDING proposals for this document version's knowledge items
37
+ knowledge_items = (
38
+ db.query(KnowledgeItem)
39
+ .filter(
40
+ KnowledgeItem.workspace_id == state.workspace_id,
41
+ KnowledgeItem.document_version_id == state.document_version_id,
42
+ )
43
+ .all()
44
+ )
45
+
46
+ ki_ids = [ki.id for ki in knowledge_items]
47
+
48
+ if not ki_ids:
49
+ return state
50
+
51
+ proposals = (
52
+ db.query(Proposal)
53
+ .filter(
54
+ Proposal.workspace_id == state.workspace_id,
55
+ Proposal.knowledge_item_id.in_(ki_ids),
56
+ Proposal.status == ProposalStatus.PENDING,
57
+ )
58
+ .all()
59
+ )
60
+
61
+ now = datetime.now(timezone.utc)
62
+
63
+ for proposal in proposals:
64
+ proposal.status = ProposalStatus.APPROVED
65
+ proposal.reviewed_at = now
66
+
67
+ # Create an auto-commit record
68
+ commit = Commit(
69
+ workspace_id=proposal.workspace_id,
70
+ proposal_id=proposal.id,
71
+ committed_by=state.workspace_id, # system commit
72
+ message=f"Auto-approved: {proposal.summary}",
73
+ changes=proposal.proposed_changes,
74
+ )
75
+ db.add(commit)
76
+
77
+ # Promote knowledge items to ACTIVE
78
+ for ki in knowledge_items:
79
+ if ki.status == KnowledgeStatus.PENDING:
80
+ ki.status = KnowledgeStatus.ACTIVE
81
+
82
+ db.commit()
83
+
84
+ except Exception:
85
+ db.rollback()
86
+ finally:
87
+ db.close()
88
+
89
+ return state
frontend/src/pages/Knowledge.css CHANGED
@@ -23,21 +23,32 @@
23
  .dw-knowledge__item-header {
24
  display: flex;
25
  align-items: center;
26
- gap: var(--space-3);
27
- margin-bottom: var(--space-2);
28
  }
29
 
30
  .dw-knowledge__item-title {
31
- font-weight: 600;
32
- font-size: var(--text-base);
33
- color: var(--color-text-primary);
34
  margin: 0;
 
 
35
  }
36
 
37
  .dw-knowledge__item-value {
 
 
 
 
 
 
 
 
38
  font-size: var(--text-sm);
39
  color: var(--color-text-secondary);
40
- margin: var(--space-1) 0;
 
41
  }
42
 
43
  .dw-knowledge__item-meta {
@@ -46,4 +57,41 @@
46
  font-size: var(--text-xs);
47
  color: var(--color-text-muted);
48
  margin-top: var(--space-2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
 
23
  .dw-knowledge__item-header {
24
  display: flex;
25
  align-items: center;
26
+ gap: var(--space-2);
27
+ margin-bottom: var(--space-1);
28
  }
29
 
30
  .dw-knowledge__item-title {
31
+ font-size: var(--text-xs);
32
+ font-weight: 500;
33
+ color: var(--color-text-muted);
34
  margin: 0;
35
+ text-transform: uppercase;
36
+ letter-spacing: 0.02em;
37
  }
38
 
39
  .dw-knowledge__item-value {
40
+ font-size: var(--text-base);
41
+ font-weight: 600;
42
+ color: var(--color-text-primary);
43
+ margin: var(--space-1) 0;
44
+ line-height: var(--leading-tight);
45
+ }
46
+
47
+ .dw-knowledge__item-summary {
48
  font-size: var(--text-sm);
49
  color: var(--color-text-secondary);
50
+ margin: var(--space-1) 0 0;
51
+ font-style: italic;
52
  }
53
 
54
  .dw-knowledge__item-meta {
 
57
  font-size: var(--text-xs);
58
  color: var(--color-text-muted);
59
  margin-top: var(--space-2);
60
+ padding-top: var(--space-2);
61
+ border-top: 1px solid var(--color-border);
62
+ }
63
+
64
+ .dw-knowledge__confidence-bar {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: var(--space-2);
68
+ font-size: var(--text-xs);
69
+ color: var(--color-text-muted);
70
+ }
71
+
72
+ .dw-knowledge__confidence-track {
73
+ width: 48px;
74
+ height: 4px;
75
+ border-radius: 2px;
76
+ background: var(--color-border);
77
+ overflow: hidden;
78
+ }
79
+
80
+ .dw-knowledge__confidence-fill {
81
+ height: 100%;
82
+ border-radius: 2px;
83
+ background: var(--color-accent);
84
+ transition: width var(--transition-base);
85
+ }
86
+
87
+ .dw-knowledge__confidence-fill--high {
88
+ background: var(--color-success);
89
+ }
90
+
91
+ .dw-knowledge__confidence-fill--medium {
92
+ background: var(--color-warning);
93
+ }
94
+
95
+ .dw-knowledge__confidence-fill--low {
96
+ background: var(--color-danger);
97
  }
frontend/src/pages/Knowledge.jsx CHANGED
@@ -26,6 +26,24 @@ const STATUS_TONE = {
26
  const TYPES = ["", "ENTITY", "CLAIM", "METHOD", "METRIC", "DATASET", "OBSERVATION", "DATE"];
27
  const STATUSES = ["", "ACTIVE", "PENDING", "CONFLICTED", "SUPERSEDED", "ARCHIVED", "REJECTED"];
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  export default function Knowledge() {
30
  const { workspace, loading: wsLoading } = useWorkspace();
31
  const [items, setItems] = useState([]);
@@ -99,21 +117,15 @@ export default function Knowledge() {
99
  <div className="dw-knowledge__item-header">
100
  <Badge tone={TYPE_TONE[item.type] || "default"}>{item.type}</Badge>
101
  <Badge tone={STATUS_TONE[item.status] || "default"}>{item.status}</Badge>
102
- {item.confidence != null && (
103
- <span style={{ fontSize: "var(--text-xs)", color: "var(--color-text-muted)" }}>
104
- Confidence: {(item.confidence * 100).toFixed(0)}%
105
- </span>
106
- )}
107
  </div>
108
  <p className="dw-knowledge__item-title">{item.title}</p>
109
  <p className="dw-knowledge__item-value">{item.value}</p>
110
  {item.summary && (
111
- <p className="dw-knowledge__item-value" style={{ fontStyle: "italic" }}>
112
- {item.summary}
113
- </p>
114
  )}
115
  <div className="dw-knowledge__item-meta">
116
- {item.filename && <span>Source: {item.filename}</span>}
117
  {item.created_at && (
118
  <span>{new Date(item.created_at).toLocaleDateString()}</span>
119
  )}
 
26
  const TYPES = ["", "ENTITY", "CLAIM", "METHOD", "METRIC", "DATASET", "OBSERVATION", "DATE"];
27
  const STATUSES = ["", "ACTIVE", "PENDING", "CONFLICTED", "SUPERSEDED", "ARCHIVED", "REJECTED"];
28
 
29
+ function ConfidenceBar({ confidence }) {
30
+ if (confidence == null) return null;
31
+ const pct = Math.round(confidence * 100);
32
+ let cls = "dw-knowledge__confidence-fill";
33
+ if (pct >= 85) cls += " dw-knowledge__confidence-fill--high";
34
+ else if (pct >= 60) cls += " dw-knowledge__confidence-fill--medium";
35
+ else cls += " dw-knowledge__confidence-fill--low";
36
+
37
+ return (
38
+ <span className="dw-knowledge__confidence-bar">
39
+ <span className="dw-knowledge__confidence-track">
40
+ <span className={cls} style={{ width: `${pct}%` }} />
41
+ </span>
42
+ {pct}%
43
+ </span>
44
+ );
45
+ }
46
+
47
  export default function Knowledge() {
48
  const { workspace, loading: wsLoading } = useWorkspace();
49
  const [items, setItems] = useState([]);
 
117
  <div className="dw-knowledge__item-header">
118
  <Badge tone={TYPE_TONE[item.type] || "default"}>{item.type}</Badge>
119
  <Badge tone={STATUS_TONE[item.status] || "default"}>{item.status}</Badge>
120
+ <ConfidenceBar confidence={item.confidence} />
 
 
 
 
121
  </div>
122
  <p className="dw-knowledge__item-title">{item.title}</p>
123
  <p className="dw-knowledge__item-value">{item.value}</p>
124
  {item.summary && (
125
+ <p className="dw-knowledge__item-summary">{item.summary}</p>
 
 
126
  )}
127
  <div className="dw-knowledge__item-meta">
128
+ {item.filename && <span>{item.filename}</span>}
129
  {item.created_at && (
130
  <span>{new Date(item.created_at).toLocaleDateString()}</span>
131
  )}