Skip to content

Commit ce1ecbf

Browse files
committed
fix: Fixes ethernal problem with FM Fact Label N!=NP
1 parent e0a2808 commit ce1ecbf

7 files changed

Lines changed: 209 additions & 56 deletions

File tree

app/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,7 @@ def load_user(user_id):
6262
logging_manager.setup_logging()
6363

6464
# CORS
65-
CORS(app, resources={
66-
r"/hubfiles/raw/*": {"origins": "https://ide.flamapy.org"}
67-
})
68-
65+
CORS(app, resources={r"/hubfiles/raw/*": {"origins": "https://ide.flamapy.org"}})
6966

7067
# Swagger API
7168
swagger_template = {

app/modules/factlabel/routes.py

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import os
1+
import json
22
import uuid
33
import logging
44
from datetime import datetime
@@ -9,61 +9,59 @@
99
from app.modules.factlabel import factlabel_bp
1010
from app.modules.hubfile.models import HubfileViewRecord
1111
from app.modules.hubfile.services import HubfileService
12-
from app.modules.factlabel.services import FactlabelService
1312

1413
logger = logging.getLogger(__name__)
1514

1615

1716
@factlabel_bp.route("/factlabel/view/<int:file_id>", methods=["GET"])
1817
def view_factlabel(file_id):
1918
file = HubfileService().get_or_404(file_id)
20-
filename = file.name
2119

22-
directory_path = os.path.join(
23-
"uploads",
24-
f"user_{file.feature_model.dataset.user_id}",
25-
f"dataset_{file.feature_model.dataset_id}",
26-
"uvl",
27-
)
20+
try:
21+
if not file.factlabel_json:
22+
return jsonify({"success": False, "error": "FactLabel not ready yet"}), 404
2823

29-
file_path = os.path.join(directory_path, filename)
24+
# 🔹 Convertir de string a dict
25+
try:
26+
content = json.loads(file.factlabel_json)
27+
except Exception:
28+
return (
29+
jsonify({"success": False, "error": "Invalid FactLabel JSON in DB"}),
30+
500,
31+
)
3032

31-
try:
32-
if os.path.exists(file_path):
33-
content = FactlabelService().get_characterization(file)
34-
# logger.info(f'JSON Content: {content}')
35-
user_cookie = request.cookies.get("view_cookie")
36-
if not user_cookie:
37-
user_cookie = str(uuid.uuid4())
33+
# --- Registro de la vista ---
34+
user_cookie = request.cookies.get("view_cookie")
35+
if not user_cookie:
36+
user_cookie = str(uuid.uuid4())
37+
38+
existing_record = HubfileViewRecord.query.filter_by(
39+
user_id=current_user.id if current_user.is_authenticated else None,
40+
file_id=file_id,
41+
view_cookie=user_cookie,
42+
).first()
3843

39-
# Check if the view record already exists for this cookie
40-
existing_record = HubfileViewRecord.query.filter_by(
44+
if not existing_record:
45+
new_view_record = HubfileViewRecord(
4146
user_id=current_user.id if current_user.is_authenticated else None,
4247
file_id=file_id,
48+
view_date=datetime.now(),
4349
view_cookie=user_cookie,
44-
).first()
50+
)
51+
db.session.add(new_view_record)
52+
db.session.commit()
4553

46-
if not existing_record:
47-
# Register file view
48-
new_view_record = HubfileViewRecord(
49-
user_id=current_user.id if current_user.is_authenticated else None,
50-
file_id=file_id,
51-
view_date=datetime.now(),
52-
view_cookie=user_cookie,
53-
)
54-
db.session.add(new_view_record)
55-
db.session.commit()
54+
# --- Preparar respuesta ---
55+
response = jsonify({"success": True, "content": content})
56+
if not request.cookies.get("view_cookie"):
57+
response = make_response(response)
58+
response.set_cookie(
59+
"view_cookie", user_cookie, max_age=60 * 60 * 24 * 365 * 2
60+
)
61+
return response
5662

57-
# Prepare response
58-
response = jsonify({"success": True, "content": content})
59-
if not request.cookies.get("view_cookie"):
60-
response = make_response(response)
61-
response.set_cookie(
62-
"view_cookie", user_cookie, max_age=60 * 60 * 24 * 365 * 2
63-
)
64-
return response
65-
else:
66-
logger.info("path doesn't exist")
67-
return jsonify({"success": False, "error": "File not found"}), 404
6863
except Exception as e:
69-
return jsonify({"success": False, "error": str(e)}), 500
64+
return (
65+
jsonify({"success": False, "error": f"Internal server error: {str(e)}"}),
66+
500,
67+
)

app/modules/hubfile/models.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
from datetime import datetime
44

55
import pytz
6-
from sqlalchemy import event
6+
from sqlalchemy import event, Text
77
from sqlalchemy.orm import joinedload, object_session
88

99
from app import db
1010
from app.modules.auth.models import User
1111
from app.modules.dataset.models import DataSet
1212
from core.managers.task_queue_manager import TaskQueueManager
1313
from dotenv import load_dotenv
14-
from urllib.parse import urlencode
1514
from flask import url_for
1615

1716
logger = logging.getLogger(__name__)
@@ -30,6 +29,7 @@ class Hubfile(db.Model):
3029
)
3130

