Compare commits

...

3 Commits

Author SHA1 Message Date
jyong
ac0d99281e add migration 2024-12-12 09:48:25 +08:00
Dr. Kiji
bbdadec1bc add download file method 2024-12-05 10:10:35 +09:00
Dr. Kiji
fa9709faa8 fork for fta 2024-12-05 10:10:35 +09:00
10 changed files with 471 additions and 0 deletions

View File

@ -62,6 +62,7 @@ from .datasets import (
external,
hit_testing,
website,
fta_test,
)
# Import explore controllers

View File

@ -0,0 +1,145 @@
import json
import requests
from flask import Response
from flask_restful import Resource, reqparse
from sqlalchemy import text
from controllers.console import api
from extensions.ext_database import db
from extensions.ext_storage import storage
from models.fta import ComponentFailure, ComponentFailureStats
class FATTestApi(Resource):
def post(self):
parser = reqparse.RequestParser()
parser.add_argument("log_process_data", nullable=False, required=True, type=str, location="args")
args = parser.parse_args()
print(args["log_process_data"])
# Extract the JSON string from the text field
json_str = args["log_process_data"].strip("```json\\n").strip("```").strip().replace("\\n", "")
log_data = json.loads(json_str)
db.session.query(ComponentFailure).delete()
for data in log_data:
if not isinstance(data, dict):
raise TypeError("Data must be a dictionary.")
required_keys = {"Date", "Component", "FailureMode", "Cause", "RepairAction", "Technician"}
if not required_keys.issubset(data.keys()):
raise ValueError(f"Data dictionary must contain the following keys: {required_keys}")
try:
# Clear existing stats
component_failure = ComponentFailure(
Date=data["Date"],
Component=data["Component"],
FailureMode=data["FailureMode"],
Cause=data["Cause"],
RepairAction=data["RepairAction"],
Technician=data["Technician"],
)
db.session.add(component_failure)
db.session.commit()
except Exception as e:
print(e)
# Clear existing stats
db.session.query(ComponentFailureStats).delete()
# Insert calculated statistics
try:
db.session.execute(
text("""
INSERT INTO component_failure_stats ("Component", "FailureMode", "Cause", "PossibleAction", "Probability", "MTBF")
SELECT
cf."Component",
cf."FailureMode",
cf."Cause",
cf."RepairAction" as "PossibleAction",
COUNT(*) * 1.0 / (SELECT COUNT(*) FROM component_failure WHERE "Component" = cf."Component") AS "Probability",
COALESCE(AVG(EXTRACT(EPOCH FROM (next_failure_date::timestamp - cf."Date"::timestamp)) / 86400.0),0)AS "MTBF"
FROM (
SELECT
"Component",
"FailureMode",
"Cause",
"RepairAction",
"Date",
LEAD("Date") OVER (PARTITION BY "Component", "FailureMode", "Cause" ORDER BY "Date") AS next_failure_date
FROM
component_failure
) cf
GROUP BY
cf."Component", cf."FailureMode", cf."Cause", cf."RepairAction";
""")
)
db.session.commit()
except Exception as e:
db.session.rollback()
print(f"Error during stats calculation: {e}")
# output format
# [
# (17, 'Hydraulic system', 'Leak', 'Hose rupture', 'Replaced hydraulic hose', 0.3333333333333333, None),
# (18, 'Hydraulic system', 'Leak', 'Seal Wear', 'Replaced the faulty seal', 0.3333333333333333, None),
# (19, 'Hydraulic system', 'Pressure drop', 'Fluid leak', 'Replaced hydraulic fluid and seals', 0.3333333333333333, None)
# ]
component_failure_stats = db.session.query(ComponentFailureStats).all()
# Convert stats to list of tuples format
stats_list = []
for stat in component_failure_stats:
stats_list.append(
(
stat.StatID,
stat.Component,
stat.FailureMode,
stat.Cause,
stat.PossibleAction,
stat.Probability,
stat.MTBF,
)
)
return {"data": stats_list}, 200
# generate-fault-tree
class GenerateFaultTreeApi(Resource):
def post(self):
parser = reqparse.RequestParser()
parser.add_argument("llm_text", nullable=False, required=True, type=str, location="args")
args = parser.parse_args()
entities = args["llm_text"].replace("```", "").replace("\\n", "\n")
print(entities)
request_data = {"fault_tree_text": entities}
url = "https://fta.cognitech-dev.live/generate-fault-tree"
headers = {"accept": "application/json", "Content-Type": "application/json"}
response = requests.post(url, json=request_data, headers=headers)
print(response.json())
return {"data": response.json()}, 200
class ExtractSVGApi(Resource):
def post(self):
parser = reqparse.RequestParser()
parser.add_argument("svg_text", nullable=False, required=True, type=str, location="args")
args = parser.parse_args()
# svg_text = ''.join(args["svg_text"].splitlines())
svg_text = args["svg_text"].replace("\n", "")
svg_text = svg_text.replace('"', '"')
print(svg_text)
svg_text_json = json.loads(svg_text)
svg_content = svg_text_json.get("data").get("svg_content")[0]
svg_content = svg_content.replace("\n", "").replace('"', '"')
file_key = "fta_svg/" + "fat.svg"
if storage.exists(file_key):
storage.delete(file_key)
storage.save(file_key, svg_content.encode("utf-8"))
generator = storage.load(file_key, stream=True)
return Response(generator, mimetype="image/svg+xml")
api.add_resource(FATTestApi, "/fta/db-handler")
api.add_resource(GenerateFaultTreeApi, "/fta/generate-fault-tree")
api.add_resource(ExtractSVGApi, "/fta/extract-svg")

