From e260e5f2182090dd098ec470fe673fa99695cdfe Mon Sep 17 00:00:00 2001 From: "LASTA_DEV01\\lasta" Date: Tue, 19 May 2026 20:31:52 +0900 Subject: [PATCH] docs --- .server-stderr.log | 713 +------ .server-stdout.log | 287 +-- README.md | 301 +-- README_old.md | 234 +++ crawler_platform.db | Bin 11796480 -> 14757888 bytes crawler_platform/app/api/routes.py | 368 +++- .../app/core/research/graph_query.py | 14 +- crawler_platform/app/main.py | 15 +- crawler_platform/app/web/frontend/src/App.tsx | 12 + .../src/components/layout/AppShell.tsx | 39 +- .../app/web/frontend/src/hooks/queryKeys.ts | 64 +- .../app/web/frontend/src/hooks/useClaims.ts | 31 +- .../app/web/frontend/src/hooks/usePlatform.ts | 146 ++ .../app/web/frontend/src/lib/api/claims.ts | 30 + .../app/web/frontend/src/lib/api/client.ts | 36 +- .../app/web/frontend/src/lib/api/index.ts | 1 + .../app/web/frontend/src/lib/api/platform.ts | 356 ++++ .../app/web/frontend/src/lib/display.ts | 64 + .../frontend/src/pages/BuildPipelinePage.tsx | 465 +++++ .../app/web/frontend/src/pages/CrawlPage.tsx | 24 +- .../web/frontend/src/pages/DashboardPage.tsx | 84 +- .../web/frontend/src/pages/ExportApiPage.tsx | 402 ++++ .../web/frontend/src/pages/GraphViewPage.tsx | 567 ++++++ .../frontend/src/pages/PageAnalysisPage.tsx | 333 ++++ .../src/pages/QualityInspectorPage.tsx | 801 ++++++++ .../app/web/frontend/src/pages/ReviewPage.tsx | 463 ++++- .../frontend/src/pages/SchemaDesignerPage.tsx | 451 +++++ .../app/web/static/assets/index-B9qLr-xp.js | 12 + .../web/static/assets/index-B9qLr-xp.js.map | 1 + .../app/web/static/assets/index-CAk04EKS.css | 1 + .../app/web/static/assets/index-DTEQpvfm.js | 11 - .../web/static/assets/index-DTEQpvfm.js.map | 1 - .../app/web/static/assets/index-uHGS9kYH.css | 1 - crawler_platform/app/web/static/index.html | 4 +- docs/PHASE_PLANNING.md | 39 + docs/PHASE_WORKFLOW.md | 123 ++ .../00_ENGINE_BOUNDARY_ANALYSIS.md | 95 + .../phase_00_001_engine_boundary_gate.md | 45 + .../phase_01_001_trafilatura_ingestion.md | 39 + .../phase_02_001_candidate_review_boundary.md | 39 + .../phase_03_001_crawl4ai_acquisition_jobs.md | 39 + ...phase_04_001_guardrails_validation_gate.md | 39 + .../phase_05_001_neo4j_projection_graphrag.md | 39 + ...hase_06_001_maintenance_loop_operations.md | 41 + .../docs/phases/PHASE0_ACCEPTANCE_GATE.md | 25 +- .../docs/phases/PHASE1_ACCEPTANCE_GATE.md | 33 + .../docs/phases/PHASE2_ACCEPTANCE_GATE.md | 35 + .../docs/phases/PHASE2_NEXT_STEPS.md | 27 + .../docs/phases/PHASE3_NEXT_STEPS.md | 28 + ontology_platform/docs/phases/PHASE_INDEX.md | 89 + ontology_platform/ont_platform/api/db_deps.py | 15 +- ontology_platform/ont_platform/api/main.py | 47 +- .../ont_platform/api/routes/__init__.py | 45 +- .../ont_platform/api/routes/crawl.py | 91 + .../ont_platform/api/routes/extraction.py | 172 +- .../ont_platform/api/routes/graph.py | 79 + .../ont_platform/api/routes/maintenance.py | 115 ++ .../ont_platform/api/routes/review.py | 335 ++++ ontology_platform/ont_platform/auth/rbac.py | 5 + .../ont_platform/billing/calculator.py | 7 +- ontology_platform/ont_platform/config.py | 32 +- .../ont_platform/core/crawler/__init__.py | 16 +- .../core/crawler/crawl4ai_adapter.py | 450 ++--- .../ont_platform/core/crawler/jobs.py | 283 +++ .../ont_platform/core/extraction/schemas.py | 89 +- .../core/extractors/web_extractor.py | 475 ++++- .../ont_platform/core/graph/__init__.py | 7 + .../ont_platform/core/graph/cypher_guard.py | 61 + .../ont_platform/core/graph/search.py | 123 ++ .../ont_platform/core/maintenance/__init__.py | 17 + .../ont_platform/core/maintenance/service.py | 574 ++++++ .../ont_platform/core/projection/__init__.py | 5 + .../core/projection/rdf_to_neo4j.py | 87 + .../ont_platform/core/review/__init__.py | 18 + .../ont_platform/core/review/promotion.py | 118 ++ .../core/review/review_service.py | 211 +++ .../ont_platform/core/validation/__init__.py | 5 +- .../ont_platform/core/validation/guards.py | 9 +- .../ont_platform/core/validation/models.py | 13 + .../core/validation/validators.py | 61 +- .../ont_platform/storage/__init__.py | 54 +- .../storage/candidate_repository.py | 445 +++++ .../ont_platform/storage/dedup_cache.py | 88 + .../ont_platform/storage/models.py | 195 +- ontology_platform/pyproject.toml | 3 +- ontology_platform/tests/conftest.py | 25 + .../tests/fixtures/korean/blog_naver.html | 21 + .../tests/fixtures/korean/news_yonhap.html | 20 + .../tests/fixtures/korean/shop_coupang.html | 19 + .../tests/integration/test_api_smoke.py | 8 + .../integration/test_phase3_crawl_jobs.py | 76 + .../test_phase6_maintenance_api.py | 111 ++ .../tests/integration/test_review_api.py | 123 ++ .../tests/integration/test_url_ingest.py | 87 + .../tests/unit/test_candidate_repository.py | 91 + .../tests/unit/test_content_unit.py | 21 + .../tests/unit/test_dedup_cache.py | 32 + .../unit/test_guardrails_validation_gate.py | 90 + .../unit/test_phase5_projection_graphrag.py | 105 ++ .../unit/test_phase6_maintenance_loop.py | 138 ++ .../tests/unit/test_review_service.py | 147 ++ .../tests/unit/test_web_extractor.py | 64 + ontology_platform_research_report.md | 1653 +++++++++++++++++ restart_all_servers.bat | 319 ++++ 104 files changed, 12898 insertions(+), 1709 deletions(-) create mode 100644 README_old.md create mode 100644 crawler_platform/app/web/frontend/src/hooks/usePlatform.ts create mode 100644 crawler_platform/app/web/frontend/src/lib/api/platform.ts create mode 100644 crawler_platform/app/web/frontend/src/lib/display.ts create mode 100644 crawler_platform/app/web/frontend/src/pages/BuildPipelinePage.tsx create mode 100644 crawler_platform/app/web/frontend/src/pages/ExportApiPage.tsx create mode 100644 crawler_platform/app/web/frontend/src/pages/GraphViewPage.tsx create mode 100644 crawler_platform/app/web/frontend/src/pages/PageAnalysisPage.tsx create mode 100644 crawler_platform/app/web/frontend/src/pages/QualityInspectorPage.tsx create mode 100644 crawler_platform/app/web/frontend/src/pages/SchemaDesignerPage.tsx create mode 100644 crawler_platform/app/web/static/assets/index-B9qLr-xp.js create mode 100644 crawler_platform/app/web/static/assets/index-B9qLr-xp.js.map create mode 100644 crawler_platform/app/web/static/assets/index-CAk04EKS.css delete mode 100644 crawler_platform/app/web/static/assets/index-DTEQpvfm.js delete mode 100644 crawler_platform/app/web/static/assets/index-DTEQpvfm.js.map delete mode 100644 crawler_platform/app/web/static/assets/index-uHGS9kYH.css create mode 100644 docs/PHASE_PLANNING.md create mode 100644 docs/PHASE_WORKFLOW.md create mode 100644 ontology_platform/docs/phases/26_05_19_engine_respect_plan/00_ENGINE_BOUNDARY_ANALYSIS.md create mode 100644 ontology_platform/docs/phases/26_05_19_engine_respect_plan/phase_00_001_engine_boundary_gate.md create mode 100644 ontology_platform/docs/phases/26_05_19_engine_respect_plan/phase_01_001_trafilatura_ingestion.md create mode 100644 ontology_platform/docs/phases/26_05_19_engine_respect_plan/phase_02_001_candidate_review_boundary.md create mode 100644 ontology_platform/docs/phases/26_05_19_engine_respect_plan/phase_03_001_crawl4ai_acquisition_jobs.md create mode 100644 ontology_platform/docs/phases/26_05_19_engine_respect_plan/phase_04_001_guardrails_validation_gate.md create mode 100644 ontology_platform/docs/phases/26_05_19_engine_respect_plan/phase_05_001_neo4j_projection_graphrag.md create mode 100644 ontology_platform/docs/phases/26_05_19_engine_respect_plan/phase_06_001_maintenance_loop_operations.md create mode 100644 ontology_platform/docs/phases/PHASE1_ACCEPTANCE_GATE.md create mode 100644 ontology_platform/docs/phases/PHASE2_ACCEPTANCE_GATE.md create mode 100644 ontology_platform/docs/phases/PHASE2_NEXT_STEPS.md create mode 100644 ontology_platform/docs/phases/PHASE3_NEXT_STEPS.md create mode 100644 ontology_platform/docs/phases/PHASE_INDEX.md create mode 100644 ontology_platform/ont_platform/api/routes/crawl.py create mode 100644 ontology_platform/ont_platform/api/routes/graph.py create mode 100644 ontology_platform/ont_platform/api/routes/maintenance.py create mode 100644 ontology_platform/ont_platform/api/routes/review.py create mode 100644 ontology_platform/ont_platform/core/crawler/jobs.py create mode 100644 ontology_platform/ont_platform/core/graph/cypher_guard.py create mode 100644 ontology_platform/ont_platform/core/graph/search.py create mode 100644 ontology_platform/ont_platform/core/maintenance/__init__.py create mode 100644 ontology_platform/ont_platform/core/maintenance/service.py create mode 100644 ontology_platform/ont_platform/core/projection/rdf_to_neo4j.py create mode 100644 ontology_platform/ont_platform/core/review/__init__.py create mode 100644 ontology_platform/ont_platform/core/review/promotion.py create mode 100644 ontology_platform/ont_platform/core/review/review_service.py create mode 100644 ontology_platform/ont_platform/storage/candidate_repository.py create mode 100644 ontology_platform/ont_platform/storage/dedup_cache.py create mode 100644 ontology_platform/tests/fixtures/korean/blog_naver.html create mode 100644 ontology_platform/tests/fixtures/korean/news_yonhap.html create mode 100644 ontology_platform/tests/fixtures/korean/shop_coupang.html create mode 100644 ontology_platform/tests/integration/test_phase3_crawl_jobs.py create mode 100644 ontology_platform/tests/integration/test_phase6_maintenance_api.py create mode 100644 ontology_platform/tests/integration/test_review_api.py create mode 100644 ontology_platform/tests/integration/test_url_ingest.py create mode 100644 ontology_platform/tests/unit/test_candidate_repository.py create mode 100644 ontology_platform/tests/unit/test_content_unit.py create mode 100644 ontology_platform/tests/unit/test_dedup_cache.py create mode 100644 ontology_platform/tests/unit/test_guardrails_validation_gate.py create mode 100644 ontology_platform/tests/unit/test_phase5_projection_graphrag.py create mode 100644 ontology_platform/tests/unit/test_phase6_maintenance_loop.py create mode 100644 ontology_platform/tests/unit/test_review_service.py create mode 100644 ontology_platform/tests/unit/test_web_extractor.py create mode 100644 ontology_platform_research_report.md create mode 100644 restart_all_servers.bat diff --git a/.server-stderr.log b/.server-stderr.log index a1dfcb8..dcdfbbc 100644 --- a/.server-stderr.log +++ b/.server-stderr.log @@ -1,715 +1,4 @@ -INFO: Started server process [12700] +INFO: Started server process [38332] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -ERROR: Exception in ASGI application -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1936, in _exec_single_context - self.dialect.do_executemany( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 949, in do_executemany - cursor.executemany(statement, parameters) -sqlite3.OperationalError: database is locked - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 421, in run_asgi - result = await app( # type: ignore[func-returns-value] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 56, in __call__ - return await self.app(scope, receive, send) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\applications.py", line 1159, in __call__ - await super().__call__(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\applications.py", line 90, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 186, in __call__ - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__ - await self.app(scope, receive, _send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__ - await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__ - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 660, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 680, in app - await route.handle(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 276, in handle - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 134, in app - await wrap_app_handling_exceptions(app, request)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 120, in app - response = await f(request) - ^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 674, in app - raw_response = await run_endpoint_function( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 330, in run_endpoint_function - return await run_in_threadpool(dependant.call, **values) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\concurrency.py", line 32, in run_in_threadpool - return await anyio.to_thread.run_sync(func) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\to_thread.py", line 63, in run_sync - return await get_async_backend().run_sync_in_worker_thread( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 2518, in run_sync_in_worker_thread - return await future - ^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 1002, in run - result = context.run(func, *args) - ^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\api\routes.py", line 432, in knowledge_gaps - with session_scope(database_url) as session: - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\contextlib.py", line 144, in __exit__ - next(self.gen) - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\database\session.py", line 46, in session_scope - session.commit() - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 2030, in commit - trans.commit(_to_root=True) - File "", line 2, in commit - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\state_changes.py", line 137, in _go - ret_value = fn(self, *arg, **kw) - ^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 1311, in commit - self._prepare_impl() - File "", line 2, in _prepare_impl - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\state_changes.py", line 137, in _go - ret_value = fn(self, *arg, **kw) - ^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 1286, in _prepare_impl - self.session.flush() - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4331, in flush - self._flush(objects) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4466, in _flush - with util.safe_reraise(): - ^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\util\langhelpers.py", line 121, in __exit__ - raise exc_value.with_traceback(exc_tb) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4427, in _flush - flush_context.execute() - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\unitofwork.py", line 466, in execute - rec.execute(self) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\unitofwork.py", line 642, in execute - util.preloaded.orm_persistence.save_obj( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\persistence.py", line 85, in save_obj - _emit_update_statements( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\persistence.py", line 912, in _emit_update_statements - c = connection.execute( - ^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1419, in execute - return meth( - ^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\sql\elements.py", line 527, in _execute_on_connection - return connection._execute_clauseelement( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1641, in _execute_clauseelement - ret = self._execute_context( - ^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1846, in _execute_context - return self._exec_single_context( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1986, in _exec_single_context - self._handle_dbapi_exception( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 2363, in _handle_dbapi_exception - raise sqlalchemy_exception.with_traceback(exc_info[2]) from e - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1936, in _exec_single_context - self.dialect.do_executemany( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 949, in do_executemany - cursor.executemany(statement, parameters) -sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked -[SQL: UPDATE knowledge_gaps SET updated_at=? WHERE knowledge_gaps.id = ?] -[parameters: [('2026-05-13 03:58:35.233275', 1), ('2026-05-13 03:58:35.234725', 2), ('2026-05-13 03:58:35.238062', 3), ('2026-05-13 03:58:35.239065', 4), ('2026-05-13 03:58:35.240068', 5), ('2026-05-13 03:58:35.240068', 6), ('2026-05-13 03:58:35.241077', 7), ('2026-05-13 03:58:35.245134', 8) ... displaying 10 of 32 total bound parameter sets ... ('2026-05-13 03:58:35.287923', 31), ('2026-05-13 03:58:35.287923', 32)]] -(Background on this error at: https://sqlalche.me/e/20/e3q8) -ERROR: Exception in ASGI application -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1936, in _exec_single_context - self.dialect.do_executemany( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 949, in do_executemany - cursor.executemany(statement, parameters) -sqlite3.OperationalError: database is locked - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 421, in run_asgi - result = await app( # type: ignore[func-returns-value] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 56, in __call__ - return await self.app(scope, receive, send) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\applications.py", line 1159, in __call__ - await super().__call__(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\applications.py", line 90, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 186, in __call__ - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__ - await self.app(scope, receive, _send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__ - await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__ - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 660, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 680, in app - await route.handle(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 276, in handle - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 134, in app - await wrap_app_handling_exceptions(app, request)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 120, in app - response = await f(request) - ^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 674, in app - raw_response = await run_endpoint_function( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 330, in run_endpoint_function - return await run_in_threadpool(dependant.call, **values) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\concurrency.py", line 32, in run_in_threadpool - return await anyio.to_thread.run_sync(func) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\to_thread.py", line 63, in run_sync - return await get_async_backend().run_sync_in_worker_thread( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 2518, in run_sync_in_worker_thread - return await future - ^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 1002, in run - result = context.run(func, *args) - ^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\api\routes.py", line 434, in knowledge_gaps - return KnowledgeGapDetector(session).list_open(project.id, max(min(limit, 300), 1)) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 27, in list_open - self.detect(project_id, limit=limit) - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 22, in detect - gaps.extend(self._underconnected_entity_gaps(project_id)) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 119, in _underconnected_entity_gaps - self._upsert_gap( - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 188, in _upsert_gap - self.session.flush() - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4331, in flush - self._flush(objects) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4466, in _flush - with util.safe_reraise(): - ^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\util\langhelpers.py", line 121, in __exit__ - raise exc_value.with_traceback(exc_tb) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4427, in _flush - flush_context.execute() - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\unitofwork.py", line 466, in execute - rec.execute(self) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\unitofwork.py", line 642, in execute - util.preloaded.orm_persistence.save_obj( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\persistence.py", line 85, in save_obj - _emit_update_statements( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\persistence.py", line 912, in _emit_update_statements - c = connection.execute( - ^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1419, in execute - return meth( - ^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\sql\elements.py", line 527, in _execute_on_connection - return connection._execute_clauseelement( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1641, in _execute_clauseelement - ret = self._execute_context( - ^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1846, in _execute_context - return self._exec_single_context( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1986, in _exec_single_context - self._handle_dbapi_exception( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 2363, in _handle_dbapi_exception - raise sqlalchemy_exception.with_traceback(exc_info[2]) from e - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1936, in _exec_single_context - self.dialect.do_executemany( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 949, in do_executemany - cursor.executemany(statement, parameters) -sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked -[SQL: UPDATE knowledge_gaps SET updated_at=? WHERE knowledge_gaps.id = ?] -[parameters: [('2026-05-13 03:59:40.992293', 1), ('2026-05-13 03:59:40.992293', 2), ('2026-05-13 03:59:40.992293', 4), ('2026-05-13 03:59:40.993551', 5), ('2026-05-13 03:59:40.994556', 6), ('2026-05-13 03:59:40.995558', 7), ('2026-05-13 03:59:40.995558', 8), ('2026-05-13 03:59:40.997570', 9) ... displaying 10 of 30 total bound parameter sets ... ('2026-05-13 03:59:41.015370', 31), ('2026-05-13 03:59:41.015370', 32)]] -(Background on this error at: https://sqlalche.me/e/20/e3q8) -C:\Users\lasta\MyProject\AI\crawler_platform\app\core\crawler\site_crawler.py:409: SAWarning: Identity map already had an identity for (, (1,), None), replacing it with newly flushed object. Are there load operations occurring inside of an event handler within the flush? - self.repository.session.flush() -ERROR: Exception in ASGI application -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1967, in _exec_single_context - self.dialect.do_execute( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 952, in do_execute - cursor.execute(statement, parameters) -sqlite3.OperationalError: database is locked - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 421, in run_asgi - result = await app( # type: ignore[func-returns-value] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 56, in __call__ - return await self.app(scope, receive, send) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\applications.py", line 1159, in __call__ - await super().__call__(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\applications.py", line 90, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 186, in __call__ - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__ - await self.app(scope, receive, _send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__ - await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__ - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 660, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 680, in app - await route.handle(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 276, in handle - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 134, in app - await wrap_app_handling_exceptions(app, request)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 120, in app - response = await f(request) - ^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 674, in app - raw_response = await run_endpoint_function( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 330, in run_endpoint_function - return await run_in_threadpool(dependant.call, **values) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\concurrency.py", line 32, in run_in_threadpool - return await anyio.to_thread.run_sync(func) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\to_thread.py", line 63, in run_sync - return await get_async_backend().run_sync_in_worker_thread( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 2518, in run_sync_in_worker_thread - return await future - ^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 1002, in run - result = context.run(func, *args) - ^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\api\routes.py", line 327, in reset_project - deleted = repo.reset_project_runtime_data(project.id) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\database\repository.py", line 113, in reset_project_runtime_data - .delete(synchronize_session=False) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\query.py", line 3217, in delete - self.session.execute( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 2351, in execute - return self._execute_internal( - ^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 2249, in _execute_internal - result: Result[Any] = compile_state_cls.orm_execute_statement( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\bulk_persistence.py", line 2033, in orm_execute_statement - return super().orm_execute_statement( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\context.py", line 306, in orm_execute_statement - result = conn.execute( - ^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1419, in execute - return meth( - ^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\sql\elements.py", line 527, in _execute_on_connection - return connection._execute_clauseelement( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1641, in _execute_clauseelement - ret = self._execute_context( - ^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1846, in _execute_context - return self._exec_single_context( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1986, in _exec_single_context - self._handle_dbapi_exception( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 2363, in _handle_dbapi_exception - raise sqlalchemy_exception.with_traceback(exc_info[2]) from e - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1967, in _exec_single_context - self.dialect.do_execute( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 952, in do_execute - cursor.execute(statement, parameters) -sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked -[SQL: DELETE FROM feedback_logs WHERE feedback_logs.project_id = ?] -[parameters: (1,)] -(Background on this error at: https://sqlalche.me/e/20/e3q8) -ERROR: Exception in ASGI application -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1967, in _exec_single_context - self.dialect.do_execute( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 952, in do_execute - cursor.execute(statement, parameters) -sqlite3.OperationalError: database is locked - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 421, in run_asgi - result = await app( # type: ignore[func-returns-value] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 56, in __call__ - return await self.app(scope, receive, send) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\applications.py", line 1159, in __call__ - await super().__call__(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\applications.py", line 90, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 186, in __call__ - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__ - await self.app(scope, receive, _send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__ - await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__ - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 660, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 680, in app - await route.handle(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 276, in handle - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 134, in app - await wrap_app_handling_exceptions(app, request)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 120, in app - response = await f(request) - ^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 674, in app - raw_response = await run_endpoint_function( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 330, in run_endpoint_function - return await run_in_threadpool(dependant.call, **values) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\concurrency.py", line 32, in run_in_threadpool - return await anyio.to_thread.run_sync(func) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\to_thread.py", line 63, in run_sync - return await get_async_backend().run_sync_in_worker_thread( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 2518, in run_sync_in_worker_thread - return await future - ^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 1002, in run - result = context.run(func, *args) - ^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\api\routes.py", line 327, in reset_project - deleted = repo.reset_project_runtime_data(project.id) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\database\repository.py", line 113, in reset_project_runtime_data - .delete(synchronize_session=False) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\query.py", line 3217, in delete - self.session.execute( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 2351, in execute - return self._execute_internal( - ^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 2249, in _execute_internal - result: Result[Any] = compile_state_cls.orm_execute_statement( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\bulk_persistence.py", line 2033, in orm_execute_statement - return super().orm_execute_statement( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\context.py", line 306, in orm_execute_statement - result = conn.execute( - ^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1419, in execute - return meth( - ^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\sql\elements.py", line 527, in _execute_on_connection - return connection._execute_clauseelement( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1641, in _execute_clauseelement - ret = self._execute_context( - ^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1846, in _execute_context - return self._exec_single_context( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1986, in _exec_single_context - self._handle_dbapi_exception( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 2363, in _handle_dbapi_exception - raise sqlalchemy_exception.with_traceback(exc_info[2]) from e - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1967, in _exec_single_context - self.dialect.do_execute( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 952, in do_execute - cursor.execute(statement, parameters) -sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked -[SQL: DELETE FROM feedback_logs WHERE feedback_logs.project_id = ?] -[parameters: (1,)] -(Background on this error at: https://sqlalche.me/e/20/e3q8) -ERROR: Exception in ASGI application -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1936, in _exec_single_context - self.dialect.do_executemany( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 949, in do_executemany - cursor.executemany(statement, parameters) -sqlite3.OperationalError: database is locked - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 421, in run_asgi - result = await app( # type: ignore[func-returns-value] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 56, in __call__ - return await self.app(scope, receive, send) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\applications.py", line 1159, in __call__ - await super().__call__(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\applications.py", line 90, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 186, in __call__ - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__ - await self.app(scope, receive, _send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__ - await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__ - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 660, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 680, in app - await route.handle(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 276, in handle - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 134, in app - await wrap_app_handling_exceptions(app, request)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 120, in app - response = await f(request) - ^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 674, in app - raw_response = await run_endpoint_function( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 330, in run_endpoint_function - return await run_in_threadpool(dependant.call, **values) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\concurrency.py", line 32, in run_in_threadpool - return await anyio.to_thread.run_sync(func) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\to_thread.py", line 63, in run_sync - return await get_async_backend().run_sync_in_worker_thread( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 2518, in run_sync_in_worker_thread - return await future - ^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 1002, in run - result = context.run(func, *args) - ^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\api\routes.py", line 434, in knowledge_gaps - return KnowledgeGapDetector(session).list_open(project.id, max(min(limit, 300), 1)) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 27, in list_open - self.detect(project_id, limit=limit) - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 22, in detect - gaps.extend(self._underconnected_entity_gaps(project_id)) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 119, in _underconnected_entity_gaps - self._upsert_gap( - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 188, in _upsert_gap - self.session.flush() - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4331, in flush - self._flush(objects) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4466, in _flush - with util.safe_reraise(): - ^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\util\langhelpers.py", line 121, in __exit__ - raise exc_value.with_traceback(exc_tb) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4427, in _flush - flush_context.execute() - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\unitofwork.py", line 466, in execute - rec.execute(self) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\unitofwork.py", line 642, in execute - util.preloaded.orm_persistence.save_obj( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\persistence.py", line 85, in save_obj - _emit_update_statements( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\persistence.py", line 912, in _emit_update_statements - c = connection.execute( - ^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1419, in execute - return meth( - ^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\sql\elements.py", line 527, in _execute_on_connection - return connection._execute_clauseelement( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1641, in _execute_clauseelement - ret = self._execute_context( - ^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1846, in _execute_context - return self._exec_single_context( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1986, in _exec_single_context - self._handle_dbapi_exception( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 2363, in _handle_dbapi_exception - raise sqlalchemy_exception.with_traceback(exc_info[2]) from e - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1936, in _exec_single_context - self.dialect.do_executemany( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 949, in do_executemany - cursor.executemany(statement, parameters) -sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked -[SQL: UPDATE knowledge_gaps SET updated_at=? WHERE knowledge_gaps.id = ?] -[parameters: [('2026-05-13 04:00:37.529636', 2), ('2026-05-13 04:00:37.530642', 4), ('2026-05-13 04:00:37.530642', 5), ('2026-05-13 04:00:37.530642', 6), ('2026-05-13 04:00:37.530642', 7), ('2026-05-13 04:00:37.530642', 8), ('2026-05-13 04:00:37.531648', 9), ('2026-05-13 04:00:37.531648', 10) ... displaying 10 of 26 total bound parameter sets ... ('2026-05-13 04:00:37.535699', 31), ('2026-05-13 04:00:37.535699', 32)]] -(Background on this error at: https://sqlalche.me/e/20/e3q8) -ERROR: Exception in ASGI application -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1936, in _exec_single_context - self.dialect.do_executemany( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 949, in do_executemany - cursor.executemany(statement, parameters) -sqlite3.OperationalError: database is locked - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 421, in run_asgi - result = await app( # type: ignore[func-returns-value] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 56, in __call__ - return await self.app(scope, receive, send) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\applications.py", line 1159, in __call__ - await super().__call__(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\applications.py", line 90, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 186, in __call__ - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__ - await self.app(scope, receive, _send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__ - await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__ - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 660, in __call__ - await self.middleware_stack(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 680, in app - await route.handle(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\routing.py", line 276, in handle - await self.app(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 134, in app - await wrap_app_handling_exceptions(app, request)(scope, receive, send) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app - raise exc - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app - await app(scope, receive, sender) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 120, in app - response = await f(request) - ^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 674, in app - raw_response = await run_endpoint_function( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\fastapi\routing.py", line 330, in run_endpoint_function - return await run_in_threadpool(dependant.call, **values) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\starlette\concurrency.py", line 32, in run_in_threadpool - return await anyio.to_thread.run_sync(func) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\to_thread.py", line 63, in run_sync - return await get_async_backend().run_sync_in_worker_thread( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 2518, in run_sync_in_worker_thread - return await future - ^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\anyio\_backends\_asyncio.py", line 1002, in run - result = context.run(func, *args) - ^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\api\routes.py", line 434, in knowledge_gaps - return KnowledgeGapDetector(session).list_open(project.id, max(min(limit, 300), 1)) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 27, in list_open - self.detect(project_id, limit=limit) - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 22, in detect - gaps.extend(self._underconnected_entity_gaps(project_id)) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 119, in _underconnected_entity_gaps - self._upsert_gap( - File "C:\Users\lasta\MyProject\AI\crawler_platform\app\core\ontology\gap_detector.py", line 188, in _upsert_gap - self.session.flush() - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4331, in flush - self._flush(objects) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4466, in _flush - with util.safe_reraise(): - ^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\util\langhelpers.py", line 121, in __exit__ - raise exc_value.with_traceback(exc_tb) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\session.py", line 4427, in _flush - flush_context.execute() - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\unitofwork.py", line 466, in execute - rec.execute(self) - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\unitofwork.py", line 642, in execute - util.preloaded.orm_persistence.save_obj( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\persistence.py", line 85, in save_obj - _emit_update_statements( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\orm\persistence.py", line 912, in _emit_update_statements - c = connection.execute( - ^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1419, in execute - return meth( - ^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\sql\elements.py", line 527, in _execute_on_connection - return connection._execute_clauseelement( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1641, in _execute_clauseelement - ret = self._execute_context( - ^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1846, in _execute_context - return self._exec_single_context( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1986, in _exec_single_context - self._handle_dbapi_exception( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 2363, in _handle_dbapi_exception - raise sqlalchemy_exception.with_traceback(exc_info[2]) from e - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\base.py", line 1936, in _exec_single_context - self.dialect.do_executemany( - File "C:\Users\lasta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\Lib\site-packages\sqlalchemy\engine\default.py", line 949, in do_executemany - cursor.executemany(statement, parameters) -sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked -[SQL: UPDATE knowledge_gaps SET updated_at=? WHERE knowledge_gaps.id = ?] -[parameters: [('2026-05-13 04:00:45.068406', 2), ('2026-05-13 04:00:45.069451', 4), ('2026-05-13 04:00:45.069451', 5), ('2026-05-13 04:00:45.069451', 6), ('2026-05-13 04:00:45.069451', 7), ('2026-05-13 04:00:45.069451', 8), ('2026-05-13 04:00:45.070461', 9), ('2026-05-13 04:00:45.070461', 10) ... displaying 10 of 26 total bound parameter sets ... ('2026-05-13 04:00:45.074511', 31), ('2026-05-13 04:00:45.074511', 32)]] -(Background on this error at: https://sqlalche.me/e/20/e3q8) diff --git a/.server-stdout.log b/.server-stdout.log index 94a08cc..c9724b1 100644 --- a/.server-stdout.log +++ b/.server-stdout.log @@ -1,227 +1,60 @@ -INFO: 127.0.0.1:55654 - "GET /health HTTP/1.1" 200 OK -INFO: 127.0.0.1:55656 - "GET /health HTTP/1.1" 200 OK -INFO: 127.0.0.1:55658 - "POST /crawl HTTP/1.1" 200 OK -INFO: 127.0.0.1:60702 - "GET /health HTTP/1.1" 200 OK -INFO: 127.0.0.1:51909 - "GET / HTTP/1.1" 200 OK -INFO: 127.0.0.1:63667 - "GET /static/assets/index-BkBg_FAU.css HTTP/1.1" 200 OK -INFO: 127.0.0.1:51909 - "GET /static/assets/index-BsoXaTIL.js HTTP/1.1" 200 OK -INFO: 127.0.0.1:60212 - "GET /favicon.ico HTTP/1.1" 404 Not Found -INFO: 127.0.0.1:63667 - "GET /projects HTTP/1.1" 200 OK -INFO: 127.0.0.1:63667 - "GET /projects/perfume_subscription HTTP/1.1" 200 OK -INFO: 127.0.0.1:63667 - "GET /ontology/perfume HTTP/1.1" 200 OK -INFO: 127.0.0.1:52091 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:63120 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:52091 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:63667 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:60212 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:52091 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:52091 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:63120 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:63667 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:52091 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:63120 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60212 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:58507 - "GET /projects/perfume_subscription/knowledge-gaps?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:51909 - "POST /extractors/models HTTP/1.1" 200 OK -INFO: 127.0.0.1:51909 - "POST /projects/reset HTTP/1.1" 200 OK -INFO: 127.0.0.1:51909 - "GET /projects HTTP/1.1" 200 OK -INFO: 127.0.0.1:51909 - "GET /projects/perfume_subscription HTTP/1.1" 200 OK -INFO: 127.0.0.1:51909 - "GET /ontology/perfume HTTP/1.1" 200 OK -INFO: 127.0.0.1:60212 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:52091 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:63667 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60212 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:51909 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:52091 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:63667 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60212 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:51909 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:52091 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:63667 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:63120 - "GET /projects/perfume_subscription/knowledge-gaps?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:58507 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:59175 - "POST /crawl-site HTTP/1.1" 200 OK -INFO: 127.0.0.1:59175 - "GET /crawl-site/jobs/1 HTTP/1.1" 200 OK -INFO: 127.0.0.1:59175 - "GET /crawl-site/jobs/1 HTTP/1.1" 200 OK -INFO: 127.0.0.1:59175 - "GET /crawl-site/jobs/1 HTTP/1.1" 200 OK -INFO: 127.0.0.1:59175 - "GET /crawl-site/jobs/1 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60573 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60167 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:54345 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:59175 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:53330 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:54345 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60573 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:59175 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60167 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:53330 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:54345 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:60573 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:62512 - "GET /projects/perfume_subscription/knowledge-gaps?limit=50 HTTP/1.1" 500 Internal Server Error -INFO: 127.0.0.1:54311 - "GET /crawl-site/jobs/1 HTTP/1.1" 200 OK -INFO: 127.0.0.1:61649 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:65400 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:64587 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:61649 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:54311 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:58516 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:65400 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:54311 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:64587 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:58516 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:65400 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:61649 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:54492 - "GET /health HTTP/1.1" 200 OK -INFO: 127.0.0.1:54231 - "GET / HTTP/1.1" 200 OK -INFO: 127.0.0.1:51157 - "GET /projects HTTP/1.1" 200 OK -INFO: 127.0.0.1:51157 - "GET /projects/perfume_subscription HTTP/1.1" 200 OK -INFO: 127.0.0.1:51157 - "GET /ontology/perfume HTTP/1.1" 200 OK -INFO: 127.0.0.1:54317 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:64933 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:62007 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:54317 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:51157 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:62007 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:64933 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:54317 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:62007 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:51157 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:54317 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:64933 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:54231 - "POST /extractors/models HTTP/1.1" 200 OK -INFO: 127.0.0.1:64937 - "GET /health HTTP/1.1" 200 OK -INFO: 127.0.0.1:51597 - "GET /projects HTTP/1.1" 200 OK -INFO: 127.0.0.1:51597 - "GET /projects/perfume_subscription HTTP/1.1" 200 OK -INFO: 127.0.0.1:51597 - "GET /ontology/perfume HTTP/1.1" 200 OK -INFO: 127.0.0.1:55174 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:56473 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55174 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:64026 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:56473 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:51597 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:64026 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55174 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:51597 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:56473 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:64026 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:55174 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:57269 - "POST /extractors/models HTTP/1.1" 200 OK -INFO: 127.0.0.1:61245 - "GET / HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /ontology/perfume HTTP/1.1" 200 OK -INFO: 127.0.0.1:51571 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:51571 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:51571 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:51571 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:51571 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:51571 - "GET / HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /ontology/perfume HTTP/1.1" 200 OK -INFO: 127.0.0.1:57549 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:57549 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:57549 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:57549 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:57549 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:57549 - "GET / HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /ontology/perfume HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60418 - "GET / HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /ontology/perfume HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "GET / HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /ontology/perfume HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:55778 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55778 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:55778 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55978 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "POST /extractors/models HTTP/1.1" 200 OK -INFO: 127.0.0.1:55246 - "GET /projects/perfume_subscription/knowledge-gaps?limit=50 HTTP/1.1" 500 Internal Server Error -INFO: 127.0.0.1:54063 - "GET /crawl-site/jobs/1 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60064 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:54063 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:53121 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:54063 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60064 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:53121 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:54063 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:60064 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:53121 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60064 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:53121 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:54063 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "POST /projects/reset HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "GET /projects HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "GET /projects/perfume_subscription HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "GET /ontology/perfume HTTP/1.1" 200 OK -INFO: 127.0.0.1:60812 - "GET /projects/perfume_subscription/ontology/proposals?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:53523 - "GET /projects/perfume_subscription/ontology/triples?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55554 - "GET /projects/perfume_subscription/ontology/registry HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "GET /projects/perfume_subscription/pipeline HTTP/1.1" 200 OK -INFO: 127.0.0.1:53523 - "GET /projects/perfume_subscription/claims?limit=200&include_candidates=true HTTP/1.1" 200 OK -INFO: 127.0.0.1:60812 - "GET /projects/perfume_subscription/entities?limit=100 HTTP/1.1" 200 OK -INFO: 127.0.0.1:60812 - "GET /projects/perfume_subscription/graph/query?kind=trend_summary HTTP/1.1" 200 OK -INFO: 127.0.0.1:53523 - "GET /projects/perfume_subscription/research/sessions?limit=20 HTTP/1.1" 200 OK -INFO: 127.0.0.1:55554 - "GET /projects/perfume_subscription/claims?limit=200&status=rejected HTTP/1.1" 200 OK -INFO: 127.0.0.1:60812 - "GET /projects/perfume_subscription/recommendation-tags HTTP/1.1" 200 OK -INFO: 127.0.0.1:53523 - "GET /projects/perfume_subscription/graph/neighborhood?limit=300 HTTP/1.1" 200 OK -INFO: 127.0.0.1:61169 - "GET /projects/perfume_subscription/extraction-logs?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:54030 - "GET /projects/perfume_subscription/knowledge-gaps?limit=50 HTTP/1.1" 200 OK -INFO: 127.0.0.1:54030 - "GET /crawl-site/jobs/1 HTTP/1.1" 404 Not Found -INFO: 127.0.0.1:57352 - "GET /projects/perfume_subscription/knowledge-gaps?limit=50 HTTP/1.1" 500 Internal Server Error -INFO: 127.0.0.1:54030 - "GET /crawl-site/jobs/1 HTTP/1.1" 404 Not Found -INFO: 127.0.0.1:54030 - "GET /crawl-site/jobs/1 HTTP/1.1" 404 Not Found -INFO: 127.0.0.1:54868 - "GET /projects/perfume_subscription/knowledge-gaps?limit=50 HTTP/1.1" 500 Internal Server Error -INFO: 127.0.0.1:54030 - "GET /crawl-site/jobs/1 HTTP/1.1" 404 Not Found -INFO: 127.0.0.1:54030 - "GET /crawl-site/jobs/1 HTTP/1.1" 404 Not Found +INFO: 127.0.0.1:51497 - "GET /static/crawl/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:51498 - "GET /static/assets/index-DmWQTzXA.js HTTP/1.1" 200 OK +INFO: 127.0.0.1:51500 - "GET /static/locales/ko/common.json HTTP/1.1" 200 OK +INFO: 127.0.0.1:55124 - "GET /static/crawl/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:55124 - "GET /static/assets/index-DmWQTzXA.js HTTP/1.1" 200 OK +INFO: 127.0.0.1:52395 - "GET /static/assets/index-uHGS9kYH.css HTTP/1.1" 200 OK +INFO: 127.0.0.1:55124 - "GET /static/locales/ko/common.json HTTP/1.1" 200 OK +INFO: 127.0.0.1:52395 - "GET /static/locales/ko/pages.json HTTP/1.1" 200 OK +INFO: 127.0.0.1:50896 - "GET /static/locales/en/common.json HTTP/1.1" 200 OK +INFO: 127.0.0.1:60772 - "GET /static/locales/en/pages.json HTTP/1.1" 200 OK +INFO: 127.0.0.1:50896 - "GET /projects HTTP/1.1" 200 OK +INFO: 127.0.0.1:60772 - "GET /projects/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:57787 - "GET /static/crawl/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:55691 - "GET /projects HTTP/1.1" 200 OK +INFO: 127.0.0.1:57787 - "GET /projects/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:51064 - "GET /projects HTTP/1.1" 200 OK +INFO: 127.0.0.1:57726 - "GET /projects/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/crawl/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/assets/index-DmWQTzXA.js HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/locales/ko/pages.json HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /static/locales/en/pages.json HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /projects HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /projects/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /static/crawl/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /static/assets/index-DmWQTzXA.js HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/assets/index-uHGS9kYH.css HTTP/1.1" 200 OK +INFO: 127.0.0.1:53782 - "GET /favicon.ico HTTP/1.1" 404 Not Found +INFO: 127.0.0.1:62676 - "GET /projects HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /projects/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /static/crawl/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/assets/index-uHGS9kYH.css HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /static/assets/index-DmWQTzXA.js HTTP/1.1" 200 OK +INFO: 127.0.0.1:53782 - "GET /favicon.ico HTTP/1.1" 404 Not Found +INFO: 127.0.0.1:51077 - "GET /projects/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /projects HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/crawl/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/assets/index-DmWQTzXA.js HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /static/assets/index-uHGS9kYH.css HTTP/1.1" 200 OK +INFO: 127.0.0.1:53782 - "GET /favicon.ico HTTP/1.1" 404 Not Found +INFO: 127.0.0.1:51077 - "GET /projects HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /projects/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/crawl/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /static/assets/index-uHGS9kYH.css HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/assets/index-DmWQTzXA.js HTTP/1.1" 200 OK +INFO: 127.0.0.1:53782 - "GET /favicon.ico HTTP/1.1" 404 Not Found +INFO: 127.0.0.1:51077 - "GET /projects HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /projects/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/crawl/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/assets/index-DmWQTzXA.js HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /static/assets/index-uHGS9kYH.css HTTP/1.1" 200 OK +INFO: 127.0.0.1:53782 - "GET /favicon.ico HTTP/1.1" 404 Not Found +INFO: 127.0.0.1:51077 - "GET /projects HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /projects/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/crawl/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:51077 - "GET /static/assets/index-uHGS9kYH.css HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /static/assets/index-DmWQTzXA.js HTTP/1.1" 200 OK +INFO: 127.0.0.1:53782 - "GET /favicon.ico HTTP/1.1" 404 Not Found +INFO: 127.0.0.1:51077 - "GET /projects HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /projects/PerfumeSubscribe_new HTTP/1.1" 200 OK +INFO: 127.0.0.1:62676 - "GET /domains HTTP/1.1" 200 OK diff --git a/README.md b/README.md index 72b9228..d2a96d5 100644 --- a/README.md +++ b/README.md @@ -1,234 +1,81 @@ -# Ontology Platform +# Ontology Platform -온톨로지 플랫폼은 웹에서 구조화된 지식(엔티티/관계)을 자동 추출, 검증, 저장하는 고속 시스템입니다. +본 프로젝트는 여러 검증된 오픈소스 기반 엔진들을 조합하여 구축된 온톨로지 구축 플랫폼이다. -**Phase 0-4** 전체 구현 완료 | 추출(10초) → 검증(<100ms) → 그래프 저장 → 벡터 검색 +목표는 기존 엔진을 폐기하거나 새로 만드는 것이 아니라: -## 🚀 빠른 시작 +- 기존 엔진을 최대한 유지하면서 +- 부족한 플랫폼 기능을 확장하고 +- 온톨로지 구축 워크플로우를 강화하며 +- 상용화 가능한 구조로 발전시키는 것이다. -### 1. 설치 - -```bash -# 기본 설치 (Phase 0-1: 추출) -pip install fastapi uvicorn pydantic trafilatura httpx - -# Phase 2 추가 (동적 페이지) -pip install crawl4ai - -# Phase 4 추가 (Neo4j) -pip install neo4j sentence-transformers -``` - -### 2. Phase 0-1만 사용 (가장 간단) - -```bash -# API 서버 시작 -python -m uvicorn ontology_platform.ont_platform.api.phase0_app:app --reload - -# URL에서 추출 -curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://example.com" -``` - -### 3. Phase 4 (그래프 검색) 포함 - -```bash -# Neo4j 시작 -docker-compose -f docker-compose.neo4j.yml up -d - -# API 서버 시작 -python -m uvicorn ontology_platform.ont_platform.api.phase0_app:app --reload - -# 추출 → 수집 → 검색 -curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://example.com" -curl -X POST "http://localhost:8000/api/v1/search/ingest" -d '{"entities": [...], "relations": [...]}' -curl "http://localhost:8000/api/v1/search/vector?query=machine+learning" -``` - -## 📋 Phase별 기능 - -| Phase | 기능 | 시간 | 상태 | -|-------|------|------|------| -| 0-1 | HTML 추출 (Trafilatura) | 10-15초 | ✅ | -| 2 | 동적 페이지 (Crawl4AI) | 20-30초 | ✅ | -| 3A | 경량 검증 (Pydantic) | <100ms | ✅ | -| 3B | SPARQL 검증 | <500ms | ✅ | -| 4 | Neo4j + 벡터 검색 | 50-200ms | ✅ | - -## 🎯 사용 예시 - -### 예시 1: 기본 추출 (10초) - -```bash -curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://wikipedia.org/wiki/Python" -``` - -응답: -```json -{ - "url": "https://wikipedia.org/wiki/Python", - "title": "Python - Wikipedia", - "entities": [ - { - "id": "E_1", - "label": "Python", - "type": "ProgrammingLanguage", - "confidence": 0.95 - } - ], - "relations": [...], - "extraction_time_sec": 9.5, - "validation_passed": true -} -``` - -### 예시 2: 동적 페이지 (25초) - -```bash -curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://app.example.com&profile=dynamic_page" -``` - -### 예시 3: 그래프 수집 + 검색 - -```bash -# 1. 추출 -RESULT=$(curl -s -X POST "http://localhost:8000/api/v1/extract/url?url=https://example.com") - -# 2. Neo4j에 수집 -curl -X POST "http://localhost:8000/api/v1/search/ingest" \ - -H "Content-Type: application/json" \ - -d "{\"entities\": $(echo $RESULT | jq '.entities'), \"relations\": $(echo $RESULT | jq '.relations')}" - -# 3. 벡터 검색 -curl "http://localhost:8000/api/v1/search/vector?query=programming&limit=10" - -# 4. 그래프 통계 -curl "http://localhost:8000/api/v1/search/stats" - -# 5. 엔티티 이웃 -curl "http://localhost:8000/api/v1/search/entity/E_1?depth=1" -``` - -## 🔧 설정 - -### Phase 선택 (validators.py) - -```python -# 경량 검증 (기본) -guard = OntologyGuard(validator_type="lightweight") - -# SPARQL 검증 -guard = OntologyGuard(validator_type="ontocast") -``` - -### Neo4j 연결 (neo4j_adapter.py) - -```python -# 기본값 -config = Neo4jConfig() # localhost:7687 - -# 커스텀 -config = Neo4jConfig( - uri="bolt://custom-host:7687", - username="user", - password="pass", - database="mydb" -) -adapter = Neo4jAdapter(config=config) -``` - -## 📊 API 문서 - -서버 시작 후: -- **Swagger UI**: http://localhost:8000/docs -- **ReDoc**: http://localhost:8000/redoc - -### 주요 엔드포인트 - -``` -POST /api/v1/extract/url 추출 -GET /api/v1/search/stats 통계 -POST /api/v1/search/vector 벡터 검색 -GET /api/v1/search/entity/{id} 이웃 탐색 -POST /api/v1/search/ingest 그래프 수집 -``` - -## 🧪 테스트 - -```bash -# Phase 0-1 -python test_phase0_extraction.py - -# Phase 2 -python test_phase2_crawl.py - -# Phase 3A -python test_phase3_validation.py - -# Phase 3B -python test_phase3_option_b.py - -# Phase 4 -python test_phase4_integration.py -``` - -## 📦 의존성 - -- **FastAPI**: API 프레임워크 -- **Trafilatura**: HTML 추출 -- **Crawl4AI**: 동적 크롤링 (선택) -- **Pydantic**: 데이터 검증 -- **Neo4j**: 그래프 DB (선택) -- **SentenceTransformers**: 벡터 임베딩 (선택) - -## 🐳 Docker - -```bash -# Neo4j만 -docker-compose -f docker-compose.neo4j.yml up -d - -# 전체 스택 (향후) -docker-compose up -d -``` - -## 📚 상세 문서 - -- [구현 요약](IMPLEMENTATION_SUMMARY.md) - Phase 0-4 전체 개요 -- [Phase 2](PHASE2_COMPLETION.md) - Crawl4AI 동적 크롤링 -- [Phase 3A](PHASE3_COMPLETION.md) - 경량 검증 -- [Phase 3B](PHASE3_OPTION_B.md) - SPARQL 검증 -- [Phase 4](PHASE4_COMPLETION.md) - Neo4j 그래프 + 벡터 검색 - -## 🎓 설계 원칙 - -1. **Phase-gated**: 각 Phase는 선택사항 -2. **Pluggable**: 여러 검증 방식 지원 -3. **Async**: 높은 동시성 -4. **Resilient**: 의존성 부재 시에도 동작 - -## 💡 다음 단계 - -### Phase 5: GraphRAG (선택) -- RDF ↔ Property Graph 변환 -- Entity Resolver -- Subgraph retrieval - -### Advanced Features -- Critic loop (자동 수정) -- Few-shot learning -- Zero-shot 분류 - -## 🔗 관련 링크 - -- [Neo4j 문서](https://neo4j.com/docs/) -- [SentenceTransformers](https://www.sbert.net/) -- [Trafilatura](https://trafilatura.python-engineering.com/) -- [FastAPI](https://fastapi.tiangolo.com/) - -## 📝 라이센스 - -MIT License +기존 엔진은 이미 검증된 기반 자산으로 간주한다. --- -**Version**: 0.4.0 (Phase 0-4 완료) -**Updated**: 2026-05-14 +# CORE DEVELOPMENT PRINCIPLES + +## 기존 엔진 재사용 원칙 + +현재 시스템은 다음과 같은 검증된 오픈소스 기반 구조들을 포함한다. + +- 웹 크롤링 엔진 +- HTML 정제 시스템 +- 엔티티 추출 구조 +- 클레임 생성 구조 +- 데이터 저장 구조 +- 기존 분석 파이프라인 + +따라서: + +- 기존 엔진을 임의로 교체하지 않는다. +- 없는 기능만 확장한다. +- 구조적 문제가 확인된 경우에만 수정 검토한다. +- 승인 없는 대규모 리팩토링을 금지한다. +- 전체 구조 재설계를 금지한다. + +--- + +# TASK EXECUTION MODES + +프로젝트 작업 모드는 세 가지로 구분된다. + +--- + +## 1. PHASE WORKFLOW MODE + +로드맵 기반 작업 수행 모드. +다음 명령이 포함될 때만 활성화. + +- ^(다음 )?(페이즈|PHASE|phase)( \d+(-\d+)?)? 진행$ + +이 경우에만 +PHASE_WORKFLOW.MD 를 읽고 작업을 진행한다. + +--- + +## 2. PLANNING MODE + +설계 및 roadmap 생성 모드. +다음 명령이 포함될 때만 활성화. + +- ^(페이즈|PHASE|phase)( \d+(-\d+)?)? (생성|계획)$ + +이 경우에만 +PHASE_PLANNING.md 를 읽고 작업을 진행한다. + +--- + +# PROJECT STRUCTURE + +```text +README.md + +/docs/phases + PHASE_WORKFLOW.md + PHASE_PLANNING.md + PHASE_INDEX.md + + /phase_01 + /phase_02 + /phase_03 \ No newline at end of file diff --git a/README_old.md b/README_old.md new file mode 100644 index 0000000..72b9228 --- /dev/null +++ b/README_old.md @@ -0,0 +1,234 @@ +# Ontology Platform + +온톨로지 플랫폼은 웹에서 구조화된 지식(엔티티/관계)을 자동 추출, 검증, 저장하는 고속 시스템입니다. + +**Phase 0-4** 전체 구현 완료 | 추출(10초) → 검증(<100ms) → 그래프 저장 → 벡터 검색 + +## 🚀 빠른 시작 + +### 1. 설치 + +```bash +# 기본 설치 (Phase 0-1: 추출) +pip install fastapi uvicorn pydantic trafilatura httpx + +# Phase 2 추가 (동적 페이지) +pip install crawl4ai + +# Phase 4 추가 (Neo4j) +pip install neo4j sentence-transformers +``` + +### 2. Phase 0-1만 사용 (가장 간단) + +```bash +# API 서버 시작 +python -m uvicorn ontology_platform.ont_platform.api.phase0_app:app --reload + +# URL에서 추출 +curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://example.com" +``` + +### 3. Phase 4 (그래프 검색) 포함 + +```bash +# Neo4j 시작 +docker-compose -f docker-compose.neo4j.yml up -d + +# API 서버 시작 +python -m uvicorn ontology_platform.ont_platform.api.phase0_app:app --reload + +# 추출 → 수집 → 검색 +curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://example.com" +curl -X POST "http://localhost:8000/api/v1/search/ingest" -d '{"entities": [...], "relations": [...]}' +curl "http://localhost:8000/api/v1/search/vector?query=machine+learning" +``` + +## 📋 Phase별 기능 + +| Phase | 기능 | 시간 | 상태 | +|-------|------|------|------| +| 0-1 | HTML 추출 (Trafilatura) | 10-15초 | ✅ | +| 2 | 동적 페이지 (Crawl4AI) | 20-30초 | ✅ | +| 3A | 경량 검증 (Pydantic) | <100ms | ✅ | +| 3B | SPARQL 검증 | <500ms | ✅ | +| 4 | Neo4j + 벡터 검색 | 50-200ms | ✅ | + +## 🎯 사용 예시 + +### 예시 1: 기본 추출 (10초) + +```bash +curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://wikipedia.org/wiki/Python" +``` + +응답: +```json +{ + "url": "https://wikipedia.org/wiki/Python", + "title": "Python - Wikipedia", + "entities": [ + { + "id": "E_1", + "label": "Python", + "type": "ProgrammingLanguage", + "confidence": 0.95 + } + ], + "relations": [...], + "extraction_time_sec": 9.5, + "validation_passed": true +} +``` + +### 예시 2: 동적 페이지 (25초) + +```bash +curl -X POST "http://localhost:8000/api/v1/extract/url?url=https://app.example.com&profile=dynamic_page" +``` + +### 예시 3: 그래프 수집 + 검색 + +```bash +# 1. 추출 +RESULT=$(curl -s -X POST "http://localhost:8000/api/v1/extract/url?url=https://example.com") + +# 2. Neo4j에 수집 +curl -X POST "http://localhost:8000/api/v1/search/ingest" \ + -H "Content-Type: application/json" \ + -d "{\"entities\": $(echo $RESULT | jq '.entities'), \"relations\": $(echo $RESULT | jq '.relations')}" + +# 3. 벡터 검색 +curl "http://localhost:8000/api/v1/search/vector?query=programming&limit=10" + +# 4. 그래프 통계 +curl "http://localhost:8000/api/v1/search/stats" + +# 5. 엔티티 이웃 +curl "http://localhost:8000/api/v1/search/entity/E_1?depth=1" +``` + +## 🔧 설정 + +### Phase 선택 (validators.py) + +```python +# 경량 검증 (기본) +guard = OntologyGuard(validator_type="lightweight") + +# SPARQL 검증 +guard = OntologyGuard(validator_type="ontocast") +``` + +### Neo4j 연결 (neo4j_adapter.py) + +```python +# 기본값 +config = Neo4jConfig() # localhost:7687 + +# 커스텀 +config = Neo4jConfig( + uri="bolt://custom-host:7687", + username="user", + password="pass", + database="mydb" +) +adapter = Neo4jAdapter(config=config) +``` + +## 📊 API 문서 + +서버 시작 후: +- **Swagger UI**: http://localhost:8000/docs +- **ReDoc**: http://localhost:8000/redoc + +### 주요 엔드포인트 + +``` +POST /api/v1/extract/url 추출 +GET /api/v1/search/stats 통계 +POST /api/v1/search/vector 벡터 검색 +GET /api/v1/search/entity/{id} 이웃 탐색 +POST /api/v1/search/ingest 그래프 수집 +``` + +## 🧪 테스트 + +```bash +# Phase 0-1 +python test_phase0_extraction.py + +# Phase 2 +python test_phase2_crawl.py + +# Phase 3A +python test_phase3_validation.py + +# Phase 3B +python test_phase3_option_b.py + +# Phase 4 +python test_phase4_integration.py +``` + +## 📦 의존성 + +- **FastAPI**: API 프레임워크 +- **Trafilatura**: HTML 추출 +- **Crawl4AI**: 동적 크롤링 (선택) +- **Pydantic**: 데이터 검증 +- **Neo4j**: 그래프 DB (선택) +- **SentenceTransformers**: 벡터 임베딩 (선택) + +## 🐳 Docker + +```bash +# Neo4j만 +docker-compose -f docker-compose.neo4j.yml up -d + +# 전체 스택 (향후) +docker-compose up -d +``` + +## 📚 상세 문서 + +- [구현 요약](IMPLEMENTATION_SUMMARY.md) - Phase 0-4 전체 개요 +- [Phase 2](PHASE2_COMPLETION.md) - Crawl4AI 동적 크롤링 +- [Phase 3A](PHASE3_COMPLETION.md) - 경량 검증 +- [Phase 3B](PHASE3_OPTION_B.md) - SPARQL 검증 +- [Phase 4](PHASE4_COMPLETION.md) - Neo4j 그래프 + 벡터 검색 + +## 🎓 설계 원칙 + +1. **Phase-gated**: 각 Phase는 선택사항 +2. **Pluggable**: 여러 검증 방식 지원 +3. **Async**: 높은 동시성 +4. **Resilient**: 의존성 부재 시에도 동작 + +## 💡 다음 단계 + +### Phase 5: GraphRAG (선택) +- RDF ↔ Property Graph 변환 +- Entity Resolver +- Subgraph retrieval + +### Advanced Features +- Critic loop (자동 수정) +- Few-shot learning +- Zero-shot 분류 + +## 🔗 관련 링크 + +- [Neo4j 문서](https://neo4j.com/docs/) +- [SentenceTransformers](https://www.sbert.net/) +- [Trafilatura](https://trafilatura.python-engineering.com/) +- [FastAPI](https://fastapi.tiangolo.com/) + +## 📝 라이센스 + +MIT License + +--- + +**Version**: 0.4.0 (Phase 0-4 완료) +**Updated**: 2026-05-14 diff --git a/crawler_platform.db b/crawler_platform.db index 2860ee0fdef7855f77e3c7b440ba9ed861f8b31a..d10a1bb03d9df53491c4421219931e87e5125759 100644 GIT binary patch delta 161409 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 bc^DS3OXa;h#Z^6}R)YOGEe?AZpWO9dcMqIt delta 62421 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 a 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

Dda27x#f&;KSl(wX{n$j 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 diff --git a/crawler_platform/app/api/routes.py b/crawler_platform/app/api/routes.py index faa64d8..8b09b30 100644 --- a/crawler_platform/app/api/routes.py +++ b/crawler_platform/app/api/routes.py @@ -1,9 +1,12 @@ from __future__ import annotations +import csv +import io +import json from dataclasses import asdict from typing import Any -from fastapi import BackgroundTasks, HTTPException +from fastapi import BackgroundTasks, HTTPException, Response from pydantic import BaseModel, Field from sqlalchemy import select @@ -253,6 +256,35 @@ class BulkClaimStatusRequest(BaseModel): reason: str | None = None +class SchemaEntityTypeRequest(BaseModel): + name: str + domain: str = "generic" + description: str | None = None + status: str = "active" + confidence: float = 1.0 + metadata: dict[str, Any] = Field(default_factory=dict) + + +class SchemaRelationTypeRequest(BaseModel): + name: str + domain: str = "generic" + description: str | None = None + allowed_subject_types: list[str] = Field(default_factory=list) + allowed_object_types: list[str] = Field(default_factory=list) + allowed_page_types: list[str] = Field(default_factory=list) + allowed_source_zones: list[str] = Field(default_factory=list) + semantic_constraints: dict[str, Any] = Field(default_factory=dict) + confidence_rules: dict[str, Any] = Field(default_factory=dict) + min_confidence: float | None = None + status: str = "active" + confidence: float = 1.0 + metadata: dict[str, Any] = Field(default_factory=dict) + + +class PipelineRerunRequest(BaseModel): + stage: str + + class MergeEntitiesRequest(BaseModel): project_name: str source_entity_id: int @@ -316,6 +348,16 @@ def _apply_claim_review(claim: models.Claim, status: str, reason: str | None) -> claim.last_seen_at = models.utcnow() +def turtle_id(value: Any) -> str: + text = "".join(ch if ch.isalnum() else "_" for ch in str(value or "").strip()) + text = "_".join(part for part in text.split("_") if part) + if not text: + return "value" + if text[0].isdigit(): + text = f"n_{text}" + return text[:120] + + def apply_crawl_request_overrides(config, request: CrawlRequest | DiscoverRequest) -> None: check_robots_txt = request.respect_robots_txt if check_robots_txt is None: @@ -596,6 +638,61 @@ def register_routes(app, database_url: str) -> None: project = KnowledgeRepository(session).get_project(project_name) return OntologyRegistry(session).registry_payload(project.id) + @app.post("/projects/{project_name}/schema/entity-types") + def create_schema_entity_type(project_name: str, request: SchemaEntityTypeRequest): + with session_scope(database_url) as session: + project = KnowledgeRepository(session).get_project(project_name) + row = OntologyRegistry(session).upsert_entity_type( + project.id, + name=request.name, + domain=request.domain, + description=request.description, + status=request.status, + confidence=min(max(request.confidence, 0.0), 1.0), + metadata={"origin": "manual_schema_designer", **request.metadata}, + ) + return { + "id": row.id, + "name": row.name, + "domain": row.domain, + "description": row.description, + "status": row.status, + "confidence": row.confidence, + } + + @app.post("/projects/{project_name}/schema/relation-types") + def create_schema_relation_type(project_name: str, request: SchemaRelationTypeRequest): + confidence_rules = dict(request.confidence_rules or {}) + if request.min_confidence is not None: + confidence_rules["min_confidence"] = min(max(request.min_confidence, 0.0), 1.0) + with session_scope(database_url) as session: + project = KnowledgeRepository(session).get_project(project_name) + row = OntologyRegistry(session).upsert_relation_type( + project.id, + name=request.name, + domain=request.domain, + description=request.description, + allowed_subject_types=request.allowed_subject_types, + allowed_object_types=request.allowed_object_types, + allowed_page_types=request.allowed_page_types, + allowed_source_zones=request.allowed_source_zones, + semantic_constraints=request.semantic_constraints, + confidence_rules=confidence_rules, + status=request.status, + confidence=min(max(request.confidence, 0.0), 1.0), + metadata={"origin": "manual_schema_designer", **request.metadata}, + ) + return { + "id": row.id, + "name": row.name, + "domain": row.domain, + "description": row.description, + "allowed_subject_types": row.allowed_subject_types or [], + "allowed_object_types": row.allowed_object_types or [], + "status": row.status, + "confidence": row.confidence, + } + @app.get("/projects/{project_name}/ontology/proposals") def ontology_proposals(project_name: str, limit: int = 100): with session_scope(database_url) as session: @@ -655,6 +752,127 @@ def register_routes(app, database_url: str) -> None: ) return results + @app.get("/projects/{project_name}/export") + def export_ontology( + project_name: str, + format: str = "json", + status: str = "validated_claim", + include_evidence: bool = True, + limit: int = 1000, + ): + fmt = format.strip().lower() + bounded_limit = max(min(limit, 5000), 1) + with session_scope(database_url) as session: + project = KnowledgeRepository(session).get_project(project_name) + query = ( + select(models.Claim, models.Source, models.Page, models.Entity) + .join(models.Source, models.Claim.source_id == models.Source.id) + .join(models.Page, models.Claim.page_id == models.Page.id, isouter=True) + .join(models.Entity, models.Claim.subject_entity_id == models.Entity.id) + .where(models.Claim.project_id == project.id) + ) + if status and status != "all": + normalized = _normalize_claim_status(status) or status + query = query.where(models.Claim.status == normalized) + rows = session.execute( + query.order_by(models.Claim.confidence.desc(), models.Claim.last_seen_at.desc()).limit(bounded_limit) + ).all() + items: list[dict[str, Any]] = [] + for claim, source, page, subject in rows: + object_entity = session.get(models.Entity, claim.object_entity_id) if claim.object_entity_id else None + evidence = None + if include_evidence: + evidence = session.scalar( + select(models.Evidence) + .where(models.Evidence.claim_id == claim.id) + .order_by(models.Evidence.created_at.desc()) + ) + items.append( + { + "claim_id": claim.id, + "subject": subject.name, + "subject_type": subject.entity_type, + "predicate": claim.predicate, + "object": object_entity.name if object_entity else claim.object_value, + "object_type": object_entity.entity_type if object_entity else claim.value_type, + "status": claim.status, + "confidence": claim.confidence, + "source": source.name, + "source_url": page.url if page else None, + "evidence_text": evidence.evidence_text if evidence else None, + "created_by": claim.extraction_method, + "last_seen_at": claim.last_seen_at.isoformat() if claim.last_seen_at else None, + } + ) + + filename = f"{project_name}_ontology.{fmt if fmt != 'turtle' else 'ttl'}" + if fmt == "json": + return { + "project": project_name, + "status": status, + "count": len(items), + "claims": items, + } + if fmt == "csv": + output = io.StringIO() + fieldnames = [ + "claim_id", + "subject", + "subject_type", + "predicate", + "object", + "object_type", + "status", + "confidence", + "source", + "source_url", + "evidence_text", + "created_by", + "last_seen_at", + ] + writer = csv.DictWriter(output, fieldnames=fieldnames) + writer.writeheader() + for item in items: + row = {key: json.dumps(value, ensure_ascii=False) if isinstance(value, (dict, list)) else value for key, value in item.items()} + writer.writerow(row) + return Response( + content=output.getvalue(), + media_type="text/csv; charset=utf-8", + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) + if fmt in {"ttl", "turtle"}: + lines = [ + "@prefix ont: .", + "@prefix claim: .", + "@prefix xsd: .", + "", + ] + for item in items: + sid = turtle_id(item["subject"]) + predicate = turtle_id(item["predicate"]) + obj = item["object"] + if isinstance(obj, str) and obj.strip(): + object_repr = f'ont:{turtle_id(obj)}' + else: + object_repr = json.dumps(obj, ensure_ascii=False) + lines.extend( + [ + f"ont:{sid} ont:{predicate} {object_repr} .", + f"claim:c{item['claim_id']} ont:confidence \"{item['confidence']}\"^^xsd:decimal .", + ] + ) + if item.get("source_url"): + lines.append(f"claim:c{item['claim_id']} ont:sourceUrl {json.dumps(item['source_url'], ensure_ascii=False)} .") + if item.get("evidence_text"): + lines.append(f"claim:c{item['claim_id']} ont:evidenceText {json.dumps(item['evidence_text'], ensure_ascii=False)} .") + lines.append("") + return Response( + content="\n".join(lines), + media_type="text/turtle; charset=utf-8", + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) + raise HTTPException(status_code=400, detail=f"unsupported export format: {format}") + @app.get("/projects/{project_name}/knowledge-gaps") def knowledge_gaps(project_name: str, limit: int = 100): with session_scope(database_url) as session: @@ -942,10 +1160,30 @@ def register_routes(app, database_url: str) -> None: return research_session_payload(job) @app.get("/projects/{project_name}/graph/neighborhood") - def graph_neighborhood(project_name: str, entity_id: int | None = None, limit: int = 120): + def graph_neighborhood( + project_name: str, + entity_id: int | None = None, + limit: int = 120, + include_candidates: bool = False, + status: str | None = None, + ): with session_scope(database_url) as session: project = KnowledgeRepository(session).get_project(project_name) - return SemanticGraphQuery(session).neighborhood(project.id, entity_id, max(min(limit, 300), 1)) + if status and status != "all": + normalized = _normalize_claim_status(status) or status + statuses = [normalized] + elif status == "all": + statuses = None + elif include_candidates: + statuses = ["validated_claim", "active", "candidate_claim", "rule_candidate"] + else: + statuses = ["validated_claim"] + return SemanticGraphQuery(session).neighborhood( + project.id, + entity_id, + max(min(limit, 300), 1), + statuses=statuses, + ) @app.get("/projects/{project_name}/graph/query") def graph_query( @@ -1182,9 +1420,11 @@ def register_routes(app, database_url: str) -> None: results: list[dict[str, Any]] = [] for claim, source, page, subject in rows: object_name = None + object_type = None if claim.object_entity_id: object_entity = session.get(models.Entity, claim.object_entity_id) object_name = object_entity.name if object_entity else None + object_type = object_entity.entity_type if object_entity else None evidence = session.scalar( select(models.Evidence) .where(models.Evidence.claim_id == claim.id) @@ -1197,12 +1437,14 @@ def register_routes(app, database_url: str) -> None: "subject_type": subject.entity_type, "predicate": claim.predicate, "object": object_name, + "object_type": object_type, "object_value": claim.object_value, "source": source.name, "page_url": page.url if page else None, "confidence": claim.confidence, "confidence_reason": claim.confidence_reason, "status": claim.status, + "extraction_method": claim.extraction_method, "evidence_text": evidence.evidence_text if evidence else None, "evidence_summary": evidence.evidence_summary if evidence else None, "page_type": (claim.metadata_json or {}).get("page_type") @@ -1313,6 +1555,126 @@ def register_routes(app, database_url: str) -> None: ], } + @app.post("/projects/{project_name}/pipeline/rerun") + def rerun_pipeline_stage( + project_name: str, + request: PipelineRerunRequest, + background_tasks: BackgroundTasks, + ): + crawl_stages = { + "source-crawl", + "page-clean", + "page-classification", + "entity-extraction", + "claim-generation", + "deduplication", + "validation", + } + route_by_stage = { + "human-review": f"/review/{project_name}", + "ontology-commit": f"/graph/{project_name}", + "export": f"/export/{project_name}", + } + if request.stage not in crawl_stages: + return { + "ok": True, + "stage": request.stage, + "action": "navigate", + "route": route_by_stage.get(request.stage, f"/pipeline/{project_name}"), + "message": "This stage is controlled from its workspace screen.", + } + + with session_scope(database_url) as session: + repo = KnowledgeRepository(session) + project = repo.get_project(project_name) + config = project_config_from_project_row(session, project) + latest_job = session.scalar( + select(models.CrawlJob) + .where(models.CrawlJob.project_id == project.id) + .order_by(models.CrawlJob.scheduled_at.desc()) + ) + latest_request = dict((latest_job.metadata_json or {}).get("request") or {}) if latest_job else {} + source_name = latest_request.get("source_name") + source = None + if source_name: + source = session.scalar( + select(models.Source).where( + models.Source.project_id == project.id, + models.Source.name == source_name, + ) + ) + if source is None: + source = session.scalar( + select(models.Source) + .where(models.Source.project_id == project.id) + .order_by(models.Source.updated_at.desc()) + ) + if source is None: + raise HTTPException(status_code=400, detail="No source is registered for this project.") + url = latest_request.get("url") or source.base_url + if not url: + raise HTTPException(status_code=400, detail="No previous crawl URL or source base_url is available.") + + inner_request = SiteCrawlRequest( + config_path="", + source_name=source.name, + url=url, + extractor_provider=str(latest_request.get("extractor_provider") or "lm_studio"), + extractor_model=latest_request.get("extractor_model"), + extractor_base_url=latest_request.get("extractor_base_url") or "http://localhost:1234/v1", + check_robots_txt=bool(latest_request.get("check_robots_txt") or False), + respect_robots_txt=latest_request.get("respect_robots_txt"), + max_depth=int(latest_request.get("max_depth") or 2), + max_pages=int(latest_request.get("max_pages") or 50), + same_domain_only=bool(latest_request.get("same_domain_only", True)), + analyze_page_types=list( + latest_request.get("analyze_page_types") + or ["ProductPage", "BrandStoryPage", "ReviewPage"] + ), + ) + apply_crawl_request_overrides(config, inner_request) + job = models.CrawlJob( + project_id=project.id, + source_id=source.id, + url=url, + status="pending", + metadata_json={ + "kind": "site_crawl", + "request": inner_request.model_dump(), + "project_name": project_name, + "rerun_stage": request.stage, + "progress": { + "seed_url": url, + "visited_count": 0, + "analyzed_count": 0, + "queued_count": 1, + "skipped_count": 0, + "errors": [], + "pages": [], + }, + }, + ) + session.add(job) + session.flush() + response = crawl_job_response(job) + + task_payload = { + **inner_request.model_dump(), + "__config_dict": project_config_to_dict(config), + } + background_tasks.add_task( + run_site_crawl_job, + database_url, + response["job_id"], + task_payload, + ) + return { + **response, + "ok": True, + "stage": request.stage, + "action": "job_started", + } + @app.get("/projects/{project_name}/search") def project_search(project_name: str, q: str = "", limit: int = 10): q = (q or "").strip() diff --git a/crawler_platform/app/core/research/graph_query.py b/crawler_platform/app/core/research/graph_query.py index 5b3b9cb..eb9a04c 100644 --- a/crawler_platform/app/core/research/graph_query.py +++ b/crawler_platform/app/core/research/graph_query.py @@ -12,12 +12,20 @@ class SemanticGraphQuery: def __init__(self, session: Session): self.session = session - def neighborhood(self, project_id: int, entity_id: int | None = None, limit: int = 120) -> dict[str, Any]: + def neighborhood( + self, + project_id: int, + entity_id: int | None = None, + limit: int = 120, + statuses: list[str] | None = None, + ) -> dict[str, Any]: query = ( select(models.Claim, models.Entity) .join(models.Entity, models.Claim.subject_entity_id == models.Entity.id) - .where(models.Claim.project_id == project_id, models.Claim.status == "validated_claim") + .where(models.Claim.project_id == project_id) ) + if statuses is not None: + query = query.where(models.Claim.status.in_(statuses)) if entity_id is not None: query = query.where( (models.Claim.subject_entity_id == entity_id) | (models.Claim.object_entity_id == entity_id) @@ -40,7 +48,9 @@ class SemanticGraphQuery: "target": claim.object_entity_id, "target_value": claim.object_value if not claim.object_entity_id else None, "predicate": claim.predicate, + "status": claim.status, "confidence": claim.confidence, + "last_seen_at": claim.last_seen_at.isoformat() if claim.last_seen_at else None, "metadata": claim.metadata_json or {}, "object": object_payload, } diff --git a/crawler_platform/app/main.py b/crawler_platform/app/main.py index a10aa69..a4d0c3c 100644 --- a/crawler_platform/app/main.py +++ b/crawler_platform/app/main.py @@ -5,7 +5,6 @@ from pathlib import Path from fastapi import FastAPI, Request from fastapi.responses import FileResponse, JSONResponse -from fastapi.staticfiles import StaticFiles from crawler_platform.app.api.routes import register_routes from crawler_platform.app.core.database.session import init_db @@ -23,8 +22,18 @@ register_routes(app, DATABASE_URL) def _handle_key_error(_request: Request, exc: KeyError) -> JSONResponse: return JSONResponse(status_code=404, content={"detail": str(exc.args[0]) if exc.args else "not found"}) -if STATIC_DIR.exists(): - app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") +@app.get("/static/{asset_path:path}", include_in_schema=False) +def static_or_spa(asset_path: str): + static_root = STATIC_DIR.resolve() + target = (static_root / asset_path).resolve() + try: + target.relative_to(static_root) + except ValueError: + return JSONResponse(status_code=404, content={"detail": "not found"}) + + if target.is_file(): + return FileResponse(target) + return FileResponse(STATIC_DIR / "index.html") @app.get("/", include_in_schema=False) diff --git a/crawler_platform/app/web/frontend/src/App.tsx b/crawler_platform/app/web/frontend/src/App.tsx index da7afcd..51ff222 100644 --- a/crawler_platform/app/web/frontend/src/App.tsx +++ b/crawler_platform/app/web/frontend/src/App.tsx @@ -7,6 +7,12 @@ import ResearchPage from "@/pages/ResearchPage"; import OntologyEditorPage from "@/pages/OntologyEditorPage"; import ReviewPage from "@/pages/ReviewPage"; import DashboardPage from "@/pages/DashboardPage"; +import BuildPipelinePage from "@/pages/BuildPipelinePage"; +import PageAnalysisPage from "@/pages/PageAnalysisPage"; +import SchemaDesignerPage from "@/pages/SchemaDesignerPage"; +import QualityInspectorPage from "@/pages/QualityInspectorPage"; +import GraphViewPage from "@/pages/GraphViewPage"; +import ExportApiPage from "@/pages/ExportApiPage"; function App() { return ( @@ -16,9 +22,15 @@ function App() { } /> } /> } /> + } /> + } /> + } /> } /> } /> } /> + } /> + } /> + } /> ); diff --git a/crawler_platform/app/web/frontend/src/components/layout/AppShell.tsx b/crawler_platform/app/web/frontend/src/components/layout/AppShell.tsx index 33f1522..dec7d2a 100644 --- a/crawler_platform/app/web/frontend/src/components/layout/AppShell.tsx +++ b/crawler_platform/app/web/frontend/src/components/layout/AppShell.tsx @@ -14,9 +14,15 @@ import { Settings2, Activity, Brain, + Code2, + GitBranch, Network, ListChecks, Menu, + SearchCheck, + ShieldCheck, + ClipboardCheck, + Layers3, } from "lucide-react"; import { RootState } from "@/stores"; import { toggleSidebar } from "@/stores/slices/uiSlice"; @@ -28,25 +34,38 @@ interface NavItem { to?: string; projectPath?: string; labelKey: string; + label: string; icon: React.ComponentType<{ className?: string }>; } const navItems: NavItem[] = [ - { to: "/", labelKey: "nav.dashboard", icon: LayoutDashboard }, - { to: "/onboard", labelKey: "nav.onboard", icon: UploadCloud }, - { projectPath: "sources", labelKey: "nav.sources", icon: Settings2 }, - { projectPath: "crawl", labelKey: "nav.crawl", icon: Activity }, - { projectPath: "research", labelKey: "nav.research", icon: Brain }, - { projectPath: "editor", labelKey: "nav.editor", icon: Network }, - { projectPath: "review", labelKey: "nav.review", icon: ListChecks }, + { to: "/", labelKey: "nav.dashboard", label: "Dashboard", icon: LayoutDashboard }, + { to: "/onboard", labelKey: "nav.onboard", label: "New Project", icon: UploadCloud }, + { projectPath: "sources", labelKey: "nav.sources", label: "Source Explorer", icon: Settings2 }, + { projectPath: "crawl", labelKey: "nav.crawl", label: "Seed Crawl", icon: Activity }, + { projectPath: "pipeline", labelKey: "nav.pipeline", label: "Build Pipeline", icon: Layers3 }, + { projectPath: "analysis", labelKey: "nav.analysis", label: "Page Analysis", icon: SearchCheck }, + { projectPath: "schema", labelKey: "nav.schema", label: "Schema Designer", icon: ShieldCheck }, + { projectPath: "editor", labelKey: "nav.editor", label: "Entity Manager", icon: Network }, + { projectPath: "review", labelKey: "nav.review", label: "Claim Review", icon: ListChecks }, + { projectPath: "quality", labelKey: "nav.quality", label: "Quality Inspector", icon: ClipboardCheck }, + { projectPath: "graph", labelKey: "nav.graph", label: "Graph View", icon: GitBranch }, + { projectPath: "export", labelKey: "nav.export", label: "Export / API", icon: Code2 }, + { projectPath: "research", labelKey: "nav.research", label: "Graph Research", icon: Brain }, ]; const projectRoutePatterns = [ "/sources/:projectId", "/crawl/:projectId", + "/pipeline/:projectId", + "/analysis/:projectId", + "/schema/:projectId", "/research/:projectId", "/editor/:projectId", "/review/:projectId", + "/quality/:projectId", + "/graph/:projectId", + "/export/:projectId", ]; function projectIdFromPathname(pathname: string): string | undefined { @@ -137,7 +156,7 @@ export default function AppShell() { )}