budget-justification-generator-udm¶
budget-justification-generator-udm0.1.0noneTags: budget-justification drafting pre-award nsf nih rr-budget sf-424a narrative-generation multi-year udm structured-output json
Audience: sponsored-programs-staff, pre-award-teams, pis, proposal-developers
Manifestations in repo: prompt.md
Generates a complete, professional budget-justification narrative from a grant budget form and the project narrative / proposal. Captures both the structured per-section extracted financial data AND the generated narrative text for each section, plus the final assembled Markdown document and cross-validation notes. Organized in standard R&R Budget / SF-424A category order (A through I). Supports multi-year budgets and agency-specific rules when a NOFO / RFA is provided.
Output contract: schema.json
Contract scope: repo-local, UDM-aligned
Inputs¶
Three workflow documents uploaded together:
- Budget form / spreadsheet — R&R Budget Form (SF-424R&R), SF-424A Budget Form, Excel, or PDF.
- Project narrative / research plan — typically 5–30 pages.
- Optional NOFO / RFA — for agency-specific formatting rules, F&A caps, and cost-share requirements.
Total input size: 10–80+ pages (budget 2–10, narrative 5–30, optional NOFO 20–150+).
Outputs¶
A single JSON object with seven structured blocks:
project_metadata—project_title,pi_name,sponsoring_agency,budget_period_count,is_multi_year_budgetpersonnel_and_fringe— Sections A (Senior/Key Personnel table + total), B (Other Personnel table + total), C (Fringe Benefit Rates table + total) plus the three section narrativesequipment_and_travel— Section D (Equipment items table + total) and Section E (Domestic + Foreign Travel tables + totals + restrictions) plus the three section narrativesparticipant_support_and_other_direct_costs— Section F (Participant Support table) and Section G (nine ODC subcategories: materials, publications, consultants, ADP, subawards, equipment rental, alterations, tuition, other) plus the two section narrativesindirect_costs_and_summary— Section H (F&A rate as JSON number + base type + MTDC exclusions + cap details), Cost Sharing (amount + type + sources), and Budget Summary (period table + year-over-year variations + NOFO rules) plus the three section narrativescross_validation—arithmetic_checks[]with{check_name, status, expected, actual, discrepancy}rows,items_requiring_clarification[],missing_justification_flags[]— clearly marked as not-for-submission so an RA can delete this section fromfinal_justification_documentfinal_justification_document— Markdown string assembling everything above in R&R Budget / SF-424A category order
See schema.json for the authoritative definition and prompt.md for encoding rules (monetary structured-data fields as JSON numbers, narrative text preserves $X,XXX.XX format, no-fabrication rule, cross-validation marking).
Why this is a drafting workflow¶
Unlike the extraction-and-classification components (award-compliance-extraction-udm, subaward-extraction-udm, etc.), this component produces narrative text that the PI / pre-award analyst would otherwise spend several hours assembling. The structured data exists to support the narrative — line-item totals enable arithmetic-check generation, equipment-and-travel tables drive section-narrative generation, etc.
The schema captures the structured data so the downstream consumer can re-validate the math, but the highest-value output is the final_justification_document Markdown string. That string is Word-pasteable in standard R&R Budget / SF-424A order, including a FORMATTING ADVISORY block at the top when the NOFO specifies page limits / fonts / margins.
Contract scope¶
Repo-local, UDM-aligned. Personnel and budget-category leaf fields bind to UDM entities: Personnel, Proposal, ProposalBudget.Direct_Cost, ProposalBudget.Indirect_Cost, ProposalBudget.Period_Number, IndirectRate.Rate_Percentage, IndirectRate.Base_Type, IndirectRate.Rate_Type, CostShare.Committed_Amount, CostShare.Commitment_Type, CostShare.Is_Mandatory, Organization.Organization_Name. The seven-block drafting shape itself is repo-local.
Relationship to other components¶
| Concern | Source of truth |
|---|---|
| Agency-agnostic R&R Budget / SF-424A budget-justification drafting | budget-justification-generator-udm (this component) |
| NSF-specific eight-section budget-justification drafting | nsf-budget-justification-udm |
| NSF-specific budget-spreadsheet ingest (structured budget object only, no narrative) | nsf-budget-spreadsheet-ingest-udm |
| Pre-award budget personnel and structure extraction (no narrative generation) | proposal-budget-personnel-extraction-udm |
This component is the agency-agnostic counterpart to the NSF-specific eight-section drafting workflow. Use this component when the agency is not NSF or when the budget follows the standard R&R Budget / SF-424A category order rather than NSF's eight-section structure.
Triad integration¶
- Evaluation datasets: none yet — planned: a synthetic R&R Budget + project narrative exercise (multi-year, with cost-sharing, mixed-PI structure) validated by a Pre-Award Analyst end-to-end against the
final_justification_documentMarkdown. - Harness notes: scoring is two-layered. The structured fields (numeric totals, table rows) validate against
schema.jsonand against the arithmetic checks incross_validation. The narrative strings (section narratives +final_justification_document) require human review against the project narrative and budget form — golden cases should capture validated narrative text for diff-based scoring. - Shared UDM relationship: aligned, not owning. Leaf fields bind to UDM entities, but the seven-block drafting shape is repo-local.
Runtime topology — the Vandalizer workflow¶
The canonical runtime for this component is the budget-justification-generator workflow shipped at the top level of this repo. The single source of truth is workflows/budget-justification-generator/manifest.yaml; the companion .vandalizer.json envelope is generated by scripts/build_vandalizer_workflows.py and committed alongside. The runtime mirrors the source ui-insight/ProcessMapping/workflows/budget-justification-generator/ workflow:
- Step 1 (parallel Extraction + Generation) — four Extraction tasks, each extracting structured budget data AND generating the narrative text for specific budget categories: personnel-and-fringe (Sections A+B+C), equipment-and-travel (Sections D+E), participant-support-and-other-direct-costs (Sections F+G), indirect-costs-and-summary (Section H + Cost Sharing + Budget Summary). Each task carries an embedded SearchSet that includes both data fields and a
*_justification_narrativefield for the generated text. - Step 2 (Consolidation Prompt) — assembles the four JSON fragments into the schema-conformant seven-block object, performs the three cross-validation checks (CFR-01 Direct + Indirect = Total; CHK-01 line items sum to category totals; CHK-03 personnel costs match FTE), and renders the
final_justification_documentMarkdown string in R&R Budget / SF-424A order with the cross-validation notes section clearly marked as not-for-submission.
Regenerate the workflow JSON whenever this component bumps MINOR or MAJOR (or whenever the workflow manifest changes); CI fails if the committed .vandalizer.json drifts from a fresh build.
Manifestations¶
prompt.md— canonical, LLM-agnostic prompt
Evals¶
See evals/ for reference inputs and known-good outputs. Initial case pending: a synthetic R&R Budget + project narrative exercise validated by a Pre-Award Analyst end-to-end.
Provenance¶
Authored 2026-05-20 against the budget-justification-generator (Workflow_ID: WF-BUDGET-JUSTIFICATION-GENERATOR) process-mapping workflow in ui-insight/ProcessMapping at commit 2c1f47f46474130743af5aee44d074bcd21787e9. This is a drafting workflow (rather than a single-process extraction); it does not derive from a single processes/ map and was built to automate the budget-justification drafting step that PIs and pre-award analysts spend hours on per proposal.
Contract scope¶
-
Output format:
json_object -
Contract scope:
shared_udm_semantics_repo_local_schema -
Validation surfaces:
json_schema -
Schema entrypoints:
# -
Notes: Repo-local agency-agnostic budget-justification drafting contract. Seven structured blocks (project_metadata, four per-section data-and-narrative blocks, cross_validation, final_justification_document) capture both the per-section extracted financial data AND the generated narrative text for each R&R Budget / SF-424A section (A-I), plus the final assembled Markdown document and arithmetic / clarification / missing-justification notes. The workflow's primary deliverable is the final_justification_document Markdown string; structured-data fields exist to support arithmetic re-validation.
-
Machine-readable catalog entry:
component_catalog.json
Triad integration¶
-
UDM alignment:
shared_udm_semantics_repo_local_schema— Leaf fields bind to UDM entities: senior_key_personnel + pi_name to Personnel; project_title to Proposal.Proposal_Title; sponsoring_agency to Organization.Organization_Name; every monetary leaf to ProposalBudget.Direct_Cost or ProposalBudget.Indirect_Cost; budget_period_summary to ProposalBudget.Period_Number; fa_rate_percentage and fa_base_type and fa_rate_type to IndirectRate (Rate_Percentage, Base_Type, Rate_Type); cost_share_required / cost_share_amount / cost_share_type to CostShare (Is_Mandatory, Committed_Amount, Commitment_Type). The seven-block drafting shape itself is repo-local. -
Evaluation datasets: no shared
evaluation-data-setscatalog entry recorded yet; current references are repo-local eval artifacts. -
Harness notes: Validate JSON outputs against schema.json. Canonical single-call invocation surface is prompt.md. The companion top-level workflows/budget-justification-generator Vandalizer workflow at v0.1.0 implements the contract as four parallel Extraction tasks (each both extracting data AND generating section narratives) plus a Consolidation Prompt that computes four arithmetic checks deterministically and renders final_justification_document as a single Markdown string in R&R Budget / SF-424A category order. Scoring is two-layered — structured fields validate against schema.json and arithmetic_checks; narrative strings require human review against the project narrative and budget form.
-
Related component:
nsf-budget-justification-udm(sibling_agency_specific_vs_agency_agnostic) — Both components draft a budget justification. This component follows the agency-agnostic R&R Budget / SF-424A category order (A-I); nsf-budget-justification-udm follows NSF's eight-section structure. Use this component when the agency is not NSF or when the budget follows the standard R&R / SF-424A shape. -
Related component:
proposal-budget-personnel-extraction-udm(complementary_focus) — proposal-budget-personnel-extraction-udm extracts the budget structure and personnel listing WITHOUT generating narrative text. budget-justification-generator-udm does both — extracts the structure and drafts the narrative.
Prompt body¶
Source: prompt.md.
Show prompt
Budget Justification Generator — UDM JSON¶
Purpose: Generate a complete, professional budget justification narrative from a grant budget form and the project narrative / proposal. Takes a structured budget and a project narrative as input. Produces a formatted justification document that ties every cost to specific project aims and activities, organized in standard R&R Budget / SF-424A category order (A through I). Supports multi-year budgets and agency-specific rules when a NOFO / RFA is provided.
Expected input: Three documents uploaded together: the budget form / spreadsheet (R&R Budget Form, SF-424A, or Excel), the project narrative / research plan, and optionally the funding opportunity announcement (NOFO / RFA) for agency-specific formatting rules and F&A caps.
Expected output: A single JSON object that validates against
schema.json. The object captures both the structured per-section extracted financial data AND the generated narrative text for each section, plus the final assembled markdown document and cross-validation notes. No prose outside the JSON.
When to use this contract¶
This is a drafting workflow, not an extraction workflow. It produces a Word-pasteable budget justification narrative organized by R&R Budget / SF-424A category order (A through I) that a PI or pre-award analyst would otherwise spend several hours assembling by hand. The contract captures:
-
Per-section extracted financial data (line-item totals, FTEs, rates, allocations) from the budget form, so the downstream consumer can re-validate the math.
-
Per-section generated narrative text that ties costs to specific project aims and activities pulled from the project narrative.
-
An assembled final document (Markdown) in standard R&R Budget / SF-424A order, suitable for paste into Word.
-
Cross-validation notes capturing arithmetic checks and items requiring PI clarification, marked clearly as not-for-submission so an RA can delete them.
This component does not cover NSF-specific eight-section narrative drafting — that lives in nsf-budget-justification-udm. It does not cover ingest of an NSF budget spreadsheet — that lives in nsf-budget-spreadsheet-ingest-udm. This component is the agency-agnostic drafting workflow that follows the standard R&R Budget / SF-424A category order.
This component is one of the three prompt-library workflows that produces narrative text alongside structured data. Output validates against schema.json (draft 2020-12), but the bulk of the value is in the narrative strings.
Prompt¶
You are generating a complete, professional Budget Justification narrative from three input documents (uploaded together as workflow documents):
-
The budget form / spreadsheet (R&R Budget Form, SF-424A, or Excel).
-
The project narrative / research plan.
-
Optionally the funding opportunity announcement (NOFO / RFA) for agency-specific formatting rules, F&A caps, and cost-share requirements.
Be 100% accurate on every dollar amount, rate, calculation, and personnel detail. Reproduce financial figures exactly as they appear in the budget form. Show calculations explicitly (rate × base = total). Tie every cost to specific project activities from the narrative. Never fabricate or estimate values not present in the source documents. When a value cannot be determined from the documents, use the literal string "Not specified in the provided documents" in narrative text, and null for missing scalar fields in the structured data.
Return a single JSON object that validates against schema.json with these top-level keys:
-
project_metadata— object withproject_title,pi_name,sponsoring_agency,budget_period_count, andis_multi_year_budget. -
personnel_and_fringe— object with structured data + narrative text for Sections A (Senior/Key Personnel), B (Other Personnel), and C (Fringe Benefits). -
equipment_and_travel— object with structured data + narrative text for Sections D (Equipment), E (Travel: Domestic + Foreign). -
participant_support_and_other_direct_costs— object with structured data + narrative text for Sections F (Participant Support) and G (Other Direct Costs, subcategories G.1–G.9). -
indirect_costs_and_summary— object with structured data + narrative text for Section H (Indirect Costs / F&A), Cost Sharing, and Budget Summary. -
cross_validation— object with arithmetic check results, items requiring PI clarification, and missing-justification flags. -
final_justification_document— single Markdown string assembling everything above in R&R Budget / SF-424A category order (A through I).
Critical: monetary fields are JSON numbers, not strings¶
Apply the boss's PR #33 review feedback consistently to every monetary field in the structured data:
-
personnel_and_fringe.total_senior_personnel_cost,total_other_personnel_cost,total_fringe_benefits,salary_cap_amount -
equipment_and_travel.total_equipment_cost,total_domestic_travel_cost,total_foreign_travel_cost -
participant_support_and_other_direct_costs.total_participant_support,publication_costs,total_subaward_costs,tuition_remission,total_other_direct_costs -
indirect_costs_and_summary.fa_rate_percentage,total_direct_costs,total_indirect_costs,total_project_costs,cost_share_amount
Quoted strings inside the narrative text (final_justification_document and the per-section *_justification_narrative) preserve the document's $X,XXX.XX format for readability — that is correct. But every structured-data field above is a JSON number.
Cross-field rules (from source workflow)¶
-
Direct + Indirect = Total (CFR-01):
total_direct_costs + total_indirect_costs == total_project_costs. Flag deviations incross_validation.arithmetic_checkswithout altering values. -
Line items sum to category totals (CHK-01): per-category sub-totals computed from line items must match the budget form's category totals. Flag deviations.
-
Personnel costs match FTE (CHK-03): personnel cost calculations consistent with FTE / person-months in the budget. Flag inconsistencies as warnings.
Per-section structure¶
Each of the four mid-level blocks (personnel_and_fringe, equipment_and_travel, participant_support_and_other_direct_costs, indirect_costs_and_summary) has:
-
Structured financial data fields (numeric totals, table arrays of
{name, role, person_months, salary}etc.). -
A
*_justification_narrativestring field containing the generated professional narrative text for that section.
For the final assembled document (final_justification_document), assemble in R&R Budget / SF-424A order:
A. Senior/Key Personnel ← personnel_and_fringe.section_a_narrative
B. Other Personnel ← personnel_and_fringe.section_b_narrative
C. Fringe Benefits ← personnel_and_fringe.section_c_narrative + personnel summary table
D. Equipment ← equipment_and_travel.section_d_narrative
E. Travel ← equipment_and_travel.section_e_domestic + section_e_foreign + travel summary table
F. Participant Support ← participant_support_and_other_direct_costs.section_f_narrative
G. Other Direct Costs ← participant_support_and_other_direct_costs.section_g_narrative + ODC summary table
H. Indirect Costs ← indirect_costs_and_summary.section_h_narrative
Cost Sharing ← indirect_costs_and_summary.cost_share_narrative
Budget Summary ← indirect_costs_and_summary.budget_summary_narrative + period table for multi-year
Include a FORMATTING ADVISORY block at the top only if the NOFO specified formatting requirements (page limit, font, margins, section naming).
Cross-validation notes — for the RA's internal review only¶
cross_validation is split into:
-
arithmetic_checks— array of{check_name, status, expected, actual, discrepancy}objects.statusis"PASS"or"FAIL". -
items_requiring_clarification— array of strings, each describing a placeholder, missing detail, or PI clarification item. Empty array when none. -
missing_justification_flags— array of strings, each describing a budget category that shows a dollar amount but lacks narrative justification. Empty array when none.
The final_justification_document must place the cross-validation notes BELOW a clear "CROSS-VALIDATION NOTES — For the RA's internal review only" heading so an RA can delete everything from that heading down and have a submission-ready document.
Encoding rules¶
-
Monetary structured-data fields are JSON numbers. Narrative text preserves the document's
$X,XXX.XXformat. -
Dates use the document's stated form (this workflow is not date-heavy).
-
Booleans are JSON booleans.
-
Empty optional fields use
null(scalars) or[](arrays). Do not invent values. -
Do NOT modify dollar amounts. Reproduce every financial figure exactly as it appeared in the budget form. If amounts do not sum correctly, flag the discrepancy in
cross_validation.arithmetic_checksbut DO NOT adjust any values. -
Do NOT fabricate information. Preserve placeholder text (
"clarification needed from PI","not specified in the provided documents") and add the item toitems_requiring_clarification. -
Maintain professional tone. Third-person throughout. Remove first-person language, casual phrasing, conversational tone.
Output¶
A single JSON object. No surrounding markdown outside the JSON. The final_justification_document field carries the Markdown-formatted narrative as a string value.
Output schema¶
Source: schema.json.
Show schema.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/AI4RA/prompt-library/components/budget-justification-generator-udm/schema.json",
"title": "Budget Justification Generator \u2014 UDM Output",
"description": "JSON contract for a generated budget-justification narrative produced from a grant budget form and a project narrative (and optionally a NOFO / RFA). The output captures both the structured per-section extracted financial data and the generated narrative text for each section, plus the final assembled Markdown document and cross-validation notes. Organized in R&R Budget / SF-424A category order (A-I). Mirrors the source ui-insight/ProcessMapping budget-justification-generator workflow's four parallel extraction-and-generation tasks plus a Consolidation Prompt.",
"version": "0.1.0",
"type": "object",
"additionalProperties": false,
"required": [
"project_metadata",
"personnel_and_fringe",
"equipment_and_travel",
"participant_support_and_other_direct_costs",
"indirect_costs_and_summary",
"cross_validation",
"final_justification_document"
],
"properties": {
"project_metadata": {
"type": "object",
"additionalProperties": false,
"required": [
"project_title",
"pi_name",
"budget_period_count"
],
"properties": {
"project_title": {
"type": "string",
"minLength": 1,
"description": "Project title from the proposal. Required by source workflow. Resolves to UDM Proposal.Proposal_Title."
},
"pi_name": {
"type": "string",
"minLength": 1,
"description": "Principal Investigator name. Required by source workflow. Resolves to UDM Personnel.Last_Name."
},
"sponsoring_agency": {
"type": [
"string",
"null"
],
"description": "Sponsoring agency name when determinable. Resolves to UDM Organization.Organization_Name."
},
"budget_period_count": {
"type": "integer",
"minimum": 1,
"description": "Number of budget periods (1 for single-year, N for multi-year). Required by source workflow."
},
"is_multi_year_budget": {
"type": [
"boolean",
"null"
],
"description": "Whether the budget spans multiple years."
}
}
},
"personnel_and_fringe": {
"type": "object",
"additionalProperties": false,
"required": [
"senior_key_personnel",
"total_senior_personnel_cost",
"fringe_benefit_rates",
"total_fringe_benefits",
"section_a_narrative",
"section_b_narrative",
"section_c_narrative"
],
"description": "Sections A (Senior/Key Personnel), B (Other Personnel), C (Fringe Benefits).",
"properties": {
"senior_key_personnel": {
"type": "array",
"minItems": 1,
"description": "Senior / Key Personnel table. Required by source workflow. Resolves to UDM Personnel.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"title",
"role",
"person_months",
"requested_salary"
],
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"title": {
"type": "string",
"minLength": 1
},
"role": {
"type": "string",
"minLength": 1
},
"person_months": {
"type": "number",
"minimum": 0
},
"salary_basis": {
"type": [
"string",
"null"
]
},
"requested_salary": {
"type": "number",
"minimum": 0
}
}
}
},
"other_personnel": {
"type": "array",
"description": "Other Personnel table. Empty array when no other personnel are listed in the budget.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"category",
"count",
"person_months",
"rate"
],
"properties": {
"category": {
"type": "string",
"minLength": 1
},
"count": {
"type": "integer",
"minimum": 0
},
"person_months": {
"type": "number",
"minimum": 0
},
"rate": {
"type": [
"number",
"null"
],
"minimum": 0
}
}
}
},
"total_senior_personnel_cost": {
"type": "number",
"minimum": 0,
"description": "Total salary costs for all senior / key personnel. Required by source workflow. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"total_other_personnel_cost": {
"type": [
"number",
"null"
],
"minimum": 0,
"description": "Total salary costs for all other personnel. JSON number."
},
"fringe_benefit_rates": {
"type": "array",
"minItems": 1,
"description": "Fringe benefit rates by personnel category with calculation basis. Required by source workflow.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"category",
"rate",
"calculation_basis"
],
"properties": {
"category": {
"type": "string",
"minLength": 1
},
"rate": {
"type": "string",
"minLength": 1,
"description": "Rate as a percentage string (e.g., '28.5%')."
},
"calculation_basis": {
"type": "string",
"minLength": 1
}
}
}
},
"total_fringe_benefits": {
"type": "number",
"minimum": 0,
"description": "Total fringe benefit costs. Required by source workflow. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"is_salary_cap_applicable": {
"type": [
"boolean",
"null"
],
"description": "Whether any personnel salary is subject to an agency salary cap."
},
"salary_cap_amount": {
"type": [
"number",
"null"
],
"minimum": 0,
"description": "Agency salary cap amount when applicable. JSON number."
},
"year_over_year_personnel_changes": {
"type": [
"string",
"null"
],
"description": "Description of personnel effort or salary changes across budget years. Null for single-year budgets."
},
"section_a_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated professional justification text for Section A (Senior/Key Personnel)."
},
"section_b_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated professional justification text for Section B (Other Personnel). Use 'No other personnel costs are requested for this project.' when other_personnel is empty."
},
"section_c_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated professional justification text for Section C (Fringe Benefits)."
}
}
},
"equipment_and_travel": {
"type": "object",
"additionalProperties": false,
"required": [
"equipment_items",
"total_equipment_cost",
"domestic_travel",
"foreign_travel",
"total_domestic_travel_cost",
"total_foreign_travel_cost",
"section_d_narrative",
"section_e_domestic_narrative",
"section_e_foreign_narrative"
],
"description": "Sections D (Equipment), E (Travel: Domestic + Foreign).",
"properties": {
"equipment_items": {
"type": "array",
"description": "Each equipment item with name, cost, and purpose. Empty array when no equipment is requested.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"cost",
"purpose"
],
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"cost": {
"type": "number",
"minimum": 0
},
"purpose": {
"type": "string",
"minLength": 1
}
}
}
},
"total_equipment_cost": {
"type": "number",
"minimum": 0,
"description": "Total equipment costs. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"equipment_threshold_applied": {
"type": [
"string",
"null"
],
"description": "Equipment cost threshold (e.g., 'Standard $5,000 threshold per institutional policy', '$10,000 threshold per 2 CFR 200.313'). Null when defaulted."
},
"domestic_travel": {
"type": "array",
"description": "Domestic trips. Empty array when no domestic travel is requested.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"destination",
"purpose",
"travelers",
"duration_days",
"total_cost"
],
"properties": {
"destination": {
"type": "string",
"minLength": 1
},
"purpose": {
"type": "string",
"minLength": 1
},
"travelers": {
"type": "integer",
"minimum": 1
},
"duration_days": {
"type": "integer",
"minimum": 1
},
"total_cost": {
"type": "number",
"minimum": 0
}
}
}
},
"foreign_travel": {
"type": "array",
"description": "Foreign trips. Empty array when no foreign travel is requested.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"destination",
"purpose",
"travelers",
"duration_days",
"total_cost"
],
"properties": {
"destination": {
"type": "string",
"minLength": 1
},
"purpose": {
"type": "string",
"minLength": 1
},
"travelers": {
"type": "integer",
"minimum": 1
},
"duration_days": {
"type": "integer",
"minimum": 1
},
"total_cost": {
"type": "number",
"minimum": 0
}
}
}
},
"total_domestic_travel_cost": {
"type": "number",
"minimum": 0,
"description": "Total domestic travel costs. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"total_foreign_travel_cost": {
"type": "number",
"minimum": 0,
"description": "Total foreign travel costs. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"is_foreign_travel_restricted": {
"type": [
"boolean",
"null"
],
"description": "Whether the NOFO restricts foreign travel."
},
"travel_cost_basis": {
"type": [
"string",
"null"
],
"description": "Basis for travel cost estimates (GSA rates, quotes, etc.)."
},
"section_d_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated professional justification text for Section D (Equipment). Use 'No equipment costs are requested for this project.' when equipment_items is empty."
},
"section_e_domestic_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated professional justification text for Section E \u2014 Domestic Travel."
},
"section_e_foreign_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated professional justification text for Section E \u2014 Foreign Travel."
}
}
},
"participant_support_and_other_direct_costs": {
"type": "object",
"additionalProperties": false,
"required": [
"total_participant_support",
"total_other_direct_costs",
"section_f_narrative",
"section_g_narrative"
],
"description": "Sections F (Participant Support) and G (Other Direct Costs, subcategories G.1-G.9).",
"properties": {
"participant_support_costs": {
"type": "array",
"description": "Participant support cost breakdown (stipends, travel, subsistence, other). Empty array when none.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"category",
"cost"
],
"properties": {
"category": {
"type": "string",
"minLength": 1
},
"cost": {
"type": "number",
"minimum": 0
}
}
}
},
"total_participant_support": {
"type": "number",
"minimum": 0,
"description": "Total participant / trainee support costs. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"materials_and_supplies": {
"type": "array",
"description": "Materials and supplies with categories and costs. Empty array when none.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"category",
"cost"
],
"properties": {
"category": {
"type": "string",
"minLength": 1
},
"cost": {
"type": "number",
"minimum": 0
}
}
}
},
"publication_costs": {
"type": [
"number",
"null"
],
"minimum": 0,
"description": "Publication and documentation costs. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"consultant_services": {
"type": "array",
"description": "Each consultant with name/TBD, rate, days, total, and scope. Empty array when no consultants.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"rate",
"days",
"total",
"scope"
],
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"rate": {
"type": "number",
"minimum": 0
},
"days": {
"type": "integer",
"minimum": 0
},
"total": {
"type": "number",
"minimum": 0
},
"scope": {
"type": "string",
"minLength": 1
}
}
}
},
"adp_computing_services": {
"type": [
"string",
"null"
],
"description": "Computing services and costs."
},
"subaward_details": {
"type": "array",
"description": "Each subaward with institution, PI, scope, and total cost. Empty array when no subawards.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"institution",
"pi",
"scope",
"total_cost"
],
"properties": {
"institution": {
"type": "string",
"minLength": 1
},
"pi": {
"type": "string",
"minLength": 1
},
"scope": {
"type": "string",
"minLength": 1
},
"total_cost": {
"type": "number",
"minimum": 0
}
}
}
},
"total_subaward_costs": {
"type": [
"number",
"null"
],
"minimum": 0,
"description": "Total subaward / subcontract costs. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"equipment_rental": {
"type": [
"string",
"null"
],
"description": "Equipment or facility rental costs."
},
"alterations_and_renovations": {
"type": [
"string",
"null"
],
"description": "Alteration and renovation costs."
},
"tuition_remission": {
"type": [
"number",
"null"
],
"minimum": 0,
"description": "Graduate student tuition remission costs. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"other_direct_costs": {
"type": "array",
"description": "Other direct costs not fitting above categories. Empty array when none.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"category",
"cost"
],
"properties": {
"category": {
"type": "string",
"minLength": 1
},
"cost": {
"type": "number",
"minimum": 0
}
}
}
},
"total_other_direct_costs": {
"type": "number",
"minimum": 0,
"description": "Total of all Other Direct Cost subcategories. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"section_f_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated professional justification text for Section F (Participant Support). Use 'No participant / trainee support costs are requested for this project.' when total_participant_support is 0."
},
"section_g_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated professional justification text for Section G (Other Direct Costs) \u2014 assembles G.1-G.9 subsections in order."
}
}
},
"indirect_costs_and_summary": {
"type": "object",
"additionalProperties": false,
"required": [
"fa_rate_percentage",
"fa_base_type",
"total_direct_costs",
"total_indirect_costs",
"total_project_costs",
"section_h_narrative",
"cost_share_narrative",
"budget_summary_narrative"
],
"description": "Section H (Indirect Costs / F&A), Cost Sharing, and Budget Summary.",
"properties": {
"fa_rate_percentage": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "F&A (indirect cost) rate as a percentage (e.g., 56.5 for 56.5%). Required by source workflow. JSON number. Resolves to UDM IndirectRate.Rate_Percentage."
},
"fa_base_type": {
"type": "string",
"minLength": 1,
"description": "F&A rate base type (MTDC, TDC, Salaries and Wages, etc.). Required by source workflow. Resolves to UDM IndirectRate.Base_Type."
},
"fa_rate_type": {
"type": [
"string",
"null"
],
"description": "Type of F&A rate (on-campus, off-campus, etc.). Defaults to 'Federally negotiated rate' when not stated. Resolves to UDM IndirectRate.Rate_Type."
},
"mtdc_base_exclusions": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "Items excluded from the MTDC base (equipment, participant support, subaward amounts over $25,000, tuition, capital expenditures). Empty array defaults to standard MTDC exclusions."
},
"is_fa_rate_capped": {
"type": [
"boolean",
"null"
],
"description": "Whether the NOFO imposes a cap on the F&A rate."
},
"fa_rate_cap_details": {
"type": [
"string",
"null"
],
"description": "Details of any F&A rate cap from the NOFO."
},
"total_direct_costs": {
"type": "number",
"minimum": 0,
"description": "Total direct costs across all budget categories. Required by source workflow. JSON number. Resolves to UDM ProposalBudget.Direct_Cost."
},
"total_indirect_costs": {
"type": "number",
"minimum": 0,
"description": "Total indirect (F&A) costs. Required by source workflow. JSON number. Resolves to UDM ProposalBudget.Indirect_Cost."
},
"total_project_costs": {
"type": "number",
"minimum": 0,
"description": "Total project costs (direct + indirect). Required by source workflow. JSON number."
},
"cost_share_required": {
"type": [
"boolean",
"null"
],
"description": "Whether cost sharing or matching is required. Resolves to UDM CostShare.Is_Mandatory."
},
"cost_share_amount": {
"type": [
"number",
"null"
],
"minimum": 0,
"description": "Total cost-share commitment amount. JSON number. Resolves to UDM CostShare.Committed_Amount."
},
"cost_share_type": {
"type": [
"string",
"null"
],
"description": "Type of cost share (cash, in-kind, third-party, waived IDC). Resolves to UDM CostShare.Commitment_Type."
},
"cost_share_sources": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "Sources of cost-share contributions. Empty array when none specified."
},
"budget_period_summary": {
"type": "array",
"description": "Year-by-year budget summary table for multi-year budgets. Empty array for single-period budgets. Resolves to UDM ProposalBudget.Period_Number.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"period_number",
"direct_costs",
"indirect_costs",
"total"
],
"properties": {
"period_number": {
"type": "integer",
"minimum": 1
},
"direct_costs": {
"type": "number",
"minimum": 0
},
"indirect_costs": {
"type": "number",
"minimum": 0
},
"total": {
"type": "number",
"minimum": 0
}
}
}
},
"year_over_year_variations": {
"type": [
"string",
"null"
],
"description": "Explanation of year-over-year budget changes. Null for single-year budgets."
},
"nofo_budget_rules": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "Agency-specific budget rules or restrictions from the NOFO. Empty array when no NOFO provided or no specific rules identified."
},
"nofo_page_limit_for_justification": {
"type": [
"string",
"null"
],
"description": "Page limit for the budget justification from the NOFO."
},
"section_h_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated professional justification text for Section H (Indirect Costs). Show the MTDC calculation."
},
"cost_share_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated professional justification text for Cost Sharing. Use 'No cost sharing is required for this project and none is proposed.' when not required."
},
"budget_summary_narrative": {
"type": "string",
"minLength": 1,
"description": "Generated budget summary overview with totals and year-over-year explanation."
}
}
},
"cross_validation": {
"type": "object",
"additionalProperties": false,
"required": [
"arithmetic_checks",
"items_requiring_clarification",
"missing_justification_flags"
],
"description": "Cross-validation notes for the RA's internal review only. NOT for submission to the sponsor.",
"properties": {
"arithmetic_checks": {
"type": "array",
"description": "Arithmetic check results.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"check_name",
"status"
],
"properties": {
"check_name": {
"type": "string",
"minLength": 1
},
"status": {
"type": "string",
"enum": [
"PASS",
"FAIL"
]
},
"expected": {
"type": [
"number",
"string",
"null"
]
},
"actual": {
"type": [
"number",
"string",
"null"
]
},
"discrepancy": {
"type": [
"number",
"string",
"null"
]
}
}
}
},
"items_requiring_clarification": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "Items where the budget or narrative used placeholder text or 'clarification needed from PI'. Empty array when none."
},
"missing_justification_flags": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "Budget categories that show a dollar amount but lack narrative justification. Empty array when all categories have justification."
}
}
},
"final_justification_document": {
"type": "string",
"minLength": 1,
"description": "The complete, assembled budget-justification narrative as a single Markdown string in R&R Budget / SF-424A category order (A through I) plus Cost Sharing and Budget Summary, with the Cross-Validation Notes section clearly marked as not-for-submission."
}
}
}
Changelog¶
Source: CHANGELOG.md.
All notable changes to this component. Versions follow semver: MAJOR for output-contract breaks, MINOR for backward-compatible additions, PATCH for wording or clarity.
[0.1.0] — 2026-05-20¶
- Initial experimental release.
- Schema derived from the
budget-justification-generatorv2 Vandalizer workflow inui-insight/ProcessMapping(four parallel extraction-and-generation tasks, 60+ source fields, one cross-field rule, three validation-plan checks). - Seven-block shape (
project_metadata+ four mid-level data-and-narrative blocks +cross_validation+final_justification_document) captures both the structured per-section extracted financial data AND the generated narrative text per section. Thefinal_justification_documentfield carries the assembled Markdown narrative. - Category
draftingbecause the workflow's primary output is generated narrative text — distinguishes this component from the extraction-and-classification components in the repo. - Monetary fields typed as JSON numbers, not quoted strings:
total_senior_personnel_cost,total_other_personnel_cost,total_fringe_benefits,salary_cap_amount,senior_key_personnel[].requested_salary,equipment_items[].cost,total_equipment_cost,domestic_travel[].total_cost,foreign_travel[].total_cost,total_domestic_travel_cost,total_foreign_travel_cost,participant_support_costs[].cost,total_participant_support,materials_and_supplies[].cost,publication_costs,consultant_services[].{rate, total},subaward_details[].total_cost,total_subaward_costs,tuition_remission,other_direct_costs[].cost,total_other_direct_costs,fa_rate_percentage,total_direct_costs,total_indirect_costs,total_project_costs,cost_share_amount,budget_period_summary[].{direct_costs, indirect_costs, total}. Applies the boss's PR #33 review feedback consistently. Fringe rates remain as strings (e.g.,"28.5%") because they carry a percentage suffix;fa_rate_percentageis a JSON number (e.g.,56.5) because it does not. cross_validation.arithmetic_checks[]typed as objects with astatus: "PASS"/"FAIL"two-value enum. Mirrors the source workflow's arithmetic-check pattern fromextraction-tasks/consolidation.md.- Source
Is_Required: truefields mirrored intorequiredlists at the block level:project_title,pi_name,budget_period_count,senior_key_personnel,total_senior_personnel_cost,fringe_benefit_rates,total_fringe_benefits, the three personnel-and-fringe section narratives,fa_rate_percentage,fa_base_type,total_direct_costs,total_indirect_costs,total_project_costs, all four equipment-and-travel section narratives, the two participant-support-and-ODC section narratives, the three indirect-costs-and-summary narratives, andfinal_justification_document. senior_key_personneltyped withminItems: 1(every proposal has senior / key personnel by definition).- UDM column bindings preserved at the leaf level:
Personnel,Proposal.Proposal_Title,Organization.Organization_Name,ProposalBudget.Direct_Cost,ProposalBudget.Indirect_Cost,ProposalBudget.Period_Number,IndirectRate.Rate_Percentage,IndirectRate.Base_Type,IndirectRate.Rate_Type,CostShare.Is_Mandatory,CostShare.Committed_Amount,CostShare.Commitment_Type. The seven-block drafting shape itself is repo-local. - No eval cases yet — status
experimentaluntil at least one golden extraction is added underevals/cases/.