DIV-45 commited on
Commit
a3d1427
·
verified ·
1 Parent(s): 978ddf1

feat: connect NVIDIA Nemotron evidence reviewer

Browse files

Built with OpenAI Codex: connect the verified private Nemotron Mini 4B ZeroGPU reviewer and publish the GitHub link.

FIELD_NOTES.md CHANGED
@@ -35,6 +35,10 @@ The system deliberately separates three responsibilities:
35
  The models can read and route an investigation. They cannot silently invent a
36
  nutrition value or override the evidence standard.
37
 
 
 
 
 
38
  ## What the investigation agent does
39
 
40
  Each packet creates a claim-dependent investigation plan. A `NO ADDED SUGAR`
@@ -62,6 +66,15 @@ a stratified held-out split. PacketCourt only enables the router after its
62
  measured result is recorded in the model card and its suggestions remain
63
  bounded by deterministic policy fallbacks.
64
 
 
 
 
 
 
 
 
 
 
65
  ## Persuasion Gap
66
 
67
  Claim verification alone was not enough. A `HIGH PROTEIN` claim can be
@@ -79,6 +92,7 @@ the decision with the user.
79
  - `10` transparent investigation traces are exported.
80
  - The vision model has `1.30B` parameters.
81
  - The fine-tuned evidence router has approximately `4.4M` parameters.
 
82
  - The complete product interface is responsive and built on Gradio.
83
 
84
  ## What PacketCourt refuses to claim
 
35
  The models can read and route an investigation. They cannot silently invent a
36
  nutrition value or override the evidence standard.
37
 
38
+ NVIDIA `Nemotron-Mini-4B-Instruct` performs a second, independent review after
39
+ the investigation plan completes. It can identify the highest-priority missing
40
+ evidence, but it cannot alter PacketCourt's deterministic verdict.
41
+
42
  ## What the investigation agent does
43
 
44
  Each packet creates a claim-dependent investigation plan. A `NO ADDED SUGAR`
 
66
  measured result is recorded in the model card and its suggestions remain
67
  bounded by deterministic policy fallbacks.
68
 
69
+ ## A failed Nemotron deployment
70
+
71
+ The first NVIDIA reviewer used `NVIDIA-Nemotron-3-Nano-4B-BF16`. A real
72
+ ZeroGPU probe failed because the hybrid Mamba runtime required a specialized
73
+ CUDA build unavailable in the standard Gradio image. Rather than claim a model
74
+ that did not run, PacketCourt switched to `Nemotron-Mini-4B-Instruct`. The
75
+ replacement completed a real ZeroGPU evidence-gap review before it was
76
+ connected to the product.
77
+
78
  ## Persuasion Gap
79
 
80
  Claim verification alone was not enough. A `HIGH PROTEIN` claim can be
 
92
  - `10` transparent investigation traces are exported.
93
  - The vision model has `1.30B` parameters.
94
  - The fine-tuned evidence router has approximately `4.4M` parameters.
95
+ - The independent NVIDIA Nemotron reviewer has approximately `4B` parameters.
96
  - The complete product interface is responsive and built on Gradio.
97
 
98
  ## What PacketCourt refuses to claim
README.md CHANGED
@@ -12,6 +12,7 @@ tags:
12
  - track:backyard
13
  - sponsor:openbmb
14
  - sponsor:openai
 
15
  - achievement:offbrand
16
  - achievement:tiny
17
  - achievement:sharing
@@ -55,6 +56,8 @@ flowchart LR
55
  M --> A["Investigation agent"]
56
  A --> FR["Fine-tuned evidence router<br/>4.4M parameters"]
57
  FR --> A
 
 
58
  A --> P["Deterministic evidence parser<br/>CPU"]
59
  P --> C["Claim-to-evidence audit"]
60
  P --> N["Whole-packet nutrition math"]
@@ -75,10 +78,11 @@ flowchart LR
75
 
76
  Photo transcription uses the 1.30B-parameter OpenBMB `MiniCPM-V-4.6` through
