ml-visoft commited on
Commit
333a6de
·
1 Parent(s): 8011e6a

Minimal HTML GUI.

Browse files

Session is stored in db
Mocked chatGpt calls, to test async loading.
Works local and docker.

Files changed (5) hide show
  1. eval_code.py +77 -0
  2. main.py +269 -125
  3. requirements.txt +1 -0
  4. test/test_dbops.py +27 -0
  5. test/test_eval_code.py +28 -0
eval_code.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import time
3
+
4
+ """
5
+ We will handle here the code evaluation phase.
6
+
7
+ """
8
+ import json
9
+
10
+ def clean_prompt_answer(answer):
11
+ """
12
+ Chatgpt4 is ok, does not pollute the code but 3.5 encloses it in ```
13
+
14
+ :param answer:
15
+ :return:
16
+ """
17
+ cleaned = []
18
+ for l in answer.split("\n"):
19
+ if l.startswith("```"):
20
+ continue
21
+ else:
22
+ cleaned.append(l)
23
+ return "\n".join(cleaned)
24
+
25
+ def parse_chatgpt_answer(ans_text):
26
+ try:
27
+ js_answer = json.loads(ans_text)
28
+ except:
29
+ # for now we dump the error in console
30
+ import traceback
31
+ exception = traceback.format_exc()
32
+ print(exception)
33
+ return {"error":exception}
34
+ return js_answer
35
+
36
+ def eval_code_by_chatgpt(openai_client, ccode):
37
+ """
38
+ Will evaluate a piece of code using our heavily tuned prompt!
39
+
40
+ :param openai_client:
41
+ :param ccode:
42
+ :return:
43
+ """
44
+ time.sleep(3)
45
+ try:
46
+ return """[
47
+ {
48
+ "criteria": "DRY",
49
+ "explanation": "The memory allocation and initialization for ``p1``, ``p2``, and ``p3`` are repetitive. Consider creating a function like ``allocateAndInitializeMemory``."
50
+ },
51
+ {
52
+ "criteria": "SRP",
53
+ "explanation": "The ``main`` function handles memory allocation, initialization, and printing. You should separate these responsibilities into different functions like ``allocateMemory``, ``initializeData``, and ``printData``."
54
+ },
55
+ {
56
+ "criteria": "NAME",
57
+ "explanation": "``x1`` should be called ``title``, ``y1`` should be called ``author``, ``z1`` should be called ``year``, ``p1`` should be called ``titlePtr``, ``p2`` should be called ``authorPtr``, ``p3`` should be called ``yearPtr``."
58
+ }
59
+ ]"""
60
+ assert openai_client is not None
61
+ except:
62
+ import traceback
63
+ traceback.print_exc()
64
+ return {"error":"There was an error while parsing the answer. Maybe ChatGPT is overloaded?"}
65
+
66
+ def eval_the_piece_of_c_code(openai_client, ccode):
67
+ """
68
+ Main entrypoint to this module. Will be called from backend
69
+
70
+ :param ccode:
71
+ :return:
72
+ """
73
+ chatgpt_ans = eval_code_by_chatgpt(openai_client, ccode)
74
+ chatgpt_js = parse_chatgpt_answer(chatgpt_ans)
75
+ return chatgpt_js
76
+
77
+
main.py CHANGED
@@ -1,6 +1,13 @@
 
 
 
1
  from fasthtml.common import *
2
  import os
3
- import uuid
 
 
 
 
4
 
5
  OAUTH_CLIENT_ID = os.environ.get('OAUTH_CLIENT_ID')
6
  OAUTH_SCOPES = os.environ.get('OAUTH_SCOPES')
@@ -8,130 +15,267 @@ OAUTH_CLIENT_SECRET = os.environ.get('OAUTH_CLIENT_SECRET')
8
  OPENID_PROVIDER_URL = os.environ.get('OPENID_PROVIDER_URL')
9
  SPACE_HOST = os.environ.get('SPACE_HOST')
10
 
