개선관리 요구사항과 Google 로그인을 완료
This commit is contained in:
parent
cc0a15b7c6
commit
2a39636163
112 changed files with 10166 additions and 527 deletions
|
|
@ -56,6 +56,10 @@ class DeliberatePracticeConflictError(RuntimeError):
|
|||
pass
|
||||
|
||||
|
||||
class DeliberatePracticeFeedbackDisabledError(PermissionError):
|
||||
"""처방/연습 원천 회기의 학습자 피드백 스냅샷이 비활성이다."""
|
||||
|
||||
|
||||
def _value(row: Mapping[str, Any], key: str, default: Any = None) -> Any:
|
||||
try:
|
||||
return row[key]
|
||||
|
|
@ -63,6 +67,14 @@ def _value(row: Mapping[str, Any], key: str, default: Any = None) -> Any:
|
|||
return default
|
||||
|
||||
|
||||
def _public_row(row: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"""API 응답에서 정책 판정 전용 내부 열을 제거한다."""
|
||||
|
||||
payload = dict(row)
|
||||
payload.pop("source_learner_feedback_enabled", None)
|
||||
return payload
|
||||
|
||||
|
||||
def _canonical_hash(payload: Mapping[str, Any]) -> str:
|
||||
serialized = json.dumps(
|
||||
payload,
|
||||
|
|
@ -202,11 +214,15 @@ async def _latest_snapshot(
|
|||
) -> Mapping[str, Any] | None:
|
||||
return await conn.fetchrow(
|
||||
"""
|
||||
SELECT snapshot_id, session_id, snapshot_no, content_hash, graph_payload,
|
||||
evidence_turn_ids, created_at
|
||||
FROM app.competency_graph_snapshot
|
||||
WHERE learner_id = $1
|
||||
ORDER BY snapshot_no DESC
|
||||
SELECT snapshot.snapshot_id, snapshot.session_id, snapshot.snapshot_no,
|
||||
snapshot.content_hash, snapshot.graph_payload,
|
||||
snapshot.evidence_turn_ids, snapshot.created_at,
|
||||
source_session.learner_feedback_enabled
|
||||
AS source_learner_feedback_enabled
|
||||
FROM app.competency_graph_snapshot snapshot
|
||||
JOIN app.sessions source_session ON source_session.id = snapshot.session_id
|
||||
WHERE snapshot.learner_id = $1
|
||||
ORDER BY snapshot.snapshot_no DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
learner_id,
|
||||
|
|
@ -595,6 +611,37 @@ async def append_learner_attempt_submission(
|
|||
"SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))",
|
||||
f"practice-attempt:{submission_id}",
|
||||
)
|
||||
prescription_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT prescription.prescription_record_id,
|
||||
prescription.session_id,
|
||||
prescription.prescription_payload,
|
||||
prescription.created_at,
|
||||
source_session.learner_feedback_enabled
|
||||
AS source_learner_feedback_enabled
|
||||
FROM app.practice_prescription prescription
|
||||
JOIN app.sessions source_session
|
||||
ON source_session.id = prescription.session_id
|
||||
WHERE prescription.learner_id = $1
|
||||
AND prescription.prescription_key = $2
|
||||
""",
|
||||
learner_id,
|
||||
prescription_id,
|
||||
)
|
||||
if prescription_row is None:
|
||||
raise DeliberatePracticeNotFoundError(
|
||||
"practice prescription not found or not visible"
|
||||
)
|
||||
if not bool(
|
||||
_value(
|
||||
prescription_row,
|
||||
"source_learner_feedback_enabled",
|
||||
True,
|
||||
)
|
||||
):
|
||||
raise DeliberatePracticeFeedbackDisabledError(
|
||||
"learner feedback was disabled for the prescription source session"
|
||||
)
|
||||
existing = await _existing_submission(
|
||||
conn,
|
||||
table="app.practice_episode_submission",
|
||||
|
|
@ -608,19 +655,6 @@ async def append_learner_attempt_submission(
|
|||
"SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))",
|
||||
f"practice-graph:{learner_id}",
|
||||
)
|
||||
prescription_row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT prescription_record_id, session_id, prescription_payload, created_at
|
||||
FROM app.practice_prescription
|
||||
WHERE learner_id = $1 AND prescription_key = $2
|
||||
""",
|
||||
learner_id,
|
||||
prescription_id,
|
||||
)
|
||||
if prescription_row is None:
|
||||
raise DeliberatePracticeNotFoundError(
|
||||
"practice prescription not found or not visible"
|
||||
)
|
||||
source_session_id = UUID(str(_value(prescription_row, "session_id")))
|
||||
session_id = practice_session_id or source_session_id
|
||||
practice_session = await _visible_session(conn, session_id)
|
||||
|
|
@ -1218,10 +1252,13 @@ async def read_deliberate_practice(
|
|||
p.activity_mode, p.scenario_variant_id, p.scenario_novelty,
|
||||
p.difficulty_level, p.prescription_payload, p.created_at,
|
||||
c.card_key, c.coach_claim, c.evidence_turn_ids, c.source_refs,
|
||||
c.uncertainty, c.counterevidence
|
||||
c.uncertainty, c.counterevidence,
|
||||
source_session.learner_feedback_enabled
|
||||
AS source_learner_feedback_enabled
|
||||
FROM app.practice_prescription p
|
||||
JOIN app.practice_coaching_card c
|
||||
ON c.coaching_card_record_id = p.coaching_card_record_id
|
||||
JOIN app.sessions source_session ON source_session.id = p.session_id
|
||||
WHERE p.learner_id = $1
|
||||
ORDER BY p.created_at, p.prescription_record_id
|
||||
""",
|
||||
|
|
@ -1231,12 +1268,19 @@ async def read_deliberate_practice(
|
|||
episodes = list(
|
||||
await conn.fetch(
|
||||
"""
|
||||
SELECT episode_submission_id, episode_key, session_id,
|
||||
progress, mastery_allowed, mastery_blockers, uncertainty,
|
||||
evidence_turn_ids, counterevidence, assessment_payload, created_at
|
||||
FROM app.practice_episode_submission
|
||||
WHERE learner_id = $1
|
||||
ORDER BY created_at, episode_submission_id
|
||||
SELECT episode.episode_submission_id, episode.episode_key,
|
||||
episode.session_id, episode.progress,
|
||||
episode.mastery_allowed, episode.mastery_blockers,
|
||||
episode.uncertainty, episode.evidence_turn_ids,
|
||||
episode.counterevidence, episode.assessment_payload,
|
||||
episode.created_at,
|
||||
source_session.learner_feedback_enabled
|
||||
AS source_learner_feedback_enabled
|
||||
FROM app.practice_episode_submission episode
|
||||
JOIN app.sessions source_session
|
||||
ON source_session.id = episode.session_id
|
||||
WHERE episode.learner_id = $1
|
||||
ORDER BY episode.created_at, episode.episode_submission_id
|
||||
""",
|
||||
target_learner_id,
|
||||
)
|
||||
|
|
@ -1285,6 +1329,19 @@ async def read_deliberate_practice(
|
|||
else []
|
||||
)
|
||||
snapshot = await _latest_snapshot(conn, target_learner_id)
|
||||
if principal.role == Role.LEARNER and any(
|
||||
not bool(
|
||||
_value(
|
||||
item,
|
||||
"source_learner_feedback_enabled",
|
||||
True,
|
||||
)
|
||||
)
|
||||
for item in (*prescriptions, *episodes, *([snapshot] if snapshot else []))
|
||||
):
|
||||
raise DeliberatePracticeFeedbackDisabledError(
|
||||
"learner feedback was disabled for a practice source session"
|
||||
)
|
||||
decision = (
|
||||
await conn.fetchrow(
|
||||
"""
|
||||
|
|
@ -1313,7 +1370,7 @@ async def read_deliberate_practice(
|
|||
).append(payload)
|
||||
episode_payloads: list[dict[str, Any]] = []
|
||||
for row in episodes:
|
||||
payload = dict(row)
|
||||
payload = _public_row(row)
|
||||
payload["attempts"] = attempts_by_episode.get(
|
||||
UUID(str(_value(row, "episode_submission_id"))), []
|
||||
)
|
||||
|
|
@ -1321,7 +1378,7 @@ async def read_deliberate_practice(
|
|||
return {
|
||||
"learner_id": target_learner_id,
|
||||
"clinical_claim_allowed": False,
|
||||
"prescriptions": [dict(item) for item in prescriptions],
|
||||
"prescriptions": [_public_row(item) for item in prescriptions],
|
||||
"episodes": episode_payloads,
|
||||
"competency_graph": (
|
||||
_value(snapshot, "graph_payload") if snapshot is not None else None
|
||||
|
|
@ -1335,6 +1392,7 @@ async def read_deliberate_practice(
|
|||
|
||||
__all__ = [
|
||||
"DeliberatePracticeConflictError",
|
||||
"DeliberatePracticeFeedbackDisabledError",
|
||||
"DeliberatePracticeNotFoundError",
|
||||
"DeliberatePracticeStateError",
|
||||
"append_learner_attempt_submission",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue