Files
tiandihe/backend/sqlite_sync.py
Joshi fc8b38d44d feat(追踪系统): 修改钢卷追踪逻辑为按钢卷号升序处理
- 前端调整位置数量从5个改为4个并更新提示信息
- 后端修改SQL查询按COILID升序获取钢卷
- 新增按钢卷号范围查询功能
- 实现追踪状态管理,支持追踪结束检测和恢复
- 优化信号处理逻辑,支持末卷重复信号处理
2026-04-13 14:57:44 +08:00

455 lines
14 KiB
Python

"""
SQLite mirror for PDI_PLTM and CMPT_PL_TRACKMAP.
On startup (and on demand) the service pulls all rows from Oracle and
upserts them into a local SQLite file (hefa_l2.db).
Whenever the FastAPI endpoints write to Oracle they also call the
corresponding sqlite_* helper here so the two databases stay in sync.
"""
import sqlite3
import logging
import os
from typing import Any, Dict, List, Optional
logger = logging.getLogger(__name__)
DB_PATH = os.path.join(os.path.dirname(__file__), "hefa_l2.db")
# ─────────────────────────────────────────────────────────────
# Connection helper
# ─────────────────────────────────────────────────────────────
def get_sqlite() -> sqlite3.Connection:
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
return conn
# ─────────────────────────────────────────────────────────────
# Schema bootstrap
# ─────────────────────────────────────────────────────────────
PDI_DDL = """
CREATE TABLE IF NOT EXISTS PDI_PLTM (
SID INTEGER,
ROLLPROGRAMNB INTEGER,
SEQUENCENB INTEGER,
STATUS INTEGER DEFAULT 0,
SCHEDULE_CODE TEXT,
COILID TEXT NOT NULL PRIMARY KEY,
ENTRY_COIL_THICKNESS REAL,
ENTRY_COIL_THICKNESS_MAX REAL,
ENTRY_COIL_THICKNESS_MIN REAL,
ENTRY_COIL_WIDTH REAL,
ENTRY_COIL_WIDTH_MAX REAL,
ENTRY_COIL_WIDTH_MIN REAL,
ENTRY_COIL_WEIGHT REAL,
ENTRY_OF_COIL_LENGTH REAL,
ENTRY_OF_COIL_INNER_DIAMETER REAL,
ENTRY_OF_COIL_OUTER_DIAMETER REAL,
TRIMMING INTEGER,
TRIMMING_WIDTH REAL,
SMP_LENGTH REAL,
SMP_NUM REAL,
SMP_FRQ TEXT,
SMP_NUM_HEAD REAL,
SMP_NUM_MID REAL,
SMP_NUM_TAIL REAL,
PRECEDING_PROCESS_CODE TEXT,
NEXT_PROCESS_CODE TEXT,
HOT_MILL_DELIVERY_TEMP REAL,
FINISHED_COIL_TEMP REAL,
CROWN_AVERAGE REAL,
COIL_FLATNESS_AVERAGE REAL,
COIL_FLATNESS_MAX_VALUE REAL,
COIL_FLATNESS_MIN_VALUE REAL,
MATERIAL_YIELD_POINT REAL,
MATERIAL_TENSILE REAL,
HOTACTFMWEDGEAVG REAL,
WEIGHT_MODE TEXT,
DUMMY_COIL_MRK TEXT,
CUT_MODE TEXT,
OFF_GAUGE_HEAD_LENGTH REAL,
OFF_GAUGE_TAIL_LENGTH REAL,
EXIT_COIL_NO TEXT,
EXIT_COIL_WEIGHT REAL,
EXIT_COIL_WEIGHT_MAX REAL,
EXIT_COIL_WEIGHT_MIN REAL,
EXIT_COIL_THICKNESS REAL,
EXIT_COIL_THICKNESS_MAX REAL,
EXIT_COIL_THICKNESS_MIN REAL,
EXIT_COIL_WIDTH REAL,
EXIT_COIL_WIDTH_MAX REAL,
EXIT_COIL_WIDTH_MIN REAL,
WORK_ORDER_NO TEXT,
ORDER_QUALITY TEXT,
STEEL_GRADE TEXT,
SG_SIGN TEXT,
ORDER_THICKNESS REAL,
ORDER_THICKNESS_MAX REAL,
ORDER_THICKNESS_MIN REAL,
ORDER_WIDTH REAL,
ORDER_WIDTH_MAX REAL,
ORDER_WIDTH_MIN REAL,
SLEEVE_CODE_OF_COLD_COIL TEXT,
PACKING_TYPE_CODE TEXT,
THK_DS TEXT,
EXT_NUM_01 TEXT,
C REAL, SI REAL, MN REAL, P REAL, S REAL,
CU REAL, NI REAL, CR REAL, MO REAL, V REAL,
TI REAL, SOL_AL REAL, FE REAL, NB REAL, N REAL, B REAL,
MG REAL, PB REAL, SN REAL, ZN REAL, ZR REAL,
NA REAL, LI REAL, GA REAL, CA REAL,
BE REAL, BI REAL, W REAL,
TA REAL, O REAL, H REAL,
AR REAL, AG REAL, AS1 REAL, CD REAL, CL REAL,
CO REAL, K REAL, SB REAL, SE REAL,
SEND_FLAG TEXT,
SEND_DATE TEXT,
TRANSACTION_ID TEXT,
VERSION INTEGER,
TEXT1 TEXT, TEXT2 TEXT, TEXT3 TEXT, TEXT4 TEXT, TEXT5 TEXT,
TOC TEXT, TOM TEXT, MOP TEXT,
POSITION INTEGER DEFAULT 0,
CROSS_SECTION_AREA REAL,
UNCOILER_TENSION REAL,
LOOPER_TENSION_1 REAL,
PL_TENSION REAL,
LOOPER_TENSION_2 REAL,
LOOPER_TENSION_3 REAL,
METERWEIGHT REAL,
METER_D_OUTSIDE REAL,
METER_WIDTH REAL,
SCRAP_CUT_HEAD_LEN REAL,
SCRAP_CUT_TAIL_LEN REAL,
COILER_DIAMETER INTEGER,
L2_GRADE TEXT,
CREATED_BY TEXT,
CREATED_DT TEXT,
CREATED_BY_NAME TEXT,
UPDATED_BY TEXT,
UPDATED_DT TEXT,
UPDATED_BY_NAME TEXT
)
"""
TRACKMAP_DDL = """
CREATE TABLE IF NOT EXISTS CMPT_PL_TRACKMAP (
POSITION INTEGER PRIMARY KEY,
COILID TEXT,
BEF_ES INTEGER,
ES INTEGER,
ENT_LOO INTEGER,
PL INTEGER,
INT_LOO INTEGER,
ST INTEGER,
EXI_LOO INTEGER,
RUN_SPEED_MIN REAL,
RUN_SPEED_MAX REAL,
WELD_SPEED_MIN REAL,
WELD_SPEED_MAX REAL,
TOC TEXT,
TOM TEXT,
MOP TEXT
)
"""
COIL_TRACK_DDL = """
CREATE TABLE IF NOT EXISTS COIL_TRACK_TEMP (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
COILID TEXT NOT NULL,
SEQUENCENB INTEGER NOT NULL,
ROLLPROGRAMNB INTEGER NOT NULL,
CREATED_DT TEXT DEFAULT (datetime('now')),
POSITION INTEGER DEFAULT 0
)
"""
def init_db():
"""Create tables if they don't exist."""
conn = get_sqlite()
try:
conn.execute(PDI_DDL)
conn.execute(TRACKMAP_DDL)
conn.execute(COIL_TRACK_DDL)
conn.commit()
logger.info("SQLite schema ready: %s", DB_PATH)
finally:
conn.close()
# ─────────────────────────────────────────────────────────────
# Full sync from Oracle → SQLite
# ─────────────────────────────────────────────────────────────
def _oracle_rows_to_dicts(cursor) -> List[Dict[str, Any]]:
columns = [col[0].upper() for col in cursor.description]
rows = []
for raw in cursor.fetchall():
row = {}
for col, val in zip(columns, raw):
if hasattr(val, 'isoformat'):
val = val.isoformat()
row[col] = val
rows.append(row)
return rows
def sync_pdi_from_oracle() -> int:
"""Pull all PDI_PLTM rows from Oracle and UPSERT into SQLite."""
from database import get_connection
oc = get_connection()
oc_cur = oc.cursor()
try:
oc_cur.execute("SELECT * FROM PDI_PLTM")
rows = _oracle_rows_to_dicts(oc_cur)
finally:
oc_cur.close()
oc.close()
if not rows:
return 0
sc = get_sqlite()
try:
cols = list(rows[0].keys())
placeholders = ", ".join([f":{c}" for c in cols])
col_list = ", ".join(cols)
sql = (
f"INSERT OR REPLACE INTO PDI_PLTM ({col_list}) "
f"VALUES ({placeholders})"
)
sc.executemany(sql, rows)
sc.commit()
logger.info("Synced %d PDI_PLTM rows to SQLite", len(rows))
return len(rows)
finally:
sc.close()
def sync_trackmap_from_oracle() -> int:
"""Pull all CMPT_PL_TRACKMAP rows from Oracle and UPSERT into SQLite."""
from database import get_connection
oc = get_connection()
oc_cur = oc.cursor()
try:
oc_cur.execute(
"SELECT POSITION, COILID, BEF_ES, ES, ENT_LOO, PL, INT_LOO, "
"ST, EXI_LOO, RUN_SPEED_MIN, RUN_SPEED_MAX, "
"WELD_SPEED_MIN, WELD_SPEED_MAX, TOC, TOM, MOP "
"FROM PLTM.CMPT_PL_TRACKMAP ORDER BY POSITION"
)
rows = _oracle_rows_to_dicts(oc_cur)
finally:
oc_cur.close()
oc.close()
if not rows:
return 0
sc = get_sqlite()
try:
cols = list(rows[0].keys())
placeholders = ", ".join([f":{c}" for c in cols])
col_list = ", ".join(cols)
sql = (
f"INSERT OR REPLACE INTO CMPT_PL_TRACKMAP ({col_list}) "
f"VALUES ({placeholders})"
)
sc.executemany(sql, rows)
sc.commit()
logger.info("Synced %d CMPT_PL_TRACKMAP rows to SQLite", len(rows))
return len(rows)
finally:
sc.close()
def sync_all_from_oracle() -> Dict[str, int]:
pdi = sync_pdi_from_oracle()
tm = sync_trackmap_from_oracle()
return {"pdi_pltm": pdi, "cmpt_pl_trackmap": tm}
# ─────────────────────────────────────────────────────────────
# Incremental write-through helpers (called after Oracle commits)
# ─────────────────────────────────────────────────────────────
def sqlite_upsert_pdi(row: Dict[str, Any]):
"""Insert or replace one PDI_PLTM row in SQLite."""
sc = get_sqlite()
try:
upper = {k.upper(): v for k, v in row.items()}
cols = list(upper.keys())
placeholders = ", ".join([f":{c}" for c in cols])
col_list = ", ".join(cols)
sc.execute(
f"INSERT OR REPLACE INTO PDI_PLTM ({col_list}) VALUES ({placeholders})",
upper
)
sc.commit()
finally:
sc.close()
def sqlite_delete_pdi(coilid: str):
sc = get_sqlite()
try:
sc.execute("DELETE FROM PDI_PLTM WHERE COILID = ?", (coilid,))
sc.commit()
finally:
sc.close()
def sqlite_upsert_trackmap(row: Dict[str, Any]):
sc = get_sqlite()
try:
upper = {k.upper(): v for k, v in row.items()}
cols = list(upper.keys())
placeholders = ", ".join([f":{c}" for c in cols])
col_list = ", ".join(cols)
sc.execute(
f"INSERT OR REPLACE INTO CMPT_PL_TRACKMAP ({col_list}) VALUES ({placeholders})",
upper
)
sc.commit()
finally:
sc.close()
def sqlite_get_max_sequencenb() -> Optional[int]:
sc = get_sqlite()
try:
cursor = sc.execute("SELECT MAX(SEQUENCENB) FROM PDI_PLTM")
row = cursor.fetchone()
return row[0] if row and row[0] is not None else None
finally:
sc.close()
def sqlite_get_coils_by_sequencenb_range(start_seq: int, end_seq: int) -> List[Dict[str, Any]]:
sc = get_sqlite()
try:
cursor = sc.execute("""
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
FROM PDI_PLTM
WHERE SEQUENCENB >= ? AND SEQUENCENB <= ?
ORDER BY COILID ASC
""", (start_seq, end_seq))
rows = cursor.fetchall()
return [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
finally:
sc.close()
def sqlite_get_coils_by_coilid(start_coilid: str, count: int = 4) -> List[Dict[str, Any]]:
sc = get_sqlite()
try:
cursor = sc.execute("""
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
FROM PDI_PLTM
WHERE COILID >= ?
ORDER BY COILID ASC
LIMIT ?
""", (start_coilid, count))
rows = cursor.fetchall()
return [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
finally:
sc.close()
def sqlite_get_first_coils(count: int = 4) -> List[Dict[str, Any]]:
sc = get_sqlite()
try:
cursor = sc.execute("""
SELECT COILID, SEQUENCENB, ROLLPROGRAMNB
FROM PDI_PLTM
ORDER BY COILID ASC
LIMIT ?
""", (count,))
rows = cursor.fetchall()
return [{"coilid": r[0], "sequencenb": r[1], "rollprogramnb": r[2]} for r in rows]
finally:
sc.close()
def sqlite_save_coils_to_track(coils: List[Dict[str, Any]]):
sc = get_sqlite()
try:
sc.execute("DELETE FROM COIL_TRACK_TEMP")
for i, coil in enumerate(coils):
sc.execute("""
INSERT INTO COIL_TRACK_TEMP (COILID, SEQUENCENB, ROLLPROGRAMNB, POSITION)
VALUES (?, ?, ?, ?)
""", (coil["coilid"], coil["sequencenb"], coil["rollprogramnb"], i + 1))
sc.commit()
logger.info(f"Saved {len(coils)} coils to track temp")
finally:
sc.close()
def sqlite_get_coil_track() -> List[Dict[str, Any]]:
sc = get_sqlite()
try:
cursor = sc.execute("""
SELECT ID, COILID, SEQUENCENB, ROLLPROGRAMNB, CREATED_DT, POSITION
FROM COIL_TRACK_TEMP
ORDER BY POSITION ASC
""")
rows = cursor.fetchall()
return [
{"id": r[0], "coilid": r[1], "sequencenb": r[2], "rollprogramnb": r[3], "created_dt": r[4], "position": r[5]}
for r in rows
]
finally:
sc.close()
def sqlite_update_coil_track_item(id: int, coilid: str, sequencenb: int, rollprogramnb: int, position: int):
sc = get_sqlite()
try:
sc.execute("""
UPDATE COIL_TRACK_TEMP
SET COILID = ?, SEQUENCENB = ?, ROLLPROGRAMNB = ?, POSITION = ?
WHERE ID = ?
""", (coilid, sequencenb, rollprogramnb, position, id))
sc.commit()
finally:
sc.close()
def sqlite_add_coil_track_item(coilid: str, sequencenb: int, rollprogramnb: int):
sc = get_sqlite()
try:
cursor = sc.execute("SELECT MAX(POSITION) FROM COIL_TRACK_TEMP")
row = cursor.fetchone()
max_pos = (row[0] or 0) + 1
sc.execute("""
INSERT INTO COIL_TRACK_TEMP (COILID, SEQUENCENB, ROLLPROGRAMNB, POSITION)
VALUES (?, ?, ?, ?)
""", (coilid, sequencenb, rollprogramnb, max_pos))
sc.commit()
finally:
sc.close()
def sqlite_delete_coil_track_item(id: int):
sc = get_sqlite()
try:
sc.execute("DELETE FROM COIL_TRACK_TEMP WHERE ID = ?", (id,))
sc.commit()
finally:
sc.close()
def sqlite_clear_coil_track():
sc = get_sqlite()
try:
sc.execute("DELETE FROM COIL_TRACK_TEMP")
sc.commit()
finally:
sc.close()