Files
rfp_response_automation/files/templates/index.html
2026-02-18 20:34:33 -03:00

1023 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block content %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Oracle AI RFP Response</title>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: false,
theme: "default",
themeVariables: {
fontSize: "14px",
nodePadding: 20,
primaryBorderColor: "#334155",
primaryTextColor: "#111",
lineColor: "#333"
},
flowchart: {
useMaxWidth: false, // 🔥 CRÍTICO → NÃO escalar automático
htmlLabels: true
}
});
</script>
<style>
/* =================================================
CLEAN ORACLE LIGHT THEME (mesmo layout, só cores)
================================================= */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* ================= BODY ================= */
body {
font-family: Arial, sans-serif;
font-size: 16px;
/* antes: gradiente azul escuro */
background: #f5f6f7;
min-height: 100vh;
/* antes: texto claro */
color: #1f2937;
padding: 40px;
}
/* ================= TITLES ================= */
h1 {
text-align: center;
margin-bottom: 40px;
/* antes azul claro */
color: #111;
}
h2 {
/* Oracle red */
color: #C74634;
margin-top: 0;
}
h3 {
color: #374151;
margin-bottom: 6px;
}
/* ================= CARDS ================= */
.card {
width: 100%;
max-width: 1600px;
margin: 0 auto 40px auto;
/* antes azul escuro */
background: #ffffff;
padding: 32px 40px;
border-radius: 18px;
/* antes #334155 */
border: 1px solid #e5e7eb;
/* sombra leve estilo docs */
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
/* ================= TEXT ================= */
.small {
font-size: 15px;
/* antes azul claro */
color: #6b7280;
line-height: 1.7;
}
.highlight {
color: #C74634;
font-weight: bold;
}
/* ================= INPUTS ================= */
textarea {
width: 100%;
height: 110px;
font-size: 16px;
padding: 14px;
border-radius: 8px;
/* antes sem borda */
border: 1px solid #e5e7eb;
background: white;
margin-top: 12px;
}
/* ================= BUTTON ================= */
button {
margin-top: 14px;
padding: 12px 26px;
font-size: 16px;
cursor: pointer;
border-radius: 8px;
border: none;
/* antes azul */
background: #C74634;
color: white;
}
button:hover {
background: #A63626;
}
/* ================= CODE BLOCKS ================= */
pre {
/* antes #020617 */
background: #f6f7f9;
padding: 22px;
white-space: pre-wrap;
border-radius: 10px;
margin-top: 12px;
font-size: 14px;
color: #111;
border: 1px solid #e5e7eb;
}
code {
display: block;
background: #f6f7f9;
padding: 16px;
border-radius: 10px;
font-size: 13px;
margin-top: 10px;
color: #111;
border: 1px solid #e5e7eb;
}
/* ================= LISTS ================= */
ul {
padding-left: 18px;
}
/* ================= HR ================= */
hr {
border: none;
border-top: 1px solid #e5e7eb;
margin: 28px 0;
}
/* ================= INFO BOXES ================= */
.rfp-help .info-box {
border-left: 5px solid #C74634;
background: #fff4f2;
color: #111;
padding: 12px 16px;
margin: 18px 0;
font-weight: 500;
}
.rfp-help .warn-box {
border-left: 5px solid #b02a37;
background: #fdecea;
color: #3b0d0c;
padding: 12px 16px;
margin: 18px 0;
font-weight: 500;
}
.hidden {
display: none;
}
/* ================= LINKS ================= */
.doc-link {
color: #C74634;
text-decoration: underline;
}
.doc-link:hover {
opacity: .8;
}
/* ================= ENDPOINT BADGE ================= */
.endpoint {
display: block;
max-width: 100%;
overflow-wrap: anywhere;
word-break: break-word;
white-space: normal;
background: #f3f4f6;
color: #111;
padding: 6px 10px;
border-radius: 4px;
font-family: Consolas, monospace;
}
/* ================= IMAGE ================= */
.responsive-img {
max-width: 100%;
height: auto;
display: block;
margin: 16px auto;
border-radius: 10px;
/* sombra leve */
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
/* ================= MERMAID ================= */
#mermaidContainer {
overflow-y: auto;
overflow-x: hidden;
}
#mermaidContainer svg {
max-width: 100% !important;
height: auto;
}
.mermaid .label {
white-space: normal !important;
word-break: break-word;
max-width: 200px;
}
</style>
</head>
<body>
<!-- ================= INTRODUCTION ================= -->
<div class="card">
<p class="small">
Oracle LAD A-Team<br/>
<span class="highlight">Cristiano Hoshikawa</span><br/>
<span class="highlight">cristiano.hoshikawa@oracle.com</span>
</p>
<p class="small">
<strong>Tutorial</strong><br>
<a href="https://docs.oracle.com/en/learn/oci-genai-pdf"
class="doc-link"
target="_blank"
rel="noopener">
Oracle Learn OCI Generative AI PDF RAG
</a>
<br>
<a href="https://github.com/hoshikawa2/rfp_response_automation"
class="doc-link"
target="_blank"
rel="noopener">
Oracle GraphRAG for RFP Validation
</a>
<br><br>
<strong>REST Service Endpoint</strong><br>
<code class="endpoint">
{{ api_base_url }}/rest/chat
</code>
</p>
<hr/>
<h2>Overview</h2>
<p class="small">
This application provides an <strong>AI-assisted RFP response engine</strong> for
Oracle Cloud Infrastructure (OCI).
It analyzes natural language requirements and returns a
<strong>structured, evidence-based technical response</strong>.
</p>
<ul class="small">
<li>Official Oracle technical documentation</li>
<li>Semantic search using vector embeddings</li>
<li>Knowledge Graph signals</li>
<li>Large Language Models (LLMs)</li>
</ul>
</div>
<!-- ================= DISCLAIMERS ================= -->
<div class="card">
<h2>Important Notes</h2>
<ul class="small">
<li>
Responses are generated by an <strong>LLM</strong>.
Even with low temperature, minor variations may occur across executions.
</li>
<li>
Results depend on wording, terminology, and framing of the requirement.
</li>
<li>
In many RFPs, an initial <strong>NO</strong> can be reframed into a valid
<strong>YES</strong> by mapping the requirement to the correct OCI service.
</li>
<li>
<strong>Human review is mandatory.</strong>
This tool supports architects and RFP teams — it does not replace them.
</li>
</ul>
<p class="small">
GraphRAG • Oracle Autonomous Database 23ai • Embeddings • Knowledge Graph • LLM • Flask API
</p>
</div>
<!-- ================= TEST AREA ================= -->
{% if current_user and current_user.role in ("admin", "user") %}
<div class="card">
<h2>Try It — Live RFP Question</h2>
<p class="small">
Enter an RFP requirement or technical question below.
The API will return a structured JSON response.
</p>
<textarea id="question" placeholder="Example: Does OCI Compute support Real Application Clusters (RAC)?"></textarea>
<button onclick="send()">Submit Question</button>
<div id="rfpBox" class="hidden">
<h3>AI Response</h3>
<pre id="answer"></pre>
</div>
</div>
{% endif %}
<!-- ================= Architectural Solution ============== -->
{% if current_user and current_user.role in ("admin", "user") %}
<div class="card">
<h2>🏗 Architecture Planner</h2>
<p class="small">
This is an advanced analysis engine for designing Architectural Solutions based on OCI resources.
It uses LRM mechanism with Chain-of-Tought to prepare solutions that require a set of components in OCI.
</p>
<textarea id="archQuestion"
placeholder="Example: Does OCI provide a single managed private enterprise chatbot service?"
style="width:100%;height:90px;margin-bottom:10px;"></textarea>
<button onclick="askArchitecture()">Generate Architecture</button>
<div id="arch-loading"
style="display:none;margin-top:12px;font-weight:bold;color:#60a5fa">
</div>
<div id="archBox" class="hidden">
<h3>Architecture Steps</h3>
<pre id="archLogs"></pre>
<pre id="archJson"></pre>
<div id="mermaidContainer"></div>
</div>
</div>
{% endif %}
<!-- ================= EXCEL RFP PROCESSOR ================= -->
<div class="card">
<h2>Submit your RFP (Excel)</h2>
<p class="small">
Upload an Excel file and receive the processed result by email.
You do not need to keep this page open.
</p>
<p class="small">
Follow the Excel format:<br>
<strong>Column A</strong>: MUST be a sequential numeric<br>
<strong>Column B, C</strong>: MUST be fill with a context. Could be a domain and sub-domain for the question<br>
<strong>Column D</strong>: Optional<br>
<strong>Column E</strong>: MUST be the main question<br>
</p>
<p>
<img src="{{ url_for('static', filename='Excel_example.png') }}"
alt="Exemplo de planilha Excel"
class="responsive-img">
</p>
<input type="email" id="email" placeholder="your@email.com" required />
<br><br>
<input type="file" id="excelFile" accept=".xlsx" />
<br><br>
<button onclick="uploadExcel()">Upload & Process</button>
<pre id="uploadStatus" class="hidden"></pre>
</div>
<!-- ================= REST API DOC ================= -->
<div class="card">
<h2>REST API Usage</h2>
<p class="small">
The service exposes a <strong>POST</strong> endpoint that accepts a JSON payload.
</p>
<code>
curl -X POST {{ api_base_url }}/rest/chat
-H "Content-Type: application/json"
-u app_user:app_password
-d '{
"question": "Does Oracle Cloud Infrastructure (OCI) Compute support online resizing of memory for running virtual machine instances?"
}'
</code>
<h3>Request Parameters</h3>
<p class="small">
<strong>question</strong> (string)<br/>
Natural language description of an RFP requirement or technical capability.
Small wording changes may affect how intent and evidence are interpreted.
</p>
</div>
<!-- ================= JSON EXPLANATION ================= -->
<div class="card">
<h2>AI Response JSON Structure</h2>
<p class="small">
The API always returns a <strong>strict and normalized JSON structure</strong>,
designed for traceability, auditing, and human validation.
</p>
<h3>answer</h3>
<p class="small">
Final assessment of the requirement:
<strong>YES</strong>, <strong>NO</strong>, or <strong>PARTIAL</strong>.
A <strong>NO</strong> means the requirement is not explicitly satisfied as written.
</p>
<h3>confidence</h3>
<p class="small">
Indicates the strength of the supporting evidence:
HIGH, MEDIUM, or LOW.
</p>
<h3>ambiguity_detected</h3>
<p class="small">
Flags whether the requirement is vague, overloaded, or open to interpretation.
</p>
<h3>confidence_reason</h3>
<p class="small">
Short explanation justifying the confidence level.
</p>
<h3>justification</h3>
<p class="small">
Technical rationale connecting the evidence to the requirement.
This is not marketing text.
</p>
<h3>evidence</h3>
<p class="small">
List of supporting references:
</p>
<ul class="small">
<li><strong>quote</strong> Exact extracted text</li>
<li><strong>source</strong> URL or document reference</li>
</ul>
</div>
<div class="card">
<div class="rfp-help">
<h2>How to Use the RFP AI with a custom Python Code</h2>
<p>
This solution exposes a REST API that allows RFP questions to be evaluated programmatically.
By consuming this API, users can execute a Python automation that reads spreadsheet files,
builds contextualized questions, sends them to the AI service, and writes the results back
to the same spreadsheet.
</p>
<p>
The automation supports both <strong>hierarchical</strong> and <strong>non-hierarchical</strong>
spreadsheet structures. Depending on how the spreadsheet is organized, the Python code
automatically determines how to construct each question, ensuring that the context sent
to the AI is accurate, consistent, and auditable.
</p>
<p>
This approach enables large RFP documents to be processed in bulk, replacing manual analysis
with a repeatable and controlled workflow driven by a REST interface and a Python execution layer.
</p>
<div class="info-box">
<strong>Source Code Download</strong><br><br>
The Python script responsible for reading RFP spreadsheets, calling the REST API,
and writing results back to the file can be downloaded below:
<br><br>
<a href="/static/process_excel_rfp.py" download>
📥 process_excel_rfp.py
</a>
</div>
<hr>
<h3>1. Hierarchical Spreadsheet</h3>
<p>
A spreadsheet is considered <strong>hierarchical</strong> when it contains a numbering column
that represents a tree structure, such as:
</p>
<pre>
1
1.1
1.1.1
1.2
</pre>
<p>
In this format:
</p>
<ul>
<li>The hierarchy is explicitly defined by the numbering</li>
<li>Parent items provide contextual meaning</li>
<li>Leaf items (those without children) are sent to the AI for evaluation</li>
</ul>
<div class="info-box">
<strong>Example:</strong><br>
Item <code>1.2.3</code> inherits context from <code>1</code><code>1.2</code>
</div>
<h3>2. Non-Hierarchical Spreadsheet</h3>
<p>
A spreadsheet is considered <strong>non-hierarchical</strong> when no valid hierarchical
numbering exists or when numbering does not represent a logical structure.
</p>
<p>
In these cases, context is distributed across specific columns, for example:
</p>
<pre>
Domain | Subdomain | Topic | Question
</pre>
<p>
The pipeline uses only explicitly declared context columns, preventing semantic noise
such as internal IDs or technical codes from being included in the prompt.
</p>
<h3>3. How the Pipeline Selects the Mode</h3>
<pre>
If the order value is hierarchical:
use numeric hierarchy
Else:
use column-based hierarchy
</pre>
<div class="info-box">
This decision ensures deterministic and auditable behavior.
</div>
<h3>4. Key Code Sections</h3>
<h4>4.1 Hierarchy Detection</h4>
<pre><code>
def is_hierarchical(num: str) -> bool:
if not num:
return False
parts = num.split(".")
return all(p.isdigit() for p in parts)
</code></pre>
<p>
This function determines whether a row belongs to the numeric hierarchy.
</p>
<h4>4.2 Hierarchical Question Builder</h4>
<pre><code>
def build_question(hierarchy: dict, current_num: str) -> str:
...
return f'Considering the context of "{context}", {specific}'
</code></pre>
<p>
This logic walks up the hierarchy tree to build a contextualized question.
</p>
<h4>4.3 Column-Based Question Builder</h4>
<pre><code>
def build_question_from_columns(row, context_cols, question_col):
...
return f'Considering the context of "{context}", {question}'
</code></pre>
<p>
This builder is used only when no numeric hierarchy exists.
</p>
<h4>4.4 Correct Row Retrieval (Critical)</h4>
<pre><code>
row = df.loc[info["row"]]
num = normalize_num(str(row.iloc[ORDER_COLUMN]))
</code></pre>
<div class="warn-box">
<strong>Important:</strong><br>
Always retrieve the correct DataFrame row before accessing column values.
If this step is skipped, hierarchical processing will not work correctly.
</div>
<h3>5. Best Practices</h3>
<ul>
<li>Do not include internal IDs or technical codes as semantic context</li>
<li>Explicitly define context columns (<code>CONTEXT_COLUMNS</code>)</li>
<li>Avoid heuristic-based hierarchy guessing</li>
<li>Prefer deterministic, auditable logic</li>
</ul>
<h3>6. Summary</h3>
<div class="info-box">
This pipeline is designed to:
<ul>
<li>Support multiple RFP spreadsheet formats</li>
<li>Eliminate semantic noise</li>
<li>Produce consistent, high-quality prompts</li>
<li>Scale for enterprise usage</li>
</ul>
</div>
</div>
</div>
<div class="card">
<div class="info-box">
<h2>Configure and Test your Custom Python Code</h2>
Before running <code>process_excel_rfp.py</code>, you must configure the input spreadsheet, the REST endpoint,
and authentication. These parameters can be set directly in the script or provided via environment variables.
</div>
<h3>0. Prerequisites</h3>
<ul>
<li>Python 3.10+ installed</li>
<li>Install dependencies: <code>pip install pandas requests openpyxl</code></li>
<li>Access to the REST API endpoint (network + credentials)</li>
</ul>
<hr>
<h3>1. Script Parameters (Edit in the .py file)</h3>
<div class="info-box">
<strong>Main configuration variables</strong><br><br>
Open <code>process_excel_rfp.py</code> and update the values below:
</div>
<pre>
EXCEL_PATH = "/path/to/your/RFP.xlsx"
API_URL = "{{ api_base_url }}/chat"
TIMEOUT = 120
ORDER_COLUMN = 0 # column index containing the order/numbering
QUESTION_COLUMN = 1 # column index containing the question text
# Use this only for NON-hierarchical spreadsheets:
CONTEXT_COLUMNS = [1, 2] # columns that contain context (domain, topic, section, etc.)
# Output column names (created if missing):
ANSWER_COL = "ANSWER"
JSON_COL = "RESULT_JSON"
</pre>
<h4>EXCEL_PATH</h4>
<p>
Full path to the spreadsheet you want to process. The script reads this file and writes a new output file
named <code>_resultado.xlsx</code> in the same folder.
</p>
<h4>API_URL</h4>
<p>
The REST endpoint exposed by the AI service. It must accept:
<code>POST</code> with JSON payload <code>{"question": "..."} </code>.
</p>
<h4>ORDER_COLUMN and QUESTION_COLUMN</h4>
<p>
The script uses <code>ORDER_COLUMN</code> to identify hierarchy (e.g., <code>1</code>, <code>1.1</code>, <code>1.1.1</code>).
The <code>QUESTION_COLUMN</code> is the text that will be sent to the AI.
</p>
<h4>CONTEXT_COLUMNS (Non-hierarchical mode)</h4>
<p>
If your spreadsheet is not hierarchical, context comes from fixed columns (for example: Domain → Subdomain → Topic).
Only the columns listed in <code>CONTEXT_COLUMNS</code> will be used to build context.
This avoids adding noisy values such as internal IDs or codes.
</p>
<hr>
<h3>2. Authentication (Environment Variables)</h3>
<div class="warn-box">
<strong>Important:</strong><br>
Do not hardcode credentials inside the spreadsheet or the script if the file will be shared.
Prefer environment variables.
</div>
<p>
The script uses HTTP Basic Auth to call the API. Configure credentials using environment variables:
</p>
<pre>
export APP_USER="YOU USER"
export APP_PASS="YOUR PASSWORD"
</pre>
<p>
On Windows PowerShell:
</p>
<pre>
setx APP_USER "YOUR USER"
setx APP_PASS "YOUR PASSWORD"
</pre>
<p>
If not provided, the script falls back to the defaults defined in the code:
<code>APP_USER</code> / <code>APP_PASS</code>.
</p>
<hr>
<h3>3. How to Run</h3>
<pre>
python process_excel_rfp.py
</pre>
<p>
The script will:
</p>
<ul>
<li>Load the spreadsheet</li>
<li>Detect whether the sheet is hierarchical (based on numbering)</li>
<li>Build a contextual question for each leaf item</li>
<li>Send each question to the REST API</li>
<li>Write ANSWER + JSON results back to a new spreadsheet file</li>
<li>Log LOW/MEDIUM confidence or NO answers into <code>queries_with_low_confidence_or_no.txt</code></li>
</ul>
<div class="info-box">
<strong>Output</strong><br><br>
A new file will be created next to the original spreadsheet:<br>
<code>RFP_result.xlsx</code>
</div>
</div>
<script>
async function send() {
document.getElementById("rfpBox").classList.remove("hidden")
const question = document.getElementById("question").value
const answerBox = document.getElementById("answer")
answerBox.textContent = "⏳ Processing request..."
const start = await fetch("/chat/start", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({question})
})
const {job_id} = await start.json()
const interval = setInterval(async () => {
const s = await fetch(`/chat/${job_id}/status`)
const status = await s.json()
if(status.status === "DONE"){
clearInterval(interval)
const r = await fetch(`/chat/${job_id}/result`)
const data = await r.json()
answerBox.textContent = JSON.stringify(data.result, null, 2)
}
}, 700)
const data = await res.json()
answerBox.textContent = JSON.stringify(data.result, null, 2)
}
</script>
<script>
async function uploadExcel() {
document.getElementById("uploadStatus").classList.remove("hidden")
const email = document.getElementById("email").value;
const fileInput = document.getElementById("excelFile");
const statusBox = document.getElementById("uploadStatus");
if (!email) {
alert("Please enter an email");
return;
}
if (!fileInput.files.length) {
alert("Please select an Excel file");
return;
}
const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("email", email);
statusBox.textContent = "⏳ Uploading and starting processing...";
const res = await fetch("/upload/excel", {
method: "POST",
body: formData
});
const data = await res.json();
if (!data.job_id) {
statusBox.textContent = "❌ Error: " + JSON.stringify(data);
return;
}
statusBox.textContent =
`✅ Upload successful\n` +
`📧 You will receive an email at: ${email}\n` +
`🆔 Job ID: ${data.job_id}\n\n` +
`You may safely close this page.`;
}
</script>
<script>
async function askArchitecture() {
document.getElementById("archBox").classList.remove("hidden")
const question = document.getElementById("archQuestion").value
const logsBox = document.getElementById("archLogs")
const jsonBox = document.getElementById("archJson")
const diagramBox = document.getElementById("mermaidContainer")
logsBox.innerText = ""
jsonBox.innerText = ""
diagramBox.innerHTML = ""
logsBox.innerText = "Starting...\n"
showLoading("Building architecture with LRM...")
const start = await fetch("/architecture/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ question })
})
const { job_id } = await start.json()
const interval = setInterval(async () => {
// =====================
// STATUS
// =====================
const statusRes = await fetch(`/architecture/${job_id}/status`)
const status = await statusRes.json()
// =====================
// 🔥 LOGS (NOVO)
// =====================
const logsRes = await fetch(`/architecture/${job_id}/logs`)
const logsData = await logsRes.json()
if (logsData.logs) {
logsBox.innerText = logsData.logs.join("\n")
logsBox.scrollTop = logsBox.scrollHeight
}
// =====================
// DONE
// =====================
if (status.status === "DONE") {
clearInterval(interval)
hideLoading()
const resultRes = await fetch(`/architecture/${job_id}/result`)
const plan = await resultRes.json()
renderArchitecture(plan)
}
// =====================
// ERROR
// =====================
if (status.status === "ERROR") {
clearInterval(interval)
hideLoading()
logsBox.innerText += "\n❌ ERROR"
}
}, 800)
}
// ==========================
// Loading helpers
// ==========================
function showLoading(msg = "Loading...") {
const el = document.getElementById("arch-loading")
el.innerText = msg
el.style.display = "block"
}
function hideLoading() {
document.getElementById("arch-loading").style.display = "none"
}
// ==========================
// Render Architecture Result
// ==========================
async function renderArchitecture(plan) {
const container = document.getElementById("mermaidContainer")
const jsonDiv = document.getElementById("archJson")
container.innerHTML = ""
jsonDiv.innerText = JSON.stringify(plan, null, 2)
if (!plan.mermaid) return
const el = document.createElement("div")
el.className = "mermaid"
el.textContent = plan.mermaid
container.appendChild(el)
await mermaid.run({ nodes: [el] })
// 🔥 ajuste automático de zoom mínimo
const svg = container.querySelector("svg")
if (!svg) return
const width = svg.getBBox().width
if (width > container.clientWidth) {
svg.style.width = width + "px" // mantém tamanho real
}
}
</script>
</body>
</html>
{% endblock %}