Upload app.py
Browse files
app.py
ADDED
@@ -0,0 +1,388 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, File, UploadFile, HTTPException
|
2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
3 |
+
import torch
|
4 |
+
import ollama
|
5 |
+
import requests
|
6 |
+
from typing import List, Dict
|
7 |
+
from pydantic import BaseModel
|
8 |
+
import base64
|
9 |
+
from pathlib import Path
|
10 |
+
import tempfile
|
11 |
+
import json
|
12 |
+
from langchain_community.document_loaders import PyPDFLoader
|
13 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
14 |
+
from langchain.embeddings import HuggingFaceBgeEmbeddings
|
15 |
+
import numpy as np
|
16 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
17 |
+
from fastapi.templating import Jinja2Templates
|
18 |
+
from fastapi.responses import HTMLResponse
|
19 |
+
from fastapi.requests import Request
|
20 |
+
import os
|
21 |
+
|
22 |
+
|
23 |
+
app = FastAPI()
|
24 |
+
|
25 |
+
# Enable CORS
|
26 |
+
app.add_middleware(
|
27 |
+
CORSMiddleware,
|
28 |
+
allow_origins=["*"],
|
29 |
+
allow_credentials=True,
|
30 |
+
allow_methods=["*"],
|
31 |
+
allow_headers=["*"],
|
32 |
+
)
|
33 |
+
|
34 |
+
|
35 |
+
class ProjectDetails(BaseModel):
|
36 |
+
construction_type: str
|
37 |
+
phase_count: int
|
38 |
+
description: str
|
39 |
+
|
40 |
+
|
41 |
+
class ResourcePhase(BaseModel):
|
42 |
+
phase_number: int
|
43 |
+
materials: Dict[str, Dict[str, float]]
|
44 |
+
timeline: str
|
45 |
+
dependencies: List[str]
|
46 |
+
|
47 |
+
|
48 |
+
class DocumentStore:
|
49 |
+
def __init__(self, embeddings_model):
|
50 |
+
self.embeddings_model = embeddings_model
|
51 |
+
self.documents = []
|
52 |
+
self.embeddings = None
|
53 |
+
|
54 |
+
def add_documents(self, documents):
|
55 |
+
self.documents = documents
|
56 |
+
texts = [doc.page_content for doc in documents]
|
57 |
+
self.embeddings = self.embeddings_model.embed_documents(texts)
|
58 |
+
|
59 |
+
def similarity_search(self, query, k=5):
|
60 |
+
query_embedding = self.embeddings_model.embed_query(query)
|
61 |
+
similarities = cosine_similarity([query_embedding], self.embeddings)[0]
|
62 |
+
top_k_indices = np.argsort(similarities)[-k:][::-1]
|
63 |
+
return [self.documents[i] for i in top_k_indices]
|
64 |
+
|
65 |
+
|
66 |
+
class IBMGraniteClient:
|
67 |
+
def __init__(self, api_key: str, project_id: str):
|
68 |
+
self.api_key = api_key
|
69 |
+
self.project_id = project_id
|
70 |
+
self.auth_token = None
|
71 |
+
|
72 |
+
def get_auth_token(self):
|
73 |
+
auth_url = "https://iam.cloud.ibm.com/identity/token"
|
74 |
+
auth_data = {
|
75 |
+
"grant_type": "urn:ibm:params:oauth:grant-type:apikey",
|
76 |
+
"apikey": self.api_key
|
77 |
+
}
|
78 |
+
auth_headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
79 |
+
|
80 |
+
response = requests.post(auth_url, data=auth_data, headers=auth_headers)
|
81 |
+
self.auth_token = response.json().get("access_token")
|
82 |
+
if not self.auth_token:
|
83 |
+
raise Exception("Failed to retrieve IBM access token")
|
84 |
+
|
85 |
+
def analyze_phase(self, phase_details: str) -> str:
|
86 |
+
if not self.auth_token:
|
87 |
+
self.get_auth_token()
|
88 |
+
|
89 |
+
url = "https://us-south.ml.cloud.ibm.com/ml/v1/text/generation?version=2023-05-29"
|
90 |
+
headers = {
|
91 |
+
"Accept": "application/json",
|
92 |
+
"Content-Type": "application/json",
|
93 |
+
"Authorization": f"Bearer {self.auth_token}"
|
94 |
+
}
|
95 |
+
|
96 |
+
prompt = f"""Analyze the following construction phase and provide detailed insights
|
97 |
+
about resource optimization, potential risks, and timeline dependencies:
|
98 |
+
{phase_details}"""
|
99 |
+
|
100 |
+
body = {
|
101 |
+
"input": prompt,
|
102 |
+
"parameters": {
|
103 |
+
"decoding_method": "greedy",
|
104 |
+
"max_new_tokens": 900,
|
105 |
+
"repetition_penalty": 1
|
106 |
+
},
|
107 |
+
"model_id": "ibm/granite-3-8b-instruct",
|
108 |
+
"project_id": self.project_id
|
109 |
+
}
|
110 |
+
|
111 |
+
response = requests.post(url, headers=headers, json=body)
|
112 |
+
if response.status_code != 200:
|
113 |
+
raise Exception(f"IBM Granite API error: {response.text}")
|
114 |
+
|
115 |
+
return response.json().get("results", [{}])[0].get("generated_text", "")
|
116 |
+
|
117 |
+
|
118 |
+
class MaterialInferenceChain:
|
119 |
+
def __init__(self, doc_store, granite_client):
|
120 |
+
self.doc_store = doc_store
|
121 |
+
self.granite_client = granite_client
|
122 |
+
|
123 |
+
async def analyze_image(self, image_path: str, project_details: ProjectDetails) -> List[ResourcePhase]:
|
124 |
+
# First get basic material requirements using LLaVA
|
125 |
+
llava_response = ollama.chat(
|
126 |
+
model="llava:13b",
|
127 |
+
messages=[
|
128 |
+
{
|
129 |
+
"role": "user",
|
130 |
+
"content": f"""
|
131 |
+
|
132 |
+
You are an expert construction engineer. Based on the construction drawing image, list every single required construction material with their quantities for a {project_details.construction_type} project.
|
133 |
+
|
134 |
+
Go through every single item in this list of materials and choose which all are required to construct the above drawing plan:
|
135 |
+
|
136 |
+
1. Concrete
|
137 |
+
Cement
|
138 |
+
Sand
|
139 |
+
Gravel (Aggregates)
|
140 |
+
Water
|
141 |
+
Reinforcement Steel (Rebars)
|
142 |
+
2. Steel
|
143 |
+
Structural Steel (I-beams, H-beams)
|
144 |
+
Reinforcement Steel (Rebars)
|
145 |
+
Steel Plates
|
146 |
+
Steel Tubes & Pipes
|
147 |
+
3. Wood
|
148 |
+
Softwood (Pine, Cedar)
|
149 |
+
Hardwood (Oak, Maple)
|
150 |
+
Plywood
|
151 |
+
Particle Board
|
152 |
+
Laminated Veneer Lumber (LVL)
|
153 |
+
Timber
|
154 |
+
4. Masonry Materials
|
155 |
+
Clay Bricks
|
156 |
+
Concrete Blocks (CMU)
|
157 |
+
Stone (Granite, Limestone)
|
158 |
+
Mortar
|
159 |
+
5. Glass
|
160 |
+
Float Glass
|
161 |
+
Tempered Glass
|
162 |
+
Laminated Glass
|
163 |
+
Insulated Glass
|
164 |
+
6. Plastics and Polymers
|
165 |
+
Polyvinyl Chloride (PVC)
|
166 |
+
High-Density Polyethylene (HDPE)
|
167 |
+
Polystyrene (PS)
|
168 |
+
Polypropylene (PP)
|
169 |
+
Fiber Reinforced Polymer (FRP)
|
170 |
+
7. Insulation Materials
|
171 |
+
Fiberglass
|
172 |
+
Mineral Wool
|
173 |
+
Polystyrene Foam (EPS, XPS)
|
174 |
+
Polyurethane Foam
|
175 |
+
Insulated Panels
|
176 |
+
8. Roofing Materials
|
177 |
+
Asphalt Shingles
|
178 |
+
Metal Sheets
|
179 |
+
Clay or Concrete Tiles
|
180 |
+
Slate
|
181 |
+
Membrane Roofing (EPDM, TPO)
|
182 |
+
9. Flooring Materials
|
183 |
+
Ceramic Tiles
|
184 |
+
Porcelain Tiles
|
185 |
+
Vinyl Flooring
|
186 |
+
Hardwood Flooring
|
187 |
+
Laminate Flooring
|
188 |
+
Carpet
|
189 |
+
Terrazzo
|
190 |
+
10. Adhesives and Sealants
|
191 |
+
Epoxy Resins
|
192 |
+
Silicone Sealants
|
193 |
+
Acrylic Sealants
|
194 |
+
Polyurethane Sealants
|
195 |
+
11. Paints and Coatings
|
196 |
+
Water-Based Paints
|
197 |
+
Oil-Based Paints
|
198 |
+
Anti-Corrosive Coatings
|
199 |
+
Fire-Resistant Paints
|
200 |
+
12. Plumbing Materials
|
201 |
+
PVC Pipes
|
202 |
+
Copper Pipes
|
203 |
+
PEX Tubing
|
204 |
+
Cast Iron Pipes
|
205 |
+
Faucets, Valves, and Fittings
|
206 |
+
13. Electrical Materials
|
207 |
+
Copper Wiring
|
208 |
+
Conduits (PVC, Metal)
|
209 |
+
Switches & Sockets
|
210 |
+
Electrical Panels
|
211 |
+
Circuit Breakers
|
212 |
+
Light Fixtures
|
213 |
+
14. Fasteners and Connectors
|
214 |
+
Nails
|
215 |
+
Screws
|
216 |
+
Bolts
|
217 |
+
Washers
|
218 |
+
Nuts
|
219 |
+
Rivets
|
220 |
+
15. Doors and Windows
|
221 |
+
Wooden Doors
|
222 |
+
Steel Doors
|
223 |
+
Aluminum Doors & Windows
|
224 |
+
uPVC Doors & Windows
|
225 |
+
16. Gypsum and Plaster
|
226 |
+
Gypsum Boards (Drywall)
|
227 |
+
Plaster of Paris (POP)
|
228 |
+
Cement Plaster
|
229 |
+
Stucco
|
230 |
+
17. Waterproofing Materials
|
231 |
+
Bituminous Waterproofing
|
232 |
+
Liquid Applied Membranes
|
233 |
+
Waterstops
|
234 |
+
Waterproofing Sheets
|
235 |
+
18. Ceramics
|
236 |
+
Ceramic Tiles
|
237 |
+
Porcelain Tiles
|
238 |
+
Sanitary Wares (Sinks, Toilets)
|
239 |
+
19. Metal Products
|
240 |
+
Aluminum Sheets
|
241 |
+
Stainless Steel Sheets
|
242 |
+
Cast Iron Products
|
243 |
+
20. Specialized Materials
|
244 |
+
Geo-synthetics (Geo-textiles, Geo-membranes)
|
245 |
+
Acoustic Panels
|
246 |
+
Fireproofing Materials
|
247 |
+
Solar Panels
|
248 |
+
|
249 |
+
|
250 |
+
|
251 |
+
|
252 |
+
Provide the materials and quantities in the following format only:
|
253 |
+
material_name: quantity unit
|
254 |
+
|
255 |
+
Do not include any additional text, explanations, or headers.
|
256 |
+
|
257 |
+
Example Output:
|
258 |
+
Concrete: 1500 cubic_meters
|
259 |
+
Steel Reinforcement: 250 tons
|
260 |
+
Structural Steel: 800 tons""",
|
261 |
+
"images": [image_path]
|
262 |
+
}
|
263 |
+
]
|
264 |
+
)
|
265 |
+
|
266 |
+
# Parse LLaVA's material suggestions
|
267 |
+
base_materials = self._parse_materials(llava_response["message"]["content"])
|
268 |
+
|
269 |
+
# Generate phases using IBM Granite
|
270 |
+
phases = []
|
271 |
+
for phase_num in range(1, project_details.phase_count + 1):
|
272 |
+
phase_prompt = f"""
|
273 |
+
Phase {phase_num} of {project_details.phase_count}
|
274 |
+
Project Type: {project_details.construction_type}
|
275 |
+
Description: {project_details.description}
|
276 |
+
Base Materials: {json.dumps(base_materials, indent=2)}
|
277 |
+
"""
|
278 |
+
|
279 |
+
phase_analysis = self.granite_client.analyze_phase(phase_prompt)
|
280 |
+
|
281 |
+
# Create phase resource allocation
|
282 |
+
phase = ResourcePhase(
|
283 |
+
phase_number=phase_num,
|
284 |
+
materials=self._allocate_materials(base_materials, phase_num, project_details.phase_count),
|
285 |
+
timeline=f"Phase {phase_num} Timeline",
|
286 |
+
dependencies=self._extract_dependencies(phase_analysis)
|
287 |
+
)
|
288 |
+
phases.append(phase)
|
289 |
+
|
290 |
+
return phases
|
291 |
+
|
292 |
+
def _parse_materials(self, llava_output: str) -> Dict[str, Dict[str, float]]:
|
293 |
+
# Parse the material list from LLaVA's output
|
294 |
+
materials = {}
|
295 |
+
for line in llava_output.split('\n'):
|
296 |
+
if ':' in line:
|
297 |
+
material, quantity_info = line.split(':', 1)
|
298 |
+
quantity, unit = quantity_info.strip().split()
|
299 |
+
materials[material.strip()] = {
|
300 |
+
"quantity": float(quantity),
|
301 |
+
"unit": unit
|
302 |
+
}
|
303 |
+
return materials
|
304 |
+
|
305 |
+
def _allocate_materials(self, base_materials: Dict, phase_num: int, total_phases: int) -> Dict:
|
306 |
+
# Allocate materials across phases (simplified version)
|
307 |
+
phase_materials = {}
|
308 |
+
for material, info in base_materials.items():
|
309 |
+
phase_materials[material] = {
|
310 |
+
"quantity": info["quantity"] / total_phases,
|
311 |
+
"unit": info["unit"]
|
312 |
+
}
|
313 |
+
return phase_materials
|
314 |
+
|
315 |
+
def _extract_dependencies(self, phase_analysis: str) -> List[str]:
|
316 |
+
# Extract dependencies from phase analysis (simplified version)
|
317 |
+
return ["Previous phase completion"] if phase_analysis else []
|
318 |
+
|
319 |
+
|
320 |
+
# Initialize services
|
321 |
+
def init_services():
|
322 |
+
# Initialize embeddings
|
323 |
+
model_name = "BAAI/bge-base-en"
|
324 |
+
encode_kwargs = {'normalize_embeddings': True}
|
325 |
+
device = "mps" if torch.backends.mps.is_available() else "cpu"
|
326 |
+
|
327 |
+
embeddings_model = HuggingFaceBgeEmbeddings(
|
328 |
+
model_name=model_name,
|
329 |
+
model_kwargs={'device': device},
|
330 |
+
encode_kwargs=encode_kwargs
|
331 |
+
)
|
332 |
+
|
333 |
+
# Initialize document store
|
334 |
+
doc_store = DocumentStore(embeddings_model)
|
335 |
+
|
336 |
+
# Initialize IBM Granite client
|
337 |
+
granite_client = IBMGraniteClient(
|
338 |
+
api_key="9FV7l0Jxqe7ceL09MeH_g9bYioIQuABXsr1j1VHbKOpr",
|
339 |
+
project_id="4aa39c25-19d7-48c1-9cf6-e31b5c223a1f"
|
340 |
+
)
|
341 |
+
|
342 |
+
return doc_store, granite_client
|
343 |
+
|
344 |
+
|
345 |
+
# Initialize services
|
346 |
+
doc_store, granite_client = init_services()
|
347 |
+
|
348 |
+
# Initialize inference chain
|
349 |
+
inference_chain = MaterialInferenceChain(doc_store, granite_client)
|
350 |
+
|
351 |
+
|
352 |
+
# Initialize Jinja2 templates
|
353 |
+
templates = Jinja2Templates(directory="templates")
|
354 |
+
|
355 |
+
@app.get("/", response_class=HTMLResponse)
|
356 |
+
async def serve_frontend(request: Request):
|
357 |
+
return templates.TemplateResponse("index.html", {"request": request})
|
358 |
+
|
359 |
+
@app.post("/analyze-project/")
|
360 |
+
async def analyze_project(
|
361 |
+
project_details: ProjectDetails,
|
362 |
+
file: UploadFile = File(...),
|
363 |
+
):
|
364 |
+
try:
|
365 |
+
# Save uploaded file temporarily
|
366 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=Path(file.filename).suffix) as temp_file:
|
367 |
+
content = await file.read()
|
368 |
+
temp_file.write(content)
|
369 |
+
temp_file.flush()
|
370 |
+
|
371 |
+
# Analyze the project
|
372 |
+
phases = await inference_chain.analyze_image(
|
373 |
+
temp_file.name,
|
374 |
+
project_details
|
375 |
+
)
|
376 |
+
|
377 |
+
return {
|
378 |
+
"project_type": project_details.construction_type,
|
379 |
+
"phases": phases
|
380 |
+
}
|
381 |
+
|
382 |
+
except Exception as e:
|
383 |
+
raise HTTPException(status_code=500, detail=str(e))
|
384 |
+
|
385 |
+
import uvicorn
|
386 |
+
|
387 |
+
if __name__ == "__main__":
|
388 |
+
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
|