|
import gradio as gr |
|
import os |
|
from datetime import datetime |
|
from reportlab.lib.pagesizes import A4, landscape |
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle |
|
from reportlab.lib import colors |
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image |
|
from reportlab.pdfgen import canvas |
|
from reportlab.lib.units import inch, cm |
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT |
|
from transformers import pipeline |
|
import io |
|
|
|
|
|
ai_assistant = pipeline( |
|
"text-generation", |
|
model="distilgpt2" |
|
) |
|
|
|
def qc_ai_recommendation(job_type, notes): |
|
"""Generate AI recommendation based on job type and inspection notes""" |
|
prompt = f""" |
|
Quality Control Inspection untuk Proyek Konstruksi. Jenis pekerjaan: {job_type}. |
|
Catatan hasil inspeksi: {notes}. |
|
Rekomendasi tindak lanjut yang dibutuhkan: |
|
""" |
|
|
|
try: |
|
result = ai_assistant(prompt, max_length=300, do_sample=True, temperature=0.7)[0]['generated_text'] |
|
|
|
response = result[len(prompt):].strip() |
|
if not response or len(response) < 20: |
|
return "Berdasarkan hasil inspeksi, direkomendasikan untuk melakukan finishing permukaan sesuai dengan standar mutu yang telah ditentukan dalam spesifikasi teknis. Pastikan pekerjaan {job_type} memenuhi kriteria toleransi dimensi dan kerapian yang disyaratkan dalam dokumen kontrak." |
|
return response |
|
except Exception as e: |
|
|
|
return "Berdasarkan hasil inspeksi, direkomendasikan untuk melakukan finishing permukaan sesuai dengan standar mutu yang telah ditentukan dalam spesifikasi teknis. Pastikan pekerjaan {job_type} memenuhi kriteria toleransi dimensi dan kerapian yang disyaratkan dalam dokumen kontrak." |
|
|
|
def ai_quality_analysis(job_type, notes): |
|
"""Analyze quality status based on inspection notes""" |
|
|
|
negative_keywords = ["rusak", "bocor", "retak", "tidak sesuai", "buruk", "longgar", |
|
"tidak rata", "miring", "keropos", "kotor", "perlu finishing", |
|
"perlu diperbaiki", "cacat", "kurang", "bermasalah"] |
|
|
|
|
|
for keyword in negative_keywords: |
|
if keyword.lower() in notes.lower(): |
|
return "Tidak Lulus", f"Terdeteksi masalah: '{keyword}' dalam catatan inspeksi." |
|
|
|
|
|
return "Lulus", "Tidak ditemukan masalah signifikan dalam catatan inspeksi." |
|
|
|
def generate_professional_pdf(project_name, location, job_type, quality_status, notes, ai_suggestion, status_reason, inspector_name="QC Inspector"): |
|
"""Generate professional PDF report""" |
|
buffer = io.BytesIO() |
|
now = datetime.now() |
|
report_date = now.strftime("%Y-%m-%d") |
|
report_time = now.strftime("%H:%M:%S") |
|
report_id = now.strftime("%Y%m%d%H%M") |
|
|
|
|
|
doc = SimpleDocTemplate( |
|
buffer, |
|
pagesize=A4, |
|
topMargin=0.5*inch, |
|
leftMargin=0.5*inch, |
|
rightMargin=0.5*inch, |
|
bottomMargin=0.5*inch |
|
) |
|
|
|
|
|
styles = getSampleStyleSheet() |
|
styles.add(ParagraphStyle( |
|
name='Title', |
|
parent=styles['Heading1'], |
|
fontSize=14, |
|
alignment=TA_CENTER, |
|
spaceAfter=0.2*inch, |
|
)) |
|
|
|
styles.add(ParagraphStyle( |
|
name='Subtitle', |
|
parent=styles['Heading2'], |
|
fontSize=12, |
|
alignment=TA_CENTER, |
|
spaceAfter=0.2*inch, |
|
)) |
|
|
|
styles.add(ParagraphStyle( |
|
name='Section', |
|
parent=styles['Heading3'], |
|
fontSize=10, |
|
spaceAfter=0.1*inch, |
|
spaceBefore=0.1*inch, |
|
)) |
|
|
|
styles.add(ParagraphStyle( |
|
name='Normal_Justified', |
|
parent=styles['Normal'], |
|
alignment=4, |
|
spaceAfter=0.1*inch, |
|
)) |
|
|
|
|
|
content = [] |
|
|
|
|
|
header_data = [ |
|
["LAPORAN INSPEKSI QUALITY CONTROL", ""], |
|
[f"No. Laporan: QC-{report_id}", f"Tanggal: {report_date}"] |
|
] |
|
|
|
t = Table(header_data, colWidths=[doc.width/2.0]*2) |
|
t.setStyle(TableStyle([ |
|
('SPAN', (0, 0), (1, 0)), |
|
('ALIGN', (0, 0), (0, 0), 'CENTER'), |
|
('FONT', (0, 0), (0, 0), 'Helvetica-Bold', 14), |
|
('FONT', (0, 1), (1, 1), 'Helvetica', 9), |
|
('ALIGN', (0, 1), (0, 1), 'LEFT'), |
|
('ALIGN', (1, 1), (1, 1), 'RIGHT'), |
|
('BOTTOMPADDING', (0, 0), (1, 0), 10), |
|
('TOPPADDING', (0, 0), (1, 0), 10), |
|
])) |
|
content.append(t) |
|
content.append(Spacer(1, 0.2*inch)) |
|
|
|
|
|
content.append(Paragraph("<b>INFORMASI PROYEK</b>", styles['Section'])) |
|
|
|
project_data = [ |
|
["Nama Proyek:", project_name], |
|
["Lokasi:", location], |
|
["Jenis Pekerjaan:", job_type], |
|
["Status Mutu:", quality_status], |
|
] |
|
|
|
t = Table(project_data, colWidths=[doc.width*0.3, doc.width*0.7]) |
|
t.setStyle(TableStyle([ |
|
('FONT', (0, 0), (0, -1), 'Helvetica-Bold', 9), |
|
('FONT', (1, 0), (1, -1), 'Helvetica', 9), |
|
('BACKGROUND', (0, 0), (0, -1), colors.lightgrey), |
|
('GRID', (0, 0), (1, -1), 0.5, colors.grey), |
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), |
|
('LEFTPADDING', (0, 0), (-1, -1), 6), |
|
('RIGHTPADDING', (0, 0), (-1, -1), 6), |
|
])) |
|
content.append(t) |
|
content.append(Spacer(1, 0.2*inch)) |
|
|
|
|
|
content.append(Paragraph("<b>DETAIL INSPEKSI</b>", styles['Section'])) |
|
|
|
inspection_data = [ |
|
["Tanggal & Waktu Inspeksi:", f"{report_date} {report_time}"], |
|
["Inspektor:", inspector_name], |
|
["Metode Inspeksi:", "Visual & Dimensional"], |
|
["Standar Referensi:", "SNI 2847:2019 / ASTM / ACI 318"], |
|
] |
|
|
|
t = Table(inspection_data, colWidths=[doc.width*0.3, doc.width*0.7]) |
|
t.setStyle(TableStyle([ |
|
('FONT', (0, 0), (0, -1), 'Helvetica-Bold', 9), |
|
('FONT', (1, 0), (1, -1), 'Helvetica', 9), |
|
('BACKGROUND', (0, 0), (0, -1), colors.lightgrey), |
|
('GRID', (0, 0), (1, -1), 0.5, colors.grey), |
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), |
|
('LEFTPADDING', (0, 0), (-1, -1), 6), |
|
('RIGHTPADDING', (0, 0), (-1, -1), 6), |
|
])) |
|
content.append(t) |
|
content.append(Spacer(1, 0.2*inch)) |
|
|
|
|
|
content.append(Paragraph("<b>CATATAN INSPEKSI</b>", styles['Section'])) |
|
|
|
notes_data = [ |
|
["Catatan:", notes], |
|
["Analisis Status:", status_reason], |
|
] |
|
|
|
t = Table(notes_data, colWidths=[doc.width*0.3, doc.width*0.7]) |
|
t.setStyle(TableStyle([ |
|
('FONT', (0, 0), (0, -1), 'Helvetica-Bold', 9), |
|
('FONT', (1, 0), (1, -1), 'Helvetica', 9), |
|
('BACKGROUND', (0, 0), (0, -1), colors.lightgrey), |
|
('GRID', (0, 0), (1, -1), 0.5, colors.grey), |
|
('VALIGN', (0, 0), (-1, -1), 'TOP'), |
|
('LEFTPADDING', (0, 0), (-1, -1), 6), |
|
('RIGHTPADDING', (0, 0), (-1, -1), 6), |
|
])) |
|
content.append(t) |
|
content.append(Spacer(1, 0.2*inch)) |
|
|
|
|
|
content.append(Paragraph("<b>REKOMENDASI</b>", styles['Section'])) |
|
|
|
if quality_status == "Tidak Lulus": |
|
recommendation_color = colors.lightcoral |
|
else: |
|
recommendation_color = colors.lightgreen |
|
|
|
recommendation_data = [ |
|
["Rekomendasi AI:", ai_suggestion], |
|
] |
|
|
|
t = Table(recommendation_data, colWidths=[doc.width*0.3, doc.width*0.7]) |
|
t.setStyle(TableStyle([ |
|
('FONT', (0, 0), (0, -1), 'Helvetica-Bold', 9), |
|
('FONT', (1, 0), (1, -1), 'Helvetica', 9), |
|
('BACKGROUND', (0, 0), (0, -1), colors.lightgrey), |
|
('BACKGROUND', (1, 0), (1, -1), recommendation_color), |
|
('GRID', (0, 0), (1, -1), 0.5, colors.grey), |
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), |
|
('LEFTPADDING', (0, 0), (-1, -1), 6), |
|
('RIGHTPADDING', (0, 0), (-1, -1), 6), |
|
])) |
|
content.append(t) |
|
content.append(Spacer(1, 0.3*inch)) |
|
|
|
|
|
content.append(Paragraph("<b>APPROVAL</b>", styles['Section'])) |
|
|
|
signature_data = [ |
|
["QC Inspector", "Project Manager", "Owner Representative"], |
|
["", "", ""], |
|
["________________", "________________", "________________"], |
|
[inspector_name, "", ""], |
|
["Tanggal: " + report_date, "Tanggal: ", "Tanggal: "], |
|
] |
|
|
|
t = Table(signature_data, colWidths=[doc.width/3.0]*3) |
|
t.setStyle(TableStyle([ |
|
('FONT', (0, 0), (2, 0), 'Helvetica-Bold', 9), |
|
('FONT', (0, 2), (2, 4), 'Helvetica', 8), |
|
('ALIGN', (0, 0), (2, 4), 'CENTER'), |
|
('VALIGN', (0, 0), (2, 4), 'MIDDLE'), |
|
('LINEBELOW', (0, 2), (2, 2), 1, colors.black), |
|
('TOPPADDING', (0, 0), (2, 0), 12), |
|
('BOTTOMPADDING', (0, 0), (2, 0), 30), |
|
('BOTTOMPADDING', (0, 2), (2, 2), 4), |
|
('TOPPADDING', (0, 2), (2, 2), 0), |
|
])) |
|
content.append(t) |
|
|
|
|
|
content.append(Spacer(1, 0.3*inch)) |
|
footer_text = "Laporan ini dibuat menggunakan teknologi AI untuk membantu analisis. Rekomendasi AI harus direview oleh Quality Control Engineer yang berpengalaman. Laporan ini bukan pengganti dari penilaian profesional." |
|
content.append(Paragraph(footer_text, ParagraphStyle(name='Footer', parent=styles['Normal'], fontSize=7, textColor=colors.grey))) |
|
|
|
|
|
doc.build(content) |
|
buffer.seek(0) |
|
return buffer |
|
|
|
def generate_qc_report(project_name, location, job_type, quality_status, notes, inspector_name="QC Inspector"): |
|
"""Generate QC report with AI recommendations""" |
|
|
|
if not quality_status: |
|
quality_status, status_reason = ai_quality_analysis(job_type, notes) |
|
else: |
|
status_reason = "Status ditentukan oleh inspektor." |
|
|
|
|
|
ai_suggestion = qc_ai_recommendation(job_type, notes) |
|
|
|
|
|
pdf_buffer = generate_professional_pdf( |
|
project_name, |
|
location, |
|
job_type, |
|
quality_status, |
|
notes, |
|
ai_suggestion, |
|
status_reason, |
|
inspector_name |
|
) |
|
|
|
|
|
pdf_filename = "qc_report.pdf" |
|
with open(pdf_filename, "wb") as f: |
|
f.write(pdf_buffer.getvalue()) |
|
|
|
return ai_suggestion, status_reason, pdf_filename |
|
|
|
with gr.Blocks(theme=gr.themes.Soft()) as app: |
|
gr.Markdown("# ποΈ QC Agent β Sistem Inspeksi & Rekomendasi AI Konstruksi") |
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
gr.Markdown("### Data Proyek") |
|
project_name = gr.Textbox(label="Nama Proyek", placeholder="Masukkan nama proyek") |
|
location = gr.Textbox(label="Lokasi Proyek", placeholder="Masukkan lokasi proyek") |
|
job_type = gr.Textbox(label="Jenis Pekerjaan", placeholder="Contoh: Kolom, Balok, Dinding, dll") |
|
inspector_name = gr.Textbox(label="Nama Inspektor", placeholder="Nama QC Inspector", value="QC Inspector") |
|
|
|
with gr.Column(): |
|
gr.Markdown("### Data Inspeksi") |
|
quality_status = gr.Radio( |
|
["Lulus", "Tidak Lulus"], |
|
label="Status Mutu", |
|
value=None, |
|
info="Kosongkan untuk analisis otomatis oleh AI" |
|
) |
|
notes = gr.Textbox( |
|
label="Catatan Inspeksi", |
|
lines=4, |
|
placeholder="Masukkan hasil observasi inspeksi secara detail" |
|
) |
|
|
|
with gr.Row(): |
|
submit = gr.Button("π Generate Laporan Inspeksi", variant="primary", scale=2) |
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
ai_output = gr.Textbox(label="π‘ Rekomendasi AI", lines=3) |
|
status_reason = gr.Textbox(label="π Analisis Status", lines=2) |
|
|
|
with gr.Row(): |
|
output_pdf = gr.File(label="π₯ Laporan QC (PDF)") |
|
|
|
submit.click( |
|
generate_qc_report, |
|
inputs=[project_name, location, job_type, quality_status, notes, inspector_name], |
|
outputs=[ai_output, status_reason, output_pdf] |
|
) |
|
|
|
gr.Markdown(""" |
|
### Panduan Penggunaan |
|
1. Isi informasi proyek dan inspeksi dengan lengkap |
|
2. Anda dapat memilih status mutu atau mengosongkannya untuk analisis otomatis oleh AI |
|
3. Berikan catatan inspeksi yang detail untuk mendapatkan rekomendasi yang akurat |
|
4. Klik 'Generate Laporan Inspeksi' untuk mendapatkan laporan QC profesional |
|
""") |
|
|
|
app.launch() |