77
  a private ZeroGPU companion. A fine-tuned 4.4M-parameter evidence router
78
- selects the investigation tools required by each claim. The main CPU Space
79
- performs deterministic evidence auditing, whole-packet calculations,
80
- persuasion-gap analysis, and refusals. ZeroGPU is requested only while reading
81
- photos.
 
82
 
83
  ## What It Audits
84
 
@@ -150,11 +154,13 @@ Current deterministic evaluation result:
150
 
151
  - Main private product: https://huggingface.co/spaces/build-small-hackathon/packetcourt
152
  - Private OpenBMB ZeroGPU vision companion: https://huggingface.co/spaces/build-small-hackathon/packetcourt-vision
 
153
  - Private golden evaluation dataset: https://huggingface.co/datasets/build-small-hackathon/packetcourt-golden-cases
154
  - Public transparent agent traces: https://huggingface.co/datasets/build-small-hackathon/packetcourt-traces
155
  - Fine-tuned evidence router: https://huggingface.co/build-small-hackathon/packetcourt-evidence-router
156
  - Public router training set: https://huggingface.co/datasets/build-small-hackathon/packetcourt-router-training
157
  - Public Field Notes report: https://huggingface.co/datasets/build-small-hackathon/packetcourt-field-notes
 
158
 
159
  ## Safety Boundary
160
 
 
12
  - track:backyard
13
  - sponsor:openbmb
14
  - sponsor:openai
15
+ - sponsor:nvidia
16
  - achievement:offbrand
17
  - achievement:tiny
18
  - achievement:sharing
 
56
  M --> A["Investigation agent"]
57
  A --> FR["Fine-tuned evidence router<br/>4.4M parameters"]
58
  FR --> A
59
+ A --> NV["NVIDIA Nemotron Mini 4B<br/>Independent evidence-gap reviewer"]
60
+ NV --> A
61
  A --> P["Deterministic evidence parser<br/>CPU"]
62
  P --> C["Claim-to-evidence audit"]
63
  P --> N["Whole-packet nutrition math"]
 
78
 
79
  Photo transcription uses the 1.30B-parameter OpenBMB `MiniCPM-V-4.6` through
80
  a private ZeroGPU companion. A fine-tuned 4.4M-parameter evidence router
81
+ selects the investigation tools required by each claim. NVIDIA
82
+ `Nemotron-Mini-4B-Instruct` independently reviews the completed investigation
83
+ for evidence gaps. The main CPU Space performs deterministic evidence auditing,
84
+ whole-packet calculations, persuasion-gap analysis, and refusals. ZeroGPU is
85
+ requested only while running the vision witness or Nemotron reviewer.
86
 
87
  ## What It Audits
88
 
 
154
 
155
  - Main private product: https://huggingface.co/spaces/build-small-hackathon/packetcourt
156
  - Private OpenBMB ZeroGPU vision companion: https://huggingface.co/spaces/build-small-hackathon/packetcourt-vision
157
+ - Private NVIDIA Nemotron reviewer: https://huggingface.co/spaces/build-small-hackathon/packetcourt-nemotron
158
  - Private golden evaluation dataset: https://huggingface.co/datasets/build-small-hackathon/packetcourt-golden-cases
159
  - Public transparent agent traces: https://huggingface.co/datasets/build-small-hackathon/packetcourt-traces
160
  - Fine-tuned evidence router: https://huggingface.co/build-small-hackathon/packetcourt-evidence-router
161
  - Public router training set: https://huggingface.co/datasets/build-small-hackathon/packetcourt-router-training
162
  - Public Field Notes report: https://huggingface.co/datasets/build-small-hackathon/packetcourt-field-notes
163
+ - Public Codex-attributed GitHub repository: https://github.com/N-45div/PacketCourt
164
 
165
  ## Safety Boundary
166
 
app.py CHANGED
@@ -17,6 +17,8 @@ sys.path.insert(0, str(ROOT / "src"))
17
  from packetcourt import audit_packet
