bivav commited on
Commit
f0435c4
·
1 Parent(s): 221f6de

Refactor database connection to use Supabase DB and Enhance project structure

Browse files
.gitignore CHANGED
@@ -19,6 +19,10 @@ wheels/
19
  *.egg-info/
20
  .installed.cfg
21
  *.egg
 
 
 
 
22
 
23
  # Virtual environments
24
  venv/
@@ -30,6 +34,9 @@ env/
30
  .env
31
  .env.local
32
 
 
 
 
33
  # UV
34
  .uv/
35
  uv.lock
@@ -41,8 +48,9 @@ uv.lock
41
  *.swo
42
  *~
43
 
44
- # Streamlit
45
- .streamlit/
 
46
 
47
  # JSON data output
48
  json_data/
 
19
  *.egg-info/
20
  .installed.cfg
21
  *.egg
22
+ MANIFEST
23
+
24
+ # Package build
25
+ src/*.egg-info/
26
 
27
  # Virtual environments
28
  venv/
 
34
  .env
35
  .env.local
36
 
37
+ # Cache directories
38
+ .cache/
39
+
40
  # UV
41
  .uv/
42
  uv.lock
 
48
  *.swo
49
  *~
50
 
51
+ # Streamlit (but keep config.toml)
52
+ .streamlit/*
53
+ !.streamlit/config.toml
54
 
55
  # JSON data output
56
  json_data/
.streamlit/config.toml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [server]
2
+ headless = true
3
+ port = 7860
4
+ address = "0.0.0.0"
5
+ fileWatcherType = "none"
6
+
7
+ [browser]
8
+ gatherUsageStats = false
9
+ serverAddress = "0.0.0.0"
10
+ serverPort = 7860
11
+
12
+ [theme]
13
+ base = "light"
Dockerfile CHANGED
@@ -9,20 +9,38 @@ RUN apt-get update && apt-get install -y \
9
  && rm -rf /var/lib/apt/lists/* \
10
  && curl -LsSf https://astral.sh/uv/install.sh | sh
11
 
12
- # Add uv to PATH
13
- ENV PATH="/root/.cargo/bin:$PATH"
14
 
15
- # Copy dependency files
 
 
 
 
 
 
 
 
 
16
  COPY pyproject.toml .
 
17
 
18
- # Install dependencies using modern uv (directly from pyproject.toml)
19
  RUN uv pip install --system --no-cache .
20
 
21
- # Copy application code
 
 
 
 
 
 
22
  COPY . .
23
 
24
  EXPOSE 7860
25
 
26
- HEALTHCHECK CMD curl --fail http://localhost:7860/_stcore/health || exit 1
 
 
27
 
28
- ENTRYPOINT ["streamlit", "run", "日次RA.py", "--server.port=7860", "--server.address=0.0.0.0"]
 
9
  && rm -rf /var/lib/apt/lists/* \
10
  && curl -LsSf https://astral.sh/uv/install.sh | sh
11
 
12
+ # Add uv to PATH (it installs to /root/.local/bin)
13
+ ENV PATH="/root/.local/bin:$PATH"
14
 
15
+ # Set Streamlit config to avoid permission issues
16
+ ENV STREAMLIT_SERVER_HEADLESS=true
17
+ ENV STREAMLIT_SERVER_FILE_WATCHER_TYPE=none
18
+ ENV STREAMLIT_BROWSER_GATHER_USAGE_STATS=false
19
+
20
+ # Set HuggingFace cache to writable directory
21
+ ENV HF_HOME=/app/.cache
22
+ ENV SENTENCE_TRANSFORMERS_HOME=/app/.cache/sentence-transformers
23
+
24
+ # Copy project files for installation
25
  COPY pyproject.toml .
26
+ COPY src/ ./src/
27
 
28
+ # Install the package with dependencies using modern uv
29
  RUN uv pip install --system --no-cache .
30
 
31
+ # Create cache directory and pre-download ML models
32
+ RUN mkdir -p /app/.cache /app/json_data && \
33
+ python3 -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L12-v2')" && \
34
+ python3 -c "from sudachipy import dictionary; dictionary.Dictionary().create()" && \
35
+ chmod -R 777 /app/.cache /app/json_data
36
+
37
+ # Copy remaining application files (env.example, schema.sql, etc.)
38
  COPY . .
39
 
40
  EXPOSE 7860
41
 
42
+ # Healthcheck (optional - comment out if causing issues)
43
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
44
+ CMD curl --fail http://localhost:7860/_stcore/health || exit 1
45
 
46
+ CMD ["streamlit", "run", "src/daily_ra/app.py", "--server.port=7860", "--server.address=0.0.0.0"]
README.md CHANGED
@@ -36,7 +36,7 @@ See [DEPLOYMENT.md](./DEPLOYMENT.md) for detailed setup instructions.
36
  # Install uv
37
  curl -LsSf https://astral.sh/uv/install.sh | sh
38
 
39
- # Install dependencies (modern way - directly from pyproject.toml)
40
  uv pip install -e .
41
 
42
  # Set up environment variables
@@ -44,7 +44,7 @@ cp env.example .env
44
  # Edit .env with your Supabase credentials
45
 
46
  # Run the app
47
- streamlit run 日次RA.py
48
  ```
49
 
50
  ### Docker
@@ -108,18 +108,14 @@ uv pip install --upgrade -e .
108
  POOL_NAME=vvkubjwatpdqnkmgmntf
109
  ```
110
 
111
- 4. Install dependencies using uv (modern way):
112
  ```bash
113
- # Option 1: Install in editable mode (recommended for development)
114
  uv pip install -e .
115
-
116
- # Option 2: Sync dependencies (if using uv.lock)
117
- uv sync
118
  ```
119
 
120
  5. Run the app locally:
121
  ```bash
122
- streamlit run 日次RA.py
123
  ```
124
 
125
  ### 3️⃣ **Deploy to Hugging Face Spaces**
@@ -128,13 +124,12 @@ uv pip install --upgrade -e .
128
 
129
  2. Go to your Space Settings → **Repository secrets**
130
 
131
- 3. Add the following secrets:
132
- - `HOST` = `aws-1-ap-northeast-1.pooler.supabase.com`
133
  - `PORT` = `5432`
134
  - `DATABASE` = `postgres`
135
- - `USERNAME` = `postgres.vvkubjwatpdqnkmgmntf`
136
  - `PASSWORD` = `your-actual-password`
137
- - `POOL_NAME` = `vvkubjwatpdqnkmgmntf`
138
 
139
  4. The app will automatically rebuild and deploy
140
 
 
36
  # Install uv
37
  curl -LsSf https://astral.sh/uv/install.sh | sh
38
 
39
+ # Install package in editable mode (installs dependencies + package)
40
  uv pip install -e .
41
 
42
  # Set up environment variables
 
44
  # Edit .env with your Supabase credentials
45
 
46
  # Run the app
47
+ streamlit run src/daily_ra/app.py
48
  ```
49
 
50
  ### Docker
 
108
  POOL_NAME=vvkubjwatpdqnkmgmntf
109
  ```
110
 
111
+ 4. Install package in editable mode:
112
  ```bash
 
113
  uv pip install -e .
 
 
 
114
  ```
115
 
116
  5. Run the app locally:
117
  ```bash
118
+ streamlit run src/daily_ra/app.py
119
  ```
120
 
121
  ### 3️⃣ **Deploy to Hugging Face Spaces**
 
124
 
125
  2. Go to your Space Settings → **Repository secrets**
126
 
127
+ 3. Add the following secrets (get these from your Supabase project settings):
128
+ - `HOST` = `aws-1-ap-northeast-1.pooler.supabase.com` (your pooler host)
129
  - `PORT` = `5432`
130
  - `DATABASE` = `postgres`
131
+ - `USERNAME` = `postgres.vvkubjwatpdqnkmgmntf` (format: postgres.YOUR_PROJECT_REF)
132
  - `PASSWORD` = `your-actual-password`
 
133
 
134
  4. The app will automatically rebuild and deploy
135
 
env.example CHANGED
@@ -1,8 +1,22 @@
1
  # Supabase PostgreSQL Connection Details
 
 
 
 
 
 
2
  HOST=aws-1-ap-northeast-1.pooler.supabase.com
 
 
3
  PORT=5432
 
 
4
  DATABASE=postgres
 
 
 
 
5
  USERNAME=postgres.vvkubjwatpdqnkmgmntf
6
- PASSWORD=your-password-here
7
- POOL_NAME=vvkubjwatpdqnkmgmntf
8
 
 
 
 
1
  # Supabase PostgreSQL Connection Details
2
+ # Copy this file to .env and fill in your actual values
3
+
4
+ # Example connection string from Supabase:
5
+ # postgresql://postgres.PROJECT_REF:[PASSWORD]@aws-0-region.pooler.supabase.com:5432/postgres
6
+
7
+ # Database host (get from Supabase project settings)
8
  HOST=aws-1-ap-northeast-1.pooler.supabase.com
9
+
10
+ # Database port (default is 5432 for PostgreSQL)
11
  PORT=5432
12
+
13
+ # Database name (usually 'postgres' for Supabase)
14
  DATABASE=postgres
15
+
16
+ # Username - IMPORTANT: Use the FULL format from Supabase
17
+ # Format: postgres.YOUR_PROJECT_REF (e.g., postgres.vvkubjwatpdqnkmgmntf)
18
+ # This is a SINGLE username, not two separate parts!
19
  USERNAME=postgres.vvkubjwatpdqnkmgmntf
 
 
20
 
21
+ # Your database password (from Supabase project settings)
22
+ PASSWORD=your-password-here
pyproject.toml CHANGED
@@ -9,6 +9,7 @@ dependencies = [
9
  "pandas>=2.0.0",
10
  "sentence-transformers>=2.2.0",
11
  "sudachipy>=0.6.0",
 
12
  "python-dotenv>=1.0.0",
13
  ]
14
 
@@ -16,5 +17,5 @@ dependencies = [
16
  requires = ["hatchling"]
17
  build-backend = "hatchling.build"
18
 
19
- [tool.uv]
20
- dev-dependencies = []
 
9
  "pandas>=2.0.0",
10
  "sentence-transformers>=2.2.0",
11
  "sudachipy>=0.6.0",
12
+ "sudachidict-core>=20240716",
13
  "python-dotenv>=1.0.0",
14
  ]
15
 
 
17
  requires = ["hatchling"]
18
  build-backend = "hatchling.build"
19
 
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["src/daily_ra"]
src/daily_ra/__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Daily Risk Assessment (日次RA) Application
3
+
4
+ A Streamlit application for daily risk assessment in construction and industrial settings,
5
+ with automatic safety rule generation.
6
+ """
7
+
8
+ __version__ = "0.1.0"
9
+
src/daily_ra/app.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import psycopg2
3
+ import json
4
+ from datetime import datetime
5
+ from sudachipy import dictionary, tokenizer
6
+ from sentence_transformers import SentenceTransformer, util
7
+ import pandas as pd
8
+ import os
9
+ from dotenv import load_dotenv
10
+
11
+ # ============================
12
+ # 🔧 1. 設定
13
+ # ============================
14
+
15
+ # Load environment variables from .env file (for local development)
16
+ load_dotenv()
17
+
18
+ # DB接続設定
19
+ DB_HOST = os.environ.get("DB_HOST")
20
+ DB_PORT = os.environ.get("DB_PORT", "5432")
21
+ DB_USER = os.environ.get("DB_USERNAME")
22
+ DB_PASSWORD = os.environ.get("DB_PASSWORD")
23
+ DB_NAME = os.environ.get("DB_NAME", "postgres")
24
+
25
+ print(DB_HOST, DB_PORT, DB_USER, DB_NAME)
26
+
27
+ # Construct PostgreSQL connection URL for Supabase
28
+ if all([DB_HOST, DB_USER, DB_PASSWORD, DB_NAME]):
29
+ DB_URL = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
30
+ print(f"🔗 Connecting to Supabase database as user '{DB_USER}'...")
31
+ else:
32
+ st.error("❌ Database connection details not found in environment variables!")
33
+ st.stop()
34
+
35
+ # ============================
36
+ # DB 接続
37
+ # ============================
38
+ try:
39
+ conn = psycopg2.connect(DB_URL)
40
+ conn.autocommit = True
41
+ cur = conn.cursor()
42
+ print(f"✅ Connected to Supabase database '{DB_NAME}'")
43
+ except Exception as e:
44
+ st.error(f"❌ Database connection failed: {str(e)}")
45
+ st.stop()
46
+
47
+ # SudachiPy セットアップ
48
+ sudachi_tokenizer = dictionary.Dictionary().create()
49
+ def sudachi_tokenizer_func(text):
50
+ tokens = sudachi_tokenizer.tokenize(text, tokenizer.Tokenizer.SplitMode.C)
51
+ return [t.surface() for t in tokens]
52
+
53
+ # SentenceTransformerモデル
54
+ model = SentenceTransformer("all-MiniLM-L12-v2")
55
+
56
+ # 正規化辞書
57
+ NORMALIZE = {
58
+ "重機": ["ショベルカー", "ユンボ", "バックホウ", "グレーダー"],
59
+ "作業員": ["作業者", "職人", "人"],
60
+ "クレーン": ["クレーン車", "吊り上げ機"],
61
+ "足場": ["仮設足場", "高所足場"],
62
+ "吊荷": ["荷", "吊り荷", "吊下げ物"]
63
+ }
64
+
65
+ # 分類キーワード
66
+ OBJECTS = ["作業員", "重機", "クレーン", "吊荷", "足場", "ダンプ"]
67
+ RISKS = ["挟まれ", "接触", "墜落", "転倒", "感電", "落下", "衝突"]
68
+
69
+ POTENTIAL_RISKS = {
70
+ ("作業員", "重機"): "作業員と重機が近接している状態",
71
+ ("作業員", "足場"): "作業員が高所作業中の可能性",
72
+ ("クレーン", "吊荷"): "吊荷の下に人がいる可能性",
73
+ ("作業員", "吊荷"): "作業員が吊荷の下にいる可能性",
74
+ }
75
+
76
+ # ============================
77
+ # 🧩 2. 関数群
78
+ # ============================
79
+
80
+ def normalize_text(text):
81
+ """表記ゆれ統一"""
82
+ for base, words in NORMALIZE.items():
83
+ for w in words:
84
+ text = text.replace(w, base)
85
+ return text
86
+
87
+ def extract_relations(text):
88
+ """
89
+ 文中の対象物とリスクを組み合わせて簡易ペア抽出
90
+ """
91
+ pairs = []
92
+ text_norm = normalize_text(text)
93
+
94
+ # 文中の対象物を検出
95
+ found_objects = [obj for obj in OBJECTS if obj in text_norm]
96
+
97
+ # 文中のリスクワードを検出
98
+ found_risks = [risk for risk in RISKS if risk in text_norm]
99
+
100
+ # 複数対象物とリスクがある場合にペア化
101
+ if len(found_objects) >= 2 and found_risks:
102
+ for i in range(len(found_objects)):
103
+ for j in range(i+1, len(found_objects)):
104
+ pairs.append((found_objects[i], found_objects[j], found_risks))
105
+ return pairs
106
+
107
+ def generate_rules(data):
108
+ """ルールベース生成"""
109
+ text = normalize_text(" ".join([
110
+ data["work_content"],
111
+ data["hazard_points"],
112
+ data["risk_identification"],
113
+ data["mitigation_measures"]
114
+ ]))
115
+
116
+ # 構文関係抽出
117
+ relations = extract_relations(text)
118
+
119
+ rules = []
120
+ for subj, obj, _ in relations:
121
+ # 潜在リスクを確認
122
+ risk_desc = POTENTIAL_RISKS.get((subj, obj)) or POTENTIAL_RISKS.get((obj, subj)) or []
123
+ rules.append({
124
+ "object1": subj,
125
+ "object2": obj,
126
+ "risk": risk_desc
127
+ })
128
+ return rules
129
+
130
+ # ============================
131
+ # 🖥 3. Streamlit UI
132
+ # ============================
133
+
134
+ st.title("日次RA入力")
135
+
136
+ with st.form("ra_form"):
137
+ work_date = st.date_input("作業日")
138
+ work_content = st.text_area("作業内容")
139
+ hazard_points = st.text_area("作業危険ポイント")
140
+ general_comments = st.text_area("元請コメント")
141
+ risk_identification = st.text_area("危険性・有害性の特定")
142
+ mitigation_measures = st.text_area("危険性・有害性の低減策")
143
+ inspection_items = st.text_area("点検事項")
144
+
145
+ submitted = st.form_submit_button("保存")
146
+
147
+ # ============================
148
+ # フォーム送信後の処理
149
+ # ============================
150
+ if submitted:
151
+ # --- 入力データを辞書にまとめる ---
152
+ form_data = {
153
+ "work_date": str(work_date),
154
+ "work_content": work_content,
155
+ "hazard_points": hazard_points,
156
+ "general_comments": general_comments,
157
+ "risk_identification": risk_identification,
158
+ "mitigation_measures": mitigation_measures,
159
+ "inspection_items": inspection_items
160
+ }
161
+
162
+ # --- ルール生成 ---
163
+ rules = generate_rules(form_data)
164
+
165
+ # --- PostgreSQL 保存 ---
166
+ sql = """INSERT INTO daily_ra
167
+ (work_date, work_content, hazard_points, general_comments, risk_identification, mitigation_measures, inspection_items, created_at)
168
+ VALUES (%s,%s,%s,%s,%s,%s,%s,NOW()) RETURNING id"""
169
+ cur.execute(sql, tuple(form_data.values()))
170
+ daily_id = cur.fetchone()[0] # PostgreSQL uses RETURNING to get the inserted ID
171
+
172
+ for r in rules:
173
+ sql_rule = """INSERT INTO rule_base (daily_ra_id, object1, object2, risk, created_at)
174
+ VALUES (%s,%s,%s,%s,NOW())"""
175
+ cur.execute(sql_rule, (daily_id, r["object1"], r["object2"], json.dumps(r["risk"], ensure_ascii=False)))
176
+
177
+ # No need to commit with autocommit=True, but keeping for clarity
178
+ conn.commit()
179
+ st.success("✅ 入力内容とルールベースの生成・保存が完了しました!")
180
+
181
+ # --- 表形式でルール表示 ---
182
+ if rules:
183
+ df = pd.DataFrame(rules)
184
+ st.subheader("🔍 生成されたルール(テーブル形式)")
185
+ st.dataframe(df)
186
+
187
+ # --- JSON作成(LLM連携用)&保存 ---
188
+ json_data = {
189
+ "daily_id": daily_id,
190
+ "rules": rules
191
+ }
192
+
193
+ # JSON保存用ディレクトリ作成
194
+ # Use absolute path in Docker, relative path locally
195
+ if os.path.exists("/app"):
196
+ json_dir = "/app/json_data"
197
+ else:
198
+ json_dir = "json_data"
199
+
200
+ try:
201
+ os.makedirs(json_dir, exist_ok=True)
202
+
203
+ # ファイル名に daily_id とタイムスタンプを付与
204
+ json_path = os.path.join(json_dir, f"daily_ra_{daily_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json")
205
+
206
+ # JSONファイルとして保存
207
+ with open(json_path, "w", encoding="utf-8") as f:
208
+ json.dump(json_data, f, ensure_ascii=False, indent=2)
209
+
210
+ st.success(f"✅ JSONファイルを保存しました: {json_path}")
211
+ except PermissionError:
212
+ st.warning("⚠️ JSONファイルの保存はスキップされました(データベースには正常に保存されています)")