11
- app, rt = fast_app()
12
-
13
- js_hf_imports = \
14
- """
15
- {
16
- "imports": {
17
- "@huggingface/hub": "https://cdn.jsdelivr.net/npm/@huggingface/hub@0.13.0/+esm"
18
- }
19
- }
20
- """
21
-
22
- js_block_hf_auth = \
23
- """
24
- import { oauthLoginUrl, oauthHandleRedirectIfPresent } from "@huggingface/hub";
25
- console.log("huggingface env", window.huggingface);
26
- let oauthResult = localStorage.getItem("oauth");
27
- if (oauthResult) {
28
- try {
29
- oauthResult = JSON.parse(oauthResult);
30
- } catch {
31
- oauthResult = null;
32
- }
33
- }
34
-
35
- oauthResult ||= await oauthHandleRedirectIfPresent();
36
- if (oauthResult) {
37
- document.querySelector("pre").textContent = JSON.stringify(oauthResult, null, 2);
38
- localStorage.setItem("oauth", JSON.stringify(oauthResult));
39
- document.getElementById("signout").style.removeProperty("display");
40
- document.getElementById("signout").onclick = async function() {
41
- localStorage.removeItem("oauth");
42
- window.location.href = window.location.href.replace(/\?.*$/, '');
43
- window.location.reload();
44
- }
45
- } else {
46
- document.getElementById("signin").style.removeProperty("display");
47
- document.getElementById("signin").onclick = async function() {
48
- // prompt=consent to re-trigger the consent screen instead of silently redirecting
49
- window.location.href = (await oauthLoginUrl({scopes: window.huggingface.variables.OAUTH_SCOPES})) + "&prompt=consent";
50
- }
51
- }
52
- """
53
-
54
- js_block_hf_auth2 = \
55
- """
56
- import { oauthLoginUrl, oauthHandleRedirectIfPresent } from "@huggingface/hub";
57
- let oauthResult = localStorage.getItem("oauth");
58
- if (oauthResult) {
59
- try {
60
- oauthResult = JSON.parse(oauthResult);
61
- } catch {
62
- oauthResult = null;
63
- }
64
- }
65
-
66
- console.log("OAuth result", oauthResult);
67
- oauthResult ||= await oauthHandleRedirectIfPresent();
68
-
69
- if (oauthResult) {
70
- document.querySelector("pre").textContent = JSON.stringify(oauthResult, null, 2);
71
- localStorage.setItem("oauth", JSON.stringify(oauthResult));
72
- console.log("There is an oauth result", oauthResult);
73
- document.getElementById("signout").style.removeProperty("display");
74
- document.getElementById("signout").onclick = async function() {
75
- localStorage.removeItem("oauth");
76
- window.location.href = window.location.href.replace(/\?.*$/, '');
77
- window.location.reload();
78
- }
79
-
80
- } else {
81
- console.log("No OAuth result. Setting the loging button event");
82
- document.getElementById("signin").style.removeProperty("display");
83
- document.getElementById("signin").onclick = async function() {
84
- // prompt=consent to re-trigger the consent screen instead of silently redirecting
85
- window.location.href = window.hf_redirect_url;
86
- }
87
- }
88
- """
89
-
90
-
91
- def get_hf_user_data():
92
- pass
93
-
94
-
95
- @rt('/', methods="get")
96
- def get():
97
- global SPACE_HOST
98
- if "localhost" in SPACE_HOST:
99
- space_host = f"htpp://{SPACE_HOST}/"
100
- else:
101
- space_host = f"https://{SPACE_HOST}/"
102
- hf_redirect_url_func = (
103
- f"import {{ oauthLoginUrl, }} from '@huggingface/hub'; \n"
104
- f"window.hf_redirect_url = await oauthLoginUrl({{clientId:'{OAUTH_CLIENT_ID}',redirectUrl:'{space_host}',"
105
- f"scopes:'{OAUTH_SCOPES}'}}) + '&prompt=consent';\n"
106
- f"console.log(window.hf_redirect_url);")
107
-
108
- header = Head(Title("C code reviewing"),
109
- Script(src="https://unpkg.com/es-module-shims@1.7.0/dist/es-module-shims.js"),
110
- Script(js_hf_imports, type="importmap"),
111
- )
112
- content = Body(Div(P("Some content!")),
113
- A(f"Space host: {space_host}", href=space_host),
114
- Pre(""),
115
- Script(hf_redirect_url_func, type="module"),
116
- Img(src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-xl-dark.svg",
117
- alt="Sign in with Hugging Face",
118
- style="cursor: pointer; display: none;",
119
- _id="signin", name=None),
120
- Button("Sign out", _id="signout", style="display: none", name=None),
121
- A("Authenticate!", href="authorized"),
122
- Script(js_block_hf_auth2, type="module"))
123
- full_page = Html(header, content)
124
- return full_page
125
-
126
-
127
- @rt('/change')
128
- def get(): return P('Nice to be here!')
129
-
130
-
131
- @rt('/authorized', methods="get")
132
- def authorized():
133
- content = Body(P("Do we have authenticated user?"))
134
- return content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
 
