mirror of
https://github.com/hoshikawa2/rfp_response_automation.git
synced 2026-03-03 16:09:35 +00:00
1023 lines
29 KiB
HTML
1023 lines
29 KiB
HTML
{% 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 %} |