View File

@ -1,4 +1,6 @@
import base64
import tempfile
from pathlib import Path
from configs import dify_config
from core.file import file_repository
@ -18,6 +20,38 @@ from .models import File, FileTransferMethod, FileType
from .tool_file_parser import ToolFileParser
def download_to_target_path(f: File, temp_dir: str, /):
if f.transfer_method == FileTransferMethod.TOOL_FILE:
tool_file = file_repository.get_tool_file(session=db.session(), file=f)
suffix = Path(tool_file.file_key).suffix
target_path = f"{temp_dir}/{next(tempfile._get_candidate_names())}{suffix}"
_download_file_to_target_path(tool_file.file_key, target_path)
return target_path
elif f.transfer_method == FileTransferMethod.LOCAL_FILE:
upload_file = file_repository.get_upload_file(session=db.session(), file=f)
suffix = Path(upload_file.key).suffix
target_path = f"{temp_dir}/{next(tempfile._get_candidate_names())}{suffix}"
_download_file_to_target_path(upload_file.key, target_path)
return target_path
else:
raise ValueError(f"Unsupported transfer method: {f.transfer_method}")
def _download_file_to_target_path(path: str, target_path: str, /):
"""
Download and return the contents of a file as bytes.
This function loads the file from storage and ensures it's in bytes format.
Args:
path (str): The path to the file in storage.
target_path (str): The path to the target file.
Raises:
ValueError: If the loaded file is not a bytes object.
"""
storage.download(path, target_path)
def get_attr(*, file: File, attr: FileAttribute):
match attr:
case FileAttribute.TYPE:

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,8 @@
from typing import Any
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
class FileExtractorProvider(BuiltinToolProviderController):
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
pass

View File

@ -0,0 +1,15 @@
identity:
author: Jyong
name: file_extractor
label:
en_US: File Extractor
zh_Hans: 文件提取
pt_BR: File Extractor
description:
en_US: Extract text from file
zh_Hans: 从文件中提取文本
pt_BR: Extract text from file
icon: icon.png
tags:
- utilities
- productivity

View File