137
  serve()
 
1
+ import json
2
+ import uuid
3
+
4
  from fasthtml.common import *
5
  import os
6
+ import eval_code
7
+ from datetime import datetime
8
+ from sqlite_minutils.db import Database
9
+
10
+ # the secrets. Will be loaded from HF, or for docker --env-file or from IDE
11
 
12
  OAUTH_CLIENT_ID = os.environ.get('OAUTH_CLIENT_ID')
13
  OAUTH_SCOPES = os.environ.get('OAUTH_SCOPES')
 
15
  OPENID_PROVIDER_URL = os.environ.get('OPENID_PROVIDER_URL')
16
  SPACE_HOST = os.environ.get('SPACE_HOST')
17
 
18
+ # The database. In memory bc it is efemerial on HF anyway.
19
+
20
+ global_database = Database(memory=True, memory_name="mem_db_name")
21
+ global_database_tables = global_database.t
22
+ session_state_table = global_database_tables.session_state
23
+ if session_state_table not in global_database_tables:
24
+ # session_id stored in cookies
25
+ # see EVAL_STATUS_x below for state
26
+ session_state_table.create(id=int, session_id=str, state=int, submitted=datetime, completed=datetime, answer=str, pk='id')
27
+ Session_State_cls = session_state_table.dataclass()
28
+
29
+ EVAL_STATE_NEW=0
30
+ EVAL_STATE_QUERY=1
31
+ EVAL_STATE_TIMEDOUT=2
32
+ EVAL_STATE_ANSWER=3
33
+ EVAL_STATE_ERROR=4
34
+
35
+
36
+ if "localhost" in SPACE_HOST:
37
+ # Are we hacking locally?
38
+ print("Localhost detected in SPACE_HOST. App started in debug+live mode!")
39
+ app, rt = fast_app(debug=True, live=True)
40
+ else:
41
+ app, rt = fast_app(debug=False, live=False)
42
+
43
+
44
+ CODE_AUGMENTATIONS=[
45
+ ("DRY", "Don't repeat yourself."),
46
+ ("SRP", "Single object[function] responsability"),
47
+ ("MC", "Magic constants."),
48
+ ("NAME", "Meaningful names in the code."),
49
+ ]
50
+
51
+ def format_code_review(code_js, html_id=""):
52
+ list_of_citerias = []
53
+ for caug_code, caug_txt in CODE_AUGMENTATIONS:
54
+ crit_tag = [H3(caug_code), P(caug_txt)]
55
+ list_of_citerias.extend(crit_tag)
56
+ # yeah, I know . . .
57
+ criteria_found = False
58
+ for eval_line in code_js:
59
+ if caug_code == eval_line["criteria"]:
60
+ eval_txt = Pre(eval_line["explanation"])
61
+ list_of_citerias.append(eval_txt)
62
+ criteria_found = True
63
+ if criteria_found is False:
64
+ list_of_citerias.append(P("Not infringed"))
65
+ return Div(*list_of_citerias, _id=html_id)
66
+
67
+ def generate_default_eval_section(html_id=""):
68
+ return Div(P("This is where criterias will show up once the code is evaluated"), _id=html_id)
69
+
70
+ def generate_working_eval_section(html_id=""):
71
+ return Div(P("Working . . ."), _id=html_id,
72
+ hx_get=f"/render_answer",
73
+ hx_trigger = "every 2s",
74
+ hx_swap = "outerHTML",
75
+ )
76
+
77
+ # How can I timeout? Well ... TBD.
78
+ @threaded
79
+ def call_gpt_and_store_result(pkid, code_to_check):
80
+ # print("evaluatign code")
81
+ try:
82
+ # Pesky way to get a new cursor, in a thread safe way, into the db. This code runs in another thread.
83
+ # Can we do better?
84
+ local_database = Database(memory=True, memory_name="mem_db_name")
85
+ local_cursor = local_database.t.session_state
86
+ Session_State_cls = local_cursor.dataclass()
87
+ local_sess_obj_lst = local_cursor(limit=1, where=f"id == {pkid}")
88
+ local_sess_obj = local_sess_obj_lst[0]
89
+ gpt_js_eval = eval_code.eval_the_piece_of_c_code(openai_client=None, ccode=code_to_check)
90
+ # print("done eval with ", gpt_js_eval)
91
+ # new_sess_obj = Session_State_cls(session_obj)
92
+ if "error" in gpt_js_eval:
93
+ local_sess_obj.state = EVAL_STATE_ERROR
94
+ local_sess_obj.answer = gpt_js_eval["error"]
95
+ local_sess_obj.completed = datetime.utcnow()
96
+ else:
97
+ local_sess_obj.state = EVAL_STATE_ANSWER
98
+ local_sess_obj.answer = json.dumps(gpt_js_eval)
99
+ local_sess_obj.completed = datetime.utcnow()
100
+ local_cursor.update(local_sess_obj)
101
+ except:
102
+ import traceback
103
+ traceback.print_exc()
104
+
105
+ def get_rendered_answer(session_id):
106
+ state_rows = session_state_table(limit=1, where=f"session_id == '{session_id}'", order_by="id DESC")
107
+ # print(state_rows)
108
+ if len(state_rows) <= 0:
109
+ return generate_default_eval_section(html_id="prompt_response")
110
+ state = state_rows[0]
111
+ if state.state == EVAL_STATE_ANSWER:
112
+ gpt_js_eval = json.loads(state.answer)
113
+ return format_code_review(gpt_js_eval, html_id="prompt_response")
114
+ if state.state == EVAL_STATE_QUERY:
115
+ return generate_working_eval_section(html_id="prompt_response")
116
+ return Div(P("There was an error:", P(state.answer)), _id="prompt_response")
117
+
118
+
119
+ @rt("/render_answer")
120
+ def get(session):
121
+ if 'session_id' not in session: return "No session ID"
122
+ session_id = session["session_id"]
123
+ return get_rendered_answer(session_id)
124
+
125
+ @rt("/")
126
+ def get(session):
127
+ if 'session_id' not in session:
128
+ session['session_id'] = str(uuid.uuid4())
129
+ inp = Textarea(id="ccodetoeval", name="ccodetoeval", placeholder="Enter a piece of C code", rows=20)
130
+ # eval_area = Pre("", id="prompt_response")
131
+ # eval_area = format_code_review([], html_id="prompt_response")
132
+ eval_area = generate_default_eval_section(html_id="prompt_response")
133
+ add = Form(Group(inp, Button("Generate")), hx_post="/submit_to_eval", hx_swap="outerHTML", target_id='prompt_response')
134
+ return Title('Image Generation Demo'), Main(H1('Magic Image Generation'), add, eval_area, cls='container')
135
+
136
+
137
+ @rt("/submit_to_eval", methods="post")
138
+ def post(ccodetoeval:str, session):
139
+ if 'session_id' not in session: return P("Bad call. No session ID")
140
+ session_id = session["session_id"]
141
+ session_obj = Session_State_cls(
142
+ session_id=session_id,
143
+ state=EVAL_STATE_QUERY,
144
+ submitted=datetime.utcnow(),
145
+ answer=""
146
+ )
147
+ # we insert and we get the new primary key
148
+ session_obj = session_state_table.insert(session_obj)
149
+ # will be executed in another thread with magic @threaded
150
+ call_gpt_and_store_result(session_obj.id, ccodetoeval)
151
+ return get_rendered_answer(session_id)
152
+
153
+
154
+ ## This code is for reference, for OAuth. BIG PITA, will be added later.
155
+
156
+ #
157
+ # js_hf_imports = \
158
+ # """
159
+ # {
160
+ # "imports": {
161
+ # "@huggingface/hub": "https://cdn.jsdelivr.net/npm/@huggingface/hub@0.13.0/+esm"
162
+ # }
163
+ # }
164
+ # """
165
+ #
166
+ # js_block_hf_auth = \
167
+ # """
168
+ # import { oauthLoginUrl, oauthHandleRedirectIfPresent } from "@huggingface/hub";
169
+ # console.log("huggingface env", window.huggingface);
170
+ # let oauthResult = localStorage.getItem("oauth");
171
+ # if (oauthResult) {
172
+ # try {
173
+ # oauthResult = JSON.parse(oauthResult);
174
+ # } catch {
175
+ # oauthResult = null;
176
+ # }
177
+ # }
178
+ #
179
+ # oauthResult ||= await oauthHandleRedirectIfPresent();
180
+ # if (oauthResult) {
181
+ # document.querySelector("pre").textContent = JSON.stringify(oauthResult, null, 2);
182
+ # localStorage.setItem("oauth", JSON.stringify(oauthResult));
183
+ # document.getElementById("signout").style.removeProperty("display");
184
+ # document.getElementById("signout").onclick = async function() {
185
+ # localStorage.removeItem("oauth");
186
+ # window.location.href = window.location.href.replace(/\?.*$/, '');
187
+ # window.location.reload();
188
+ # }
189
+ # } else {
190
+ # document.getElementById("signin").style.removeProperty("display");
191
+ # document.getElementById("signin").onclick = async function() {
192
+ # // prompt=consent to re-trigger the consent screen instead of silently redirecting
193
+ # window.location.href = (await oauthLoginUrl({scopes: window.huggingface.variables.OAUTH_SCOPES})) + "&prompt=consent";
194
+ # }
195
+ # }
196
+ # """
197
+ #
198
+ # js_block_hf_auth2 = \
199
+ # """
200
+ # import { oauthLoginUrl, oauthHandleRedirectIfPresent } from "@huggingface/hub";
201
+ # let oauthResult = localStorage.getItem("oauth");
202
+ # if (oauthResult) {
203
+ # try {
204
+ # oauthResult = JSON.parse(oauthResult);
205
+ # } catch {
206
+ # oauthResult = null;
207
+ # }
208
+ # }
209
+ #
210
+ # console.log("OAuth result", oauthResult);
211
+ # oauthResult ||= await oauthHandleRedirectIfPresent();
212
+ #
213
+ # if (oauthResult) {
214
+ # document.querySelector("pre").textContent = JSON.stringify(oauthResult, null, 2);
215
+ # localStorage.setItem("oauth", JSON.stringify(oauthResult));
216
+ # console.log("There is an oauth result", oauthResult);
217
+ # document.getElementById("signout").style.removeProperty("display");
218
+ # document.getElementById("signout").onclick = async function() {
219
+ # localStorage.removeItem("oauth");
220
+ # window.location.href = window.location.href.replace(/\?.*$/, '');
221
+ # window.location.reload();
222
+ # }
223
+ #
224
+ # } else {
225
+ # console.log("No OAuth result. Setting the loging button event");
226
+ # document.getElementById("signin").style.removeProperty("display");
227
+ # document.getElementById("signin").onclick = async function() {
228
+ # // prompt=consent to re-trigger the consent screen instead of silently redirecting
229
+ # window.location.href = window.hf_redirect_url;
230
+ # }
231
+ # }
232
+ # """
233
+ #
234
+ #
235
+ # def get_hf_user_data():
236
+ # pass
237
+
238
+
239
+ # @rt('/', methods="get")
240
+ # def get():
241
+ # global SPACE_HOST
242
+ # if "localhost" in SPACE_HOST:
243
+ # space_host = f"htpp://{SPACE_HOST}/"
244
+ # else:
245
+ # space_host = f"https://{SPACE_HOST}/"
246
+ # hf_redirect_url_func = (
247
+ # f"import {{ oauthLoginUrl, }} from '@huggingface/hub'; \n"
248
+ # f"window.hf_redirect_url = await oauthLoginUrl({{clientId:'{OAUTH_CLIENT_ID}',redirectUrl:'{space_host}',"
249
+ # f"scopes:'{OAUTH_SCOPES}'}}) + '&prompt=consent';\n"
250
+ # f"console.log(window.hf_redirect_url);")
251
+ #
252
+ # header = Head(Title("C code reviewing"),
253
+ # Script(src="https://unpkg.com/es-module-shims@1.7.0/dist/es-module-shims.js"),
254
+ # Script(js_hf_imports, type="importmap"),
255
+ # )
256
+ # content = Body(Div(P("Some content!")),
257
+ # A(f"Space host: {space_host}", href=space_host),
258
+ # Pre(""),
259
+ # Script(hf_redirect_url_func, type="module"),
260
+ # Img(src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-xl-dark.svg",
261
+ # alt="Sign in with Hugging Face",
262
+ # style="cursor: pointer; display: none;",
263
+ # _id="signin", name=None),
264
+ # Button("Sign out", _id="signout", style="display: none", name=None),
265
+ # A("Authenticate!", href="authorized"),
266
+ # Script(js_block_hf_auth2, type="module"))
267
+ # full_page = Html(header, content)
268
+ # return full_page
269
+
270
+
271
+ # @rt('/change')
272
+ # def get(): return P('Nice to be here!')
273
+
274
+
275
+ # @rt('/authorized', methods="get")
276
+ # def authorized():
277
+ # content = Body(P("Do we have authenticated user?"))
278
+ # return content
279
 
