Upload 27 files
Browse files- 2025-04-15_Introduction_to_Artificial_Intelligence.pdf +0 -0
- analysis_concept_frequencies.parquet +3 -0
- analysis_concept_similarities.parquet +3 -0
- analysis_network_results.parquet +3 -0
- bestTest.png +0 -0
- concept_embeddings.pkl +3 -0
- concept_network.pkl +3 -0
- concept_network_visualization.html +374 -0
- concept_similarities.parquet +3 -0
- concepts.parquet +3 -0
- documents.parquet +3 -0
- extractor.py +197 -0
- loaders.py +136 -0
- mentions.parquet +3 -0
- network_analysis.py +154 -0
- network_builder.py +118 -0
- plotting.py +155 -0
- relationships.parquet +3 -0
- requirements.txt +2 -0
- reset_status.py +24 -0
- run_analysis.py +125 -0
- run_extractor.py +15 -0
- run_loader.py +14 -0
- similarity.py +170 -0
- storage.py +150 -0
- temporal.py +164 -0
- test1.png +0 -0
2025-04-15_Introduction_to_Artificial_Intelligence.pdf
ADDED
Binary file (53.3 kB). View file
|
|
analysis_concept_frequencies.parquet
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:31cb3e93a0c0c1eb3f65ba695a75b03bdd2b67f80fefd9b7810497ec50100d42
|
3 |
+
size 3618
|
analysis_concept_similarities.parquet
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:0cf17175e4b6a3fac9648e20938ce6c90e4d90dbdc1c71186846a17eff77b45a
|
3 |
+
size 4846
|
analysis_network_results.parquet
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:98e8ca5669f2a601c184388dda9b34d870ef5a35e69336a4bc23adff7ffc14c2
|
3 |
+
size 5022
|
bestTest.png
ADDED
![]() |
concept_embeddings.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:aa1f484881118bfd68ca830e070e85c0e380a1be6bbef1c637ea17303bc4d167
|
3 |
+
size 17716
|
concept_network.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:acc729f59905a3394915ccb0d731ba2bc1a25b2a16f7cd524cb1cdfa451420db
|
3 |
+
size 2336
|
concept_network_visualization.html
ADDED
@@ -0,0 +1,374 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<html>
|
2 |
+
<head>
|
3 |
+
<meta charset="utf-8">
|
4 |
+
|
5 |
+
<script>function neighbourhoodHighlight(params) {
|
6 |
+
// console.log("in nieghbourhoodhighlight");
|
7 |
+
allNodes = nodes.get({ returnType: "Object" });
|
8 |
+
// originalNodes = JSON.parse(JSON.stringify(allNodes));
|
9 |
+
// if something is selected:
|
10 |
+
if (params.nodes.length > 0) {
|
11 |
+
highlightActive = true;
|
12 |
+
var i, j;
|
13 |
+
var selectedNode = params.nodes[0];
|
14 |
+
var degrees = 2;
|
15 |
+
|
16 |
+
// mark all nodes as hard to read.
|
17 |
+
for (let nodeId in allNodes) {
|
18 |
+
// nodeColors[nodeId] = allNodes[nodeId].color;
|
19 |
+
allNodes[nodeId].color = "rgba(200,200,200,0.5)";
|
20 |
+
if (allNodes[nodeId].hiddenLabel === undefined) {
|
21 |
+
allNodes[nodeId].hiddenLabel = allNodes[nodeId].label;
|
22 |
+
allNodes[nodeId].label = undefined;
|
23 |
+
}
|
24 |
+
}
|
25 |
+
var connectedNodes = network.getConnectedNodes(selectedNode);
|
26 |
+
var allConnectedNodes = [];
|
27 |
+
|
28 |
+
// get the second degree nodes
|
29 |
+
for (i = 1; i < degrees; i++) {
|
30 |
+
for (j = 0; j < connectedNodes.length; j++) {
|
31 |
+
allConnectedNodes = allConnectedNodes.concat(
|
32 |
+
network.getConnectedNodes(connectedNodes[j])
|
33 |
+
);
|
34 |
+
}
|
35 |
+
}
|
36 |
+
|
37 |
+
// all second degree nodes get a different color and their label back
|
38 |
+
for (i = 0; i < allConnectedNodes.length; i++) {
|
39 |
+
// allNodes[allConnectedNodes[i]].color = "pink";
|
40 |
+
allNodes[allConnectedNodes[i]].color = "rgba(150,150,150,0.75)";
|
41 |
+
if (allNodes[allConnectedNodes[i]].hiddenLabel !== undefined) {
|
42 |
+
allNodes[allConnectedNodes[i]].label =
|
43 |
+
allNodes[allConnectedNodes[i]].hiddenLabel;
|
44 |
+
allNodes[allConnectedNodes[i]].hiddenLabel = undefined;
|
45 |
+
}
|
46 |
+
}
|
47 |
+
|
48 |
+
// all first degree nodes get their own color and their label back
|
49 |
+
for (i = 0; i < connectedNodes.length; i++) {
|
50 |
+
// allNodes[connectedNodes[i]].color = undefined;
|
51 |
+
allNodes[connectedNodes[i]].color = nodeColors[connectedNodes[i]];
|
52 |
+
if (allNodes[connectedNodes[i]].hiddenLabel !== undefined) {
|
53 |
+
allNodes[connectedNodes[i]].label =
|
54 |
+
allNodes[connectedNodes[i]].hiddenLabel;
|
55 |
+
allNodes[connectedNodes[i]].hiddenLabel = undefined;
|
56 |
+
}
|
57 |
+
}
|
58 |
+
|
59 |
+
// the main node gets its own color and its label back.
|
60 |
+
// allNodes[selectedNode].color = undefined;
|
61 |
+
allNodes[selectedNode].color = nodeColors[selectedNode];
|
62 |
+
if (allNodes[selectedNode].hiddenLabel !== undefined) {
|
63 |
+
allNodes[selectedNode].label = allNodes[selectedNode].hiddenLabel;
|
64 |
+
allNodes[selectedNode].hiddenLabel = undefined;
|
65 |
+
}
|
66 |
+
} else if (highlightActive === true) {
|
67 |
+
// console.log("highlightActive was true");
|
68 |
+
// reset all nodes
|
69 |
+
for (let nodeId in allNodes) {
|
70 |
+
// allNodes[nodeId].color = "purple";
|
71 |
+
allNodes[nodeId].color = nodeColors[nodeId];
|
72 |
+
// delete allNodes[nodeId].color;
|
73 |
+
if (allNodes[nodeId].hiddenLabel !== undefined) {
|
74 |
+
allNodes[nodeId].label = allNodes[nodeId].hiddenLabel;
|
75 |
+
allNodes[nodeId].hiddenLabel = undefined;
|
76 |
+
}
|
77 |
+
}
|
78 |
+
highlightActive = false;
|
79 |
+
}
|
80 |
+
|
81 |
+
// transform the object into an array
|
82 |
+
var updateArray = [];
|
83 |
+
if (params.nodes.length > 0) {
|
84 |
+
for (let nodeId in allNodes) {
|
85 |
+
if (allNodes.hasOwnProperty(nodeId)) {
|
86 |
+
// console.log(allNodes[nodeId]);
|
87 |
+
updateArray.push(allNodes[nodeId]);
|
88 |
+
}
|
89 |
+
}
|
90 |
+
nodes.update(updateArray);
|
91 |
+
} else {
|
92 |
+
// console.log("Nothing was selected");
|
93 |
+
for (let nodeId in allNodes) {
|
94 |
+
if (allNodes.hasOwnProperty(nodeId)) {
|
95 |
+
// console.log(allNodes[nodeId]);
|
96 |
+
// allNodes[nodeId].color = {};
|
97 |
+
updateArray.push(allNodes[nodeId]);
|
98 |
+
}
|
99 |
+
}
|
100 |
+
nodes.update(updateArray);
|
101 |
+
}
|
102 |
+
}
|
103 |
+
|
104 |
+
function filterHighlight(params) {
|
105 |
+
allNodes = nodes.get({ returnType: "Object" });
|
106 |
+
// if something is selected:
|
107 |
+
if (params.nodes.length > 0) {
|
108 |
+
filterActive = true;
|
109 |
+
let selectedNodes = params.nodes;
|
110 |
+
|
111 |
+
// hiding all nodes and saving the label
|
112 |
+
for (let nodeId in allNodes) {
|
113 |
+
allNodes[nodeId].hidden = true;
|
114 |
+
if (allNodes[nodeId].savedLabel === undefined) {
|
115 |
+
allNodes[nodeId].savedLabel = allNodes[nodeId].label;
|
116 |
+
allNodes[nodeId].label = undefined;
|
117 |
+
}
|
118 |
+
}
|
119 |
+
|
120 |
+
for (let i=0; i < selectedNodes.length; i++) {
|
121 |
+
allNodes[selectedNodes[i]].hidden = false;
|
122 |
+
if (allNodes[selectedNodes[i]].savedLabel !== undefined) {
|
123 |
+
allNodes[selectedNodes[i]].label = allNodes[selectedNodes[i]].savedLabel;
|
124 |
+
allNodes[selectedNodes[i]].savedLabel = undefined;
|
125 |
+
}
|
126 |
+
}
|
127 |
+
|
128 |
+
} else if (filterActive === true) {
|
129 |
+
// reset all nodes
|
130 |
+
for (let nodeId in allNodes) {
|
131 |
+
allNodes[nodeId].hidden = false;
|
132 |
+
if (allNodes[nodeId].savedLabel !== undefined) {
|
133 |
+
allNodes[nodeId].label = allNodes[nodeId].savedLabel;
|
134 |
+
allNodes[nodeId].savedLabel = undefined;
|
135 |
+
}
|
136 |
+
}
|
137 |
+
filterActive = false;
|
138 |
+
}
|
139 |
+
|
140 |
+
// transform the object into an array
|
141 |
+
var updateArray = [];
|
142 |
+
if (params.nodes.length > 0) {
|
143 |
+
for (let nodeId in allNodes) {
|
144 |
+
if (allNodes.hasOwnProperty(nodeId)) {
|
145 |
+
updateArray.push(allNodes[nodeId]);
|
146 |
+
}
|
147 |
+
}
|
148 |
+
nodes.update(updateArray);
|
149 |
+
} else {
|
150 |
+
for (let nodeId in allNodes) {
|
151 |
+
if (allNodes.hasOwnProperty(nodeId)) {
|
152 |
+
updateArray.push(allNodes[nodeId]);
|
153 |
+
}
|
154 |
+
}
|
155 |
+
nodes.update(updateArray);
|
156 |
+
}
|
157 |
+
}
|
158 |
+
|
159 |
+
function selectNode(nodes) {
|
160 |
+
network.selectNodes(nodes);
|
161 |
+
neighbourhoodHighlight({ nodes: nodes });
|
162 |
+
return nodes;
|
163 |
+
}
|
164 |
+
|
165 |
+
function selectNodes(nodes) {
|
166 |
+
network.selectNodes(nodes);
|
167 |
+
filterHighlight({nodes: nodes});
|
168 |
+
return nodes;
|
169 |
+
}
|
170 |
+
|
171 |
+
function highlightFilter(filter) {
|
172 |
+
let selectedNodes = []
|
173 |
+
let selectedProp = filter['property']
|
174 |
+
if (filter['item'] === 'node') {
|
175 |
+
let allNodes = nodes.get({ returnType: "Object" });
|
176 |
+
for (let nodeId in allNodes) {
|
177 |
+
if (allNodes[nodeId][selectedProp] && filter['value'].includes((allNodes[nodeId][selectedProp]).toString())) {
|
178 |
+
selectedNodes.push(nodeId)
|
179 |
+
}
|
180 |
+
}
|
181 |
+
}
|
182 |
+
else if (filter['item'] === 'edge'){
|
183 |
+
let allEdges = edges.get({returnType: 'object'});
|
184 |
+
// check if the selected property exists for selected edge and select the nodes connected to the edge
|
185 |
+
for (let edge in allEdges) {
|
186 |
+
if (allEdges[edge][selectedProp] && filter['value'].includes((allEdges[edge][selectedProp]).toString())) {
|
187 |
+
selectedNodes.push(allEdges[edge]['from'])
|
188 |
+
selectedNodes.push(allEdges[edge]['to'])
|
189 |
+
}
|
190 |
+
}
|
191 |
+
}
|
192 |
+
selectNodes(selectedNodes)
|
193 |
+
}</script>
|
194 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vis-network/9.1.2/dist/dist/vis-network.min.css" integrity="sha512-WgxfT5LWjfszlPHXRmBWHkV2eceiWTOBvrKCNbdgDYTHrT2AeLCGbF4sZlZw3UMN3WtL0tGUoIAKsu8mllg/XA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
195 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis-network/9.1.2/dist/vis-network.min.js" integrity="sha512-LnvoEWDFrqGHlHmDD2101OrLcbsfkrzoSpvtSQtxK3RMnRV0eOkhhBN2dXHKRrUU8p2DGRTk35n4O8nWSVe1mQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
196 |
+
|
197 |
+
|
198 |
+
|
199 |
+
|
200 |
+
|
201 |
+
|
202 |
+
|
203 |
+
|
204 |
+
<center>
|
205 |
+
<h1>ChronoSense Konsept A�� (Metriklerle)</h1>
|
206 |
+
</center>
|
207 |
+
|
208 |
+
<!-- <link rel="stylesheet" href="../node_modules/vis/dist/vis.min.css" type="text/css" />
|
209 |
+
<script type="text/javascript" src="../node_modules/vis/dist/vis.js"> </script>-->
|
210 |
+
<link
|
211 |
+
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
|
212 |
+
rel="stylesheet"
|
213 |
+
integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6"
|
214 |
+
crossorigin="anonymous"
|
215 |
+
/>
|
216 |
+
<script
|
217 |
+
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
|
218 |
+
integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf"
|
219 |
+
crossorigin="anonymous"
|
220 |
+
></script>
|
221 |
+
|
222 |
+
|
223 |
+
<center>
|
224 |
+
<h1>ChronoSense Konsept A�� (Metriklerle)</h1>
|
225 |
+
</center>
|
226 |
+
<style type="text/css">
|
227 |
+
|
228 |
+
#mynetwork {
|
229 |
+
width: 100%;
|
230 |
+
height: 800px;
|
231 |
+
background-color: #ffffff;
|
232 |
+
border: 1px solid lightgray;
|
233 |
+
position: relative;
|
234 |
+
float: left;
|
235 |
+
}
|
236 |
+
|
237 |
+
|
238 |
+
|
239 |
+
|
240 |
+
#config {
|
241 |
+
float: left;
|
242 |
+
width: 400px;
|
243 |
+
height: 600px;
|
244 |
+
}
|
245 |
+
|
246 |
+
|
247 |
+
|
248 |
+
</style>
|
249 |
+
</head>
|
250 |
+
|
251 |
+
|
252 |
+
<body>
|
253 |
+
<div class="card" style="width: 100%">
|
254 |
+
|
255 |
+
|
256 |
+
<div id="mynetwork" class="card-body"></div>
|
257 |
+
</div>
|
258 |
+
|
259 |
+
|
260 |
+
|
261 |
+
<div id="config"></div>
|
262 |
+
|
263 |
+
|
264 |
+
<script type="text/javascript">
|
265 |
+
|
266 |
+
// initialize global variables.
|
267 |
+
var edges;
|
268 |
+
var nodes;
|
269 |
+
var allNodes;
|
270 |
+
var allEdges;
|
271 |
+
var nodeColors;
|
272 |
+
var originalNodes;
|
273 |
+
var network;
|
274 |
+
var container;
|
275 |
+
var options, data;
|
276 |
+
var filter = {
|
277 |
+
item : '',
|
278 |
+
property : '',
|
279 |
+
value : []
|
280 |
+
};
|
281 |
+
|
282 |
+
|
283 |
+
|
284 |
+
|
285 |
+
|
286 |
+
// This method is responsible for drawing the graph, returns the drawn network
|
287 |
+
function drawGraph() {
|
288 |
+
var container = document.getElementById('mynetwork');
|
289 |
+
|
290 |
+
|
291 |
+
|
292 |
+
// parsing and collecting nodes and edges from the python
|
293 |
+
nodes = new vis.DataSet([{"color": "#ff7f0e", "id": "b8566bb8-f043-45d0-8442-c8f3e729a626", "label": "ai", "shape": "dot", "size": 40.0, "title": "ID: b8566bb8-f043-45d0-8442-c8f3e729a626\u003cbr\u003eName: ai\u003cbr\u003edegree_centrality: 0.300\u003cbr\u003ecommunity_id: 1"}, {"color": "#2ca02c", "id": "acdb0052-9fb5-4a61-8ce3-4fa9188ccd68", "label": "unsupervised learning: finding", "shape": "dot", "size": 40.0, "title": "ID: acdb0052-9fb5-4a61-8ce3-4fa9188ccd68\u003cbr\u003eName: unsupervised learning: finding\u003cbr\u003edegree_centrality: 0.300\u003cbr\u003ecommunity_id: 2"}, {"color": "#2ca02c", "id": "c9a071e5-358b-460f-897d-5a0d68b4dc91", "label": "reinforcement learning", "shape": "dot", "size": 40.0, "title": "ID: c9a071e5-358b-460f-897d-5a0d68b4dc91\u003cbr\u003eName: reinforcement learning\u003cbr\u003edegree_centrality: 0.300\u003cbr\u003ecommunity_id: 2"}, {"color": "#d62728", "id": "8bcb0007-453a-45a8-b0f5-ccb49fc963be", "label": "deep learning", "shape": "dot", "size": 10, "title": "ID: 8bcb0007-453a-45a8-b0f5-ccb49fc963be\u003cbr\u003eName: deep learning\u003cbr\u003edegree_centrality: 0.000\u003cbr\u003ecommunity_id: 3"}, {"color": "#1f77b4", "id": "544a779d-f9b6-4720-bfdf-80a26574d819", "label": "nlp", "shape": "dot", "size": 20.0, "title": "ID: 544a779d-f9b6-4720-bfdf-80a26574d819\u003cbr\u003eName: nlp\u003cbr\u003edegree_centrality: 0.100\u003cbr\u003ecommunity_id: 0"}, {"color": "#ff7f0e", "id": "1b3a4eb6-a80f-4098-b98e-2ca50ecbdbc6", "label": "chatbots", "shape": "dot", "size": 30.0, "title": "ID: 1b3a4eb6-a80f-4098-b98e-2ca50ecbdbc6\u003cbr\u003eName: chatbots\u003cbr\u003edegree_centrality: 0.200\u003cbr\u003ecommunity_id: 1"}, {"color": "#2ca02c", "id": "ffec4610-96c3-4a0f-a592-573143619a30", "label": "supervised learning", "shape": "dot", "size": 40.0, "title": "ID: ffec4610-96c3-4a0f-a592-573143619a30\u003cbr\u003eName: supervised learning\u003cbr\u003edegree_centrality: 0.300\u003cbr\u003ecommunity_id: 2"}, {"color": "#2ca02c", "id": "c7b69b48-9fea-45de-868d-27f935a7b2b7", "label": "labeled data unsupervised learning", "shape": "dot", "size": 40.0, "title": "ID: c7b69b48-9fea-45de-868d-27f935a7b2b7\u003cbr\u003eName: labeled data unsupervised learning\u003cbr\u003edegree_centrality: 0.300\u003cbr\u003ecommunity_id: 2"}, {"color": "#1f77b4", "id": "18f1cc03-9cfc-40c8-aa86-279a700a7f58", "label": "this approach", "shape": "dot", "size": 20.0, "title": "ID: 18f1cc03-9cfc-40c8-aa86-279a700a7f58\u003cbr\u003eName: this approach\u003cbr\u003edegree_centrality: 0.100\u003cbr\u003ecommunity_id: 0"}, {"color": "#ff7f0e", "id": "78b888f4-c0bf-492e-b514-3da1f628797d", "label": "gpt-4", "shape": "dot", "size": 30.0, "title": "ID: 78b888f4-c0bf-492e-b514-3da1f628797d\u003cbr\u003eName: gpt-4\u003cbr\u003edegree_centrality: 0.200\u003cbr\u003ecommunity_id: 1"}, {"color": "#ff7f0e", "id": "903e5742-9937-42c1-917d-ea7ff7ac449e", "label": "these models", "shape": "dot", "size": 20.0, "title": "ID: 903e5742-9937-42c1-917d-ea7ff7ac449e\u003cbr\u003eName: these models\u003cbr\u003edegree_centrality: 0.100\u003cbr\u003ecommunity_id: 1"}]);
|
294 |
+
edges = new vis.DataSet([{"color": "#9370DB", "from": "b8566bb8-f043-45d0-8442-c8f3e729a626", "title": "Type: combined\u003cbr\u003eRelation: RELATED_TO\u003cbr\u003eSimilarity: 0.648", "to": "1b3a4eb6-a80f-4098-b98e-2ca50ecbdbc6", "value": 0.647527813911438}, {"color": "#9370DB", "from": "b8566bb8-f043-45d0-8442-c8f3e729a626", "title": "Type: combined\u003cbr\u003eRelation: RELATED_TO\u003cbr\u003eSimilarity: 0.648", "to": "78b888f4-c0bf-492e-b514-3da1f628797d", "value": 0.647527813911438}, {"color": "#4682B4", "from": "b8566bb8-f043-45d0-8442-c8f3e729a626", "title": "Type: similarity\u003cbr\u003eSimilarity: 0.627", "to": "903e5742-9937-42c1-917d-ea7ff7ac449e", "value": 0.6268218755722046}, {"color": "#FF6347", "from": "acdb0052-9fb5-4a61-8ce3-4fa9188ccd68", "title": "Type: extracted\u003cbr\u003eRelation: RELATED_TO", "to": "c9a071e5-358b-460f-897d-5a0d68b4dc91", "value": 0.8}, {"color": "#FF6347", "from": "acdb0052-9fb5-4a61-8ce3-4fa9188ccd68", "title": "Type: extracted\u003cbr\u003eRelation: RELATED_TO", "to": "ffec4610-96c3-4a0f-a592-573143619a30", "value": 0.8}, {"color": "#FF6347", "from": "acdb0052-9fb5-4a61-8ce3-4fa9188ccd68", "title": "Type: extracted\u003cbr\u003eRelation: RELATED_TO", "to": "c7b69b48-9fea-45de-868d-27f935a7b2b7", "value": 0.8}, {"color": "#FF6347", "from": "c9a071e5-358b-460f-897d-5a0d68b4dc91", "title": "Type: extracted\u003cbr\u003eRelation: RELATED_TO", "to": "ffec4610-96c3-4a0f-a592-573143619a30", "value": 0.8}, {"color": "#FF6347", "from": "c9a071e5-358b-460f-897d-5a0d68b4dc91", "title": "Type: extracted\u003cbr\u003eRelation: RELATED_TO", "to": "c7b69b48-9fea-45de-868d-27f935a7b2b7", "value": 0.8}, {"color": "#FF6347", "from": "544a779d-f9b6-4720-bfdf-80a26574d819", "title": "Type: extracted\u003cbr\u003eRelation: RELATED_TO", "to": "18f1cc03-9cfc-40c8-aa86-279a700a7f58", "value": 0.8}, {"color": "#FF6347", "from": "1b3a4eb6-a80f-4098-b98e-2ca50ecbdbc6", "title": "Type: extracted\u003cbr\u003eRelation: RELATED_TO", "to": "78b888f4-c0bf-492e-b514-3da1f628797d", "value": 0.8}, {"color": "#FF6347", "from": "ffec4610-96c3-4a0f-a592-573143619a30", "title": "Type: extracted\u003cbr\u003eRelation: RELATED_TO", "to": "c7b69b48-9fea-45de-868d-27f935a7b2b7", "value": 0.8}]);
|
295 |
+
|
296 |
+
nodeColors = {};
|
297 |
+
allNodes = nodes.get({ returnType: "Object" });
|
298 |
+
for (nodeId in allNodes) {
|
299 |
+
nodeColors[nodeId] = allNodes[nodeId].color;
|
300 |
+
}
|
301 |
+
allEdges = edges.get({ returnType: "Object" });
|
302 |
+
// adding nodes and edges to the graph
|
303 |
+
data = {nodes: nodes, edges: edges};
|
304 |
+
|
305 |
+
var options = {
|
306 |
+
"configure": {
|
307 |
+
"enabled": true,
|
308 |
+
"filter": [
|
309 |
+
"physics",
|
310 |
+
"nodes",
|
311 |
+
"edges"
|
312 |
+
]
|
313 |
+
},
|
314 |
+
"edges": {
|
315 |
+
"color": {
|
316 |
+
"inherit": true
|
317 |
+
},
|
318 |
+
"smooth": {
|
319 |
+
"enabled": true,
|
320 |
+
"type": "dynamic"
|
321 |
+
}
|
322 |
+
},
|
323 |
+
"interaction": {
|
324 |
+
"dragNodes": true,
|
325 |
+
"hideEdgesOnDrag": false,
|
326 |
+
"hideNodesOnDrag": false
|
327 |
+
},
|
328 |
+
"physics": {
|
329 |
+
"barnesHut": {
|
330 |
+
"avoidOverlap": 0,
|
331 |
+
"centralGravity": 0.1,
|
332 |
+
"damping": 0.09,
|
333 |
+
"gravitationalConstant": -8000,
|
334 |
+
"springConstant": 0.005,
|
335 |
+
"springLength": 150
|
336 |
+
},
|
337 |
+
"enabled": true,
|
338 |
+
"stabilization": {
|
339 |
+
"enabled": true,
|
340 |
+
"fit": true,
|
341 |
+
"iterations": 1000,
|
342 |
+
"onlyDynamicEdges": false,
|
343 |
+
"updateInterval": 50
|
344 |
+
}
|
345 |
+
}
|
346 |
+
};
|
347 |
+
|
348 |
+
|
349 |
+
|
350 |
+
|
351 |
+
|
352 |
+
// if this network requires displaying the configure window,
|
353 |
+
// put it in its div
|
354 |
+
options.configure["container"] = document.getElementById("config");
|
355 |
+
|
356 |
+
|
357 |
+
network = new vis.Network(container, data, options);
|
358 |
+
|
359 |
+
|
360 |
+
|
361 |
+
|
362 |
+
|
363 |
+
|
364 |
+
|
365 |
+
|
366 |
+
|
367 |
+
|
368 |
+
return network;
|
369 |
+
|
370 |
+
}
|
371 |
+
drawGraph();
|
372 |
+
</script>
|
373 |
+
</body>
|
374 |
+
</html>
|
concept_similarities.parquet
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:d17d91d0e64c82d91352c7d178c5f0bf6d19719c3c190036fad8395f7652fa72
|
3 |
+
size 3421
|
concepts.parquet
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:c8e21aae40aeb1ca4155d06f5df51e5d63be4aecb619ad8d15f7fbca58e9a7e6
|
3 |
+
size 3200
|
documents.parquet
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:158b86abf339fb8862e128d62c1e64e4dc4536a3937eaf85e44e839eb12448f4
|
3 |
+
size 3921
|
extractor.py
ADDED
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/extraction/extractor.py (AttributeError DÜZELTİLMİŞ TAM KOD)
|
2 |
+
|
3 |
+
import spacy
|
4 |
+
from pathlib import Path
|
5 |
+
import logging
|
6 |
+
import itertools
|
7 |
+
import re
|
8 |
+
import string
|
9 |
+
|
10 |
+
# Yerel modüllerimizi içe aktaralım
|
11 |
+
from src.data_management import storage
|
12 |
+
from src.data_management import loaders # extract_text_from_pdf için
|
13 |
+
|
14 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
15 |
+
|
16 |
+
# --- spaCy Model Yükleme ---
|
17 |
+
nlp = None
|
18 |
+
STOP_WORDS = set()
|
19 |
+
try:
|
20 |
+
nlp = spacy.load("en_core_web_lg")
|
21 |
+
logging.info("spaCy 'en_core_web_lg' modeli başarıyla yüklendi.")
|
22 |
+
STOP_WORDS = nlp.Defaults.stop_words
|
23 |
+
except OSError:
|
24 |
+
logging.error("spaCy 'en_core_web_lg' modeli bulunamadı. Lütfen indirin: python -m spacy download en_core_web_lg")
|
25 |
+
|
26 |
+
# --- Konsept Belirleme Kriterleri (Aynı kaldı) ---
|
27 |
+
TRUSTED_ENTITY_LABELS = {"PRODUCT", "ORG", "WORK_OF_ART"}
|
28 |
+
OTHER_ENTITY_LABELS = {"PERSON", "EVENT", "LAW", "NORP", "FAC", "GPE", "LOC"}
|
29 |
+
NOUN_CHUNK_PATTERNS = re.compile(r".*\b(learning|network|model|algorithm|system|technique|approach|agent|layer|architecture|transformer|attention)\b$", re.IGNORECASE)
|
30 |
+
MIN_CONCEPT_WORDS = 1
|
31 |
+
MAX_CONCEPT_WORDS = 6
|
32 |
+
AI_KEYWORDS = {"artificial intelligence", "machine learning", "deep learning",
|
33 |
+
"neural network", "reinforcement learning", "transformer", "llm",
|
34 |
+
"large language model", "computer vision", "natural language processing",
|
35 |
+
"algorithm", "model", "gpt", "bert", "agent", "attention", "supervised",
|
36 |
+
"unsupervised", "classification", "regression", "clustering"}
|
37 |
+
# --- İlişki Çıkarımı için Fiiller ve Desenler ---
|
38 |
+
RELATION_VERBS = {
|
39 |
+
"use": "USES", "utilize": "USES", "apply": "USES", "employ": "USES",
|
40 |
+
"improve": "IMPROVES", "enhance": "IMPROVES", "extend": "IMPROVES", "outperform": "IMPROVES",
|
41 |
+
"base on": "BASED_ON", "rely on": "BASED_ON",
|
42 |
+
"compare": "COMPARES_TO", "relate": "RELATED_TO", "associate": "RELATED_TO", "link": "RELATED_TO",
|
43 |
+
"propose": "PROPOSES", "introduce": "PROPOSES", "develop": "PROPOSES",
|
44 |
+
}
|
45 |
+
|
46 |
+
def normalize_and_validate_concept(text: str, is_entity: bool = False, entity_label: str = "") -> str | None:
|
47 |
+
""" Verilen metni temizler, doğrular... """
|
48 |
+
cleaned_text = text.strip()
|
49 |
+
word_count = len(cleaned_text.split())
|
50 |
+
if not (MIN_CONCEPT_WORDS <= word_count <= MAX_CONCEPT_WORDS): return None
|
51 |
+
if cleaned_text and all(word.lower() in STOP_WORDS for word in re.findall(r'\b\w+\b', cleaned_text)): return None
|
52 |
+
if cleaned_text.isdigit() or all(c in string.punctuation for c in cleaned_text): return None
|
53 |
+
generic_phrases = {"this approach", "these models", "this technique", "this system",
|
54 |
+
"the model", "the algorithm", "the method", "the approach",
|
55 |
+
"the system", "the technique", "our model", "our approach"}
|
56 |
+
if cleaned_text.lower() in generic_phrases: return None
|
57 |
+
return cleaned_text
|
58 |
+
|
59 |
+
def find_verb_relation(token1: spacy.tokens.Token, token2: spacy.tokens.Token) -> tuple[str, str] | None:
|
60 |
+
""" İki token arasındaki dependency path'e bakarak fiil ilişkisi bulur. """
|
61 |
+
common_ancestor = None
|
62 |
+
ancestors1 = list(token1.ancestors)
|
63 |
+
ancestors2 = list(token2.ancestors)
|
64 |
+
for t in reversed(ancestors1):
|
65 |
+
if t in ancestors2:
|
66 |
+
common_ancestor = t
|
67 |
+
break
|
68 |
+
if not common_ancestor: return None
|
69 |
+
|
70 |
+
verb1 = None; head = token1
|
71 |
+
while head != common_ancestor:
|
72 |
+
if head.pos_ == "VERB": verb1 = head; break
|
73 |
+
head = head.head
|
74 |
+
verb2 = None; head = token2
|
75 |
+
while head != common_ancestor:
|
76 |
+
if head.pos_ == "VERB": verb2 = head; break
|
77 |
+
head = head.head
|
78 |
+
|
79 |
+
verb_token = None
|
80 |
+
if common_ancestor.pos_ == "VERB": verb_token = common_ancestor
|
81 |
+
elif verb1 and verb1 == verb2: verb_token = verb1
|
82 |
+
# elif verb1: verb_token = verb1 # Tek taraflı fiilleri şimdilik yoksayalım
|
83 |
+
# elif verb2: verb_token = verb2
|
84 |
+
elif common_ancestor.head.pos_ == "VERB": verb_token = common_ancestor.head
|
85 |
+
|
86 |
+
if verb_token:
|
87 |
+
verb_lemma = verb_token.lemma_
|
88 |
+
# *** HATA DÜZELTME: Bu satırı geçici olarak kaldırıyoruz/yorum yapıyoruz ***
|
89 |
+
# if verb_token.is_aux or verb_token.is_stop:
|
90 |
+
# return None
|
91 |
+
# **********************************************************************
|
92 |
+
for verb, rel_type in RELATION_VERBS.items():
|
93 |
+
if verb_lemma == verb or verb_lemma in verb.split():
|
94 |
+
logging.debug(f"Fiil ilişkisi bulundu: {token1.text}... {verb_lemma} ({rel_type}) ...{token2.text}")
|
95 |
+
return rel_type, verb_lemma
|
96 |
+
return None
|
97 |
+
|
98 |
+
def extract_entities_and_relations(text: str, doc_id: str):
|
99 |
+
""" Metinden konseptleri, mention'ları ve İYİLEŞTİRİLMİŞ ilişkileri çıkarır. """
|
100 |
+
if not nlp: raise RuntimeError("spaCy modeli yüklenemedi.")
|
101 |
+
spacy_doc = nlp(text)
|
102 |
+
potential_concepts = {}; mentions_in_doc = []; valid_mentions = {}
|
103 |
+
processed_spans = set(); added_relations = set()
|
104 |
+
|
105 |
+
# 1. Adayları Bul
|
106 |
+
candidates = []
|
107 |
+
for ent in spacy_doc.ents:
|
108 |
+
if ent.label_ in TRUSTED_ENTITY_LABELS or ent.label_ in OTHER_ENTITY_LABELS:
|
109 |
+
candidates.append({"span": ent, "is_entity": True, "label": ent.label_})
|
110 |
+
for chunk in spacy_doc.noun_chunks:
|
111 |
+
is_covered = any(ent_data["span"].start_char <= chunk.start_char and ent_data["span"].end_char >= chunk.end_char
|
112 |
+
for ent_data in candidates if ent_data["is_entity"])
|
113 |
+
if not is_covered:
|
114 |
+
candidates.append({"span": chunk, "is_entity": False, "label": ""})
|
115 |
+
|
116 |
+
# 2. Adayları Filtrele, Normalleştir ve Kaydet
|
117 |
+
for data in candidates:
|
118 |
+
span = data["span"];
|
119 |
+
if span in processed_spans: continue
|
120 |
+
validated_text = normalize_and_validate_concept(span.text, data["is_entity"], data["label"])
|
121 |
+
if not validated_text: processed_spans.add(span); continue
|
122 |
+
concept_lemma = span.lemma_.lower().strip() if span.lemma_ else validated_text.lower()
|
123 |
+
is_concept = False
|
124 |
+
if data["is_entity"] and data["label"] in TRUSTED_ENTITY_LABELS: is_concept = True
|
125 |
+
elif NOUN_CHUNK_PATTERNS.match(validated_text): is_concept = True
|
126 |
+
elif any(keyword in concept_lemma.split() or keyword in validated_text.lower().split() for keyword in AI_KEYWORDS): is_concept = True
|
127 |
+
elif validated_text.isupper() and len(validated_text) > 1 and len(validated_text) < 6: is_concept = True
|
128 |
+
|
129 |
+
if is_concept:
|
130 |
+
concept_id = storage.add_concept(validated_text)
|
131 |
+
if concept_id:
|
132 |
+
mention_id = storage.add_mention(
|
133 |
+
doc_id=doc_id, concept_id=concept_id,
|
134 |
+
context=span.sent.text, start=span.start_char, end=span.end_char
|
135 |
+
)
|
136 |
+
if mention_id:
|
137 |
+
mention_data = {
|
138 |
+
"mention_id": mention_id, "concept_id": concept_id,
|
139 |
+
"start_char": span.start_char, "end_char": span.end_char,
|
140 |
+
"sentence": span.sent, "root_token": span.root
|
141 |
+
}
|
142 |
+
mentions_in_doc.append(mention_data); valid_mentions[mention_id] = mention_data
|
143 |
+
processed_spans.add(span)
|
144 |
+
|
145 |
+
# 3. İlişkileri Çıkar
|
146 |
+
for sentence in spacy_doc.sents:
|
147 |
+
mentions_in_sentence = [m for m in mentions_in_doc if m["sentence"] == sentence]
|
148 |
+
if len(mentions_in_sentence) >= 2:
|
149 |
+
for m1_data, m2_data in itertools.combinations(mentions_in_sentence, 2):
|
150 |
+
c1_id = m1_data["concept_id"]; c2_id = m2_data["concept_id"]
|
151 |
+
if c1_id == c2_id: continue
|
152 |
+
rel_pair = tuple(sorted((c1_id, c2_id)))
|
153 |
+
if rel_pair in added_relations: continue
|
154 |
+
relation_found = False
|
155 |
+
relation_info = find_verb_relation(m1_data["root_token"], m2_data["root_token"])
|
156 |
+
if relation_info:
|
157 |
+
rel_type, verb = relation_info
|
158 |
+
storage.add_relationship(
|
159 |
+
source_concept_id=c1_id, target_concept_id=c2_id, rel_type=rel_type,
|
160 |
+
mention_id=m1_data["mention_id"], doc_id=doc_id, sentence=sentence.text
|
161 |
+
)
|
162 |
+
relation_found = True; added_relations.add(rel_pair)
|
163 |
+
if not relation_found:
|
164 |
+
storage.add_relationship(
|
165 |
+
source_concept_id=c1_id, target_concept_id=c2_id, rel_type="RELATED_TO",
|
166 |
+
mention_id=m1_data["mention_id"], doc_id=doc_id, sentence=sentence.text
|
167 |
+
)
|
168 |
+
added_relations.add(rel_pair)
|
169 |
+
|
170 |
+
def process_documents_for_extraction():
|
171 |
+
""" Dokümanları işler ve durumu günceller... (Öncekiyle aynı) """
|
172 |
+
if not nlp: raise RuntimeError("spaCy modeli yüklenemedi.")
|
173 |
+
logging.info("Gelişmiş bilgi çıkarımı için dokümanlar işleniyor...")
|
174 |
+
documents_df = storage.load_dataframe('documents', storage.DOC_COLUMNS)
|
175 |
+
docs_to_process = documents_df[documents_df['status'] == 'added']
|
176 |
+
if docs_to_process.empty:
|
177 |
+
logging.info("Durumu 'added' olan ve işlenecek doküman bulunamadı.")
|
178 |
+
return
|
179 |
+
processed_count = 0; failed_count = 0
|
180 |
+
for index, doc_row in docs_to_process.iterrows():
|
181 |
+
doc_id = doc_row['doc_id']; filepath = Path(doc_row['filepath'])
|
182 |
+
logging.info(f"İşleniyor: {filepath.name} (ID: {doc_id})")
|
183 |
+
text = loaders.extract_text_from_pdf(filepath)
|
184 |
+
if text:
|
185 |
+
try:
|
186 |
+
extract_entities_and_relations(text, doc_id)
|
187 |
+
storage.update_document_status(doc_id, 'processed_v3') # Yeni versiyon durumu
|
188 |
+
processed_count += 1
|
189 |
+
except Exception as e:
|
190 |
+
logging.exception(f"'{filepath.name}' işlenirken BEKLENMEYEN HATA oluştu: {e}")
|
191 |
+
storage.update_document_status(doc_id, 'extraction_failed_v3')
|
192 |
+
failed_count += 1
|
193 |
+
else:
|
194 |
+
logging.warning(f"Metin çıkarılamadı: {filepath.name}")
|
195 |
+
storage.update_document_status(doc_id, 'text_extraction_failed')
|
196 |
+
failed_count += 1
|
197 |
+
logging.info(f"Gelişmiş bilgi çıkarımı tamamlandı. Başarılı: {processed_count}, Başarısız: {failed_count}")
|
loaders.py
ADDED
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import PyPDF2 # PDF dosyalarını okumak için
|
2 |
+
from pathlib import Path
|
3 |
+
from datetime import datetime
|
4 |
+
import logging
|
5 |
+
import re # Tarih ayrıştırma için Regular Expressions
|
6 |
+
|
7 |
+
# Mevcut modüldeki storage fonksiyonlarını içe aktar (aynı klasörde olduğu için .)
|
8 |
+
from .storage import add_document, load_dataframe, save_dataframe, DOC_COLUMNS
|
9 |
+
|
10 |
+
# Ham veri klasörünün yolu
|
11 |
+
RAW_DATA_PATH = Path("data/raw")
|
12 |
+
|
13 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
14 |
+
|
15 |
+
def extract_text_from_pdf(pdf_path: Path) -> str | None:
|
16 |
+
"""
|
17 |
+
Verilen PDF dosyasının metin içeriğini çıkarır.
|
18 |
+
|
19 |
+
Args:
|
20 |
+
pdf_path (Path): PDF dosyasının yolu.
|
21 |
+
|
22 |
+
Returns:
|
23 |
+
str | None: Çıkarılan metin veya hata durumunda None.
|
24 |
+
"""
|
25 |
+
try:
|
26 |
+
with open(pdf_path, 'rb') as file:
|
27 |
+
reader = PyPDF2.PdfReader(file)
|
28 |
+
text = ""
|
29 |
+
for page in reader.pages:
|
30 |
+
page_text = page.extract_text()
|
31 |
+
if page_text:
|
32 |
+
text += page_text + "\n" # Sayfalar arasına yeni satır ekle
|
33 |
+
logging.info(f"Metin çıkarıldı: {pdf_path.name}")
|
34 |
+
return text
|
35 |
+
except Exception as e:
|
36 |
+
logging.error(f"PDF metni çıkarılırken hata ({pdf_path.name}): {e}")
|
37 |
+
# Şifreli PDF'ler veya bozuk dosyalar PyPDF2 tarafından hata verebilir
|
38 |
+
if "password" in str(e).lower():
|
39 |
+
logging.warning(f"Dosya şifreli olabilir: {pdf_path.name}")
|
40 |
+
return None
|
41 |
+
|
42 |
+
def parse_date_from_filename(filename: str) -> datetime | None:
|
43 |
+
"""
|
44 |
+
Dosya adından YYYY-MM-DD veya YYYYMMDD formatında tarih ayrıştırmaya çalışır.
|
45 |
+
|
46 |
+
Args:
|
47 |
+
filename (str): Dosya adı.
|
48 |
+
|
49 |
+
Returns:
|
50 |
+
datetime | None: Bulunan tarih veya None.
|
51 |
+
"""
|
52 |
+
# Örnek: 2023-10-26_paper.pdf, 20231026-paper.pdf, 2023_10_26 paper.pdf
|
53 |
+
patterns = [
|
54 |
+
r"(\d{4}-\d{2}-\d{2})", # YYYY-MM-DD
|
55 |
+
r"(\d{4}_\d{2}_\d{2})", # YYYY_MM_DD
|
56 |
+
r"(\d{8})" # YYYYMMDD
|
57 |
+
]
|
58 |
+
for pattern in patterns:
|
59 |
+
match = re.search(pattern, filename)
|
60 |
+
if match:
|
61 |
+
date_str = match.group(1).replace("_", "-") # Alt çizgiyi tireye çevir
|
62 |
+
try:
|
63 |
+
# Sadece tarih kısmını al, saat bilgisi ekleme
|
64 |
+
return datetime.strptime(date_str, '%Y-%m-%d').date()
|
65 |
+
except ValueError:
|
66 |
+
continue # Geçersiz tarih formatı varsa diğer deseni dene
|
67 |
+
logging.warning(f"Dosya adından geçerli tarih ayrıştırılamadı: {filename}")
|
68 |
+
return None
|
69 |
+
|
70 |
+
def process_raw_documents():
|
71 |
+
"""
|
72 |
+
'data/raw/' klasöründeki tüm PDF dosyalarını işler,
|
73 |
+
tarihlerini ayrıştırır ve sisteme ekler (eğer zaten ekli değillerse).
|
74 |
+
"""
|
75 |
+
if not RAW_DATA_PATH.exists():
|
76 |
+
logging.error(f"Ham veri klasörü bulunamadı: {RAW_DATA_PATH}")
|
77 |
+
return
|
78 |
+
|
79 |
+
logging.info(f"'{RAW_DATA_PATH}' klasöründeki PDF dosyaları işleniyor...")
|
80 |
+
processed_count = 0
|
81 |
+
added_count = 0
|
82 |
+
|
83 |
+
# Tüm PDF dosyalarını bul
|
84 |
+
pdf_files = list(RAW_DATA_PATH.glob('*.pdf'))
|
85 |
+
|
86 |
+
if not pdf_files:
|
87 |
+
logging.warning(f"'{RAW_DATA_PATH}' klasöründe işlenecek PDF dosyası bulunamadı.")
|
88 |
+
return
|
89 |
+
|
90 |
+
for pdf_path in pdf_files:
|
91 |
+
processed_count += 1
|
92 |
+
filename = pdf_path.name
|
93 |
+
filepath_str = str(pdf_path.resolve()) # Tam dosya yolunu al
|
94 |
+
|
95 |
+
# Dosya adından tarihi ayrıştır
|
96 |
+
publication_date = parse_date_from_filename(filename)
|
97 |
+
|
98 |
+
if publication_date:
|
99 |
+
# Dokümanı sisteme ekle (storage modülünü kullanarak)
|
100 |
+
# add_document, zaten varsa None yerine mevcut ID'yi döndürecek şekilde güncellendi
|
101 |
+
doc_id = add_document(filepath_str, publication_date)
|
102 |
+
if doc_id:
|
103 |
+
# Eğer yeni eklendiyse (veya mevcut ID döndüyse), sayacı artırabiliriz
|
104 |
+
# Şimdilik sadece eklenip eklenmediğini kontrol etmek yeterli
|
105 |
+
# Gerçek ekleme 'add_document' içinde loglanıyor
|
106 |
+
pass # Şimdilik ek bir işlem yapmıyoruz
|
107 |
+
|
108 |
+
else:
|
109 |
+
logging.warning(f"'{filename}' için yayın tarihi bulunamadı, doküman eklenemedi.")
|
110 |
+
|
111 |
+
logging.info(f"Toplam {processed_count} PDF dosyası tarandı.")
|
112 |
+
# Gerçekte kaç tane yeni eklendiği bilgisini storage loglarından takip edebiliriz.
|
113 |
+
|
114 |
+
# --- Metin Çıkarma ve Kaydetme (Sonraki Fazlar İçin Hazırlık) ---
|
115 |
+
# İleride bu fonksiyonu çağırıp metinleri ayrı dosyalara kaydedebiliriz
|
116 |
+
# ve documents_df'i güncelleyebiliriz.
|
117 |
+
#
|
118 |
+
# def extract_and_save_text(doc_id: str, pdf_path: Path):
|
119 |
+
# text = extract_text_from_pdf(pdf_path)
|
120 |
+
# if text:
|
121 |
+
# # Metni kaydet (örn: data/processed_data/text/{doc_id}.txt)
|
122 |
+
# text_path = DATA_PATH / "text" / f"{doc_id}.txt"
|
123 |
+
# text_path.parent.mkdir(parents=True, exist_ok=True)
|
124 |
+
# try:
|
125 |
+
# with open(text_path, 'w', encoding='utf-8') as f:
|
126 |
+
# f.write(text)
|
127 |
+
# logging.info(f"Metin '{text_path}' olarak kaydedildi.")
|
128 |
+
# # documents_df'i güncelle (status='text_extracted', processed_text_path=str(text_path))
|
129 |
+
# docs_df = load_dataframe('documents', DOC_COLUMNS)
|
130 |
+
# doc_index = docs_df[docs_df['doc_id'] == doc_id].index
|
131 |
+
# if not doc_index.empty:
|
132 |
+
# docs_df.loc[doc_index, 'status'] = 'text_extracted'
|
133 |
+
# docs_df.loc[doc_index, 'processed_text_path'] = str(text_path)
|
134 |
+
# save_dataframe(docs_df, 'documents')
|
135 |
+
# except Exception as e:
|
136 |
+
# logging.error(f"Metin kaydedilirken hata ({doc_id}): {e}")
|
mentions.parquet
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:49749194f77092f6c3b9e6eacd4ef3a3c34f9d5d1f9c766a51123bdc57885c24
|
3 |
+
size 9877
|
network_analysis.py
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/analysis/network_analysis.py
|
2 |
+
|
3 |
+
import networkx as nx
|
4 |
+
import pandas as pd
|
5 |
+
import logging
|
6 |
+
|
7 |
+
# Topluluk tespiti için Louvain metodu (önce 'pip install python-louvain community' yapılmalı)
|
8 |
+
try:
|
9 |
+
import community.community_louvain as community_louvain
|
10 |
+
community_lib_available = True
|
11 |
+
except ImportError:
|
12 |
+
logging.warning("'community' (python-louvain) kütüphanesi bulunamadı. Topluluk tespiti yapılamayacak. Kurulum için: pip install python-louvain community")
|
13 |
+
community_lib_available = False
|
14 |
+
|
15 |
+
# Yerel modüller
|
16 |
+
from src.data_management import storage
|
17 |
+
|
18 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
19 |
+
|
20 |
+
def calculate_centrality(graph: nx.Graph) -> dict:
|
21 |
+
"""
|
22 |
+
Graf üzerindeki düğümler için merkeziyet metriklerini hesaplar.
|
23 |
+
|
24 |
+
Args:
|
25 |
+
graph (nx.Graph): Analiz edilecek NetworkX grafı.
|
26 |
+
|
27 |
+
Returns:
|
28 |
+
dict: {node_id: {'degree': float, 'betweenness': float, 'eigenvector': float (veya None)}}
|
29 |
+
formatında metrikleri içeren sözlük.
|
30 |
+
"""
|
31 |
+
metrics = {}
|
32 |
+
if not graph or graph.number_of_nodes() == 0:
|
33 |
+
return metrics
|
34 |
+
|
35 |
+
try:
|
36 |
+
degree_centrality = nx.degree_centrality(graph)
|
37 |
+
except Exception as e:
|
38 |
+
logging.error(f"Degree Centrality hesaplanırken hata: {e}")
|
39 |
+
degree_centrality = {}
|
40 |
+
|
41 |
+
try:
|
42 |
+
betweenness_centrality = nx.betweenness_centrality(graph)
|
43 |
+
except Exception as e:
|
44 |
+
logging.error(f"Betweenness Centrality hesaplanırken hata: {e}")
|
45 |
+
betweenness_centrality = {}
|
46 |
+
|
47 |
+
try:
|
48 |
+
# Eigenvector centrality bağlantısız (disconnected) graflarda veya bazı durumlarda hata verebilir
|
49 |
+
# max_iter artırılabilir veya hata yakalanabilir
|
50 |
+
eigenvector_centrality = nx.eigenvector_centrality(graph, max_iter=500, tol=1e-06)
|
51 |
+
except Exception as e:
|
52 |
+
logging.warning(f"Eigenvector Centrality hesaplanırken hata (graf bağlantısız olabilir): {e}")
|
53 |
+
eigenvector_centrality = {} # Hata durumunda boş bırak
|
54 |
+
|
55 |
+
# Metrikleri birleştir
|
56 |
+
for node in graph.nodes():
|
57 |
+
metrics[node] = {
|
58 |
+
'degree_centrality': degree_centrality.get(node, 0.0),
|
59 |
+
'betweenness_centrality': betweenness_centrality.get(node, 0.0),
|
60 |
+
'eigenvector_centrality': eigenvector_centrality.get(node, None) # Hata durumunda None olabilir
|
61 |
+
}
|
62 |
+
logging.info("Merkeziyet metrikleri hesaplandı.")
|
63 |
+
return metrics
|
64 |
+
|
65 |
+
def detect_communities(graph: nx.Graph) -> dict | None:
|
66 |
+
"""
|
67 |
+
Louvain algoritması kullanarak graf üzerindeki toplulukları tespit eder.
|
68 |
+
|
69 |
+
Args:
|
70 |
+
graph (nx.Graph): Analiz edilecek NetworkX grafı.
|
71 |
+
|
72 |
+
Returns:
|
73 |
+
dict | None: {node_id: community_id} formatında bölümleme sözlüğü veya hata/kütüphane yoksa None.
|
74 |
+
"""
|
75 |
+
if not community_lib_available:
|
76 |
+
return None # Kütüphane yoksa hesaplama yapma
|
77 |
+
if not graph or graph.number_of_nodes() == 0:
|
78 |
+
return None # Boş graf
|
79 |
+
|
80 |
+
# Louvain metodu yönlendirilmemiş graflarda daha iyi çalışır.
|
81 |
+
# Eğer graf yönlü ise, yönlendirilmemişe çevir (veya uyarı ver).
|
82 |
+
# Bizim grafımız zaten yönlendirilmemiş (nx.Graph).
|
83 |
+
# Ağırlıklı kenarları kullanabilir (varsayılan weight='weight')
|
84 |
+
try:
|
85 |
+
partition = community_louvain.best_partition(graph, weight='weight') # Kenar ağırlıklarını dikkate al
|
86 |
+
num_communities = len(set(partition.values()))
|
87 |
+
logging.info(f"Louvain ile topluluk tespiti tamamlandı. {num_communities} topluluk bulundu.")
|
88 |
+
return partition
|
89 |
+
except Exception as e:
|
90 |
+
logging.exception(f"Topluluk tespiti sırasında hata oluştu: {e}")
|
91 |
+
return None
|
92 |
+
|
93 |
+
|
94 |
+
def get_network_analysis_results(graph: nx.Graph) -> pd.DataFrame | None:
|
95 |
+
"""
|
96 |
+
Merkeziyet ve topluluk analizlerini yapar ve sonuçları bir DataFrame'de birleştirir.
|
97 |
+
|
98 |
+
Args:
|
99 |
+
graph (nx.Graph): Analiz edilecek NetworkX grafı.
|
100 |
+
|
101 |
+
Returns:
|
102 |
+
pd.DataFrame | None: 'concept_id', 'name', 'degree_centrality', 'betweenness_centrality',
|
103 |
+
'eigenvector_centrality', 'community_id' sütunlarını içeren DataFrame
|
104 |
+
veya hata durumunda None.
|
105 |
+
"""
|
106 |
+
if not graph or graph.number_of_nodes() == 0:
|
107 |
+
logging.warning("Analiz için boş veya geçersiz graf sağlandı.")
|
108 |
+
return None
|
109 |
+
|
110 |
+
logging.info("Ağ analizi metrikleri hesaplanıyor...")
|
111 |
+
centrality_metrics = calculate_centrality(graph)
|
112 |
+
community_partition = detect_communities(graph)
|
113 |
+
|
114 |
+
# Sonuçları bir DataFrame'e dönüştür
|
115 |
+
analysis_data = []
|
116 |
+
concepts_df = storage.load_dataframe('concepts', storage.CONCEPT_COLUMNS) # İsimler için yükle
|
117 |
+
|
118 |
+
for node_id, metrics in centrality_metrics.items():
|
119 |
+
node_data = {
|
120 |
+
'concept_id': node_id,
|
121 |
+
'name': graph.nodes[node_id].get('name', 'N/A'), # Graf düğümünden al
|
122 |
+
'degree_centrality': metrics.get('degree_centrality'),
|
123 |
+
'betweenness_centrality': metrics.get('betweenness_centrality'),
|
124 |
+
'eigenvector_centrality': metrics.get('eigenvector_centrality'),
|
125 |
+
'community_id': community_partition.get(node_id, -1) if community_partition else -1 # Topluluk yoksa -1
|
126 |
+
}
|
127 |
+
analysis_data.append(node_data)
|
128 |
+
|
129 |
+
if not analysis_data:
|
130 |
+
logging.warning("Ağ analizi sonucu veri üretilemedi.")
|
131 |
+
return None
|
132 |
+
|
133 |
+
analysis_df = pd.DataFrame(analysis_data)
|
134 |
+
|
135 |
+
# Eğer graf düğümlerinde isim yoksa, concepts_df'ten almayı dene (yedek)
|
136 |
+
if 'N/A' in analysis_df['name'].values and concepts_df is not None:
|
137 |
+
analysis_df = analysis_df.drop(columns=['name']) # Eski 'name' sütununu sil
|
138 |
+
analysis_df = pd.merge(analysis_df, concepts_df[['concept_id', 'name']], on='concept_id', how='left')
|
139 |
+
# Sütun sırasını ayarla
|
140 |
+
cols = ['concept_id', 'name'] + [col for col in analysis_df.columns if col not in ['concept_id', 'name']]
|
141 |
+
analysis_df = analysis_df[cols]
|
142 |
+
|
143 |
+
|
144 |
+
logging.info("Ağ analizi sonuçları DataFrame'e dönüştürüldü.")
|
145 |
+
return analysis_df
|
146 |
+
|
147 |
+
|
148 |
+
def save_network_analysis(analysis_df: pd.DataFrame):
|
149 |
+
""" Ağ analizi sonuçlarını Parquet dosyasına kaydeder. """
|
150 |
+
if analysis_df is not None and not analysis_df.empty:
|
151 |
+
storage.save_dataframe(analysis_df, storage.NETWORK_ANALYSIS_FILENAME)
|
152 |
+
logging.info(f"Ağ analizi sonuçları '{storage.NETWORK_ANALYSIS_FILENAME}.parquet' olarak kaydedildi.")
|
153 |
+
else:
|
154 |
+
logging.warning("Kaydedilecek ağ analizi sonucu bulunamadı.")
|
network_builder.py
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/analysis/network_builder.py (DÜZELTİLMİŞ TAM KOD)
|
2 |
+
|
3 |
+
import networkx as nx
|
4 |
+
import pandas as pd
|
5 |
+
import logging
|
6 |
+
|
7 |
+
# Yerel modüller
|
8 |
+
from src.data_management import storage
|
9 |
+
|
10 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
11 |
+
|
12 |
+
# Grafı kaydetmek için dosya adı
|
13 |
+
GRAPH_FILENAME = "concept_network"
|
14 |
+
# Benzerlik dosyasının adı (Doğrudan burada tanımlayalım veya similarity'den import edelim)
|
15 |
+
# storage modülünde değil!
|
16 |
+
SIMILARITY_FILENAME = "concept_similarities"
|
17 |
+
|
18 |
+
def build_concept_network(similarity_threshold: float = 0.60,
|
19 |
+
include_similarity_edges: bool = True,
|
20 |
+
include_extracted_edges: bool = True) -> nx.Graph | None:
|
21 |
+
"""
|
22 |
+
Konseptler, çıkarılmış ilişkiler ve anlamsal benzerliklerden bir NetworkX grafı oluşturur.
|
23 |
+
|
24 |
+
Args:
|
25 |
+
similarity_threshold (float): Grafiğe eklenecek minimum anlamsal benzerlik skoru.
|
26 |
+
include_similarity_edges (bool): Benzerlik kenarlarını dahil et.
|
27 |
+
include_extracted_edges (bool): Metinden çıkarılan ilişki kenarlarını dahil et.
|
28 |
+
|
29 |
+
Returns:
|
30 |
+
nx.Graph | None: Oluşturulan NetworkX grafı veya hata durumunda None.
|
31 |
+
"""
|
32 |
+
logging.info("Konsept ağı oluşturuluyor...")
|
33 |
+
if not include_similarity_edges and not include_extracted_edges:
|
34 |
+
logging.warning("Hem benzerlik hem de çıkarılmış ilişki kenarları devre dışı bırakıldı.")
|
35 |
+
|
36 |
+
# Temel verileri yükle
|
37 |
+
concepts_df = storage.load_dataframe('concepts', storage.CONCEPT_COLUMNS)
|
38 |
+
relationships_df = storage.load_dataframe('relationships', storage.RELATIONSHIP_COLUMNS)
|
39 |
+
# *** DÜZELTME: SIMILARITY_FILENAME doğrudan kullanılıyor ***
|
40 |
+
similarity_df = storage.load_dataframe(SIMILARITY_FILENAME, ['concept_id_1', 'concept_id_2', 'similarity'])
|
41 |
+
|
42 |
+
if concepts_df is None or concepts_df.empty:
|
43 |
+
logging.error("Ağ oluşturmak için konsept verisi bulunamadı.")
|
44 |
+
return None
|
45 |
+
|
46 |
+
G = nx.Graph()
|
47 |
+
|
48 |
+
# 1. Adım: Konseptleri Düğüm Olarak Ekle
|
49 |
+
node_count = 0
|
50 |
+
valid_concept_ids = set() # Grafiğe eklenen geçerli ID'leri takip et
|
51 |
+
for index, row in concepts_df.iterrows():
|
52 |
+
concept_id = row['concept_id']
|
53 |
+
concept_name = row['name']
|
54 |
+
if pd.notna(concept_id) and pd.notna(concept_name):
|
55 |
+
G.add_node(concept_id, name=concept_name)
|
56 |
+
valid_concept_ids.add(concept_id)
|
57 |
+
node_count += 1
|
58 |
+
else:
|
59 |
+
logging.warning(f"Geçersiz konsept verisi atlandı: ID={concept_id}, Name={concept_name}")
|
60 |
+
logging.info(f"{node_count} konsept düğüm olarak eklendi.")
|
61 |
+
|
62 |
+
edge_count_extracted = 0
|
63 |
+
edge_count_similarity = 0
|
64 |
+
updated_edge_count = 0
|
65 |
+
|
66 |
+
# 2. Adım: Çıkarılmış İlişkileri Kenar Olarak Ekle
|
67 |
+
if include_extracted_edges and relationships_df is not None and not relationships_df.empty:
|
68 |
+
logging.info("Çıkarılmış ilişkiler kenar olarak ekleniyor...")
|
69 |
+
for index, row in relationships_df.iterrows():
|
70 |
+
source_id = row['source_concept_id']
|
71 |
+
target_id = row['target_concept_id']
|
72 |
+
rel_type = row['type'] or 'RELATED_TO'
|
73 |
+
|
74 |
+
# Düğümlerin grafide olduğundan ve geçerli olduğundan emin ol
|
75 |
+
if source_id in valid_concept_ids and target_id in valid_concept_ids:
|
76 |
+
if G.has_edge(source_id, target_id):
|
77 |
+
G.edges[source_id, target_id]['relation_type'] = rel_type
|
78 |
+
G.edges[source_id, target_id]['type'] = 'extracted'
|
79 |
+
else:
|
80 |
+
G.add_edge(source_id, target_id, type='extracted', relation_type=rel_type, weight=0.8)
|
81 |
+
edge_count_extracted += 1
|
82 |
+
else:
|
83 |
+
logging.warning(f"İlişki için düğüm(ler) bulunamadı veya geçersiz: {source_id} -> {target_id}")
|
84 |
+
logging.info(f"{edge_count_extracted} çıkarılmış ilişki kenarı eklendi.")
|
85 |
+
|
86 |
+
# 3. Adım: Anlamsal Benzerlikleri Kenar Olarak Ekle
|
87 |
+
if include_similarity_edges and similarity_df is not None and not similarity_df.empty:
|
88 |
+
logging.info(f"Anlamsal benzerlikler (Eşik > {similarity_threshold:.2f}) kenar olarak ekleniyor...")
|
89 |
+
filtered_similarity = similarity_df[(similarity_df['similarity'] >= similarity_threshold) & (similarity_df['similarity'] < 1.0)]
|
90 |
+
logging.info(f"{len(similarity_df)} benzerlik çiftinden {len(filtered_similarity)} tanesi eşik değerinin üzerinde (ve < 1.0).")
|
91 |
+
|
92 |
+
for index, row in filtered_similarity.iterrows():
|
93 |
+
id1 = row['concept_id_1']
|
94 |
+
id2 = row['concept_id_2']
|
95 |
+
similarity = row['similarity']
|
96 |
+
|
97 |
+
if id1 in valid_concept_ids and id2 in valid_concept_ids:
|
98 |
+
if G.has_edge(id1, id2):
|
99 |
+
G.edges[id1, id2]['similarity'] = similarity
|
100 |
+
if 'weight' not in G.edges[id1, id2] or similarity > G.edges[id1, id2].get('weight', 0):
|
101 |
+
G.edges[id1, id2]['weight'] = similarity
|
102 |
+
# Eğer extracted ilişki varsa, tipi 'combined' yapabiliriz?
|
103 |
+
G.edges[id1, id2]['type'] = 'combined' if G.edges[id1, id2].get('type') == 'extracted' else G.edges[id1, id2].get('type', 'similarity') # Önceliği koru veya birleştir
|
104 |
+
updated_edge_count += 1
|
105 |
+
else:
|
106 |
+
G.add_edge(id1, id2, type='similarity', weight=similarity)
|
107 |
+
edge_count_similarity += 1
|
108 |
+
else:
|
109 |
+
logging.warning(f"Benzerlik için düğüm(ler) bulunamadı veya geçersiz: {id1} <-> {id2}")
|
110 |
+
logging.info(f"{edge_count_similarity} yeni benzerlik kenarı eklendi, {updated_edge_count} mevcut kenara benzerlik/tip bilgisi eklendi.")
|
111 |
+
|
112 |
+
total_edges = G.number_of_edges()
|
113 |
+
logging.info(f"Konsept ağı oluşturuldu. Düğüm sayısı: {G.number_of_nodes()}, Kenar sayısı: {total_edges}.")
|
114 |
+
|
115 |
+
# 4. Adım: Grafı Kaydet
|
116 |
+
storage.save_network(G, GRAPH_FILENAME)
|
117 |
+
|
118 |
+
return G
|
plotting.py
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/visualization/plotting.py (Ağ Metrikleri ile Görselleştirme Güncellendi)
|
2 |
+
|
3 |
+
import networkx as nx
|
4 |
+
from pyvis.network import Network
|
5 |
+
import logging
|
6 |
+
from pathlib import Path
|
7 |
+
import pandas as pd
|
8 |
+
import random # Renk paleti için
|
9 |
+
|
10 |
+
# Yerel modüller
|
11 |
+
from src.data_management import storage
|
12 |
+
|
13 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
14 |
+
|
15 |
+
# Görselleştirme dosyalarının kaydedileceği yer
|
16 |
+
OUTPUT_DIR = Path("output/graphs")
|
17 |
+
DEFAULT_GRAPH_FILENAME = "concept_network"
|
18 |
+
# Analiz sonuçları dosyasının adı (storage'dan da alınabilirdi)
|
19 |
+
DEFAULT_ANALYSIS_FILENAME = storage.NETWORK_ANALYSIS_FILENAME
|
20 |
+
|
21 |
+
|
22 |
+
# Basit bir renk paleti (daha fazla renk eklenebilir veya matplotlib colormap kullanılabilir)
|
23 |
+
# Viridis, tab10, Set3 gibi paletler iyi çalışır
|
24 |
+
# Örnek: import matplotlib.cm as cm; colors = [cm.tab10(i) for i in range(10)]
|
25 |
+
DEFAULT_COLORS = [
|
26 |
+
"#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
|
27 |
+
"#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"
|
28 |
+
]
|
29 |
+
|
30 |
+
def get_color_for_community(community_id, colors=DEFAULT_COLORS):
|
31 |
+
""" Verilen community ID için paletten bir renk döndürür. """
|
32 |
+
if community_id < 0 or community_id is None or pd.isna(community_id): # Topluluk yoksa veya geçersizse
|
33 |
+
return "#CCCCCC" # Gri
|
34 |
+
return colors[int(community_id) % len(colors)] # Modulo ile renk tekrarı
|
35 |
+
|
36 |
+
def scale_value(value, min_val=0, max_val=1, new_min=10, new_max=50):
|
37 |
+
""" Bir değeri belirli bir aralığa ölçekler (örn: merkeziyet -> düğüm boyutu). """
|
38 |
+
if max_val == min_val or value is None or pd.isna(value): # Bölme hatasını veya None değerini engelle
|
39 |
+
return new_min # Veya ortalama bir değer?
|
40 |
+
# Ölçekleme: (value - min) / (max - min) * (new_max - new_min) + new_min
|
41 |
+
scaled = ((value - min_val) / (max_val - min_val)) * (new_max - new_min) + new_min
|
42 |
+
return max(new_min, min(scaled, new_max)) # Sonuçların min/max arasında kalmasını sağla
|
43 |
+
|
44 |
+
|
45 |
+
def visualize_network(graph: nx.Graph | None = None,
|
46 |
+
graph_filename: str = DEFAULT_GRAPH_FILENAME,
|
47 |
+
analysis_filename: str = DEFAULT_ANALYSIS_FILENAME,
|
48 |
+
output_filename: str = "concept_network_visualization.html",
|
49 |
+
show_buttons: bool = True,
|
50 |
+
physics_solver: str = 'barnesHut',
|
51 |
+
size_metric: str = 'degree_centrality', # Boyut için kullanılacak metrik
|
52 |
+
color_metric: str = 'community_id', # Renk için kullanılacak metrik
|
53 |
+
height: str = "800px",
|
54 |
+
width: str = "100%"
|
55 |
+
) -> str | None:
|
56 |
+
"""
|
57 |
+
Ağ grafını Pyvis ile görselleştirir. Düğüm boyutu ve rengi için ağ
|
58 |
+
analizi metriklerini kullanır.
|
59 |
+
"""
|
60 |
+
if graph is None:
|
61 |
+
logging.info(f"Graf sağlanmadı, '{graph_filename}.pkl' dosyasından yükleniyor...")
|
62 |
+
graph = storage.load_network(graph_filename)
|
63 |
+
|
64 |
+
if graph is None or not isinstance(graph, nx.Graph) or graph.number_of_nodes() == 0:
|
65 |
+
logging.error("Görselleştirilecek geçerli veya boş olmayan bir graf bulunamadı.")
|
66 |
+
return None
|
67 |
+
|
68 |
+
# Ağ analizi sonuçlarını yükle
|
69 |
+
logging.info(f"Ağ analizi sonuçları '{analysis_filename}.parquet' dosyasından yükleniyor...")
|
70 |
+
analysis_df = storage.load_dataframe(analysis_filename, []) # Sütunları bilmediğimiz için boş liste
|
71 |
+
metrics_dict = {}
|
72 |
+
min_size_val, max_size_val = 0, 1 # Boyut ölçekleme için min/max
|
73 |
+
|
74 |
+
if analysis_df is not None and not analysis_df.empty and 'concept_id' in analysis_df.columns:
|
75 |
+
# Eksik metrik sütunlarını kontrol et ve ekle (NaN ile)
|
76 |
+
required_metrics = [size_metric, color_metric]
|
77 |
+
for metric in required_metrics:
|
78 |
+
if metric not in analysis_df.columns:
|
79 |
+
logging.warning(f"Analiz sonuçlarında '{metric}' sütunu bulunamadı. Varsayılan değerler kullanılacak.")
|
80 |
+
analysis_df[metric] = None
|
81 |
+
|
82 |
+
# Boyut metriği için min/max değerleri bul (NaN olmayanlardan)
|
83 |
+
if size_metric in analysis_df.columns and analysis_df[size_metric].notna().any():
|
84 |
+
min_size_val = analysis_df[size_metric].min()
|
85 |
+
max_size_val = analysis_df[size_metric].max()
|
86 |
+
|
87 |
+
# Kolay erişim için sözlüğe çevir
|
88 |
+
metrics_dict = analysis_df.set_index('concept_id').to_dict('index')
|
89 |
+
logging.info("Ağ analizi metrikleri yüklendi.")
|
90 |
+
else:
|
91 |
+
logging.warning("Ağ analizi sonuçları yüklenemedi veya boş. Varsayılan düğüm boyutları/renkleri kullanılacak.")
|
92 |
+
|
93 |
+
|
94 |
+
logging.info(f"'{output_filename}' için Pyvis ağı oluşturuluyor...")
|
95 |
+
net = Network(notebook=False, height=height, width=width, heading='ChronoSense Konsept Ağı (Metriklerle)', cdn_resources='remote')
|
96 |
+
net.barnes_hut(gravity=-8000, central_gravity=0.1, spring_length=150, spring_strength=0.005, damping=0.09)
|
97 |
+
|
98 |
+
# Düğümleri (Nodes) Pyvis'e ekle (Boyut ve Renk ile)
|
99 |
+
for node, attrs in graph.nodes(data=True):
|
100 |
+
node_label = attrs.get('name', str(node))
|
101 |
+
node_metrics = metrics_dict.get(node, {}) # Bu düğüm için metrikleri al, yoksa boş dict
|
102 |
+
|
103 |
+
# Boyutu hesapla
|
104 |
+
size_val = node_metrics.get(size_metric)
|
105 |
+
node_size = scale_value(size_val, min_size_val, max_size_val, new_min=10, new_max=40) # 10-40 arası boyut
|
106 |
+
|
107 |
+
# Rengi hesapla
|
108 |
+
color_val = node_metrics.get(color_metric)
|
109 |
+
node_color = get_color_for_community(color_val)
|
110 |
+
|
111 |
+
# Başlığı (Title) güncelle (metrikleri ekle)
|
112 |
+
node_title = f"ID: {node}<br>Name: {attrs.get('name', 'N/A')}"
|
113 |
+
node_title += f"<br>{size_metric}: {size_val:.3f}" if pd.notna(size_val) else ""
|
114 |
+
node_title += f"<br>{color_metric}: {int(color_val)}" if pd.notna(color_val) else ""
|
115 |
+
|
116 |
+
net.add_node(node, label=node_label, title=node_title, size=node_size, color=node_color)
|
117 |
+
|
118 |
+
# Kenarları (Edges) Pyvis'e ekle (Öncekiyle aynı, sadece renk/kalınlık ayarları biraz daha belirgin)
|
119 |
+
for source, target, attrs in graph.edges(data=True):
|
120 |
+
edge_title = f"Type: {attrs.get('type', 'N/A')}"
|
121 |
+
edge_value = 0.5 ; edge_color = "#DDDDDD" # Daha soluk varsayılan
|
122 |
+
|
123 |
+
edge_type = attrs.get('type')
|
124 |
+
weight = attrs.get('weight', 0)
|
125 |
+
|
126 |
+
if edge_type == 'extracted':
|
127 |
+
edge_title += f"<br>Relation: {attrs.get('relation_type', 'N/A')}"
|
128 |
+
edge_value = max(0.6, weight) # extracted ilişkiler biraz daha belirgin olsun
|
129 |
+
edge_color = "#FF6347" # Koyu turuncu/kırmızımsı
|
130 |
+
elif edge_type == 'similarity':
|
131 |
+
sim_score = attrs.get('similarity', weight)
|
132 |
+
edge_title += f"<br>Similarity: {sim_score:.3f}"
|
133 |
+
edge_value = sim_score # Benzerlikle orantılı
|
134 |
+
edge_color = "#4682B4" # Çelik mavisi
|
135 |
+
elif edge_type == 'combined':
|
136 |
+
edge_title += f"<br>Relation: {attrs.get('relation_type', 'N/A')}"
|
137 |
+
sim_score = attrs.get('similarity', weight)
|
138 |
+
edge_title += f"<br>Similarity: {sim_score:.3f}"
|
139 |
+
edge_value = max(0.6, sim_score) # Combined da belirgin olsun
|
140 |
+
edge_color = "#9370DB" # Orta mor
|
141 |
+
|
142 |
+
net.add_edge(source, target, title=edge_title, value=max(0.1, edge_value), color=edge_color)
|
143 |
+
|
144 |
+
if show_buttons:
|
145 |
+
net.show_buttons(filter_=['physics', 'nodes', 'edges'])
|
146 |
+
|
147 |
+
try:
|
148 |
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
149 |
+
output_path = OUTPUT_DIR / output_filename
|
150 |
+
net.save_graph(str(output_path))
|
151 |
+
logging.info(f"Ağ görselleştirmesi başarıyla '{output_path}' olarak kaydedildi.")
|
152 |
+
return str(output_path)
|
153 |
+
except Exception as e:
|
154 |
+
logging.exception(f"Ağ görselleştirmesi kaydedilirken hata oluştu: {e}")
|
155 |
+
return None
|
relationships.parquet
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a41341ec01d3c46cd036ac155cc8ea50d773221f34ab578f067bc21d7581f5fe
|
3 |
+
size 10289
|
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
pip install pandas numpy spacy scikit-learn networkx matplotlib plotly pyvis streamlit PyPDF2 sentence-transformers pytest pyarrow
|
2 |
+
python -m spacy download en_core_web_lg
|
reset_status.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# reset_status.py
|
2 |
+
import pandas as pd
|
3 |
+
# storage modülünü doğru import etmek için src'yi sys.path'e ekleyebilir veya PYTHONPATH ayarlayabiliriz.
|
4 |
+
# En kolayı çalıştırmadan önce PYTHONPATH ayarlamak veya geçici olarak sys.path'e eklemek.
|
5 |
+
import sys
|
6 |
+
from pathlib import Path
|
7 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
8 |
+
|
9 |
+
from src.data_management.storage import load_dataframe, save_dataframe, DOC_COLUMNS
|
10 |
+
|
11 |
+
print("Doküman durumları 'added' olarak sıfırlanıyor...")
|
12 |
+
df = load_dataframe('documents', DOC_COLUMNS)
|
13 |
+
|
14 |
+
if not df.empty:
|
15 |
+
# Sadece işlenmiş veya hata almış olanları sıfırla
|
16 |
+
reset_mask = df['status'].str.startswith('processed', na=False) | df['status'].str.contains('failed', na=False)
|
17 |
+
if reset_mask.any():
|
18 |
+
df.loc[reset_mask, 'status'] = 'added'
|
19 |
+
save_dataframe(df, 'documents')
|
20 |
+
print(f"{reset_mask.sum()} dokümanın durumu 'added' olarak sıfırlandı.")
|
21 |
+
else:
|
22 |
+
print("Durumu sıfırlanacak doküman bulunamadı ('processed' veya 'failed' durumunda olan).")
|
23 |
+
else:
|
24 |
+
print("Doküman DataFrame'i bulunamadı veya boş.")
|
run_analysis.py
ADDED
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# run_analysis.py (Ağ Analizi Metrikleri Eklendi)
|
2 |
+
import time
|
3 |
+
import pandas as pd
|
4 |
+
import sys
|
5 |
+
from pathlib import Path
|
6 |
+
import networkx as nx
|
7 |
+
import webbrowser
|
8 |
+
import logging
|
9 |
+
|
10 |
+
# src klasöründeki modüllere erişim için
|
11 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
12 |
+
|
13 |
+
from src.analysis.temporal import calculate_concept_frequencies
|
14 |
+
from src.analysis.similarity import calculate_concept_embeddings, calculate_similarity_matrix
|
15 |
+
from src.analysis.network_builder import build_concept_network
|
16 |
+
# YENİ importlar:
|
17 |
+
from src.analysis.network_analysis import get_network_analysis_results, save_network_analysis
|
18 |
+
from src.visualization.plotting import visualize_network
|
19 |
+
from src.data_management.storage import load_dataframe, save_dataframe, CONCEPT_COLUMNS, FREQUENCY_FILENAME, SIMILARITY_FILENAME, NETWORK_ANALYSIS_FILENAME # YENİ: NETWORK_ANALYSIS_FILENAME
|
20 |
+
|
21 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
22 |
+
pd.set_option('display.max_rows', 100)
|
23 |
+
pd.set_option('display.max_columns', 10)
|
24 |
+
pd.set_option('display.width', 1000)
|
25 |
+
|
26 |
+
if __name__ == "__main__":
|
27 |
+
print(">>> Analizler Çalıştırılıyor (Frekans + Benzerlik + Ağ + Metrikler + Görselleştirme) <<<")
|
28 |
+
overall_start_time = time.time()
|
29 |
+
concepts_df = None
|
30 |
+
frequency_results_df = None
|
31 |
+
similarity_results_df = None
|
32 |
+
concept_network = None # Grafı saklamak için
|
33 |
+
network_analysis_df = None # Analiz sonuçlarını saklamak için
|
34 |
+
|
35 |
+
# --- 1. Frekans Analizi ---
|
36 |
+
print("\n--- 1. Frekans Hesaplaması ---"); start_time = time.time()
|
37 |
+
# ... (önceki kodla aynı, sadece print süresi değişebilir) ...
|
38 |
+
frequency_df = calculate_concept_frequencies(time_period='YS')
|
39 |
+
if frequency_df is not None:
|
40 |
+
concepts_df = load_dataframe('concepts', CONCEPT_COLUMNS)
|
41 |
+
if not frequency_df.empty:
|
42 |
+
print(f"Toplam {len(frequency_df)} frekans kaydı hesaplandı.")
|
43 |
+
if concepts_df is not None and not concepts_df.empty:
|
44 |
+
frequency_results_df = pd.merge(frequency_df, concepts_df[['concept_id', 'name']], on='concept_id', how='left')
|
45 |
+
frequency_results_df = frequency_results_df[['concept_id', 'name', 'time_period_start', 'frequency']]
|
46 |
+
frequency_results_df.sort_values(by=['name', 'time_period_start'], inplace=True)
|
47 |
+
print("\n--- Konsept Frekansları (Yıllık) ---"); print(frequency_results_df.to_string())
|
48 |
+
save_dataframe(frequency_results_df, FREQUENCY_FILENAME)
|
49 |
+
else: print("\nKonsept isimleri yüklenemedi..."); print(frequency_df.to_string())
|
50 |
+
else: print("Frekans hesaplandı ancak sonuç boş."); save_dataframe(pd.DataFrame(columns=['concept_id', 'name', 'time_period_start', 'frequency']), FREQUENCY_FILENAME)
|
51 |
+
else: print("Frekans hesaplaması sırasında bir hata oluştu.")
|
52 |
+
print(f"--- Frekans Hesaplaması Tamamlandı. Süre: {time.time() - start_time:.2f} saniye ---")
|
53 |
+
|
54 |
+
# --- 2. Anlamsal Benzerlik Analizi ---
|
55 |
+
print("\n--- 2. Anlamsal Benzerlik Hesaplaması ---"); start_time = time.time()
|
56 |
+
# ... (önceki kodla aynı, sadece print süresi değişebilir) ...
|
57 |
+
try:
|
58 |
+
concept_embeddings = calculate_concept_embeddings(force_recalculate=False)
|
59 |
+
if concept_embeddings:
|
60 |
+
similarity_df = calculate_similarity_matrix(concept_embeddings, force_recalculate=False)
|
61 |
+
if similarity_df is not None and not similarity_df.empty:
|
62 |
+
print(f"Toplam {len(similarity_df)} konsept çifti için benzerlik hesaplandı/yüklendi.")
|
63 |
+
if concepts_df is None or concepts_df.empty: concepts_df = load_dataframe('concepts', CONCEPT_COLUMNS)
|
64 |
+
if concepts_df is not None and not concepts_df.empty:
|
65 |
+
sim_results = pd.merge(similarity_df, concepts_df[['concept_id', 'name']], left_on='concept_id_1', right_on='concept_id', how='left').rename(columns={'name': 'name_1'}).drop(columns=['concept_id'])
|
66 |
+
sim_results = pd.merge(sim_results, concepts_df[['concept_id', 'name']], left_on='concept_id_2', right_on='concept_id', how='left').rename(columns={'name': 'name_2'}).drop(columns=['concept_id'])
|
67 |
+
sim_results = sim_results[['concept_id_1', 'name_1', 'concept_id_2', 'name_2', 'similarity']]
|
68 |
+
sim_results.sort_values(by='similarity', ascending=False, inplace=True)
|
69 |
+
similarity_results_df = sim_results
|
70 |
+
print("\n--- En Benzer Konsept Çiftleri (Top 20) ---"); print(similarity_results_df.head(20).to_string(index=False))
|
71 |
+
save_dataframe(similarity_results_df, SIMILARITY_FILENAME)
|
72 |
+
else: print("\nKonsept isimleri yüklenemedi..."); print(similarity_df.sort_values(by='similarity', ascending=False).head(20).to_string(index=False))
|
73 |
+
elif similarity_df is not None: print("Benzerlik hesaplandı ancak sonuç boş."); save_dataframe(pd.DataFrame(columns=['concept_id_1', 'name_1', 'concept_id_2', 'name_2', 'similarity']), SIMILARITY_FILENAME)
|
74 |
+
except Exception as e: logging.exception("Benzerlik hesaplama sırasında beklenmedik hata oluştu.")
|
75 |
+
print(f"--- Benzerlik Hesaplaması Tamamlandı. Süre: {time.time() - start_time:.2f} saniye ---")
|
76 |
+
|
77 |
+
# --- 3. Ağ Oluşturma ---
|
78 |
+
print("\n--- 3. Konsept Ağı Oluşturma ---"); start_time = time.time()
|
79 |
+
# GÜNCELLEME: Ağ nesnesini değişkende tut
|
80 |
+
concept_network = build_concept_network(similarity_threshold=0.60)
|
81 |
+
if concept_network is not None:
|
82 |
+
print("\n--- Oluşturulan Ağ Bilgileri ---")
|
83 |
+
print(f"Düğüm Sayısı (Konseptler): {concept_network.number_of_nodes()}")
|
84 |
+
print(f"Kenar Sayısı (İlişkiler/Benzerlikler): {concept_network.number_of_edges()}")
|
85 |
+
print(f"Ağ başarıyla oluşturuldu ve kaydedildi.")
|
86 |
+
else:
|
87 |
+
print("Konsept ağı oluşturulamadı.")
|
88 |
+
print(f"--- Ağ Oluşturma Tamamlandı. Süre: {time.time() - start_time:.2f} saniye ---")
|
89 |
+
|
90 |
+
|
91 |
+
# --- YENİ: 4. Ağ Analizi (Metrik Hesaplama) ---
|
92 |
+
print("\n--- 4. Ağ Analizi Metrikleri ---"); start_time = time.time()
|
93 |
+
if concept_network is not None and concept_network.number_of_nodes() > 0:
|
94 |
+
network_analysis_df = get_network_analysis_results(concept_network)
|
95 |
+
if network_analysis_df is not None and not network_analysis_df.empty:
|
96 |
+
# Sonuçları kaydet
|
97 |
+
save_network_analysis(network_analysis_df)
|
98 |
+
print("Ağ metrikleri hesaplandı ve kaydedildi.")
|
99 |
+
# En yüksek derece merkeziyetine sahip ilk 10 konsepti göster
|
100 |
+
print("\n--- En Merkezi Konseptler (Degree Centrality Top 10) ---")
|
101 |
+
print(network_analysis_df.sort_values(by='degree_centrality', ascending=False).head(10).to_string(index=False))
|
102 |
+
else:
|
103 |
+
print("Ağ metrikleri hesaplanamadı veya sonuç boş.")
|
104 |
+
else:
|
105 |
+
print("Ağ analizi yapmak için geçerli bir ağ bulunamadı.")
|
106 |
+
print(f"--- Ağ Analizi Tamamlandı. Süre: {time.time() - start_time:.2f} saniye ---")
|
107 |
+
|
108 |
+
|
109 |
+
# --- YENİ SIRA: 5. Ağ Görselleştirme ---
|
110 |
+
print("\n--- 5. Ağ Görselleştirmesi Oluşturma ---"); start_time = time.time()
|
111 |
+
visualization_path = None
|
112 |
+
if concept_network is not None:
|
113 |
+
# GÜNCELLEME: Analiz sonuçlarını da görselleştirmeye gönderebiliriz (ileride plotting.py'ı güncelleyince)
|
114 |
+
# Şimdilik sadece grafı gönderiyoruz.
|
115 |
+
visualization_path = visualize_network(graph=concept_network, output_filename="concept_network_visualization.html")
|
116 |
+
if visualization_path:
|
117 |
+
print(f"\nBaşarılı! İnteraktif ağ görselleştirmesi oluşturuldu:\n-> {visualization_path}")
|
118 |
+
print("\nBu HTML dosyasını web tarayıcınızda açarak ağı inceleyebilirsiniz.")
|
119 |
+
else: print("Ağ görselleştirmesi oluşturulurken bir sorun oluştu.")
|
120 |
+
else: print("Ağ oluşturulamadığı için görselleştirme yapılamıyor.")
|
121 |
+
print(f"--- Ağ Görselleştirme Tamamlandı. Süre: {time.time() - start_time:.2f} saniye ---")
|
122 |
+
|
123 |
+
|
124 |
+
overall_end_time = time.time()
|
125 |
+
print(f"\n<<< Tüm İşlemler Tamamlandı. Toplam Süre: {overall_end_time - overall_start_time:.2f} saniye >>>")
|
run_extractor.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
# src klasöründeki modüllerimize erişmek için
|
3 |
+
from src.extraction.extractor import process_documents_for_extraction
|
4 |
+
|
5 |
+
if __name__ == "__main__":
|
6 |
+
print(">>> Bilgi çıkarıcı çalıştırılıyor...")
|
7 |
+
print("Not: Bu işlem dokümanların uzunluğuna ve sayısına göre biraz zaman alabilir.")
|
8 |
+
start_time = time.time()
|
9 |
+
|
10 |
+
# Ana çıkarım fonksiyonumuzu çağırıyoruz
|
11 |
+
process_documents_for_extraction()
|
12 |
+
|
13 |
+
end_time = time.time()
|
14 |
+
print(f"<<< Bilgi çıkarıcı tamamlandı. Süre: {end_time - start_time:.2f} saniye.")
|
15 |
+
print(f"Kontrol edilmesi gereken dosyalar: data/processed_data/ klasöründeki concepts.parquet, mentions.parquet, relationships.parquet ve güncellenmiş documents.parquet")
|
run_loader.py
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
# src klasöründeki modüllerimize erişmek için
|
3 |
+
from src.data_management.loaders import process_raw_documents
|
4 |
+
|
5 |
+
if __name__ == "__main__":
|
6 |
+
print(">>> Veri yükleyici çalıştırılıyor...")
|
7 |
+
start_time = time.time()
|
8 |
+
|
9 |
+
# Ana işlem fonksiyonumuzu çağırıyoruz
|
10 |
+
process_raw_documents()
|
11 |
+
|
12 |
+
end_time = time.time()
|
13 |
+
print(f"<<< Veri yükleyici tamamlandı. Süre: {end_time - start_time:.2f} saniye.")
|
14 |
+
print(f"Kontrol edilmesi gereken dosya: data/processed_data/documents.parquet")
|
similarity.py
ADDED
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/analysis/similarity.py
|
2 |
+
|
3 |
+
import pandas as pd
|
4 |
+
import numpy as np
|
5 |
+
from sentence_transformers import SentenceTransformer
|
6 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
7 |
+
import logging
|
8 |
+
from pathlib import Path
|
9 |
+
|
10 |
+
# Yerel modüller
|
11 |
+
from src.data_management import storage
|
12 |
+
|
13 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
14 |
+
|
15 |
+
# Benzerlik matrisini kaydetmek için dosya adı
|
16 |
+
SIMILARITY_FILENAME = "concept_similarities"
|
17 |
+
EMBEDDINGS_FILENAME = "concept_embeddings" # Vektörleri de kaydedebiliriz
|
18 |
+
|
19 |
+
def calculate_concept_embeddings(model_name: str = 'all-MiniLM-L6-v2', force_recalculate: bool = False) -> dict[str, np.ndarray] | None:
|
20 |
+
"""
|
21 |
+
Her konsept için ortalama embedding vektörünü hesaplar.
|
22 |
+
Mention'ların context_snippet'lerini kullanır.
|
23 |
+
Hesaplanmış embedding'leri yüklemeye çalışır, yoksa hesaplar.
|
24 |
+
|
25 |
+
Args:
|
26 |
+
model_name (str): Kullanılacak Sentence Transformer modeli.
|
27 |
+
force_recalculate (bool): Daha önce hesaplanmış olsa bile yeniden hesaplamaya zorla.
|
28 |
+
|
29 |
+
Returns:
|
30 |
+
dict[str, np.ndarray] | None: Concept ID -> Ortalama Embedding Vektörü sözlüğü veya hata durumunda None.
|
31 |
+
"""
|
32 |
+
embeddings_filepath = storage.DATA_PATH / f"{EMBEDDINGS_FILENAME}.pkl" # Pickle ile saklayalım
|
33 |
+
|
34 |
+
if not force_recalculate and embeddings_filepath.exists():
|
35 |
+
try:
|
36 |
+
embeddings = pd.read_pickle(embeddings_filepath)
|
37 |
+
logging.info(f"Önceden hesaplanmış embedding'ler '{embeddings_filepath}' dosyasından yüklendi.")
|
38 |
+
# Dosyadan yüklenen bir sözlük olmalı
|
39 |
+
if isinstance(embeddings, dict):
|
40 |
+
return embeddings
|
41 |
+
else:
|
42 |
+
logging.warning("Yüklenen embedding dosyası beklenen formatta (dict) değil. Yeniden hesaplanacak.")
|
43 |
+
except Exception as e:
|
44 |
+
logging.error(f"Embedding'ler yüklenirken hata: {e}. Yeniden hesaplanacak.")
|
45 |
+
|
46 |
+
logging.info("Konsept embedding'leri hesaplanıyor...")
|
47 |
+
mentions_df = storage.load_dataframe('mentions', storage.MENTION_COLUMNS)
|
48 |
+
|
49 |
+
if mentions_df is None or mentions_df.empty:
|
50 |
+
logging.warning("Hesaplama için mention verisi bulunamadı.")
|
51 |
+
return None
|
52 |
+
|
53 |
+
# Geçerli context snippet'i olan mention'ları al
|
54 |
+
mentions_df.dropna(subset=['context_snippet', 'concept_id'], inplace=True)
|
55 |
+
if mentions_df.empty:
|
56 |
+
logging.warning("Geçerli context snippet bulunamadı.")
|
57 |
+
return None
|
58 |
+
|
59 |
+
# Modeli yükle (ilk seferde internetten indirilebilir)
|
60 |
+
try:
|
61 |
+
model = SentenceTransformer(model_name)
|
62 |
+
logging.info(f"Sentence Transformer modeli '{model_name}' yüklendi.")
|
63 |
+
except Exception as e:
|
64 |
+
logging.exception(f"Sentence Transformer modeli '{model_name}' yüklenirken hata: {e}")
|
65 |
+
return None
|
66 |
+
|
67 |
+
# Konseptlere göre grupla
|
68 |
+
grouped_mentions = mentions_df.groupby('concept_id')['context_snippet'].apply(list)
|
69 |
+
|
70 |
+
concept_embeddings = {}
|
71 |
+
logging.info(f"{len(grouped_mentions)} konsept için embedding hesaplanacak...")
|
72 |
+
|
73 |
+
# Her konsept için embedding'leri hesapla ve ortalamasını al
|
74 |
+
for concept_id, snippets in grouped_mentions.items():
|
75 |
+
if not snippets: continue # Boş snippet listesi varsa atla
|
76 |
+
try:
|
77 |
+
# Tüm snippet'ların embedding'lerini tek seferde hesapla (daha verimli)
|
78 |
+
embeddings = model.encode(snippets, show_progress_bar=False) # İlerleme çubuğunu kapat
|
79 |
+
# Ortalama embedding'i hesapla
|
80 |
+
avg_embedding = np.mean(embeddings, axis=0)
|
81 |
+
concept_embeddings[concept_id] = avg_embedding
|
82 |
+
except Exception as e:
|
83 |
+
logging.error(f"Concept ID {concept_id} için embedding hesaplanırken hata: {e}")
|
84 |
+
continue # Bu konsepti atla
|
85 |
+
|
86 |
+
# Hesaplanan embedding'leri kaydet
|
87 |
+
try:
|
88 |
+
storage.DATA_PATH.mkdir(parents=True, exist_ok=True)
|
89 |
+
pd.to_pickle(concept_embeddings, embeddings_filepath)
|
90 |
+
logging.info(f"Hesaplanan embedding'ler '{embeddings_filepath}' dosyasına kaydedildi.")
|
91 |
+
except Exception as e:
|
92 |
+
logging.error(f"Embedding'ler kaydedilirken hata: {e}")
|
93 |
+
|
94 |
+
|
95 |
+
logging.info(f"{len(concept_embeddings)} konsept için ortalama embedding hesaplandı.")
|
96 |
+
return concept_embeddings
|
97 |
+
|
98 |
+
|
99 |
+
def calculate_similarity_matrix(concept_embeddings: dict, force_recalculate: bool = False) -> pd.DataFrame | None:
|
100 |
+
"""
|
101 |
+
Verilen embedding vektörleri arasındaki kosinüs benzerliğini hesaplar.
|
102 |
+
Hesaplanmış benzerlikleri yüklemeye çalışır, yoksa hesaplar.
|
103 |
+
|
104 |
+
Args:
|
105 |
+
concept_embeddings (dict[str, np.ndarray]): Concept ID -> Embedding Vektörü sözlüğü.
|
106 |
+
force_recalculate (bool): Daha önce hesaplanmış olsa bile yeniden hesaplamaya zorla.
|
107 |
+
|
108 |
+
Returns:
|
109 |
+
pd.DataFrame | None: 'concept_id_1', 'concept_id_2', 'similarity' sütunlarını
|
110 |
+
içeren DataFrame veya hata durumunda None.
|
111 |
+
"""
|
112 |
+
similarity_filepath = storage.DATA_PATH / f"{SIMILARITY_FILENAME}.parquet"
|
113 |
+
|
114 |
+
if not force_recalculate and similarity_filepath.exists():
|
115 |
+
try:
|
116 |
+
similarity_df = storage.load_dataframe(SIMILARITY_FILENAME, ['concept_id_1', 'concept_id_2', 'similarity'])
|
117 |
+
logging.info(f"Önceden hesaplanmış benzerlik matrisi '{similarity_filepath}' dosyasından yüklendi.")
|
118 |
+
if similarity_df is not None and not similarity_df.empty:
|
119 |
+
return similarity_df
|
120 |
+
else:
|
121 |
+
logging.warning("Yüklenen benzerlik dosyası boş veya hatalı. Yeniden hesaplanacak.")
|
122 |
+
except Exception as e:
|
123 |
+
logging.error(f"Benzerlik matrisi yüklenirken hata: {e}. Yeniden hesaplanacak.")
|
124 |
+
|
125 |
+
|
126 |
+
if not concept_embeddings:
|
127 |
+
logging.error("Benzerlik hesaplamak için embedding verisi bulunamadı.")
|
128 |
+
return None
|
129 |
+
|
130 |
+
logging.info("Konseptler arası benzerlik matrisi hesaplanıyor...")
|
131 |
+
|
132 |
+
# Sözlükten sıralı liste ve matris oluştur
|
133 |
+
concept_ids = list(concept_embeddings.keys())
|
134 |
+
embedding_matrix = np.array(list(concept_embeddings.values()))
|
135 |
+
|
136 |
+
# Boyut kontrolü
|
137 |
+
if embedding_matrix.ndim != 2 or embedding_matrix.shape[0] != len(concept_ids):
|
138 |
+
logging.error(f"Embedding matrisinin boyutları ({embedding_matrix.shape}) beklenenden farklı.")
|
139 |
+
return None
|
140 |
+
|
141 |
+
# Kosinüs benzerliğini hesapla
|
142 |
+
try:
|
143 |
+
similarity_matrix = cosine_similarity(embedding_matrix)
|
144 |
+
except Exception as e:
|
145 |
+
logging.exception(f"Kosinüs benzerliği hesaplanırken hata: {e}")
|
146 |
+
return None
|
147 |
+
|
148 |
+
# Matrisi DataFrame'e dönüştür (uzun format)
|
149 |
+
similarity_data = []
|
150 |
+
num_concepts = len(concept_ids)
|
151 |
+
for i in range(num_concepts):
|
152 |
+
for j in range(i + 1, num_concepts): # Sadece üçgenin üstünü al (j > i) ve kendini (i=j) atla
|
153 |
+
similarity_data.append({
|
154 |
+
'concept_id_1': concept_ids[i],
|
155 |
+
'concept_id_2': concept_ids[j],
|
156 |
+
'similarity': similarity_matrix[i, j]
|
157 |
+
})
|
158 |
+
|
159 |
+
similarity_df = pd.DataFrame(similarity_data)
|
160 |
+
|
161 |
+
if similarity_df.empty:
|
162 |
+
logging.warning("Hesaplama sonucu benzerlik verisi üretilemedi.")
|
163 |
+
# Boş DataFrame kaydetmeyelim, None döndürelim
|
164 |
+
return None
|
165 |
+
|
166 |
+
# Hesaplanan benzerlikleri kaydet
|
167 |
+
storage.save_dataframe(similarity_df, SIMILARITY_FILENAME)
|
168 |
+
|
169 |
+
logging.info(f"Benzerlik matrisi hesaplandı ve kaydedildi. {len(similarity_df)} çift.")
|
170 |
+
return similarity_df
|
storage.py
ADDED
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/data_management/storage.py (TÜM SABİTLERİ İÇEREN DOĞRU TAM KOD)
|
2 |
+
|
3 |
+
import pandas as pd
|
4 |
+
from pathlib import Path
|
5 |
+
import logging
|
6 |
+
import uuid
|
7 |
+
from datetime import datetime
|
8 |
+
import networkx as nx
|
9 |
+
import pickle
|
10 |
+
import string
|
11 |
+
|
12 |
+
# Temel veri klasörünün yolu
|
13 |
+
DATA_PATH = Path("data/processed_data")
|
14 |
+
# NetworkX graf dosyalarının yolu
|
15 |
+
NETWORK_PATH = Path("output/networks")
|
16 |
+
|
17 |
+
# --- TÜM GEREKLİ SABİT TANIMLARI ---
|
18 |
+
FREQUENCY_FILENAME = "analysis_concept_frequencies"
|
19 |
+
SIMILARITY_FILENAME = "analysis_concept_similarities"
|
20 |
+
NETWORK_ANALYSIS_FILENAME = "analysis_network_results"
|
21 |
+
GRAPH_FILENAME = "concept_network"
|
22 |
+
EMBEDDINGS_FILENAME = "concept_embeddings"
|
23 |
+
# ------------------------------------
|
24 |
+
|
25 |
+
# DataFrame sütun isimleri
|
26 |
+
DOC_COLUMNS = ['doc_id', 'filepath', 'publication_date', 'status', 'processed_text_path']
|
27 |
+
CONCEPT_COLUMNS = ['concept_id', 'name', 'aliases']
|
28 |
+
MENTION_COLUMNS = ['mention_id', 'doc_id', 'concept_id', 'context_snippet', 'start_char', 'end_char']
|
29 |
+
RELATIONSHIP_COLUMNS = ['relationship_id', 'source_concept_id', 'target_concept_id', 'type', 'mention_id', 'doc_id', 'sentence']
|
30 |
+
NETWORK_ANALYSIS_COLUMNS = ['concept_id', 'name', 'degree_centrality', 'betweenness_centrality', 'eigenvector_centrality', 'community_id']
|
31 |
+
|
32 |
+
# Logging ayarları
|
33 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
34 |
+
|
35 |
+
# --- DataFrame Yükleme/Kaydetme (Değişiklik yok) ---
|
36 |
+
def load_dataframe(filename: str, columns: list) -> pd.DataFrame:
|
37 |
+
filepath = DATA_PATH / f"{filename}.parquet"
|
38 |
+
if filepath.exists():
|
39 |
+
try:
|
40 |
+
df = pd.read_parquet(filepath)
|
41 |
+
logging.info(f"'{filepath}' başarıyla yüklendi.")
|
42 |
+
if columns: # Check columns only if a list is provided
|
43 |
+
for col in columns:
|
44 |
+
if col not in df.columns:
|
45 |
+
logging.warning(f"'{filepath}' dosyasında '{col}' sütunu eksik. Ekleniyor...")
|
46 |
+
df[col] = None
|
47 |
+
return df
|
48 |
+
except Exception as e:
|
49 |
+
logging.error(f"'{filepath}' yüklenirken hata oluştu: {e}")
|
50 |
+
return pd.DataFrame(columns=columns if columns else None)
|
51 |
+
else:
|
52 |
+
logging.info(f"'{filepath}' bulunamadı. Boş DataFrame oluşturuluyor.")
|
53 |
+
return pd.DataFrame(columns=columns if columns else None)
|
54 |
+
|
55 |
+
def save_dataframe(df: pd.DataFrame, filename: str):
|
56 |
+
DATA_PATH.mkdir(parents=True, exist_ok=True)
|
57 |
+
filepath = DATA_PATH / f"{filename}.parquet"
|
58 |
+
try:
|
59 |
+
for col in df.select_dtypes(include=['object']).columns:
|
60 |
+
if df[col].map(type).isin([list, dict, datetime, pd.Timestamp]).any(): continue
|
61 |
+
df[col] = df[col].where(pd.notnull(df[col]), None)
|
62 |
+
try: df[col] = df[col].astype(pd.StringDtype())
|
63 |
+
except TypeError: logging.debug(f"Sütun '{col}' StringDtype'a çevrilemedi, orijinal tip korunuyor.")
|
64 |
+
df.to_parquet(filepath, index=False)
|
65 |
+
logging.info(f"DataFrame başarıyla '{filepath}' olarak kaydedildi.")
|
66 |
+
except Exception as e:
|
67 |
+
logging.error(f"DataFrame '{filepath}' olarak kaydedilirken hata oluştu: {e}")
|
68 |
+
|
69 |
+
# --- Doküman Yönetimi (Değişiklik yok) ---
|
70 |
+
def add_document(filepath_str: str, publication_date) -> str | None:
|
71 |
+
documents_df = load_dataframe('documents', DOC_COLUMNS)
|
72 |
+
filepath_str = str(Path(filepath_str).resolve())
|
73 |
+
existing_doc = documents_df[documents_df['filepath'] == filepath_str]
|
74 |
+
if not existing_doc.empty:
|
75 |
+
existing_doc_id = existing_doc['doc_id'].iloc[0]
|
76 |
+
logging.warning(f"Doküman zaten kayıtlı: {filepath_str} (ID: {existing_doc_id})")
|
77 |
+
return str(existing_doc_id)
|
78 |
+
new_doc_id = str(uuid.uuid4())
|
79 |
+
try: pub_date_obj = pd.to_datetime(publication_date).date()
|
80 |
+
except ValueError: logging.error(f"Geçersiz tarih formatı: {publication_date}. None olarak kaydedilecek."); pub_date_obj = None
|
81 |
+
new_document_data = {'doc_id': new_doc_id, 'filepath': filepath_str, 'publication_date': pub_date_obj, 'status': 'added', 'processed_text_path': None}
|
82 |
+
new_row_df = pd.DataFrame([new_document_data])
|
83 |
+
if pub_date_obj is not None: new_row_df['publication_date'] = pd.to_datetime(new_row_df['publication_date']); dtype_dict = {'publication_date': 'datetime64[s]'}
|
84 |
+
else: dtype_dict = {}
|
85 |
+
documents_df = pd.concat([documents_df, new_row_df], ignore_index=True)
|
86 |
+
for col, dtype in dtype_dict.items():
|
87 |
+
try: documents_df[col] = documents_df[col].astype(dtype)
|
88 |
+
except TypeError: logging.warning(f"Sütun '{col}' tipi '{dtype}' olarak ayarlanamadı.")
|
89 |
+
save_dataframe(documents_df, 'documents')
|
90 |
+
logging.info(f"Yeni doküman eklendi: {filepath_str} (ID: {new_doc_id})")
|
91 |
+
return new_doc_id
|
92 |
+
|
93 |
+
def update_document_status(doc_id: str, new_status: str, text_path: str | None = None):
|
94 |
+
docs_df = load_dataframe('documents', DOC_COLUMNS)
|
95 |
+
doc_index = docs_df[docs_df['doc_id'] == doc_id].index
|
96 |
+
if not doc_index.empty:
|
97 |
+
idx = doc_index[0]
|
98 |
+
docs_df.loc[idx, 'status'] = new_status
|
99 |
+
if text_path: docs_df.loc[idx, 'processed_text_path'] = text_path
|
100 |
+
save_dataframe(docs_df, 'documents')
|
101 |
+
logging.info(f"Doküman durumu güncellendi: ID {doc_id} -> {new_status}")
|
102 |
+
else: logging.warning(f"Durumu güncellenecek doküman bulunamadı: ID {doc_id}")
|
103 |
+
|
104 |
+
# --- Konsept, Mention, İlişki Yönetimi (Değişiklik yok) ---
|
105 |
+
def add_concept(raw_name: str) -> str | None:
|
106 |
+
concepts_df = load_dataframe('concepts', CONCEPT_COLUMNS)
|
107 |
+
name = raw_name.lower().strip().strip(string.punctuation + string.whitespace)
|
108 |
+
if name.endswith("'s"): name = name[:-2].strip()
|
109 |
+
name = ' '.join(name.split())
|
110 |
+
if not name or len(name) < 2: return None
|
111 |
+
existing_concept = concepts_df[concepts_df['name'] == name]
|
112 |
+
if not existing_concept.empty: return str(existing_concept['concept_id'].iloc[0])
|
113 |
+
new_concept_id = str(uuid.uuid4()); new_concept_data = {'concept_id': new_concept_id, 'name': name, 'aliases': [raw_name]}
|
114 |
+
new_row_df = pd.DataFrame([new_concept_data]); concepts_df = pd.concat([concepts_df, new_row_df], ignore_index=True)
|
115 |
+
concepts_df['aliases'] = concepts_df['aliases'].astype('object')
|
116 |
+
save_dataframe(concepts_df, 'concepts')
|
117 |
+
logging.info(f"Yeni konsept eklendi: '{name}' (Orijinal: '{raw_name}', ID: {new_concept_id})")
|
118 |
+
return new_concept_id
|
119 |
+
|
120 |
+
def add_mention(doc_id: str, concept_id: str, context: str, start: int, end: int) -> str | None:
|
121 |
+
if concept_id is None: return None
|
122 |
+
mentions_df = load_dataframe('mentions', MENTION_COLUMNS); new_mention_id = str(uuid.uuid4())
|
123 |
+
new_mention_data = {'mention_id': new_mention_id, 'doc_id': doc_id, 'concept_id': concept_id, 'context_snippet': context[:500], 'start_char': start, 'end_char': end}
|
124 |
+
new_row_df = pd.DataFrame([new_mention_data]); mentions_df = pd.concat([mentions_df, new_row_df], ignore_index=True)
|
125 |
+
save_dataframe(mentions_df, 'mentions'); return new_mention_id
|
126 |
+
|
127 |
+
def add_relationship(source_concept_id: str, target_concept_id: str, rel_type: str, mention_id: str | None, doc_id: str, sentence: str) -> str | None:
|
128 |
+
if source_concept_id is None or target_concept_id is None: return None
|
129 |
+
relationships_df = load_dataframe('relationships', RELATIONSHIP_COLUMNS); new_relationship_id = str(uuid.uuid4())
|
130 |
+
new_relationship_data = {'relationship_id': new_relationship_id, 'source_concept_id': source_concept_id, 'target_concept_id': target_concept_id, 'type': rel_type, 'mention_id': mention_id, 'doc_id': doc_id, 'sentence': sentence[:500]}
|
131 |
+
new_row_df = pd.DataFrame([new_relationship_data]); relationships_df = pd.concat([relationships_df, new_row_df], ignore_index=True)
|
132 |
+
save_dataframe(relationships_df, 'relationships'); return new_relationship_id
|
133 |
+
|
134 |
+
# --- NetworkX Graf Yükleme/Kaydetme (Değişiklik yok) ---
|
135 |
+
def save_network(graph: nx.Graph, filename: str):
|
136 |
+
NETWORK_PATH.mkdir(parents=True, exist_ok=True); filepath = NETWORK_PATH / f"{filename}.pkl"
|
137 |
+
try:
|
138 |
+
with open(filepath, 'wb') as f: pickle.dump(graph, f)
|
139 |
+
logging.info(f"NetworkX grafı başarıyla '{filepath}' olarak kaydedildi.")
|
140 |
+
except Exception as e: logging.error(f"Graf '{filepath}' olarak kaydedilirken hata: {e}")
|
141 |
+
|
142 |
+
def load_network(filename: str) -> nx.Graph | None:
|
143 |
+
filepath = NETWORK_PATH / f"{filename}.pkl"
|
144 |
+
if filepath.exists():
|
145 |
+
try:
|
146 |
+
with open(filepath, 'rb') as f: graph = pickle.load(f)
|
147 |
+
logging.info(f"NetworkX grafı '{filepath}' başarıyla yüklendi.")
|
148 |
+
return graph
|
149 |
+
except Exception as e: logging.error(f"Graf '{filepath}' yüklenirken hata: {e}"); return nx.Graph()
|
150 |
+
else: logging.warning(f"Graf dosyası bulunamadı: '{filepath}'"); return nx.Graph()
|
temporal.py
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# src/analysis/temporal.py (Yarı ömür fonksiyonu eklendi)
|
2 |
+
|
3 |
+
import pandas as pd
|
4 |
+
import numpy as np
|
5 |
+
from scipy.optimize import curve_fit
|
6 |
+
import logging
|
7 |
+
from pathlib import Path
|
8 |
+
from datetime import datetime
|
9 |
+
|
10 |
+
# Yerel modüllerimizi içe aktaralım
|
11 |
+
from src.data_management import storage
|
12 |
+
|
13 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
14 |
+
|
15 |
+
def calculate_concept_frequencies(time_period: str = 'Y') -> pd.DataFrame | None:
|
16 |
+
"""
|
17 |
+
Konseptlerin zaman içindeki kullanım sıklıklarını hesaplar. (Önceki kodla aynı)
|
18 |
+
"""
|
19 |
+
logging.info(f"Konsept frekansları '{time_period}' periyodu için hesaplanıyor...")
|
20 |
+
mentions_df = storage.load_dataframe('mentions', storage.MENTION_COLUMNS)
|
21 |
+
documents_df = storage.load_dataframe('documents', storage.DOC_COLUMNS)
|
22 |
+
|
23 |
+
if mentions_df is None or documents_df is None:
|
24 |
+
logging.error("Mention veya Document verisi yüklenemedi. Frekans hesaplanamıyor.")
|
25 |
+
return None
|
26 |
+
if mentions_df.empty:
|
27 |
+
logging.warning("Mention verisi boş. Frekans hesaplanamıyor.")
|
28 |
+
return pd.DataFrame(columns=['concept_id', 'time_period_start', 'frequency'])
|
29 |
+
if documents_df.empty:
|
30 |
+
logging.warning("Document verisi boş. Tarih bilgisi alınamıyor, frekans hesaplanamıyor.")
|
31 |
+
return pd.DataFrame(columns=['concept_id', 'time_period_start', 'frequency'])
|
32 |
+
|
33 |
+
docs_subset = documents_df[['doc_id', 'publication_date']].copy()
|
34 |
+
try:
|
35 |
+
docs_subset['publication_date'] = pd.to_datetime(docs_subset['publication_date'], errors='coerce')
|
36 |
+
except Exception as e:
|
37 |
+
logging.error(f"Dokümanlardaki 'publication_date' sütunu datetime'a çevrilemedi: {e}")
|
38 |
+
return None
|
39 |
+
|
40 |
+
original_doc_count = len(docs_subset)
|
41 |
+
docs_subset.dropna(subset=['publication_date'], inplace=True)
|
42 |
+
valid_date_count = len(docs_subset)
|
43 |
+
if original_doc_count > valid_date_count:
|
44 |
+
logging.warning(f"{original_doc_count - valid_date_count} dokümanın geçerli yayın tarihi yok, frekans hesaplamasına dahil edilmeyecek.")
|
45 |
+
|
46 |
+
if docs_subset.empty:
|
47 |
+
logging.warning("Geçerli yayın tarihine sahip doküman bulunamadı. Frekans hesaplanamıyor.")
|
48 |
+
return pd.DataFrame(columns=['concept_id', 'time_period_start', 'frequency'])
|
49 |
+
|
50 |
+
mentions_with_dates = pd.merge(mentions_df, docs_subset, on='doc_id', how='inner')
|
51 |
+
|
52 |
+
if mentions_with_dates.empty:
|
53 |
+
logging.warning("Mention'lar ile doküman tarihleri birleştirilemedi veya sonuç boş.")
|
54 |
+
return pd.DataFrame(columns=['concept_id', 'time_period_start', 'frequency'])
|
55 |
+
|
56 |
+
logging.info(f"{len(mentions_with_dates)} mention için tarih bilgisi bulundu.")
|
57 |
+
|
58 |
+
try:
|
59 |
+
frequency_df = mentions_with_dates.groupby(
|
60 |
+
['concept_id', pd.Grouper(key='publication_date', freq=time_period)]
|
61 |
+
).size().reset_index(name='frequency')
|
62 |
+
frequency_df.rename(columns={'publication_date': 'time_period_start'}, inplace=True)
|
63 |
+
logging.info(f"Frekans hesaplaması tamamlandı. {len(frequency_df)} satır sonuç üretildi.")
|
64 |
+
frequency_df.sort_values(by=['concept_id', 'time_period_start'], inplace=True)
|
65 |
+
return frequency_df
|
66 |
+
except Exception as e:
|
67 |
+
logging.exception(f"Frekans hesaplanırken hata oluştu: {e}")
|
68 |
+
return None
|
69 |
+
|
70 |
+
# --- YENİ: Yarı Ömür Hesaplama ---
|
71 |
+
|
72 |
+
def exponential_decay(t, A, decay_rate):
|
73 |
+
"""Üstel bozulma fonksiyonu: A * exp(-decay_rate * t)."""
|
74 |
+
# Decay rate negatif olmamalı (bozunma varsayımı)
|
75 |
+
decay_rate = max(0, decay_rate) # Negatifse sıfır yap
|
76 |
+
return A * np.exp(-decay_rate * t)
|
77 |
+
|
78 |
+
def calculate_half_life(concept_id: str,
|
79 |
+
frequency_df: pd.DataFrame,
|
80 |
+
concept_name: str | None = None,
|
81 |
+
min_data_points: int = 4,
|
82 |
+
min_decay_rate: float = 1e-6) -> float | None:
|
83 |
+
"""
|
84 |
+
Verilen konsept için frekans verisine üstel bozulma modeli uygulayarak
|
85 |
+
yarı ömrü (yıl olarak) hesaplar.
|
86 |
+
|
87 |
+
Args:
|
88 |
+
concept_id (str): Hesaplanacak konseptin ID'si.
|
89 |
+
frequency_df (pd.DataFrame): calculate_concept_frequencies'ten dönen DataFrame.
|
90 |
+
('concept_id', 'time_period_start', 'frequency' sütunları olmalı).
|
91 |
+
concept_name (str | None): Loglama için konseptin adı (opsiyonel).
|
92 |
+
min_data_points (int): Yarı ömür hesaplamak için gereken minimum zaman noktası sayısı.
|
93 |
+
min_decay_rate (float): Kabul edilebilir minimum bozunma oranı (çok küçükse yarı ömür sonsuz kabul edilir).
|
94 |
+
|
95 |
+
Returns:
|
96 |
+
float | None: Hesaplanan yarı ömür (yıl olarak) veya hesaplanamazsa None.
|
97 |
+
np.inf dönebilir eğer bozunma oranı çok küçükse.
|
98 |
+
"""
|
99 |
+
log_prefix = f"Yarı Ömür ({concept_name or concept_id}):"
|
100 |
+
|
101 |
+
if frequency_df is None or frequency_df.empty:
|
102 |
+
logging.warning(f"{log_prefix} Frekans verisi boş.")
|
103 |
+
return None
|
104 |
+
|
105 |
+
# Konsepte ait veriyi filtrele ve zamana göre sırala
|
106 |
+
concept_data = frequency_df[frequency_df['concept_id'] == concept_id].sort_values(by='time_period_start').copy()
|
107 |
+
|
108 |
+
# Yeterli veri noktası var mı?
|
109 |
+
if len(concept_data) < min_data_points:
|
110 |
+
logging.info(f"{log_prefix} Yeterli veri noktası yok ({len(concept_data)} < {min_data_points}). Hesaplama yapılamıyor.")
|
111 |
+
return None
|
112 |
+
|
113 |
+
# Zamanı sayısal değere çevir (ilk yıldan itibaren geçen yıl sayısı)
|
114 |
+
try:
|
115 |
+
# İlk zaman noktasını t=0 kabul et
|
116 |
+
start_date = concept_data['time_period_start'].min()
|
117 |
+
# Zaman farkını gün olarak hesapla ve yıla çevir
|
118 |
+
concept_data['time_elapsed_years'] = (concept_data['time_period_start'] - start_date).dt.days / 365.25
|
119 |
+
except Exception as e:
|
120 |
+
logging.error(f"{log_prefix} Zaman farkı hesaplanırken hata: {e}")
|
121 |
+
return None
|
122 |
+
|
123 |
+
time_values = concept_data['time_elapsed_years'].values
|
124 |
+
frequency_values = concept_data['frequency'].values
|
125 |
+
|
126 |
+
# Frekanslar artıyor mu veya sabit mi kontrol et (basit kontrol)
|
127 |
+
# Eğer son değer ilk değerden büyükse veya tüm değerler aynıysa, bozunma yok kabul et
|
128 |
+
if frequency_values[-1] > frequency_values[0] or np.all(frequency_values == frequency_values[0]):
|
129 |
+
logging.info(f"{log_prefix} Veride belirgin bir azalma gözlenmedi. Yarı ömür hesaplanamıyor.")
|
130 |
+
return None # Veya np.inf? Şimdilik None.
|
131 |
+
|
132 |
+
# Modeli uydurmak için başlangıç tahminleri
|
133 |
+
initial_A_guess = frequency_values[0] # İlk frekans değeri
|
134 |
+
initial_lambda_guess = 0.1 # Küçük pozitif bir bozunma oranı tahmini
|
135 |
+
|
136 |
+
try:
|
137 |
+
# curve_fit ile modeli verilere uydur
|
138 |
+
params, covariance = curve_fit(
|
139 |
+
exponential_decay,
|
140 |
+
time_values,
|
141 |
+
frequency_values,
|
142 |
+
p0=[initial_A_guess, initial_lambda_guess],
|
143 |
+
bounds=([0, 0], [np.inf, np.inf]) # Parametrelerin pozitif olmasını sağla
|
144 |
+
# maxfev artırılabilir eğer "Optimal parameters not found" hatası alınırsa
|
145 |
+
)
|
146 |
+
|
147 |
+
A_fit, decay_rate_fit = params
|
148 |
+
|
149 |
+
# Bozunma oranı anlamlı mı?
|
150 |
+
if decay_rate_fit < min_decay_rate:
|
151 |
+
logging.info(f"{log_prefix} Hesaplanan bozunma oranı ({decay_rate_fit:.4f}) çok düşük. Yarı ömür sonsuz kabul ediliyor.")
|
152 |
+
return np.inf # Sonsuz yarı ömür
|
153 |
+
|
154 |
+
# Yarı ömrü hesapla: ln(2) / decay_rate
|
155 |
+
half_life_years = np.log(2) / decay_rate_fit
|
156 |
+
logging.info(f"{log_prefix} Başarıyla hesaplandı. A={A_fit:.2f}, Bozunma Oranı={decay_rate_fit:.4f}, Yarı Ömür={half_life_years:.2f} yıl.")
|
157 |
+
return half_life_years
|
158 |
+
|
159 |
+
except RuntimeError as e:
|
160 |
+
logging.warning(f"{log_prefix} Üstel bozulma modeli uydurulamadı: {e}. Yarı ömür hesaplanamıyor.")
|
161 |
+
return None
|
162 |
+
except Exception as e:
|
163 |
+
logging.exception(f"{log_prefix} Yarı ömür hesaplanırken beklenmeyen hata: {e}")
|
164 |
+
return None
|
test1.png
ADDED
![]() |