@ -0,0 +1,45 @@
import tempfile
from typing import Any, Union
from core.file.enums import FileType
from core.file.file_manager import download_to_target_path
from core.rag.extractor.text_extractor import TextExtractor
from core.rag.splitter.fixed_text_splitter import FixedRecursiveCharacterTextSplitter
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.errors import ToolParameterValidationError
from core.tools.tool.builtin_tool import BuiltinTool
class FileExtractorTool(BuiltinTool):
def _invoke(
self,
user_id: str,
tool_parameters: dict[str, Any],
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
"""
invoke tools
"""
# image file for workflow mode
file = tool_parameters.get("text_file")
if file and file.type != FileType.DOCUMENT:
raise ToolParameterValidationError("Not a valid document")
if file:
with tempfile.TemporaryDirectory() as temp_dir:
file_path = download_to_target_path(file, temp_dir)
extractor = TextExtractor(file_path, autodetect_encoding=True)
documents = extractor.extract()
character_splitter = FixedRecursiveCharacterTextSplitter.from_encoder(
chunk_size=tool_parameters.get("max_token", 500),
chunk_overlap=0,
fixed_separator=tool_parameters.get("separator", "\n\n"),
separators=["\n\n", "", ". ", " ", ""],
embedding_model_instance=None,
)
chunks = character_splitter.split_documents(documents)
content = "\n".join([chunk.page_content for chunk in chunks])
return self.create_text_message(content)
else:
raise ToolParameterValidationError("Please provide either file")

View File

@ -0,0 +1,49 @@
identity:
name: text extractor
author: Jyong
label:
en_US: Text extractor
zh_Hans: Text 文本解析
description:
en_US: Extract content from text file and support split to chunks by split characters and token length
zh_Hans: 支持从文本文件中提取内容并支持通过分割字符和令牌长度分割成块
pt_BR: Extract content from text file and support split to chunks by split characters and token length
description:
human:
en_US: Text extractor is a text extract tool
zh_Hans: Text extractor 是一个文本提取工具
pt_BR: Text extractor is a text extract tool
llm: Text extractor is a tool used to extract text file
parameters:
- name: text_file
type: file
label:
en_US: Text file
human_description:
en_US: The text file to be extracted.
zh_Hans: 要提取的 text 文档。
llm_description: you should not input this parameter. just input the image_id.
form: llm
- name: separator
type: string
required: false
label:
en_US: split character
zh_Hans: 分隔符号
human_description:
en_US: Text content split character
zh_Hans: 用于文档分隔的符号
llm_description: it is used for split content to chunks
form: form
- name: max_token
type: number
required: false
label:
en_US: Maximum chunk length
zh_Hans: 最大分段长度
human_description:
en_US: Maximum chunk length
zh_Hans: 最大分段长度
llm_description: it is used for limit chunk's max length
form: form

View File

@ -0,0 +1,96 @@
"""add_fat_test
Revision ID: 49f175ff56cb
Revises: 43fa78bc3b7d
Create Date: 2024-11-05 03:26:22.578321
"""
from alembic import op
import models as models
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '49f175ff56cb'
down_revision = '01d6889832f7'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('component_failure',
sa.Column('FailureID', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('Date', sa.Date(), nullable=False),
sa.Column('Component', sa.String(length=255), nullable=False),
sa.Column('FailureMode', sa.String(length=255), nullable=False),
sa.Column('Cause', sa.String(length=255), nullable=False),
sa.Column('RepairAction', sa.Text(), nullable=True),
sa.Column('Technician', sa.String(length=255), nullable=False),
sa.PrimaryKeyConstraint('FailureID', name=op.f('component_failure_pkey')),
sa.UniqueConstraint('Date', 'Component', 'FailureMode', 'Cause', 'Technician', name='unique_failure_entry')
)
op.create_table('component_failure_stats',
sa.Column('StatID', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('Component', sa.String(length=255), nullable=False),
sa.Column('FailureMode', sa.String(length=255), nullable=False),
sa.Column('Cause', sa.String(length=255), nullable=False),
sa.Column('PossibleAction', sa.Text(), nullable=True),
sa.Column('Probability', sa.Float(), nullable=False),
sa.Column('MTBF', sa.Float(), nullable=False),
sa.PrimaryKeyConstraint('StatID', name=op.f('component_failure_stats_pkey'))
)
op.create_table('incident_data',
sa.Column('IncidentID', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('IncidentDescription', sa.Text(), nullable=False),
sa.Column('IncidentDate', sa.Date(), nullable=False),
sa.Column('Consequences', sa.Text(), nullable=True),
sa.Column('ResponseActions', sa.Text(), nullable=True),
sa.PrimaryKeyConstraint('IncidentID', name=op.f('incident_data_pkey'))
)
op.create_table('maintenance',
sa.Column('MaintenanceID', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('MaintenanceType', sa.String(length=255), nullable=False),
sa.Column('MaintenanceDate', sa.Date(), nullable=False),
sa.Column('ServiceDescription', sa.Text(), nullable=True),
sa.Column('PartsReplaced', sa.Text(), nullable=True),
sa.Column('Technician', sa.String(length=255), nullable=False),
sa.PrimaryKeyConstraint('MaintenanceID', name=op.f('maintenance_pkey'))
)
op.create_table('operational_data',
sa.Column('OperationID', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('CraneUsage', sa.Integer(), nullable=False),
sa.Column('LoadWeight', sa.Float(), nullable=False),
sa.Column('LoadFrequency', sa.Integer(), nullable=False),
sa.Column('EnvironmentalConditions', sa.Text(), nullable=True),
sa.PrimaryKeyConstraint('OperationID', name=op.f('operational_data_pkey'))
)
op.create_table('reliability_data',
sa.Column('ComponentID', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('ComponentName', sa.String(length=255), nullable=False),
sa.Column('MTBF', sa.Float(), nullable=False),
sa.Column('FailureRate', sa.Float(), nullable=False),
sa.PrimaryKeyConstraint('ComponentID', name=op.f('reliability_data_pkey'))
)
op.create_table('safety_data',
sa.Column('SafetyID', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('SafetyInspectionDate', sa.Date(), nullable=False),
sa.Column('SafetyFindings', sa.Text(), nullable=True),
sa.Column('SafetyIncidentDescription', sa.Text(), nullable=True),
sa.Column('ComplianceStatus', sa.String(length=50), nullable=False),
sa.PrimaryKeyConstraint('SafetyID', name=op.f('safety_data_pkey'))
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('safety_data')
op.drop_table('reliability_data')
op.drop_table('operational_data')
op.drop_table('maintenance')
op.drop_table('incident_data')
op.drop_table('component_failure_stats')
op.drop_table('component_failure')
# ### end Alembic commands ###

78
api/models/fta.py Normal file
View File

@ -0,0 +1,78 @@
from extensions.ext_database import db
class ComponentFailure(db.Model):
__tablename__ = "component_failure"
__table_args__ = (
db.UniqueConstraint("Date", "Component", "FailureMode", "Cause", "Technician", name="unique_failure_entry"),
)
FailureID = db.Column(db.Integer, primary_key=True, autoincrement=True)
Date = db.Column(db.Date, nullable=False)
Component = db.Column(db.String(255), nullable=False)
FailureMode = db.Column(db.String(255), nullable=False)
Cause = db.Column(db.String(255), nullable=False)
RepairAction = db.Column(db.Text, nullable=True)
Technician = db.Column(db.String(255), nullable=False)
class Maintenance(db.Model):
__tablename__ = "maintenance"
MaintenanceID = db.Column(db.Integer, primary_key=True, autoincrement=True)
MaintenanceType = db.Column(db.String(255), nullable=False)
MaintenanceDate = db.Column(db.Date, nullable=False)
ServiceDescription = db.Column(db.Text, nullable=True)
PartsReplaced = db.Column(db.Text, nullable=True)
Technician = db.Column(db.String(255), nullable=False)
class OperationalData(db.Model):
__tablename__ = "operational_data"
OperationID = db.Column(db.Integer, primary_key=True, autoincrement=True)
CraneUsage = db.Column(db.Integer, nullable=False)
LoadWeight = db.Column(db.Float, nullable=False)
LoadFrequency = db.Column(db.Integer, nullable=False)
EnvironmentalConditions = db.Column(db.Text, nullable=True)
class IncidentData(db.Model):
__tablename__ = "incident_data"
IncidentID = db.Column(db.Integer, primary_key=True, autoincrement=True)
IncidentDescription = db.Column(db.Text, nullable=False)
IncidentDate = db.Column(db.Date, nullable=False)
Consequences = db.Column(db.Text, nullable=True)
ResponseActions = db.Column(db.Text, nullable=True)
class ReliabilityData(db.Model):
__tablename__ = "reliability_data"
ComponentID = db.Column(db.Integer, primary_key=True, autoincrement=True)
ComponentName = db.Column(db.String(255), nullable=False)
MTBF = db.Column(db.Float, nullable=False)
FailureRate = db.Column(db.Float, nullable=False)
class SafetyData(db.Model):
__tablename__ = "safety_data"
SafetyID = db.Column(db.Integer, primary_key=True, autoincrement=True)
SafetyInspectionDate = db.Column(db.Date, nullable=False)
SafetyFindings = db.Column(db.Text, nullable=True)
SafetyIncidentDescription = db.Column(db.Text, nullable=True)
ComplianceStatus = db.Column(db.String(50), nullable=False)
class ComponentFailureStats(db.Model):
__tablename__ = "component_failure_stats"
StatID = db.Column(db.Integer, primary_key=True, autoincrement=True)
Component = db.Column(db.String(255), nullable=False)
FailureMode = db.Column(db.String(255), nullable=False)
Cause = db.Column(db.String(255), nullable=False)
PossibleAction = db.Column(db.Text, nullable=True)
Probability = db.Column(db.Float, nullable=False)
MTBF = db.Column(db.Float, nullable=False)