280
 
281
  serve()
requirements.txt CHANGED
@@ -1 +1,2 @@
1
  python-fasthtml
 
 
1
  python-fasthtml
2
+ fastsql==1.0.1
test/test_dbops.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fasthtml.common import *
2
+ from sqlite_minutils.db import Database
3
+ def test_some_sqldb_inmem():
4
+
5
+ db = Database(memory=True)
6
+ assert db is not None
7
+ db_tables = db.t
8
+ session_states = db_tables.session_states
9
+ if session_states not in db.t:
10
+ session_states.create(prompt=str, session_id=str, id=int, folder=str, pk='id')
11
+ Session_state = session_states.dataclass()
12
+ sess = Session_state(prompt="Some prompt", folder="Somewhere")
13
+ session_states.insert(sess)
14
+ session_states.insert(sess)
15
+ session_states.insert(sess)
16
+ sess2 = Session_state(prompt="Some prompt", folder="Somewhere new")
17
+ # insert AND get the newly inserted ID
18
+ g = session_states.insert(sess2)
19
+ assert g.id > 3
20
+ g.prompt = "New prompt"
21
+ # Update and check if it can be retrieved
22
+ session_states.update(g)
23
+ # retrieval
24
+ newg = session_states(where=f"id={g.id}")
25
+ assert len(newg) == 1
26
+ assert newg[0].prompt == "New prompt"
27
+
test/test_eval_code.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ import eval_code
3
+
4
+ TYPICAL_ANSWER = """[
5
+ {
6
+ "criteria": "DRY",
7
+ "explanation": "The memory allocation and initialization for ``p1``, ``p2``, and ``p3`` are repetitive. Consider creating a function like ``allocateAndInitializeMemory``."
8
+ },
9
+ {
10
+ "criteria": "SRP",
11
+ "explanation": "The ``main`` function handles memory allocation, initialization, and printing. You should separate these responsibilities into different functions like ``allocateMemory``, ``initializeData``, and ``printData``."
12
+ },
13
+ {
14
+ "criteria": "NAME",
15
+ "explanation": "``x1`` should be called ``title``, ``y1`` should be called ``author``, ``z1`` should be called ``year``, ``p1`` should be called ``titlePtr``, ``p2`` should be called ``authorPtr``, ``p3`` should be called ``yearPtr``."
16
+ }
17
+ ]"""
18
+
19
+
20
+ def test_parse_chatgpt_answer():
21
+ ans = eval_code.parse_chatgpt_answer(TYPICAL_ANSWER)
22
+ assert ans is not None
23
+ assert len(ans) > 0
24
+ an_eval = ans[0]
25
+ assert "criteria" in an_eval.keys()
26
+ assert "explanation" in an_eval.keys()
27
+
28
+