18
  from packetcourt.ocr import extract_text
19
  from packetcourt.remote_vision import extract_remote, is_configured
 
 
20
  from packetcourt.samples import SAMPLES
21
  from packetcourt.vlm import model_status
22
 
@@ -26,6 +28,29 @@ class AuditRequest(BaseModel):
26
  back_text: str
27
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def build_gradio_engine() -> gr.Blocks:
30
  with gr.Blocks(title="PacketCourt Engine") as engine:
31
  gr.Markdown(
@@ -37,7 +62,7 @@ def build_gradio_engine() -> gr.Blocks:
37
  back = gr.Textbox(label="Back-label evidence")
38
  output = gr.JSON(label="Evidence case")
39
  gr.Button("Audit").click(
40
- lambda front_text, back_text: audit_packet(front_text, back_text).model_dump(mode="json"),
41
  [front, back],
42
  output,
43
  )
@@ -68,6 +93,11 @@ def model() -> dict:
68
  if os.getenv("PACKETCOURT_ROUTER", "0") == "1"
69
  else "deterministic fallback"
70
  )
 
 
 
 
 
71
  if is_configured():
72
  status.update(
73
  enabled=True,
@@ -79,7 +109,7 @@ def model() -> dict:
79
 
80
  @app.post("/api/audit")
81
  def audit(request: AuditRequest) -> dict:
82
- return audit_packet(request.front_text, request.back_text).model_dump(mode="json")
83
 
84
 
85
  @app.post("/api/ocr")
 
17
  from packetcourt import audit_packet
18
  from packetcourt.ocr import extract_text
19
  from packetcourt.remote_vision import extract_remote, is_configured
20
+ from packetcourt.remote_nemotron import is_configured as nemotron_is_configured
21
+ from packetcourt.remote_nemotron import review as nemotron_review
22
  from packetcourt.samples import SAMPLES
23
  from packetcourt.vlm import model_status
24
 
 
28
  back_text: str
29
 
30
 
31
+ def run_audit(front_text: str, back_text: str):
32
+ result = audit_packet(front_text, back_text)
33
+ if not nemotron_is_configured():
34
+ return result
35
+ snapshot = {
36
+ "claims": [claim.model_dump(mode="json") for claim in result.claims],
37
+ "investigation": result.investigation.model_dump(),
38
+ "nutrition": result.nutrition.model_dump(),
39
+ "ingredients_found": bool(result.ingredients),
40
+ "expiry": result.expiry.model_dump(),
41
+ "limitations": result.limitations,
42
+ }
43
+ try:
44
+ result.agent_review = nemotron_review(snapshot)
45
+ except Exception as exc:
46
+ result.agent_review = {
47
+ "status": "UNAVAILABLE",
48
+ "rationale": f"Nemotron review unavailable: {type(exc).__name__}",
49
+ "model": "nvidia/Nemotron-Mini-4B-Instruct",
50
+ }
51
+ return result
52
+
53
+
54
  def build_gradio_engine() -> gr.Blocks:
55
  with gr.Blocks(title="PacketCourt Engine") as engine:
56
  gr.Markdown(
 
62
  back = gr.Textbox(label="Back-label evidence")
63
  output = gr.JSON(label="Evidence case")
64
  gr.Button("Audit").click(
65
+ lambda front_text, back_text: run_audit(front_text, back_text).model_dump(mode="json"),
66
  [front, back],
67
  output,
68
  )
 
93
  if os.getenv("PACKETCOURT_ROUTER", "0") == "1"
94
  else "deterministic fallback"
95
  )
96
+ status["nemotron_reviewer"] = (
97
+ "nvidia/Nemotron-Mini-4B-Instruct"
98
+ if nemotron_is_configured()
99
+ else "not configured"
100
+ )
101
  if is_configured():
102
  status.update(
103
  enabled=True,
 
109
 
110
  @app.post("/api/audit")
111
  def audit(request: AuditRequest) -> dict:
112
+ return run_audit(request.front_text, request.back_text).model_dump(mode="json")
113
 
114
 
115
  @app.post("/api/ocr")
frontend/app.js CHANGED
@@ -59,6 +59,12 @@ function render(data) {
59
  $("#missing-evidence").textContent = data.investigation.missing_evidence.length
60
  ? data.investigation.missing_evidence.join(" · ")
61
  : "None. The required evidence path completed.";
 
 
 
 
 
 
62
  $("#claim-grid").innerHTML = data.claims.length
63
  ? data.claims.map((claim) => `
64
  <article class="claim-card ${verdictClass[claim.verdict]}">
 
59
  $("#missing-evidence").textContent = data.investigation.missing_evidence.length
60
  ? data.investigation.missing_evidence.join(" · ")
61
  : "None. The required evidence path completed.";
62
+ const review = data.agent_review;
63
+ $("#nemotron-review").innerHTML = review.status === "NOT_REQUESTED"
64
+ ? ""
65
+ : `<b>NVIDIA NEMOTRON INDEPENDENT REVIEW · ${escapeHtml(review.status)}</b>
66
+ <strong>${escapeHtml(review.priority || "No additional action requested.")}</strong>
67
+ <span>${escapeHtml(review.evidence_request || review.rationale)}</span>`;
68
  $("#claim-grid").innerHTML = data.claims.length
69
  ? data.claims.map((claim) => `
70
  <article class="claim-card ${verdictClass[claim.verdict]}">
frontend/index.html CHANGED
@@ -104,6 +104,7 @@
104
  <p><b>STOP REASON</b><span id="stop-reason"></span></p>
105
  <p><b>MISSING EVIDENCE</b><span id="missing-evidence"></span></p>
106
  </div>
 
107
  </section>
108
  <section class="gap-section">
109
  <div class="gap-heading"><p class="kicker">PERSUASION GAP</p><h3>Material context the front leaves quiet.</h3></div>
 
104
  <p><b>STOP REASON</b><span id="stop-reason"></span></p>
105
  <p><b>MISSING EVIDENCE</b><span id="missing-evidence"></span></p>
106
  </div>
107
+ <div class="nemotron-review" id="nemotron-review"></div>
108
  </section>
109
  <section class="gap-section">
110
  <div class="gap-heading"><p class="kicker">PERSUASION GAP</p><h3>Material context the front leaves quiet.</h3></div>
frontend/styles.css CHANGED
@@ -15,6 +15,7 @@ main{max-width:1320px;margin:auto;padding:0 4vw}.hero{min-height:670px;display:g
15
  .sample-card{padding:24px;background:var(--cream);border:1px solid var(--line);border-radius:16px;cursor:pointer;text-align:left;transition:.2s}.sample-card:hover{border-color:var(--red);transform:translateY(-3px)}.sample-card b{display:block;font-size:19px;margin-bottom:8px}.sample-card span{font-size:13px;color:var(--muted)}
16
  .results{border-top:1px solid var(--line)}.hidden{display:none}.case-score{width:120px;height:120px;border:1px solid var(--ink);border-radius:50%;display:flex;align-items:center;justify-content:center;gap:8px}.case-score span{font:800 45px "Playfair Display"}.case-score small{font:500 8px/1.4 "DM Mono"}
17
  .agent-section{margin-bottom:28px;padding:26px;border:1px solid var(--line);background:var(--cream);border-radius:18px}.agent-heading{display:flex;justify-content:space-between;align-items:end;gap:20px;margin-bottom:18px}.agent-heading h3{font:700 clamp(27px,4vw,46px)/1 Georgia,serif;margin:0}.agent-heading>span{font:500 9px ui-monospace,SFMono-Regular,Menlo,monospace;padding:8px 11px;border:1px solid var(--line);border-radius:99px;color:var(--green)}.agent-steps{display:grid;grid-template-columns:repeat(2,1fr);gap:9px}.agent-steps article{display:grid;grid-template-columns:auto 1fr;gap:10px;padding:15px;border:1px solid var(--line);border-radius:11px;background:#f8f3e9}.agent-steps article>span{font:500 10px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--red)}.agent-steps b{font-size:13px;text-transform:capitalize}.agent-steps p{font-size:11px;line-height:1.4;color:var(--muted);margin:5px 0}.agent-steps small{grid-column:2;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;text-transform:uppercase;color:var(--green)}.agent-stop{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:9px}.agent-stop p{margin:0;padding:14px;border-top:1px solid var(--line);font-size:11px;line-height:1.5}.agent-stop b,.agent-stop span{display:block}.agent-stop b{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted);margin-bottom:5px}
 
18
  .gap-section{margin-bottom:28px;padding:26px;border:1px solid var(--ink);background:#1b1b17;color:var(--cream);border-radius:18px}.gap-heading{display:flex;justify-content:space-between;gap:20px;align-items:end;margin-bottom:18px}.gap-heading .kicker{color:#bdb4a6}.gap-heading h3{font:700 clamp(27px,4vw,46px)/1 Georgia,serif;max-width:700px;margin:0}.gap-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}.gap-card,.gap-empty{padding:19px;border:1px solid #4a483f;border-radius:13px;background:#26251f}.gap-card.high{border-color:var(--red)}.gap-card.medium{border-color:var(--amber)}.gap-severity{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.13em;text-transform:uppercase;color:#d9a65e}.gap-card h4{font-size:20px;margin:10px 0 16px}.gap-compare{display:grid;grid-template-columns:1fr 1fr;gap:10px}.gap-compare p{margin:0;padding:11px;background:#313029;border-radius:8px;font-size:12px;line-height:1.45}.gap-compare b{display:block;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;color:#bdb4a6;margin-bottom:5px}.gap-card .evidence{border-top-color:#4a483f}.gap-empty{display:grid;gap:5px;color:#bdb4a6}
19
  .claim-grid{grid-template-columns:repeat(2,1fr)}.claim-card{background:var(--cream);border:1px solid var(--line);border-top:6px solid var(--muted);border-radius:16px;padding:23px}.claim-card.supported{border-top-color:var(--green)}.claim-card.contradicted{border-top-color:var(--red)}.claim-card.context{border-top-color:var(--amber)}.claim-top{display:flex;justify-content:space-between;gap:10px;align-items:start}.claim-name{font-size:21px;font-weight:800}.verdict{font:500 8px/1.3 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.08em;border:1px solid var(--line);border-radius:99px;padding:7px 9px;text-align:right}.confidence{display:block;margin-top:8px;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}.summary{min-height:45px;color:#534d43;line-height:1.5}.evidence{padding:11px 0;border-top:1px solid var(--line)}.evidence b,.evidence span{display:block}.evidence b{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.12em;color:var(--muted);text-transform:uppercase}.evidence span{font-size:13px;margin-top:4px}.caveat{font-size:11px;color:var(--muted);margin-top:15px}
20
  .evidence-summary{margin-top:16px}.evidence-summary article{padding:25px;border:1px solid var(--line);border-radius:16px;background:var(--cream)}#nutrition-grid div{display:flex;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--line);font-size:13px}.date-card{background:var(--ink)!important;color:var(--cream)}.date-card .kicker{color:#cfc5b6}.date-card h3{font:700 28px/1.15 "Playfair Display"}details{margin-top:16px;border:1px solid var(--line);border-radius:14px;padding:17px;background:var(--cream)}summary{cursor:pointer;font-weight:700}pre{white-space:pre-wrap;font:11px/1.5 "DM Mono";overflow:auto}
 
15
  .sample-card{padding:24px;background:var(--cream);border:1px solid var(--line);border-radius:16px;cursor:pointer;text-align:left;transition:.2s}.sample-card:hover{border-color:var(--red);transform:translateY(-3px)}.sample-card b{display:block;font-size:19px;margin-bottom:8px}.sample-card span{font-size:13px;color:var(--muted)}
16
  .results{border-top:1px solid var(--line)}.hidden{display:none}.case-score{width:120px;height:120px;border:1px solid var(--ink);border-radius:50%;display:flex;align-items:center;justify-content:center;gap:8px}.case-score span{font:800 45px "Playfair Display"}.case-score small{font:500 8px/1.4 "DM Mono"}
17
  .agent-section{margin-bottom:28px;padding:26px;border:1px solid var(--line);background:var(--cream);border-radius:18px}.agent-heading{display:flex;justify-content:space-between;align-items:end;gap:20px;margin-bottom:18px}.agent-heading h3{font:700 clamp(27px,4vw,46px)/1 Georgia,serif;margin:0}.agent-heading>span{font:500 9px ui-monospace,SFMono-Regular,Menlo,monospace;padding:8px 11px;border:1px solid var(--line);border-radius:99px;color:var(--green)}.agent-steps{display:grid;grid-template-columns:repeat(2,1fr);gap:9px}.agent-steps article{display:grid;grid-template-columns:auto 1fr;gap:10px;padding:15px;border:1px solid var(--line);border-radius:11px;background:#f8f3e9}.agent-steps article>span{font:500 10px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--red)}.agent-steps b{font-size:13px;text-transform:capitalize}.agent-steps p{font-size:11px;line-height:1.4;color:var(--muted);margin:5px 0}.agent-steps small{grid-column:2;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;text-transform:uppercase;color:var(--green)}.agent-stop{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:9px}.agent-stop p{margin:0;padding:14px;border-top:1px solid var(--line);font-size:11px;line-height:1.5}.agent-stop b,.agent-stop span{display:block}.agent-stop b{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted);margin-bottom:5px}
18
+ .nemotron-review{display:grid;gap:6px;margin-top:12px;padding:16px;border:1px solid #9fc2a9;border-radius:11px;background:#edf5eb}.nemotron-review:empty{display:none}.nemotron-review b{font:600 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.1em;color:var(--green)}.nemotron-review strong{font-size:14px}.nemotron-review span{font-size:11px;line-height:1.5;color:var(--muted)}
19
  .gap-section{margin-bottom:28px;padding:26px;border:1px solid var(--ink);background:#1b1b17;color:var(--cream);border-radius:18px}.gap-heading{display:flex;justify-content:space-between;gap:20px;align-items:end;margin-bottom:18px}.gap-heading .kicker{color:#bdb4a6}.gap-heading h3{font:700 clamp(27px,4vw,46px)/1 Georgia,serif;max-width:700px;margin:0}.gap-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}.gap-card,.gap-empty{padding:19px;border:1px solid #4a483f;border-radius:13px;background:#26251f}.gap-card.high{border-color:var(--red)}.gap-card.medium{border-color:var(--amber)}.gap-severity{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.13em;text-transform:uppercase;color:#d9a65e}.gap-card h4{font-size:20px;margin:10px 0 16px}.gap-compare{display:grid;grid-template-columns:1fr 1fr;gap:10px}.gap-compare p{margin:0;padding:11px;background:#313029;border-radius:8px;font-size:12px;line-height:1.45}.gap-compare b{display:block;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;color:#bdb4a6;margin-bottom:5px}.gap-card .evidence{border-top-color:#4a483f}.gap-empty{display:grid;gap:5px;color:#bdb4a6}
20
  .claim-grid{grid-template-columns:repeat(2,1fr)}.claim-card{background:var(--cream);border:1px solid var(--line);border-top:6px solid var(--muted);border-radius:16px;padding:23px}.claim-card.supported{border-top-color:var(--green)}.claim-card.contradicted{border-top-color:var(--red)}.claim-card.context{border-top-color:var(--amber)}.claim-top{display:flex;justify-content:space-between;gap:10px;align-items:start}.claim-name{font-size:21px;font-weight:800}.verdict{font:500 8px/1.3 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.08em;border:1px solid var(--line);border-radius:99px;padding:7px 9px;text-align:right}.confidence{display:block;margin-top:8px;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}.summary{min-height:45px;color:#534d43;line-height:1.5}.evidence{padding:11px 0;border-top:1px solid var(--line)}.evidence b,.evidence span{display:block}.evidence b{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.12em;color:var(--muted);text-transform:uppercase}.evidence span{font-size:13px;margin-top:4px}.caveat{font-size:11px;color:var(--muted);margin-top:15px}
21
  .evidence-summary{margin-top:16px}.evidence-summary article{padding:25px;border:1px solid var(--line);border-radius:16px;background:var(--cream)}#nutrition-grid div{display:flex;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--line);font-size:13px}.date-card{background:var(--ink)!important;color:var(--cream)}.date-card .kicker{color:#cfc5b6}.date-card h3{font:700 28px/1.15 "Playfair Display"}details{margin-top:16px;border:1px solid var(--line);border-radius:14px;padding:17px;background:var(--cream)}summary{cursor:pointer;font-weight:700}pre{white-space:pre-wrap;font:11px/1.5 "DM Mono";overflow:auto}
src/packetcourt/models.py CHANGED
@@ -80,6 +80,14 @@ class InvestigationPlan(BaseModel):
80
  router_model: str = "deterministic fallback"
81
 
82
 
 
 
 
 
 
 
 
 
83
  class PacketAudit(BaseModel):
84
  claims: list[ClaimAudit]
85
  nutrition: NutritionFacts
@@ -91,3 +99,4 @@ class PacketAudit(BaseModel):
91
  back_text: str
92
  limitations: list[str]
93
  investigation: InvestigationPlan
 
 
80
  router_model: str = "deterministic fallback"
81
 
82
 
83
+ class AgentReview(BaseModel):
84
+ status: str = "NOT_REQUESTED"
85
+ priority: str = ""
86
+ evidence_request: str = ""
87
+ rationale: str = ""
88
+ model: str = ""
89
+
90
+
91
  class PacketAudit(BaseModel):
92
  claims: list[ClaimAudit]
93
  nutrition: NutritionFacts
 
99
  back_text: str
100
  limitations: list[str]
101
  investigation: InvestigationPlan
102
+ agent_review: AgentReview = Field(default_factory=AgentReview)
src/packetcourt/remote_nemotron.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from functools import lru_cache
6
+
7
+
8
+ def is_configured() -> bool:
9
+ return bool(os.getenv("PACKETCOURT_NEMOTRON_SPACE"))
10
+
11
+
12
+ @lru_cache(maxsize=1)
13
+ def _client():
14
+ from gradio_client import Client
15
+
16
+ try:
17
+ return Client(os.environ["PACKETCOURT_NEMOTRON_SPACE"], hf_token=os.getenv("HF_TOKEN"))
18
+ except TypeError:
19
+ return Client(os.environ["PACKETCOURT_NEMOTRON_SPACE"], token=os.getenv("HF_TOKEN"))
20
+
21
+
22
+ def review(snapshot: dict) -> dict:
23
+ result = _client().predict(json.dumps(snapshot), api_name="/predict")
24
+ return json.loads(result)
tests/test_audit.py CHANGED
@@ -87,3 +87,4 @@ def test_investigation_requests_missing_evidence_and_stops_explicitly():
87
  assert any(step.tool == "inspect_nutrition" for step in result.investigation.steps)
88
  assert any("nutrition panel" in item.lower() for item in result.investigation.missing_evidence)
89
  assert "missing-evidence" in result.investigation.stop_reason
 
 
87
  assert any(step.tool == "inspect_nutrition" for step in result.investigation.steps)
88
  assert any("nutrition panel" in item.lower() for item in result.investigation.missing_evidence)
89
  assert "missing-evidence" in result.investigation.stop_reason
90
+ assert result.agent_review.status == "NOT_REQUESTED"