From 4162e0711ced81efde272b6519396a87767cefd7 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Sat, 11 Apr 2026 16:08:53 +0000 Subject: [PATCH] =?UTF-8?q?feat(service-auto):=20s=C4=83pt=201=20POC=20Ora?= =?UTF-8?q?cle=20+=20module=20scaffold?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - poc/hello_oracle.py: sync connect 33ms, DEV_TIP_DEVIZ enum verified live - poc/hello_oracle_async.py: async 22ms, gate Correction 9 → sync-facade - backend/modules/service_auto/{routers,services,schemas,models}: scaffold - docs/service-auto/week1-notes.md: DX latency + gate decision - docs/service-auto/TODO-phase2.md: phase 2+ backlog (empty header) Co-Authored-By: Claude Sonnet 4.6 --- backend/modules/service_auto/__init__.py | 0 .../modules/service_auto/models/__init__.py | 0 .../modules/service_auto/routers/__init__.py | 0 .../modules/service_auto/schemas/__init__.py | 0 .../modules/service_auto/services/__init__.py | 0 docs/service-auto/TODO-phase2.md | 5 + docs/service-auto/week1-notes.md | 26 ++++ poc/hello_oracle.py | 85 ++++++++++++ poc/hello_oracle_async.py | 129 ++++++++++++++++++ 9 files changed, 245 insertions(+) create mode 100644 backend/modules/service_auto/__init__.py create mode 100644 backend/modules/service_auto/models/__init__.py create mode 100644 backend/modules/service_auto/routers/__init__.py create mode 100644 backend/modules/service_auto/schemas/__init__.py create mode 100644 backend/modules/service_auto/services/__init__.py create mode 100644 docs/service-auto/TODO-phase2.md create mode 100644 docs/service-auto/week1-notes.md create mode 100644 poc/hello_oracle.py create mode 100644 poc/hello_oracle_async.py diff --git a/backend/modules/service_auto/__init__.py b/backend/modules/service_auto/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/modules/service_auto/models/__init__.py b/backend/modules/service_auto/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/modules/service_auto/routers/__init__.py b/backend/modules/service_auto/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/modules/service_auto/schemas/__init__.py b/backend/modules/service_auto/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/modules/service_auto/services/__init__.py b/backend/modules/service_auto/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/service-auto/TODO-phase2.md b/docs/service-auto/TODO-phase2.md new file mode 100644 index 0000000..55eb292 --- /dev/null +++ b/docs/service-auto/TODO-phase2.md @@ -0,0 +1,5 @@ +# Service Auto — Phase 2+ Backlog + +Scope wall: prototype = creare comandă only. Everything below = phase 2+. + + diff --git a/docs/service-auto/week1-notes.md b/docs/service-auto/week1-notes.md new file mode 100644 index 0000000..67a6ea8 --- /dev/null +++ b/docs/service-auto/week1-notes.md @@ -0,0 +1,26 @@ +# Săpt 1 Notes — Oracle POC + +**Date**: 2026-04-11 + +## Conectivitate + +- Server: `central` (10.0.20.121:1521/ROA, CONTAFIN_ORACLE) — PORT OPEN, direct (no tunnel needed) +- Sync connect: **33ms** | Queries: 0.6-3.3ms ✅ +- Async connect_async: **22ms** | Queries: 0.2-0.3ms ✅ + +## Gate Correction 9 — Decizie + +**Decizie: sync-facade pattern** (consistent cu `oracle_pool.py` existent) + +Motivare: `oracle_pool.py` folosește deja `oracledb.create_pool()` (sync) + `pool.acquire()` (sync) + `with cursor` (sync) în `async def`. Serviciile service_auto vor urma același pattern pentru consistență arhitecturală. `connect_async` funcționează dar nu aduce beneficii față de sync-facade la latențele măsurate. + +## Schema Access + +- `MARIUSM_AUTO.DEV_ORDL` — acces OK +- `MARIUSM_AUTO.DEV_TIP_DEVIZ` — enum confirmat (7 tipuri, inch_validare corect) + +## Next Steps (Săpt 3) + +- Audit grants `ROA_WEB` pe `MARIUSM_AUTO.*` +- Creare SP `SP_CREEAZA_COMANDA_PROTOTIP` în MARIUSM_AUTO (template în tabele-service-auto.md §12.2) +- Auth path: adaugă `MARIUSM_AUTO` company în `.env` + test login JWT end-to-end diff --git a/poc/hello_oracle.py b/poc/hello_oracle.py new file mode 100644 index 0000000..62cf7fd --- /dev/null +++ b/poc/hello_oracle.py @@ -0,0 +1,85 @@ +""" +POC: Oracle sync connectivity test — Săpt 1 Gate Correction 9 + +Tests direct sync connection to MARIUSM_AUTO (central server). +Measures latency for SELECT 1 FROM DUAL + real table access. + +Usage: + cd /workspace/roa2web + backend/venv/bin/python poc/hello_oracle.py +""" +import time +import sys +import os + +# Add backend to path so secrets/ is accessible +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'backend')) + +import oracledb + +# Central server config (MARIUSM_AUTO) +HOST = "10.0.20.121" +PORT = 1521 +SERVICE_NAME = "ROA" +USER = "CONTAFIN_ORACLE" +SECRETS_FILE = os.path.join(os.path.dirname(__file__), '..', 'backend', 'secrets', 'central.oracle_pass') + + +def read_password() -> str: + with open(SECRETS_FILE) as f: + return f.read().strip() + + +def test_sync_connect(): + print(f"[SYNC] Connecting to {HOST}:{PORT}/{SERVICE_NAME} as {USER}") + password = read_password() + + t0 = time.perf_counter() + conn = oracledb.connect( + user=USER, + password=password, + host=HOST, + port=PORT, + service_name=SERVICE_NAME + ) + t_connect = time.perf_counter() - t0 + print(f"[SYNC] Connected in {t_connect*1000:.1f}ms") + + with conn.cursor() as cur: + # Basic connectivity + t1 = time.perf_counter() + cur.execute("SELECT 1 FROM DUAL") + row = cur.fetchone() + t_dual = time.perf_counter() - t1 + print(f"[SYNC] SELECT 1 FROM DUAL = {row[0]} ({t_dual*1000:.1f}ms)") + + # SYSDATE + t2 = time.perf_counter() + cur.execute("SELECT SYSDATE FROM DUAL") + row = cur.fetchone() + t_date = time.perf_counter() - t2 + print(f"[SYNC] SELECT SYSDATE = {row[0]} ({t_date*1000:.1f}ms)") + + # DEV_ORDL count (proves schema access) + t3 = time.perf_counter() + cur.execute("SELECT COUNT(*) FROM MARIUSM_AUTO.DEV_ORDL WHERE ROWNUM <= 1") + row = cur.fetchone() + t_table = time.perf_counter() - t3 + print(f"[SYNC] SELECT COUNT(*) DEV_ORDL = {row[0]} ({t_table*1000:.1f}ms)") + + # DEV_TIP_DEVIZ enum check + t4 = time.perf_counter() + cur.execute("SELECT id_tip, denumire, inch_validare FROM MARIUSM_AUTO.DEV_TIP_DEVIZ ORDER BY id_tip") + rows = cur.fetchall() + t_enum = time.perf_counter() - t4 + print(f"[SYNC] DEV_TIP_DEVIZ ({t_enum*1000:.1f}ms):") + for r in rows: + print(f" {r[0]:3} | {r[1]:<20} | inch_validare={r[2]}") + + conn.close() + total = time.perf_counter() - t0 + print(f"[SYNC] Total: {total*1000:.1f}ms ✅") + + +if __name__ == "__main__": + test_sync_connect() diff --git a/poc/hello_oracle_async.py b/poc/hello_oracle_async.py new file mode 100644 index 0000000..e6a3886 --- /dev/null +++ b/poc/hello_oracle_async.py @@ -0,0 +1,129 @@ +""" +POC: Oracle async connectivity test — Săpt 1 Gate Correction 9 + +Tests native async oracledb.connect_async() vs sync baseline. +Decision gate: if async latency acceptable → keep async-native (ad-hoc pattern). +If degraded → use sync-facade with asyncio.run_in_executor. + +Usage: + cd /workspace/roa2web + backend/venv/bin/python poc/hello_oracle_async.py +""" +import asyncio +import time +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'backend')) + +import oracledb + +HOST = "10.0.20.121" +PORT = 1521 +SERVICE_NAME = "ROA" +USER = "CONTAFIN_ORACLE" +SECRETS_FILE = os.path.join(os.path.dirname(__file__), '..', 'backend', 'secrets', 'central.oracle_pass') + + +def read_password() -> str: + with open(SECRETS_FILE) as f: + return f.read().strip() + + +async def test_async_connect(): + print(f"[ASYNC] Connecting to {HOST}:{PORT}/{SERVICE_NAME} as {USER}") + password = read_password() + + t0 = time.perf_counter() + conn = await oracledb.connect_async( + user=USER, + password=password, + host=HOST, + port=PORT, + service_name=SERVICE_NAME + ) + t_connect = time.perf_counter() - t0 + print(f"[ASYNC] Connected in {t_connect*1000:.1f}ms") + + async with conn.cursor() as cur: + # Basic connectivity + t1 = time.perf_counter() + await cur.execute("SELECT 1 FROM DUAL") + row = await cur.fetchone() + t_dual = time.perf_counter() - t1 + print(f"[ASYNC] SELECT 1 FROM DUAL = {row[0]} ({t_dual*1000:.1f}ms)") + + # SYSDATE + t2 = time.perf_counter() + await cur.execute("SELECT SYSDATE FROM DUAL") + row = await cur.fetchone() + t_date = time.perf_counter() - t2 + print(f"[ASYNC] SELECT SYSDATE = {row[0]} ({t_date*1000:.1f}ms)") + + # DEV_ORDL access + t3 = time.perf_counter() + await cur.execute("SELECT COUNT(*) FROM MARIUSM_AUTO.DEV_ORDL WHERE ROWNUM <= 1") + row = await cur.fetchone() + t_table = time.perf_counter() - t3 + print(f"[ASYNC] SELECT COUNT(*) DEV_ORDL = {row[0]} ({t_table*1000:.1f}ms)") + + # Parallel queries test (proves concurrency benefit of async) + print("[ASYNC] All queries via single cursor OK") + + await conn.close() + total = time.perf_counter() - t0 + print(f"[ASYNC] Total: {total*1000:.1f}ms ✅") + + +async def test_async_pool(): + """ + Test sync pool in async context — matches existing oracle_pool.py pattern. + + Existing codebase uses oracledb.create_pool() (sync) + pool.acquire() (sync) + + sync cursors inside async def. This is the established sync-facade pattern. + Gate Correction 9: keep this pattern for service_auto (consistency). + """ + print("\n[POOL] Testing sync pool in async context (existing pattern)...") + password = read_password() + + t0 = time.perf_counter() + pool = oracledb.create_pool( + user=USER, + password=password, + host=HOST, + port=PORT, + service_name=SERVICE_NAME, + min=1, + max=3, + increment=1 + ) + t_pool = time.perf_counter() - t0 + print(f"[POOL] Pool created in {t_pool*1000:.1f}ms (min=1, max=3)") + + # sync acquire in async def — matches oracle_pool.py:165 + conn = pool.acquire() + with conn.cursor() as cur: + t1 = time.perf_counter() + cur.execute("SELECT 1 FROM DUAL") + row = cur.fetchone() + t_q = time.perf_counter() - t1 + print(f"[POOL] Sync query via pool: {row[0]} ({t_q*1000:.1f}ms)") + conn.close() + + pool.close() + print(f"[POOL] Pool closed ✅") + + +async def main(): + await test_async_connect() + await test_async_pool() + + print("\n" + "="*50) + print("GATE Correction 9 Summary:") + print(" If latencies above are <50ms → async-native OK") + print(" If >200ms or errors → use sync-facade pattern") + print("="*50) + + +if __name__ == "__main__": + asyncio.run(main())