3231
feature_model = db.relationship("FeatureModel", back_populates="hubfiles")
32+
factlabel_json = db.Column(Text, nullable=True)
3333

3434
def get_formatted_size(self):
3535
from app.modules.dataset.services import SizeService
@@ -65,7 +65,7 @@ def get_full_path(self) -> str:
6565
"uvl",
6666
self.name,
6767
)
68-
68+
6969
def get_ide_url(self) -> str:
7070
"""
7171
Devuelve la URL lista para abrir este hubfile en Flamapy IDE,
@@ -119,7 +119,7 @@ def __repr__(self):
119119

120120

121121
@event.listens_for(Hubfile, "after_insert")
122-
def hubfile_aupdated_listener(mapper, connection, target):
122+
def hubfile_after_insert_listener(mapper, connection, target):
123123
session = object_session(target)
124124

125125
hubfile_with_fm = (
@@ -131,6 +131,13 @@ def hubfile_aupdated_listener(mapper, connection, target):
131131
path = hubfile_with_fm.get_full_path()
132132

133133
task_manager = TaskQueueManager()
134+
135+
# Transformación UVL
136+
task_manager.enqueue_task(
137+
"app.modules.hubfile.tasks.transform_uvl", path=path, timeout=30
138+
)
139+
140+
# Fact Label
134141
task_manager.enqueue_task(
135-
"app.modules.hubfile.tasks.transform_uvl", path=path, timeout=300
142+
"app.modules.hubfile.tasks.compute_factlabel", hubfile_id=target.id, timeout=30
136143
)

app/modules/hubfile/tasks.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
import json
12
import logging
23
import os
4+
from app.modules.factlabel.services import FactlabelService
5+
from app.modules.hubfile.models import Hubfile
36
from flamapy.metamodels.fm_metamodel.transformations import (
47
UVLReader,
58
GlencoeWriter,
69
SPLOTWriter,
710
)
811
from flamapy.metamodels.pysat_metamodel.transformations import FmToPysat, DimacsWriter
912
import time
13+
from app import create_app, db
14+
from sqlalchemy.orm import sessionmaker
1015

1116
logger = logging.getLogger(__name__)
1217

@@ -80,3 +85,40 @@ def transform_uvl(path, retries=5, delay=2):
8085
logger.info(f"CNF file created at: {cnf_path}")
8186
except Exception as e:
8287
logger.error(f"Error in CNF transformation: {e}")
88+
89+
90+
app = create_app()
91+
SessionLocal = sessionmaker(bind=db.engine)
92+
93+
94+
def compute_factlabel(hubfile_id: int):
95+
logger.info(f"[FACTLABEL] Worker DB URL: {db.engine.url}")
96+
logger.info(f"[FACTLABEL] Starting computation for Hubfile {hubfile_id}")
97+
98+
with app.app_context():
99+
session = SessionLocal()
100+
try:
101+
hubfile = session.get(Hubfile, hubfile_id)
102+
if not hubfile:
103+
logger.warning(f"[FACTLABEL] Hubfile {hubfile_id} not found")
104+
return
105+
106+
# 👉 Generar caracterización real
107+
content = FactlabelService().get_characterization(hubfile)
108+
109+
# Guardar como string JSON (porque factlabel_json es Text)
110+
hubfile.factlabel_json = json.dumps(content)
111+
112+
session.add(hubfile)
113+
session.commit()
114+
115+
logger.info(
116+
f"[FACTLABEL] ✅ FactLabel computed and stored for Hubfile {hubfile_id}"
117+
)
118+
except Exception as e:
119+
logger.exception(
120+
f"[FACTLABEL] Error computing FactLabel for Hubfile {hubfile_id}: {e}"
121+
)
122+
session.rollback()
123+
finally:
124+
session.close()

app/modules/hubfile/templates/hubfile/view_file.html

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@
214214
alt="UVL Logo"
215215
height="24"
216216
class="ms-auto mb-2" />
217-
<pre class="m-0" style="white-space: pre-wrap;">{{ uvl_content }}</pre>
217+
<pre class="m-0" style="white-space: pre-wrap; font-size: 0.85rem;">{{ uvl_content }}</pre>
218+
218219
</div>
219220
</div>
220221

@@ -265,18 +266,35 @@
265266
.then(response => response.json())
266267
.then(data => {
267268
if (data.success) {
268-
drawFMFactLabel(data.content); // versión limpia mejorada
269+
drawFMFactLabel(data.content); // Fact Label disponible
269270
} else {
270-
console.error("Error:", data.error);
271-
d3.select("#FMFactLabelChart").text("Error loading fact label.");
271+
console.warn("FM FactLabel not yet available:", data.error);
272+
d3.select("#FMFactLabelChart")
273+
.append("text")
274+
.attr("x", "50%")
275+
.attr("y", "50%")
276+
.attr("text-anchor", "middle")
277+
.attr("dominant-baseline", "middle")
278+
.attr("font-size", "14px")
279+
.attr("fill", "#999")
280+
.text("FM FactLabel not yet available");
272281
}
273282
})
274283
.catch(error => {
275284
console.error("Fetch error:", error);
276-
d3.select("#FMFactLabelChart").text("Could not load fact label.");
285+
d3.select("#FMFactLabelChart")
286+
.append("text")
287+
.attr("x", "50%")
288+
.attr("y", "50%")
289+
.attr("text-anchor", "middle")
290+
.attr("dominant-baseline", "middle")
291+
.attr("font-size", "14px")
292+
.attr("fill", "red")
293+
.text("Error loading FM FactLabel");
277294
});
278295
}
279296

297+
280298
$(function () {
281299
const table = $('#kt_files_table').DataTable({
282300
pageLength: 5,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""empty message
2+
3+
Revision ID: e70a525896e5
4+
Revises: 001
5+
Create Date: 2025-09-13 00:02:47.603069
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import mysql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'e70a525896e5'
14+
down_revision = '001'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
with op.batch_alter_table('hubfiles', schema=None) as batch_op:
22+
batch_op.alter_column('factlabel_json',
23+
existing_type=mysql.LONGTEXT(charset='utf8mb4', collation='utf8mb4_bin'),
24+
type_=sa.Text(),
25+
existing_nullable=True)
26+
27+
# ### end Alembic commands ###
28+
29+
30+
def downgrade():
31+
# ### commands auto generated by Alembic - please adjust! ###
32+
with op.batch_alter_table('hubfiles', schema=None) as batch_op:
33+
batch_op.alter_column('factlabel_json',
34+
existing_type=sa.Text(),
35+
type_=mysql.LONGTEXT(charset='utf8mb4', collation='utf8mb4_bin'),
36+
existing_nullable=True)
37+
38+
# ### end Alembic commands ###
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import click
2+
from flask.cli import with_appcontext
3+
4+
5+
@click.command(
6+
"factlabel:generate-missing",
7+
help="Enqueue tasks to generate FactLabels for all Hubfiles that do not have one yet.",
8+
)
9+
@with_appcontext
10+
def factlabel_generate_missing():
11+
from app.modules.hubfile.models import Hubfile
12+
from core.managers.task_queue_manager import TaskQueueManager
13+
14+
click.echo(click.style("🔎 Looking for hubfiles without FactLabel...", fg="cyan"))
15+
16+
missing = Hubfile.query.filter(
17+
(Hubfile.factlabel_json.is_(None)) | (Hubfile.factlabel_json == "")
18+
).all()
19+
20+
if not missing:
21+
click.echo(click.style("✅ All hubfiles already have FactLabels!", fg="green"))
22+
return
23+
24+
click.echo(
25+
click.style(f"Found {len(missing)} hubfiles missing FactLabels.", fg="yellow")
26+
)
27+
28+
task_manager = TaskQueueManager()
29+
count_enqueued = 0
30+
31+
for hubfile in missing:
32+
try:
33+
task_manager.enqueue_task(
34+
"app.modules.hubfile.tasks.compute_factlabel",
35+
hubfile_id=hubfile.id,
36+
timeout=30, # ⏱️ límite de 30s
37+
)
38+
count_enqueued += 1
39+
click.echo(
40+
click.style(
41+
f"📤 Hubfile {hubfile.id} enqueued for FactLabel", fg="cyan"
42+
)
43+
)
44+
except Exception as e:
45+
click.echo(
46+
click.style(f"❌ Could not enqueue Hubfile {hubfile.id}: {e}", fg="red")
47+
)
48+
49+
click.echo(
50+
click.style(
51+
f"\n🎉 Enqueued {count_enqueued} jobs for FactLabel generation.", fg="green"
52+
)
53+
)

0 commit comments

Comments
 (0)