Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
8.6 KiB
JSON

{"repo": "akimsoule/law.ollama.extact", "files": {"src/main/java/org/law/runner/LlmExtractorRunner.java": {"49ac853e4b436c2d00c210cc7c1cc06a156c6890": {"t": 1786068305.7722974, "n": 8151, "h": "f2b33b8417401c1e", "c": "package org.law.runner;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\nimport org.law.service.extract.OcrService;\nimport org.law.service.process.QAService;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.StandardOpenOption;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Stream;\n\npublic class LlmExtractorRunner {\n\n private static final Path PDF_DIR = Paths.get(\"src/main/resources/data/loi\");\n private static final Path OUTPUT_DIR = Paths.get(\"src/main/resources/data/llm_output\");\n private static final Path CACHE_DIR = Paths.get(\"src/main/resources/data/llm_cache\");\n private static final String API_URL = \"https://api.deepseek.com/chat/completions\";\n private static final String MODEL = \"deepseek-v4-flash\";\n private static final String ERRORS_LOG = \"errors.log\";\n private static final double TEMPERATURE = 0.1;\n private static final int MAX_TOKENS = 32000;\n private static final int MAX_RETRIES = 3;\n\n public static void main(String[] args) throws IOException {\n String apiKey = System.getenv(\"DEEPSEEK_API_KEY\");\n if (apiKey == null || apiKey.isBlank()) {\n System.err.println(\"❌ DEEPSEEK_API_KEY manquante.\");\n return;\n }\n\n Files.createDirectories(OUTPUT_DIR);\n Files.createDirectories(CACHE_DIR);\n\n boolean force = Arrays.asList(args).contains(\"--force\");\n String targetPdf = null;\n for (int i = 0; i < args.length; i++) {\n if (\"--pdf\".equals(args[i]) && i + 1 < args.length) {\n targetPdf = args[i + 1];\n }\n }\n\n QAService qa = new QAService();\n\n try (Stream<Path> pdfs = Files.list(PDF_DIR).filter(p -> p.toString().endsWith(\".pdf\"))) {\n for (Path pdfPath : pdfs.toList()) {\n String pdfName = pdfPath.getFileName().toString();\n if (targetPdf != null && !pdfName.equals(targetPdf)) {\n continue;\n }\n\n String baseName = pdfName.replace(\".pdf\", \"\");\n Path targetJson = OUTPUT_DIR.resolve(baseName + \".json\");\n\n if (!force && Files.exists(targetJson)) {\n System.out.println(\"⏭️ SKIP (LLM existe déjà) : \" + pdfName);\n continue;\n }\n\n System.out.println(\"🤖 TRAITEMENT LLM : \" + pdfName);\n try {\n String ocrText = new OcrService().getOcrText(pdfPath.toFile());\n JSONObject json = callDeepSeekAndValidate(pdfName, ocrText, apiKey, force);\n\n List<String> qaErrors = qa.validateJson(json);\n\n Files.writeString(targetJson, json.toString(2));\n\n if (!qaErrors.isEmpty()) {\n StringBuilder logEntry = new StringBuilder();\n logEntry.append(\"[\").append(Instant.now()).append(\"] ERREUR QA SUR \").append(pdfName)\n .append(\"\\n\");\n for (String err : qaErrors) {\n logEntry.append(\" - \").append(err).append(\"\\n\");\n }\n logEntry.append(\"---\\n\");\n\n Files.writeString(Paths.get(ERRORS_LOG), logEntry.toString(),\n StandardOpenOption.CREATE, StandardOpenOption.APPEND);\n System.err.println(\" ⚠️ QA Failed -> voir \" + ERRORS_LOG);\n } else {\n System.out.println(\"✅ LLM OK & QA Validé : \" + targetJson);\n }\n } catch (Exception e) {\n String errMsg = \"[\" + Instant.now() + \"] CRASH \" + pdfName + \": \" + e.getMessage() + \"\\n---\\n\";\n Files.writeString(Paths.get(ERRORS_LOG), errMsg, StandardOpenOption.CREATE,\n StandardOpenOption.APPEND);\n System.err.println(\"❌ Échec LLM sur \" + pdfName + \" (Loggé dans \" + ERRORS_LOG + \")\");\n }\n }\n }\n }\n\n private static JSONObject callDeepSeekAndValidate(String pdfName, String text, String apiKey, boolean force)\n throws Exception {\n Path cacheFile = CACHE_DIR.resolve(pdfName.replace(\".pdf\", \".json\"));\n boolean useCache = !force && Files.exists(cacheFile);\n String rawJson;\n\n if (useCache) {\n System.out.println(\"💾 Cache Hit : \" + pdfName);\n rawJson = Files.readString(cacheFile);\n } else {\n rawJson = callDeepSeekAPI(text, pdfName, apiKey);\n Files.writeString(cacheFile, rawJson);\n }\n\n JSONObject json = new JSONObject(rawJson);\n validateStrict(json, pdfName);\n return json;\n }\n\n private static String callDeepSeekAPI(String text, String pdfName, String apiKey) throws Exception {\n String system = \"\"\"\n Tu es un expert juridique. Retourne UNIQUEMENT un JSON valide.\n SCHÉMA REQUIS : {\"metadata\":{\"lawNumber\":\"\",\"lawDate\":\"\",\"lawObject\":\"\",\"signatories\":[{\"name\":\"\",\"role\":\"\"}]} ,\"articles\":[{\"index\":\"\",\"content\":\"\"}]}\n Règles : pas d'invention, préserve l'ordre, champs obligatoires lawNumber & articles.\n \"\"\";\n\n JSONObject payload = new JSONObject();\n payload.put(\"model\", MODEL);\n payload.put(\"temperature\", TEMPERATURE);\n payload.put(\"response_format\", new JSONObject().put(\"type\", \"json_object\"));\n payload.put(\"max_tokens\", MAX_TOKENS);\n payload.put(\"messages\", new JSONArray()\n .put(new JSONObject().put(\"role\", \"system\").put(\"content\", system))\n .put(new JSONObject().put(\"role\", \"user\").put(\"content\",\n \"FICHIER SOURCE : \" + pdfName + \"\\nTEXTE OCR :\\n<<<\\n\" + text + \"\\n>>>\")));\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(URI.create(API_URL))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + apiKey)\n .POST(HttpRequest.BodyPublishers.ofString(payload.toString()))\n .build();\n\n HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());\n if (response.statusCode() != 200) {\n throw new IOException(\"API Error \" + response.statusCode() + \": \" + response.body());\n }\n\n JSONObject jsonResp = new JSONObject(response.body());\n if (jsonResp.has(\"error\")) {\n throw new RuntimeException(jsonResp.getJSONObject(\"error\").optString(\"message\", \"Erreur DeepSeek\"));\n }\n\n if (jsonResp.has(\"usage\")) {\n JSONObject usage = jsonResp.getJSONObject(\"usage\");\n System.out.printf(\" 📊 Tokens: %d (In) + %d (Out) = %d%n\",\n usage.optInt(\"prompt_tokens\"),\n usage.optInt(\"completion_tokens\"),\n usage.optInt(\"total_tokens\"));\n }\n\n return jsonResp.getJSONArray(\"choices\")\n .getJSONObject(0)\n .getJSONObject(\"message\")\n .getString(\"content\");\n }\n\n private static void validateStrict(JSONObject json, String pdfName) {\n if (!json.has(\"metadata\") || !json.getJSONObject(\"metadata\").has(\"lawNumber\")\n || json.getJSONObject(\"metadata\").optString(\"lawNumber\").isBlank()) {\n throw new RuntimeException(\"lawNumber manquant pour \" + pdfName);\n }\n\n if (!json.has(\"articles\") || json.getJSONArray(\"articles\").length() < 2) {\n throw new RuntimeException(\"Moins de 2 articles détectés pour \" + pdfName);\n }\n\n JSONObject metadata = json.getJSONObject(\"metadata\");\n metadata.put(\"source_pdf\", pdfName);\n metadata.put(\"extracted_by\", \"LLM_DeepSeek\");\n metadata.put(\"extracted_at\", Instant.now().toString());\n }\n}\n"}}}}