diff --git a/AraSpell.py b/archive/legacy_scripts/AraSpell.py similarity index 100% rename from AraSpell.py rename to archive/legacy_scripts/AraSpell.py diff --git a/Grammer_Rules.py b/archive/legacy_scripts/Grammer_Rules.py similarity index 100% rename from Grammer_Rules.py rename to archive/legacy_scripts/Grammer_Rules.py diff --git a/PuncAra.py b/archive/legacy_scripts/PuncAra.py similarity index 100% rename from PuncAra.py rename to archive/legacy_scripts/PuncAra.py diff --git a/gradio Spelling.py b/archive/legacy_scripts/gradio Spelling.py similarity index 100% rename from gradio Spelling.py rename to archive/legacy_scripts/gradio Spelling.py diff --git a/tests/deep_dive_expanded.json b/archive/old_tests/deep_dive_expanded.json similarity index 100% rename from tests/deep_dive_expanded.json rename to archive/old_tests/deep_dive_expanded.json diff --git a/tests/deep_dive_expanded.py b/archive/old_tests/deep_dive_expanded.py similarity index 100% rename from tests/deep_dive_expanded.py rename to archive/old_tests/deep_dive_expanded.py diff --git a/tests/deep_dive_gaps.json b/archive/old_tests/deep_dive_gaps.json similarity index 100% rename from tests/deep_dive_gaps.json rename to archive/old_tests/deep_dive_gaps.json diff --git a/tests/deep_dive_gaps.py b/archive/old_tests/deep_dive_gaps.py similarity index 100% rename from tests/deep_dive_gaps.py rename to archive/old_tests/deep_dive_gaps.py diff --git a/tests/deep_dive_output.json b/archive/old_tests/deep_dive_output.json similarity index 100% rename from tests/deep_dive_output.json rename to archive/old_tests/deep_dive_output.json diff --git a/tests/deep_dive_test.py b/archive/old_tests/deep_dive_test.py similarity index 100% rename from tests/deep_dive_test.py rename to archive/old_tests/deep_dive_test.py diff --git a/tests/gap_filling_results.json b/archive/old_tests/gap_filling_results.json similarity index 100% rename from tests/gap_filling_results.json rename to archive/old_tests/gap_filling_results.json diff --git a/tests/gap_filling_tests.py b/archive/old_tests/gap_filling_tests.py similarity index 100% rename from tests/gap_filling_tests.py rename to archive/old_tests/gap_filling_tests.py diff --git a/tests/phase0_investigation.py b/archive/old_tests/phase0_investigation.py similarity index 100% rename from tests/phase0_investigation.py rename to archive/old_tests/phase0_investigation.py diff --git a/tests/phase0_results.json b/archive/old_tests/phase0_results.json similarity index 100% rename from tests/phase0_results.json rename to archive/old_tests/phase0_results.json diff --git a/tests/phase5_investigation.py b/archive/old_tests/phase5_investigation.py similarity index 100% rename from tests/phase5_investigation.py rename to archive/old_tests/phase5_investigation.py diff --git a/tests/phase5_results.json b/archive/old_tests/phase5_results.json similarity index 100% rename from tests/phase5_results.json rename to archive/old_tests/phase5_results.json diff --git a/tests/phase8_adversarial_audit.py b/archive/old_tests/phase8_adversarial_audit.py similarity index 100% rename from tests/phase8_adversarial_audit.py rename to archive/old_tests/phase8_adversarial_audit.py diff --git a/tests/phase9_results.json b/archive/old_tests/phase9_results.json similarity index 100% rename from tests/phase9_results.json rename to archive/old_tests/phase9_results.json diff --git a/tests/phase9_validation.py b/archive/old_tests/phase9_validation.py similarity index 100% rename from tests/phase9_validation.py rename to archive/old_tests/phase9_validation.py diff --git a/tests/round2_b2_b3_live.json b/archive/old_tests/round2_b2_b3_live.json similarity index 100% rename from tests/round2_b2_b3_live.json rename to archive/old_tests/round2_b2_b3_live.json diff --git a/tests/round2_b2_b3_live.py b/archive/old_tests/round2_b2_b3_live.py similarity index 100% rename from tests/round2_b2_b3_live.py rename to archive/old_tests/round2_b2_b3_live.py diff --git a/tests/round2_live_tests.py b/archive/old_tests/round2_live_tests.py similarity index 100% rename from tests/round2_live_tests.py rename to archive/old_tests/round2_live_tests.py diff --git a/tests/round2_results.json b/archive/old_tests/round2_results.json similarity index 100% rename from tests/round2_results.json rename to archive/old_tests/round2_results.json diff --git a/ARCHITECTURAL_ANALYSIS.md b/archive/phase_reports/ARCHITECTURAL_ANALYSIS.md similarity index 100% rename from ARCHITECTURAL_ANALYSIS.md rename to archive/phase_reports/ARCHITECTURAL_ANALYSIS.md diff --git a/DEPLOYMENT_REPORT.md b/archive/phase_reports/DEPLOYMENT_REPORT.md similarity index 100% rename from DEPLOYMENT_REPORT.md rename to archive/phase_reports/DEPLOYMENT_REPORT.md diff --git a/EDITOR_REFACTOR_PLAN.md b/archive/phase_reports/EDITOR_REFACTOR_PLAN.md similarity index 100% rename from EDITOR_REFACTOR_PLAN.md rename to archive/phase_reports/EDITOR_REFACTOR_PLAN.md diff --git a/EXAMPLE_WALKTHROUGH.md b/archive/phase_reports/EXAMPLE_WALKTHROUGH.md similarity index 100% rename from EXAMPLE_WALKTHROUGH.md rename to archive/phase_reports/EXAMPLE_WALKTHROUGH.md diff --git a/FINAL_PRODUCTION_AUDIT.md b/archive/phase_reports/FINAL_PRODUCTION_AUDIT.md similarity index 100% rename from FINAL_PRODUCTION_AUDIT.md rename to archive/phase_reports/FINAL_PRODUCTION_AUDIT.md diff --git a/IMPLEMENTATION_COMPLETE.md b/archive/phase_reports/IMPLEMENTATION_COMPLETE.md similarity index 100% rename from IMPLEMENTATION_COMPLETE.md rename to archive/phase_reports/IMPLEMENTATION_COMPLETE.md diff --git a/IMPLEMENTATION_SUMMARY.md b/archive/phase_reports/IMPLEMENTATION_SUMMARY.md similarity index 100% rename from IMPLEMENTATION_SUMMARY.md rename to archive/phase_reports/IMPLEMENTATION_SUMMARY.md diff --git a/PHASE_1_COMPLETE_VERIFICATION.md b/archive/phase_reports/PHASE_1_COMPLETE_VERIFICATION.md similarity index 100% rename from PHASE_1_COMPLETE_VERIFICATION.md rename to archive/phase_reports/PHASE_1_COMPLETE_VERIFICATION.md diff --git a/PHASE_1_DELIVERY.md b/archive/phase_reports/PHASE_1_DELIVERY.md similarity index 100% rename from PHASE_1_DELIVERY.md rename to archive/phase_reports/PHASE_1_DELIVERY.md diff --git a/PHASE_2_DESIGN_PLAN.md b/archive/phase_reports/PHASE_2_DESIGN_PLAN.md similarity index 100% rename from PHASE_2_DESIGN_PLAN.md rename to archive/phase_reports/PHASE_2_DESIGN_PLAN.md diff --git a/PHASE_2_STATUS.md b/archive/phase_reports/PHASE_2_STATUS.md similarity index 100% rename from PHASE_2_STATUS.md rename to archive/phase_reports/PHASE_2_STATUS.md diff --git a/PHASE_4_COMPLETION_REPORT.md b/archive/phase_reports/PHASE_4_COMPLETION_REPORT.md similarity index 100% rename from PHASE_4_COMPLETION_REPORT.md rename to archive/phase_reports/PHASE_4_COMPLETION_REPORT.md diff --git a/PHASE_4_DOCUMENT_MANAGEMENT.md b/archive/phase_reports/PHASE_4_DOCUMENT_MANAGEMENT.md similarity index 100% rename from PHASE_4_DOCUMENT_MANAGEMENT.md rename to archive/phase_reports/PHASE_4_DOCUMENT_MANAGEMENT.md diff --git a/PHASE_4_IMPLEMENTATION_PLAN.md b/archive/phase_reports/PHASE_4_IMPLEMENTATION_PLAN.md similarity index 100% rename from PHASE_4_IMPLEMENTATION_PLAN.md rename to archive/phase_reports/PHASE_4_IMPLEMENTATION_PLAN.md diff --git a/PHASE_4_RISK_ASSESSMENT.md b/archive/phase_reports/PHASE_4_RISK_ASSESSMENT.md similarity index 100% rename from PHASE_4_RISK_ASSESSMENT.md rename to archive/phase_reports/PHASE_4_RISK_ASSESSMENT.md diff --git a/PHASE_4_TEST_PLAN.md b/archive/phase_reports/PHASE_4_TEST_PLAN.md similarity index 100% rename from PHASE_4_TEST_PLAN.md rename to archive/phase_reports/PHASE_4_TEST_PLAN.md diff --git a/PHASE_4_VERIFICATION_AUDIT.md b/archive/phase_reports/PHASE_4_VERIFICATION_AUDIT.md similarity index 100% rename from PHASE_4_VERIFICATION_AUDIT.md rename to archive/phase_reports/PHASE_4_VERIFICATION_AUDIT.md diff --git a/PHASE_5_AUTHENTICATION_PLAN.md b/archive/phase_reports/PHASE_5_AUTHENTICATION_PLAN.md similarity index 100% rename from PHASE_5_AUTHENTICATION_PLAN.md rename to archive/phase_reports/PHASE_5_AUTHENTICATION_PLAN.md diff --git a/PHASE_5_AUTH_DECISION.md b/archive/phase_reports/PHASE_5_AUTH_DECISION.md similarity index 100% rename from PHASE_5_AUTH_DECISION.md rename to archive/phase_reports/PHASE_5_AUTH_DECISION.md diff --git a/PHASE_5_COMPLETION_REPORT.md b/archive/phase_reports/PHASE_5_COMPLETION_REPORT.md similarity index 100% rename from PHASE_5_COMPLETION_REPORT.md rename to archive/phase_reports/PHASE_5_COMPLETION_REPORT.md diff --git a/PHASE_5_IMPLEMENTATION_PLAN.md b/archive/phase_reports/PHASE_5_IMPLEMENTATION_PLAN.md similarity index 100% rename from PHASE_5_IMPLEMENTATION_PLAN.md rename to archive/phase_reports/PHASE_5_IMPLEMENTATION_PLAN.md diff --git a/PHASE_6_IMPLEMENTATION_SUMMARY.md b/archive/phase_reports/PHASE_6_IMPLEMENTATION_SUMMARY.md similarity index 100% rename from PHASE_6_IMPLEMENTATION_SUMMARY.md rename to archive/phase_reports/PHASE_6_IMPLEMENTATION_SUMMARY.md diff --git a/PHASE_6_VERIFICATION_REPORT.md b/archive/phase_reports/PHASE_6_VERIFICATION_REPORT.md similarity index 100% rename from PHASE_6_VERIFICATION_REPORT.md rename to archive/phase_reports/PHASE_6_VERIFICATION_REPORT.md diff --git a/PHASE_7_COMPLETION_REPORT.md b/archive/phase_reports/PHASE_7_COMPLETION_REPORT.md similarity index 100% rename from PHASE_7_COMPLETION_REPORT.md rename to archive/phase_reports/PHASE_7_COMPLETION_REPORT.md diff --git a/PLAYWRIGHT_TEST_REPORT.md b/archive/phase_reports/PLAYWRIGHT_TEST_REPORT.md similarity index 100% rename from PLAYWRIGHT_TEST_REPORT.md rename to archive/phase_reports/PLAYWRIGHT_TEST_REPORT.md diff --git a/PROJECT_TECHNICAL_AUDIT.md b/archive/phase_reports/PROJECT_TECHNICAL_AUDIT.md similarity index 100% rename from PROJECT_TECHNICAL_AUDIT.md rename to archive/phase_reports/PROJECT_TECHNICAL_AUDIT.md diff --git a/REMOVED_AND_MODIFIED_FUNCTIONS.md b/archive/phase_reports/REMOVED_AND_MODIFIED_FUNCTIONS.md similarity index 100% rename from REMOVED_AND_MODIFIED_FUNCTIONS.md rename to archive/phase_reports/REMOVED_AND_MODIFIED_FUNCTIONS.md diff --git a/TEST_REPORT.md b/archive/phase_reports/TEST_REPORT.md similarity index 100% rename from TEST_REPORT.md rename to archive/phase_reports/TEST_REPORT.md diff --git a/TEST_RESULTS_AND_VALIDATION.md b/archive/phase_reports/TEST_RESULTS_AND_VALIDATION.md similarity index 100% rename from TEST_RESULTS_AND_VALIDATION.md rename to archive/phase_reports/TEST_RESULTS_AND_VALIDATION.md diff --git a/THEME_SYSTEM.md b/archive/phase_reports/THEME_SYSTEM.md similarity index 100% rename from THEME_SYSTEM.md rename to archive/phase_reports/THEME_SYSTEM.md diff --git a/VERIFICATION_5_DUPLICATES.md b/archive/phase_reports/VERIFICATION_5_DUPLICATES.md similarity index 100% rename from VERIFICATION_5_DUPLICATES.md rename to archive/phase_reports/VERIFICATION_5_DUPLICATES.md diff --git a/VERIFICATION_6_CURSOR.md b/archive/phase_reports/VERIFICATION_6_CURSOR.md similarity index 100% rename from VERIFICATION_6_CURSOR.md rename to archive/phase_reports/VERIFICATION_6_CURSOR.md diff --git a/VERIFICATION_7_SELECTION.md b/archive/phase_reports/VERIFICATION_7_SELECTION.md similarity index 100% rename from VERIFICATION_7_SELECTION.md rename to archive/phase_reports/VERIFICATION_7_SELECTION.md diff --git a/VERIFICATION_AUDIT_INDEX.md b/archive/phase_reports/VERIFICATION_AUDIT_INDEX.md similarity index 100% rename from VERIFICATION_AUDIT_INDEX.md rename to archive/phase_reports/VERIFICATION_AUDIT_INDEX.md diff --git a/VERIFICATION_AUDIT_SUMMARY.md b/archive/phase_reports/VERIFICATION_AUDIT_SUMMARY.md similarity index 100% rename from VERIFICATION_AUDIT_SUMMARY.md rename to archive/phase_reports/VERIFICATION_AUDIT_SUMMARY.md diff --git a/VERIFICATION_REPORT_1-4.md b/archive/phase_reports/VERIFICATION_REPORT_1-4.md similarity index 100% rename from VERIFICATION_REPORT_1-4.md rename to archive/phase_reports/VERIFICATION_REPORT_1-4.md diff --git a/atef_prompt.md b/atef_prompt.md deleted file mode 100644 index a0315588b2176a48025351a086f4378c0eb57454..0000000000000000000000000000000000000000 --- a/atef_prompt.md +++ /dev/null @@ -1,198 +0,0 @@ -\# Agent Operating Instructions - - - -\--- - - - -\## 1. Think Before Coding - -Don't assume. Surface tradeoffs. Name confusion early. - - - -\- \*\*State assumptions explicitly\*\* — if uncertain, ask rather than guess - -\- \*\*Present multiple interpretations\*\* — don't pick silently when ambiguity exists - -\- \*\*Push back when warranted\*\* — if a simpler approach exists, say so - -\- \*\*Stop when confused\*\* — name what's unclear before writing a single line - - - -\--- - - - -\## 2. Simplicity First - -Minimum code that solves the problem. Nothing speculative. - - - -\- No features beyond what was asked - -\- No abstractions for single-use code - -\- No "flexibility" that wasn't requested - -\- No error handling for impossible scenarios - -\- If 200 lines could be 50, rewrite it - - - -\*\*Test:\*\* Would a senior engineer call this overcomplicated? If yes, simplify. - - - -\--- - - - -\## 3. Surgical Changes - -Touch only what you must. Clean up only your own mess. - - - -When editing existing code: - -\- Don't "improve" adjacent code, comments, or formatting - -\- Don't refactor things that aren't broken - -\- Match existing style, even if you'd do it differently - -\- If you notice unrelated dead code, \*\*mention it — don't delete it\*\* - - - -When your changes create orphans: - -\- Remove imports/variables/functions that \*\*your\*\* changes made unused - -\- Leave pre-existing dead code unless explicitly asked to remove it - - - -\*\*Test:\*\* Every changed line should trace directly to the user's request. - - - -\--- - - - -\## 4. Goal-Driven Execution - -Define success criteria. Loop until verified. Never mark done without proof. - - - -Transform vague tasks into verifiable goals: - - - -| Instead of... | Transform to... | - -|----------------------|------------------------------------------------------| - -| "Add validation" | "Write tests for invalid inputs, then make them pass"| - -| "Fix the bug" | "Write a test that reproduces it, then make it pass" | - -| "Refactor X" | "Ensure tests pass before and after" | - - - -For multi-step tasks, state a brief plan upfront: - -1. \[Step] → verify: \[check] - -2\. \[Step] → verify: \[check] - -3\. \[Step] → verify: \[check] - -\*\*Test:\*\* Would a staff engineer approve this before you mark it done? - - - -\--- - - - -\## 5. Subagent Strategy - -Use subagents to keep the main context window clean. - - - -\- Offload research, exploration, and parallel analysis to subagents - -\- One focused task per subagent — no bundling - -\- For complex problems, throw more compute at it via parallel subagents - -\- Synthesize results back into the main thread; don't chain noise - - - -\--- - - - -\## 6. Autonomous Bug Fixing - -When given a bug report: fix it. No hand-holding required. - - - -\- Point at logs, errors, failing tests — then resolve them - -\- Go fix failing CI without being asked how - -\- Zero context-switching required from the user - - - -\--- - - - -\## 7. Self-Improvement Loop - -After any correction, extract the pattern and prevent recurrence. - - - -\- Update `tasks/lessons.md` with: what went wrong, why, and the rule that prevents it - -\- Review `tasks/lessons.md` at session start for the current project - -\- Ruthlessly iterate until the mistake rate on that class of error drops to zero - - - -\--- - - - -\## Task Execution Protocol - - - -1\. \*\*Plan first\*\* — write plan to `tasks/todo.md` with checkable items - -2\. \*\*Check in\*\* — verify plan before starting implementation - -3\. \*\*Track progress\*\* — mark items complete as you go - -4\. \*\*Explain changes\*\* — high-level summary at each step - -5\. \*\*Document results\*\* — add a review section to `tasks/todo.md` - -6\. \*\*Capture lessons\*\* — update `tasks/lessons.md` after any correction - diff --git a/audit_ckl.json b/audit_ckl.json deleted file mode 100644 index 344408e22819a73daeb7b383f92d6f4f7cb5bfa2..0000000000000000000000000000000000000000 --- a/audit_ckl.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "C.1 Supabase configured": { - "passed": true, - "detail": "" - }, - "C.2 Supabase client in frontend": { - "passed": true, - "detail": "" - }, - "C.3 DB tables referenced in code": { - "passed": true, - "detail": "Found: ['documents', 'summaries', 'settings']" - }, - "C.4 RLS blocks unauthenticated": { - "passed": true, - "detail": "HTTP 401" - }, - "C.5 Invalid JWT blocked": { - "passed": true, - "detail": "HTTP 401" - }, - "K.1u health concurrent": { - "passed": true, - "detail": "1/1 ok, avg=0.59s, max=0.59s, err=0%" - }, - "K.3u health concurrent": { - "passed": true, - "detail": "3/3 ok, avg=0.65s, max=0.74s, err=0%" - }, - "K.5u health concurrent": { - "passed": true, - "detail": "5/5 ok, avg=0.79s, max=0.99s, err=0%" - }, - "K.1u analyze concurrent": { - "passed": true, - "detail": "1/1 ok, avg=4.4s, max=4.4s" - }, - "K.3u analyze concurrent": { - "passed": true, - "detail": "3/3 ok, avg=8.5s, max=12.2s" - }, - "L.1 API handles missing 'text' field": { - "passed": true, - "detail": "" - }, - "L.2 long text": { - "passed": false, - "detail": "HTTPSConnectionPool(host='bayan10-bayan-api.hf.space', port=443): Read timed out. (read timeout=60)" - }, - "L.3 XSS prevention": { - "passed": false, - "detail": "HTTPSConnectionPool(host='bayan10-bayan-api.hf.space', port=443): Read timed out. (read timeout=60)" - }, - "L.4 SQL injection": { - "passed": false, - "detail": "HTTPSConnectionPool(host='bayan10-bayan-api.hf.space', port=443): Read timed out. (read timeout=30)" - }, - "L.5 CORS configured": { - "passed": false, - "detail": "CORS: " - } -} \ No newline at end of file diff --git a/audit_results.json b/audit_results.json deleted file mode 100644 index cf8e6f63aac8e3afa6a5b87b71d046e236a9ea0b..0000000000000000000000000000000000000000 --- a/audit_results.json +++ /dev/null @@ -1,187 +0,0 @@ -{ - "total": 45, - "passed": 39, - "failed": 6, - "results": { - "I.1 /api/health status": { - "passed": true, - "detail": "HTTP 200" - }, - "I.2 /api/health schema": { - "passed": true, - "detail": "" - }, - "I.3 spelling loaded": { - "passed": false, - "detail": "" - }, - "I.4 grammar loaded": { - "passed": false, - "detail": "" - }, - "I.5 punctuation loaded": { - "passed": false, - "detail": "" - }, - "I.6 summarization loaded": { - "passed": true, - "detail": "" - }, - "I.7 /api/spelling status": { - "passed": true, - "detail": "" - }, - "I.8 /api/spelling returns corrected": { - "passed": true, - "detail": "['corrected_text', 'original_text', 'status']" - }, - "I.9 /api/grammar status": { - "passed": true, - "detail": "" - }, - "I.10 /api/grammar schema": { - "passed": true, - "detail": "['corrected_text', 'original_text', 'status']" - }, - "I.11 /api/punctuation status": { - "passed": true, - "detail": "" - }, - "I.12 /api/punctuation schema": { - "passed": true, - "detail": "" - }, - "I.13 /api/punctuation adds marks": { - "passed": true, - "detail": "" - }, - "I.14 /api/summarize status": { - "passed": true, - "detail": "" - }, - "I.15 /api/summarize returns summary": { - "passed": true, - "detail": "['original_length', 'status', 'summary', 'summary_length']" - }, - "I.16 summary shorter than input": { - "passed": true, - "detail": "" - }, - "I.17 /api/analyze status": { - "passed": true, - "detail": "" - }, - "I.18 /api/analyze schema": { - "passed": true, - "detail": "" - }, - "I.19 /api/analyze empty text handling": { - "passed": true, - "detail": "" - }, - "I.20 /api/analyze invalid JSON handling": { - "passed": true, - "detail": "" - }, - "G.spell 'الطقص' → 'الطقس'": { - "passed": false, - "detail": "got: 'الط قص'" - }, - "G.spell 'المدرسه' → 'المدرسة'": { - "passed": true, - "detail": "got: 'المدرسة'" - }, - "G.spell 'الأجتماع' → 'الاجتماع'": { - "passed": false, - "detail": "got: 'الأج اجتماع'" - }, - "G.gram 'المهندسون يعملوا' contains 'يعملون'": { - "passed": true, - "detail": "got: 'المهندسون يعملون'" - }, - "G.punc 'هل تعرف أين المكان' has '؟'": { - "passed": true, - "detail": "got: 'هل تعرف أين المكان؟'" - }, - "G.punc 'ما أجمل هذا المنظر' has '!'": { - "passed": true, - "detail": "got: 'ما أجمل هذا المنظر!'" - }, - "H.1 pipeline returns suggestions": { - "passed": true, - "detail": "2 suggestions" - }, - "H.1 latency < 120s": { - "passed": true, - "detail": "5.8s" - }, - "H.1 offsets correct": { - "passed": true, - "detail": "" - }, - "H.1 no overlaps": { - "passed": true, - "detail": "" - }, - "H.2 pipeline returns suggestions": { - "passed": true, - "detail": "4 suggestions" - }, - "H.2 latency < 120s": { - "passed": true, - "detail": "16.3s" - }, - "H.2 offsets correct": { - "passed": true, - "detail": "" - }, - "H.2 no overlaps": { - "passed": false, - "detail": "" - }, - "H.3 pipeline returns suggestions": { - "passed": true, - "detail": "5 suggestions" - }, - "H.3 latency < 120s": { - "passed": true, - "detail": "8.5s" - }, - "H.3 offsets correct": { - "passed": true, - "detail": "" - }, - "H.3 no overlaps": { - "passed": true, - "detail": "" - }, - "J.1 health latency": { - "passed": true, - "detail": "0.62s" - }, - "J.2 analyze short text latency": { - "passed": true, - "detail": "4.8s" - }, - "J.3 summarize latency": { - "passed": true, - "detail": "2.2s" - }, - "M.1 HF Space running": { - "passed": true, - "detail": "" - }, - "M.2 environment": { - "passed": true, - "detail": "" - }, - "M.3 Supabase configured": { - "passed": true, - "detail": "" - }, - "M.4 frontend loads": { - "passed": true, - "detail": "87732 bytes" - } - } -} \ No newline at end of file diff --git a/build.py b/build.py deleted file mode 100644 index 2ffae699535eea23fcebdff16506662912811082..0000000000000000000000000000000000000000 --- a/build.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Build script for Vercel deployment. -Injects Supabase credentials from environment variables into index.html meta tags. - -Usage: - Set SUPABASE_URL and SUPABASE_ANON_KEY environment variables, then run: - python build.py -""" - -import os -import sys - -def main(): - html_path = os.path.join('src', 'index.html') - - if not os.path.exists(html_path): - print(f"❌ {html_path} not found") - sys.exit(1) - - supabase_url = os.environ.get('SUPABASE_URL', '') - supabase_anon_key = os.environ.get('SUPABASE_ANON_KEY', '') - - if not supabase_url or not supabase_anon_key: - print("⚠️ Warning: SUPABASE_URL or SUPABASE_ANON_KEY not set. Auth will run in offline mode.") - - with open(html_path, 'r', encoding='utf-8') as f: - html = f.read() - - # Inject Supabase credentials into meta tags - html = html.replace( - '', - f'' - ) - html = html.replace( - '', - f'' - ) - - # Remove development-only SDK scripts that won't exist in production - html = html.replace('\n', '') - html = html.replace('\r\n', '') - html = html.replace('\n', '') - html = html.replace('\r\n', '') - - with open(html_path, 'w', encoding='utf-8') as f: - f.write(html) - - print(f"✅ Supabase credentials injected into {html_path}") - if supabase_url: - print(f" SUPABASE_URL: {supabase_url[:40]}...") - print(f" Dev SDK scripts removed") - -if __name__ == '__main__': - main() diff --git a/check_dependencies.py b/check_dependencies.py deleted file mode 100644 index cfb36bc77a8ba2d773730420ba33e7b14c4587a4..0000000000000000000000000000000000000000 --- a/check_dependencies.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Check if all required dependencies are installed.""" - -import sys - -required_packages = { - 'flask': 'Flask', - 'flask_cors': 'flask-cors', - 'transformers': 'transformers', - 'torch': 'torch', - 'sentencepiece': 'sentencepiece', -} - -missing = [] -for module, package in required_packages.items(): - try: - __import__(module) - print(f"✓ {package} is installed") - except ImportError: - print(f"✗ {package} is NOT installed") - missing.append(package) - -if missing: - print(f"\nMissing packages: {', '.join(missing)}") - print("Install them with: pip install -r requirements.txt") - sys.exit(1) -else: - print("\nAll dependencies are installed!") - sys.exit(0) - diff --git a/exhaustive_check.py b/exhaustive_check.py deleted file mode 100644 index 49c46c5e775f1a486f3570447fe0466231506ea6..0000000000000000000000000000000000000000 --- a/exhaustive_check.py +++ /dev/null @@ -1,362 +0,0 @@ -""" -ULTRA-EXHAUSTIVE: Every single bullet point from the plan. -Fixes false negatives from v1. -""" -import re, os, glob - -def load(p): - return open(p, encoding='utf-8').read() - -html = load('src/index.html') -tokens = load('src/css/tokens.css') -base = load('src/css/base.css') -comp = load('src/css/components.css') -editor = load('src/js/editor.js') -ui = load('src/js/ui.js') -fmt = load('src/js/format.js') -docs_ui = load('src/js/documents-cloud/documents-ui.js') -renderer = load('src/js/renderer.js') -selection_js = load('src/js/selection.js') - -all_js = '' -for f in glob.glob('src/js/**/*.js', recursive=True): - all_js += load(f) + '\n' -all_css = tokens + base + comp - -done = 0 -miss = 0 -items = [] - -def c(name, result): - global done, miss - if result: - done += 1 - items.append(('DONE', name)) - else: - miss += 1 - items.append(('MISS', name)) - -def section(t): - items.append(('SEC', t)) - -# ═══════════════════════════════════════════ -section('PHASE 1: tokens.css') -# ═══════════════════════════════════════════ -c('--shadow-xs', '--shadow-xs' in tokens) -c('--shadow-glow (primary-tinted)', '--shadow-glow' in tokens) -c('--transition-spring (cubic-bezier)', '--transition-spring' in tokens and 'cubic-bezier' in tokens) -c('--gradient-primary', '--gradient-primary' in tokens) -c('--gradient-surface', '--gradient-surface' in tokens) -c('--radius-xl: 1.5rem', '--radius-xl' in tokens and '1.5rem' in tokens) -c('--color-skeleton', '--color-skeleton' in tokens or 'skeleton' in base) - -section('PHASE 1: base.css') -c('Custom scrollbar webkit', '::-webkit-scrollbar' in base) -c('Custom scrollbar firefox (thin)', 'scrollbar-width: thin' in base) -c('Dark/light scrollbar theming', 'data-theme' in base or ':root' in base) -c('scroll-behavior: smooth', 'scroll-behavior: smooth' in base) -c('::selection highlight', '::selection' in base) -c('Focus-visible outlines', 'focus-visible' in (base + comp)) -c('--spacing-section / section-gap', '--spacing-section' in tokens or 'section' in tokens.lower()) -c('@keyframes shimmer', '@keyframes shimmer' in base) -c('.skeleton class', '.skeleton' in base) -c('Button press scale(0.97)', 'scale(0.97)' in base) - -section('PHASE 1: components.css — Navigation') -c('Glassmorphism blur(16px) saturate(180%)', 'blur(16px) saturate(180%)' in comp) -c('Bottom border glow on scroll', 'nav-scrolled' in comp) -c('Active nav link indicator', 'nav-link' in comp or 'nav' in comp.lower()) - -section('PHASE 1: components.css — Buttons') -c('Gradient-accent transition', 'transition' in comp) -c('Press state scale(0.97)', 'scale(0.97)' in (base + comp)) -c('Disabled opacity + cursor:not-allowed + desaturation', 'cursor: not-allowed' in comp and 'grayscale' in comp) -c('Focus-visible ring', 'focus-visible' in comp) -c('Feedback state (pulse/flash)', 'pulse' in comp.lower() or '@keyframes' in comp) - -section('PHASE 1: components.css — Cards') -c('Card hover translateY + glow shadow', 'translateY' in comp and 'shadow-glow' in comp) -c('Feature icon pulse on hover', 'pulse' in comp.lower()) - -section('PHASE 1: components.css — Modals') -c('Modal slide-up entrance @keyframes modalSlideUp', '@keyframes modalSlideUp' in comp) - -section('PHASE 1: components.css — Toast') -c('Toast component exists', 'toast' in comp.lower()) -c('Toast icons: .toast--success', '.toast--success' in comp) -c('Toast icons: .toast--warning', '.toast--warning' in comp) -c('Toast icons: .toast--error', '.toast--error' in comp) - -section('PHASE 1: components.css — Skeleton') -c('.skeleton shimmer animation', '.skeleton' in base and 'shimmer' in base) - -section('PHASE 1: components.css — Empty States') -c('.empty-state component in CSS', '.empty-state' in comp) -c('Applied to empty doc list (JS)', 'empty-state' in docs_ui) - -section('PHASE 1: components.css — Confirm Dialog') -c('.confirm-dialog CSS', '.confirm-dialog' in comp) -c('showConfirmDialog() in HTML', 'showConfirmDialog' in html) - -section('PHASE 1: components.css — Bottom Sheet') -c('Bottom sheet transitions', 'bottom-sheet' in comp or 'bottom-sheet' in html) -c('Drag handle visual', 'handle' in comp.lower() or 'drag' in html.lower()) - -section('PHASE 1: components.css — Pricing') -c('.pricing-glow active plan', '.pricing-glow' in comp) -c('.beta-shimmer animation', '.beta-shimmer' in comp and '@keyframes betaShimmer' in comp) -c('Coming soon blur/opacity', 'opacity' in comp) - -# ═══════════════════════════════════════════ -section('PHASE 2: Brand Identity') -# ═══════════════════════════════════════════ -c('Favicon SVG exists', os.path.exists('src/favicon.svg')) -c('Nav logo SVG (book icon + gradient)', 'grad1' in html) -c('Footer logo consistent (same SVG)', html.count('grad1') >= 2) -c('Wordmark text-gradient everywhere', html.count('text-gradient') >= 3) -c('SVG icons present throughout', html.count(' 20) - -# ═══════════════════════════════════════════ -section('PHASE 3: Landing Page — Hero') -# ═══════════════════════════════════════════ -c('Subheadline: تدقيق القرآن', '\u062a\u062f\u0642\u064a\u0642 \u0627\u0644\u0642\u0631\u0622\u0646' in html) -c('Subheadline: تحويل اللهجات', '\u062a\u062d\u0648\u064a\u0644 \u0627\u0644\u0644\u0647\u062c\u0627\u062a' in html) -c('← arrows on hero CTA', '\u2190 \u0627\u0628\u062f\u0623' in html) -c('٧ أدوات (not ٨)', '\u0667 \u0623\u062f\u0648\u0627\u062a' in html and '\u0668 \u0623\u062f\u0648\u0627\u062a' not in html) -c('Stats min-width styling', 'min-w' in html or 'min-width' in html or 'text-center' in html) -c('Floating badge animation', 'animate' in html.lower() or 'fade' in html.lower()) - -section('PHASE 3: Landing Page — Features Preview') -c('Feature cards present', 'feature' in html.lower()) -c('← arrow on features CTA', '\u2190 \u0627\u0643\u062a\u0634\u0641' in html) -c('Equal-height cards (flex)', 'flex' in html.lower()) - -section('PHASE 3: Landing Page — How It Works') -c('Step numbers', 'step' in html.lower() or '\u0661' in html) -c('← arrow on How It Works CTA', '\u2190 \u062c\u0631\u0651\u0628' in html) - -# ═══════════════════════════════════════════ -section('PHASE 4: Features Page') -# ═══════════════════════════════════════════ -c('page-features section exists', 'page-features' in html) -c('Bayyinah CTA with ↗', '\u2197' in html) -c('8 feature sections', html.count('feature-detail-section') >= 7 or html.count('feature-item') >= 7) - -# ═══════════════════════════════════════════ -section('PHASE 5: Pricing Page') -# ═══════════════════════════════════════════ -c('pricing-glow in HTML', 'pricing-glow' in html) -c('beta-shimmer in HTML', 'beta-shimmer' in html) -c('تدقيق النص القرآني in pricing', '\u062a\u062f\u0642\u064a\u0642 \u0627\u0644\u0646\u0635 \u0627\u0644\u0642\u0631\u0622\u0646\u064a' in html) -c('تحويل اللهجات إلى الفصحى in pricing', '\u062a\u062d\u0648\u064a\u0644 \u0627\u0644\u0644\u0647\u062c\u0627\u062a \u0625\u0644\u0649 \u0627\u0644\u0641\u0635\u062d\u0649' in html) -c('Pricing CTA ← arrow', '\u2190' in html) -c('Coming soon label', '\u0642\u0631\u064a\u0628' in html) - -# ═══════════════════════════════════════════ -section('PHASE 6.1: Editor Toolbar') -# ═══════════════════════════════════════════ -c('macOS dots (red/yellow/green)', 'dot--red' in html and 'dot--yellow' in html and 'dot--green' in html) -c('Red dot tooltip: مسح المحرر', '\u0645\u0633\u062d \u0627\u0644\u0645\u062d\u0631\u0631' in html) -c('Yellow dot tooltip: طي لوحة', '\u0637\u064a \u0644\u0648\u062d\u0629' in html) -c('Green dot tooltip: توسيع المحرر', '\u062a\u0648\u0633\u064a\u0639 \u0627\u0644\u0645\u062d\u0631\u0631' in html) -c('Red dot → showConfirmDialog (not window.confirm)', 'showConfirmDialog' in html) -c('Tab labels (كتابة, تلخيص, تحويل)', '\u0643\u062a\u0627\u0628\u0629' in html and '\u062a\u0644\u062e\u064a\u0635' in html) - -section('PHASE 6.2: Format Toolbar') -c('Bold tooltip غامق', '\u063a\u0627\u0645\u0642' in html) -c('Italic tooltip مائل', '\u0645\u0627\u0626\u0644' in html) -c('Underline tooltip تحته خط', '\u062a\u062d\u062a\u0647 \u062e\u0637' in html) -c('Undo tooltip تراجع', '\u062a\u0631\u0627\u062c\u0639' in html) -c('Redo tooltip إعادة', '\u0625\u0639\u0627\u062f\u0629' in html) -c('Dropdown smooth animation CSS', 'translateY(-8px)' in comp or 'transform' in comp) -c('Active item highlight', 'active' in fmt.lower()) -c('Keyboard nav ArrowDown/ArrowUp', 'ArrowDown' in fmt and 'ArrowUp' in fmt) -c('Close on click outside', 'closeAllFmtDropdowns' in fmt) -c('Close on Escape', "'Escape'" in fmt) -c('COLOR_PALETTE swatches', 'COLOR_PALETTE' in fmt) -c('Color reset to default (removeFormat)', 'removeFormat' in fmt) - -section('PHASE 6.3: Editor Surface') -c('Placeholder with instructions', 'data-placeholder' in html) - -section('PHASE 6.4: Suggestion Popover') -c('Placement: off-screen clipping prevention', 'innerWidth' in editor or 'innerHeight' in editor) -c('اختر التصحيح المناسب hint', '\u0627\u062e\u062a\u0631 \u0627\u0644\u062a\u0635\u062d\u064a\u062d' in html) -c('Escape للإغلاق hint', 'Escape' in html) -c('تجاهل dismiss button', '\u062a\u062c\u0627\u0647\u0644' in html) - -section('PHASE 6.5: Suggestion Sidebar') -c('Empty: نصك ممتاز', '\\u0646\\u0635\\u0643' in ui or '\u0646\u0635\u0643 \u0645\u0645\u062a\u0627\u0632' in ui) -c('تطبيق الكل count (N)', 'countLabel' in ui or '(\u0660' in ui or 'count' in ui.lower()) -c('Score ring (score-circle)', 'score-circle' in html) -c('Shimmer skeleton in analysis', 'skeleton' in ui) - -section('PHASE 6.6: Editor Footer Stats') -c('char-count', 'char-count' in html) -c('sentence-count', 'sentence-count' in html) -c('reading-time', 'reading-time' in html) -c('Word goal', 'word-goal' in html or 'wordGoal' in all_js) -c('Save toast', '\\u062a\\u0645 \\u0627\\u0644\\u062d\\u0641\\u0638' in all_js or '\u062a\u0645 \u0627\u0644\u062d\u0641\u0638' in all_js) -c('Copy toast', '\\u062a\\u0645 \\u0627\\u0644\\u0646\\u0633\\u062e' in all_js or '\u062a\u0645 \u0627\u0644\u0646\u0633\u062e' in (html + all_js)) -c('Import error toast', 'error' in html.lower() and 'import' in html.lower()) -c('Export toast', 'export' in html.lower() and ('toast' in html.lower() or 'showToast' in html)) - -section('PHASE 6.7: Documents Panel') -c('Docs empty state icon', 'empty-state' in docs_ui) -c('Docs search input', 'docs-search' in html or 'search' in docs_ui.lower()) -c('Docs delete → showConfirmDialog', 'showConfirmDialog' in docs_ui) - -section('PHASE 6.8: Summarize Panel') -c('Summary loading: جاري توليد', '\u062c\u0627\u0631\u064a \u062a\u0648\u0644\u064a\u062f' in html) -c('Summary error: حدث خطأ', '\u062d\u062f\u062b \u062e\u0637\u0623' in html) -c('Summary mode toggle', 'summary-mode' in html) -c('Summary stats', 'summary-stats' in html) -c('Summary copy button', 'copySummary' in html) -c('Summary export dropdown', 'exportSummaryAs' in html) - -section('PHASE 6.9: Dialect Panel') -c('Dialect char counter', 'dialect-char-count' in html) -c('Dialect loading: جاري التحويل', '\u062c\u0627\u0631\u064a \u0627\u0644\u062a\u062d\u0648\u064a\u0644' in html) -c('Dialect error: حدث خطأ أثناء التحويل', '\u062d\u062f\u062b \u062e\u0637\u0623 \u0623\u062b\u0646\u0627\u0621 \u0627\u0644\u062a\u062d\u0648\u064a\u0644' in html) -c('Dialect timeout: انتهى وقت الانتظار', '\u0627\u0646\u062a\u0647\u0649 \u0648\u0642\u062a \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631' in html) -c('Dialect copy + apply buttons', 'copyDialectResult' in html and 'applyDialectResult' in html) - -section('PHASE 6.10: Quran Modal') -# Fix: search within verifyQuranText function specifically -quran_func_start = html.find('async function verifyQuranText') -quran_func = html[quran_func_start:quran_func_start+5000] if quran_func_start >= 0 else '' -c('Quran entrance animation (modalSlideUp)', 'modalSlideUp' in comp) -c('Ctrl+Q shortcut (KeyQ)', 'KeyQ' in html) -c('Escape closes', 'Escape' in html) -c('Copy verified text', '\u062a\u0645 \u0646\u0633\u062e \u0627\u0644\u0646\u0635 \u0627\u0644\u0645\u062f\u0642\u0642' in html) -c('Apply verified text', '\u062a\u0645 \u062a\u0637\u0628\u064a\u0642 \u0627\u0644\u0646\u0635 \u0627\u0644\u0642\u0631\u0622\u0646\u064a' in html) -c('Copy translation', '\u062a\u0645 \u0646\u0633\u062e \u0627\u0644\u062a\u0631\u062c\u0645\u0629' in html) -c('Language dropdown', 'quran-lang' in html) -c('Quran error: catch block', 'catch' in quran_func) -c('Quran error: timeout message', '\u0627\u0646\u062a\u0647\u0649 \u0648\u0642\u062a' in quran_func) -c('Quran loading indicator', '\u062c\u0627\u0631\u064a \u0627\u0644\u0628\u062d\u062b' in html) - -section('PHASE 6.11: Mobile Components') -c('Bottom sheet (suggestions)', 'bottom-sheet' in html) -c('Mobile drawer', 'mobile-drawer' in html) -c('Mobile menu button', 'mobile-menu-btn' in html) -c('Touch targets ≥ 44px', 'min-height: 44px' in (all_css + html) or '44px' in html or 'touch' in comp.lower()) - -# ═══════════════════════════════════════════ -section('PHASE 7.1: Auth Flows') -# ═══════════════════════════════════════════ -c('Auth gate modal', 'auth-gate' in html) -c('Google sign-in', 'google' in all_js.lower() or 'Google' in html) -c('Guest flow', '\u0627\u0644\u062a\u062c\u0631\u0628\u0629' in html or 'guest' in all_js.lower()) -c('Offline banner', 'offline-banner' in html) - -section('PHASE 7.2: Document Flows') -c('Doc save toast', '\\u062a\\u0645 \\u0627\\u0644\\u062d\\u0641\\u0638' in all_js or '\u062a\u0645 \u0627\u0644\u062d\u0641\u0638' in all_js) -c('Doc delete → custom dialog', 'showConfirmDialog' in docs_ui) - -section('PHASE 7.3: Summary Flow') -c('Summary loading state', '\u062c\u0627\u0631\u064a \u062a\u0648\u0644\u064a\u062f' in html) - -section('PHASE 7.4: Settings — Auto-sync') -c('localStorage used', 'localStorage' in all_js) -c('Theme persisted', 'theme' in html.lower() and 'localStorage' in html) - -section('PHASE 7.5: Refresh/Restore') -c('bayan_editor_draft', 'bayan_editor_draft' in editor) -c('Dismissed words persist', '_saveDismissedWords' in editor or 'dismissedWords' in editor.lower()) - -section('PHASE 7.6: Error States') -c('/api/analyze error → toast', 'showToast' in editor) -c('/api/summarize error → panel', '\u062d\u062f\u062b \u062e\u0637\u0623' in html) -c('/api/dialect error → catch', '\u062d\u062f\u062b \u062e\u0637\u0623 \u0623\u062b\u0646\u0627\u0621 \u0627\u0644\u062a\u062d\u0648\u064a\u0644' in html) -c('/api/quran error → catch', 'catch' in quran_func) -c('Network delay indicator (10s)', 'longerTimer' in editor or '10000' in editor) -c('Offline banner', 'offline-banner' in html) - -section('PHASE 7.7: Empty States') -c('Editor placeholder', 'data-placeholder' in html) -c('Documents empty state', 'empty-state' in docs_ui) -c('Suggestions: نصك ممتاز', '\\u0646\\u0635\\u0643' in ui) -c('Summary: instructions before generation', '\u062a\u0644\u062e\u064a\u0635' in html) -c('Dialect: hidden until result', 'is-hidden' in html) - -# ═══════════════════════════════════════════ -section('PHASE 8: Responsive Design') -# ═══════════════════════════════════════════ -c('@media queries', '@media' in all_css) -c('Mobile breakpoint 768px', '768px' in all_css or '768px' in html) -c('Bottom sheet for mobile', 'bottom-sheet' in html) -c('Mobile drawer smooth', 'mobile-drawer' in html) -c('Mobile menu toggle', 'mobile-menu-btn' in html) -c('Typography scaling on mobile', 'text-4xl' in html or 'text-3xl' in html or 'font-size' in all_css) - -# ═══════════════════════════════════════════ -section('PHASE 9: Global Polish') -# ═══════════════════════════════════════════ -head_section = html.split('')[0] -c('Meta desc: القرآن', '\u0627\u0644\u0642\u0631\u0622\u0646' in head_section) -c('Meta desc: اللهجات', '\u0627\u0644\u0644\u0647\u062c\u0627\u062a' in head_section) -c('404 arrow: → العودة (back=correct)', '\u2192 \u0627\u0644\u0639\u0648\u062f\u0629' in html) -c('Scroll-to-top button', 'scroll-top-btn' in html) -c('Toast error type used', "'error'" in (html + all_js)) -c('Toast warning type used', "'warning'" in (html + all_js)) -c('Toast success type used', "'success'" in (html + all_js)) -c('showConfirmDialog replaces window.confirm', 'showConfirmDialog' in html and 'window.confirm' not in html) - -# ═══════════════════════════════════════════ -section('ARCHITECTURAL SAFETY') -# ═══════════════════════════════════════════ -c('renderer.js preserved (render())', 'function render' in renderer or 'render' in renderer) -c('selection.js preserved (saveSelection)', 'saveSelection' in selection_js) -c('selection.js preserved (restoreSelection)', 'restoreSelection' in selection_js) -c('No React', 'react' not in html.lower() and 'React' not in html) -c('No Vue', 'vue' not in html.lower() and 'Vue' not in html) -c('No Angular', 'angular' not in html.lower() and 'Angular' not in html) -c('Core: getEditorText()', 'getEditorText' in editor) -c('Core: /api/analyze', '/api/analyze' in editor) -c('Core: render()', 'render(' in editor) -c('Core: setEditorHTML()', 'setEditorHTML' in editor) -c('Core: restoreSelection()', 'restoreSelection' in editor) - -# ═══════════════════════════════════════════ -section('6 DESIGN DECISIONS') -# ═══════════════════════════════════════════ -c('D1: ← on forward CTAs', '\u2190 \u0627\u0628\u062f\u0623' in html and '\u2190 \u0627\u0643\u062a\u0634\u0641' in html and '\u2190 \u062c\u0631\u0651\u0628' in html) -c('D2: Quran+Dialect in Pricing', '\u062a\u062f\u0642\u064a\u0642 \u0627\u0644\u0646\u0635 \u0627\u0644\u0642\u0631\u0622\u0646\u064a' in html and '\u062a\u062d\u0648\u064a\u0644 \u0627\u0644\u0644\u0647\u062c\u0627\u062a' in html) -c('D3: ٧ أدوات', '\u0667 \u0623\u062f\u0648\u0627\u062a' in html) -c('D4: Shimmer skeletons', '@keyframes shimmer' in base) -c('D5: macOS dots + Arabic tooltips', '\u0645\u0633\u062d \u0627\u0644\u0645\u062d\u0631\u0631' in html) -c('D6: Auto-sync (no settings panel)', 'localStorage' in all_js) - -# ═══════════════════════════════════════════ -# Check for window.confirm still used (should be 0) -section('REGRESSION: No window.confirm') -confirm_count = html.count('window.confirm') -c('window.confirm removed (0 instances)', confirm_count == 0) -if confirm_count > 0: - for i, line in enumerate(html.split('\n'), 1): - if 'window.confirm' in line: - items.append(('MISS', f' → Still at line {i}: {line.strip()[:80]}')) - miss += 1 - -# ═══════════════════════════════════════════ -# PRINT RESULTS -# ═══════════════════════════════════════════ -print() -for typ, name in items: - if typ == 'SEC': - print(f'\n{"="*60}') - print(f' {name}') - print(f'{"="*60}') - elif typ == 'DONE': - print(f' \u2705 {name}') - else: - print(f' \u274c MISS: {name}') - -print(f'\n{"="*60}') -print(f' TOTAL: {done} DONE / {miss} MISSING out of {done+miss}') -if miss == 0: - print(' \U0001f389 EVERYTHING IS IMPLEMENTED!') -else: - print(f' \u26a0\ufe0f {miss} items need attention') -print(f'{"="*60}') diff --git a/find_offsets.py b/find_offsets.py deleted file mode 100644 index 85591b120f9c8afa8511dcfc851f7b6e639d1c4a..0000000000000000000000000000000000000000 --- a/find_offsets.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -# find_offsets.py - Find correct character offsets for the test text - -text = "ذهبو الى المدرسة ثم ذهبو الى البيت ثم ذهبو مرة اخرى" - -print(f"Text: {text}") -print(f"Length: {len(text)} characters\n") - -# Find all occurrences of "ذهبو" -word = "ذهبو" -print(f'Finding all occurrences of "{word}":\n') - -start = 0 -occurrence = 1 -offsets = [] - -while True: - pos = text.find(word, start) - if pos == -1: - break - end = pos + len(word) - offsets.append((pos, end)) - print(f"Occurrence {occurrence}: [{pos}:{end}] = '{text[pos:end]}'") - start = pos + 1 - occurrence += 1 - -print(f"\nCorrect test offsets:") -print([{"start": start, "end": end, "original": "ذهبو", "correction": "ذهبوا", "type": "spelling"} for start, end in offsets]) diff --git a/gap_check.py b/gap_check.py deleted file mode 100644 index 9ad6479157dca31411e6f4500c0ebc175702f29b..0000000000000000000000000000000000000000 --- a/gap_check.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -HONEST gap analysis: Plan vs Actually Implemented -""" -import re, os - -def load(path): - return open(path, encoding='utf-8').read() - -html = load('src/index.html') -tokens = load('src/css/tokens.css') -base = load('src/css/base.css') -comp = load('src/css/components.css') -editor = load('src/js/editor.js') -ui = load('src/js/ui.js') -fmt = load('src/js/format.js') -docs = load('src/js/documents-cloud/documents-ui.js') - -done = 0 -miss = 0 - -def check(name, result): - global done, miss - if result: - done += 1 - print(f' \u2705 {name}') - else: - miss += 1 - print(f' \u274c MISSING: {name}') - -print('=' * 70) -print('PHASE 1: tokens.css') -print('=' * 70) -check('--shadow-xs', '--shadow-xs' in tokens) -check('--shadow-glow', '--shadow-glow' in tokens) -check('--transition-spring', '--transition-spring' in tokens) -check('--gradient-primary', '--gradient-primary' in tokens) -check('--gradient-surface', '--gradient-surface' in tokens) -check('--radius-xl', '--radius-xl' in tokens) -check('--color-skeleton', '--color-skeleton' in tokens or 'skeleton' in base) - -print() -print('PHASE 1: base.css') -check('smooth scroll', 'scroll-behavior: smooth' in base) -check('custom scrollbar', '::-webkit-scrollbar' in base) -check('::selection', '::selection' in base) -check('focus-visible', 'focus-visible' in base) -check('shimmer keyframes', '@keyframes shimmer' in base) -check('.skeleton class', '.skeleton' in base) -check('button scale(0.97)', 'scale(0.97)' in base) - -print() -print('PHASE 1: components.css') -check('Nav glassmorphism saturate', 'saturate(180%)' in comp) -check('Nav bottom border glow on scroll', 'nav-scrolled' in comp or 'border-glow' in comp or 'scrolled' in comp.lower()) -check('Active nav underline indicator', 'nav-link' in comp and ('underline' in comp or 'active' in comp.lower())) -check('Button disabled state', 'cursor: not-allowed' in comp) -check('Button focus-visible ring', 'focus-visible' in comp) -check('Card hover translate-y + glow', '.card-hover' in comp) -check('Feature icon pulse hover', 'pulse' in comp.lower() or '@keyframes' in comp) -check('Modal slideUp entrance', '@keyframes modalSlideUp' in comp) -check('Toast icons per type', '.toast--success' in comp) -check('.empty-state component', '.empty-state' in comp) -check('.confirm-dialog', '.confirm-dialog' in comp) -check('.pricing-glow', '.pricing-glow' in comp) -check('.beta-shimmer', '.beta-shimmer' in comp) -check('Bottom sheet drag handle', 'drag-handle' in comp or 'sheet-handle' in comp or 'sheet__handle' in comp) - -print() -print('=' * 70) -print('PHASE 3: LANDING PAGE') -print('=' * 70) -check('Hero mentions Quran', '\u062a\u062f\u0642\u064a\u0642 \u0627\u0644\u0642\u0631\u0622\u0646' in html) -check('Hero mentions dialects', '\u062a\u062d\u0648\u064a\u0644 \u0627\u0644\u0644\u0647\u062c\u0627\u062a' in html) -check('\u0667 \u0623\u062f\u0648\u0627\u062a', '\u0667 \u0623\u062f\u0648\u0627\u062a' in html) -check('Hero CTA \u2190', '\u2190 \u0627\u0628\u062f\u0623' in html) -check('Features CTA \u2190', '\u2190 \u0627\u0643\u062a\u0634\u0641' in html) -check('How It Works CTA \u2190', '\u2190 \u062c\u0631\u0651\u0628' in html) - -print() -print('=' * 70) -print('PHASE 5: PRICING') -print('=' * 70) -check('pricing-glow in HTML', 'pricing-glow' in html) -check('beta-shimmer in HTML', 'beta-shimmer' in html) -check('Quran in pricing', '\u062a\u062f\u0642\u064a\u0642 \u0627\u0644\u0646\u0635 \u0627\u0644\u0642\u0631\u0622\u0646\u064a' in html) -check('Dialect in pricing', '\u062a\u062d\u0648\u064a\u0644 \u0627\u0644\u0644\u0647\u062c\u0627\u062a \u0625\u0644\u0649 \u0627\u0644\u0641\u0635\u062d\u0649' in html) - -print() -print('=' * 70) -print('PHASE 6: EDITOR') -print('=' * 70) -check('6.1 Red dot tooltip', '\u0645\u0633\u062d \u0627\u0644\u0645\u062d\u0631\u0631' in html) -check('6.1 showConfirmDialog', 'showConfirmDialog' in html) -check('6.2 Dropdowns close outside', "closeAllFmtDropdowns" in fmt) -check('6.2 Dropdowns Escape', 'Escape' in fmt) -check('6.2 Dropdown keyboard nav (arrow keys)', 'ArrowDown' in fmt or 'ArrowUp' in fmt) -check('6.2 Color reset to default', 'reset' in fmt.lower() or 'default' in fmt.lower()) -check('6.5 Apply All count', 'countLabel' in ui) -check('6.5 Shimmer skeleton', 'skeleton' in ui) -check('6.7 Docs empty icon', 'empty-state' in docs) -check('6.7 Docs delete dialog', 'showConfirmDialog' in docs) -check('6.10 Quran modal animation class', 'modal-animate' in html or 'modalSlideUp' in html or 'quran-modal' in comp) - -print() -print('=' * 70) -print('PHASE 7: EDGE CASES') -print('=' * 70) -check('7.6 Analyze error toast', 'showToast' in editor and ('\u062a\u0639\u0630' in editor or '\\u062a\\u0639\\u0630' in editor)) -check('7.6 Network delay indicator (10s)', 'taking longer' in editor.lower() or '\u0623\u0637\u0648\u0644' in editor or 'longerTimer' in editor) - -print() -print('=' * 70) -print('PHASE 9: GLOBAL POLISH') -print('=' * 70) -check('Meta Quran', '\u0627\u0644\u0642\u0631\u0622\u0646' in html.split('')[0]) -check('Meta dialects', '\u0627\u0644\u0644\u0647\u062c\u0627\u062a' in html.split('')[0]) -check('Footer Quran link', '\u062a\u062f\u0642\u064a\u0642 \u0627\u0644\u0642\u0631\u0622\u0646' in html) -check('Footer Dialect link', '\u062a\u062d\u0648\u064a\u0644 \u0627\u0644\u0644\u0647\u062c\u0627\u062a' in html) -check('404 arrow', '\u2192 \u0627\u0644\u0639\u0648\u062f\u0629' in html) -check('Toast types fixed (warning)', "'warning'" in html) - -# Count all toasts missing types -import re -toasts_html = re.findall(r"showToast\('([^']+)'\)", html) -toasts_no_type = [t for t in toasts_html if t.startswith('\u2713') or t.startswith('\u062a\u0645')] -# These should default to success which is fine - -print() -print('=' * 70) -print(f'TOTAL: {done} DONE / {miss} MISSING') -print('=' * 70) diff --git a/inference_hf.py b/inference_hf.py deleted file mode 100644 index f2d396340be2f8fa1f5ba15cf6238c96a1ead730..0000000000000000000000000000000000000000 --- a/inference_hf.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Example usage: - from bayan_inference_hf import BayanConverter - converter = BayanConverter() - result = converter.convert("عايز اشتكي من موظف في فرعكم") - print(result) -""" - -from transformers import AutoTokenizer, AutoModelForSeq2SeqLM -import torch - -class BayanConverter: - PREFIX = "حوّل إلى الفصحى: " - REPO_ID = "bayan10/dialect-to-msa-model" # ← الموديل على HuggingFace Hub - - def __init__(self, model_path: str = None, device: str = None): - """ - model_path: لو None، بيحمّل من HuggingFace Hub (bayan10/dialect-to-msa-model) - لو حددت مسار محلي، بيحمّل من هناك بدل كده - """ - self.device = device or ("cuda" if torch.cuda.is_available() else "cpu") - source = model_path or self.REPO_ID - - print(f"Loading model from '{source}' on {self.device}...") - self.tokenizer = AutoTokenizer.from_pretrained(source) - self.model = AutoModelForSeq2SeqLM.from_pretrained(source).to(self.device) - self.model.eval() - print("Ready.") - - def convert(self, dialect_text: str, num_beams: int = 4) -> str: - """تحويل جملة عامية واحدة إلى الفصحى الحديثة.""" - input_text = self.PREFIX + dialect_text - inputs = self.tokenizer( - input_text, - return_tensors="pt", - max_length=128, - truncation=True, - ).to(self.device) - - with torch.no_grad(): - outputs = self.model.generate( - **inputs, - max_length=128, - num_beams=num_beams, - early_stopping=True, - no_repeat_ngram_size=3, - ) - return self.tokenizer.decode(outputs[0], skip_special_tokens=True) - - def convert_batch(self, texts: list[str], num_beams: int = 4) -> list[str]: - """تحويل مجموعة جمل دفعة واحدة (أسرع من واحدة واحدة).""" - inputs_list = [self.PREFIX + t for t in texts] - inputs = self.tokenizer( - inputs_list, - return_tensors="pt", - max_length=128, - truncation=True, - padding=True, - ).to(self.device) - - with torch.no_grad(): - outputs = self.model.generate( - **inputs, - max_length=128, - num_beams=num_beams, - early_stopping=True, - no_repeat_ngram_size=3, - ) - return self.tokenizer.batch_decode(outputs, skip_special_tokens=True) diff --git a/inspect_decoder.py b/inspect_decoder.py deleted file mode 100644 index 5b1a6b75fa617c27933e7037efde214d4ded83cf..0000000000000000000000000000000000000000 --- a/inspect_decoder.py +++ /dev/null @@ -1,29 +0,0 @@ - -import torch -from pathlib import Path - -model_path = Path(r"e:\تقليد جرامرلي\BAYAN\models\Spelling\Model\Spelling RAW.pt") - -try: - print(f"Loading {model_path}...") - checkpoint = torch.load(model_path, map_location="cpu") - state_dict = checkpoint["model_state_dict"] - - print("Checking decoder keys...") - found_bert = False - for k in state_dict.keys(): - if k.startswith("decoder.bert"): - print(f"Found: {k}") - found_bert = True - break - - if not found_bert: - print("No decoder.bert keys found.") - # Check what IS in decoder - for k in state_dict.keys(): - if k.startswith("decoder.") and not "cls" in k: - print(f"Decoder key: {k}") - break - -except Exception as e: - print(f"Error: {e}") diff --git a/inspect_model.py b/inspect_model.py deleted file mode 100644 index 7b7ba7dbdfa2b53ce2f1aaa0d1190d937a8c98c7..0000000000000000000000000000000000000000 --- a/inspect_model.py +++ /dev/null @@ -1,33 +0,0 @@ - -import torch -import sys -from pathlib import Path - -model_path = Path(r"e:\تقليد جرامرلي\BAYAN\models\Spelling\Model\Spelling RAW.pt") - -try: - print(f"Loading {model_path}...") - # Load on CPU to be safe and fast - state_dict = torch.load(model_path, map_location="cpu") - - if isinstance(state_dict, dict): - print("Loaded as dict. Keys:") - keys = list(state_dict.keys()) - print(f"Total keys: {len(keys)}") - print("First 20 keys:") - for k in keys[:20]: - print(k) - - # Check for specific architectures - if any(k.startswith("bert.") for k in keys): - print("\nLooks like BERT.") - elif any(k.startswith("roberta.") for k in keys): - print("\nLooks like RoBERTa.") - elif any(k.startswith("transformer.") for k in keys): - print("\nLooks like a Transformer.") - else: - print(f"Loaded object type: {type(state_dict)}") - print(state_dict) - -except Exception as e: - print(f"Error: {e}") diff --git a/inspect_model_details.py b/inspect_model_details.py deleted file mode 100644 index 30625d725565028552915043c8b3ab11a90cf67c..0000000000000000000000000000000000000000 --- a/inspect_model_details.py +++ /dev/null @@ -1,53 +0,0 @@ - -import torch -import sys -from pathlib import Path - -model_path = Path(r"e:\تقليد جرامرلي\BAYAN\models\Spelling\Model\Spelling RAW.pt") - -try: - print(f"Loading {model_path}...") - checkpoint = torch.load(model_path, map_location="cpu") - state_dict = checkpoint["model_state_dict"] - - # 1. Vocab and Hidden Size - if "encoder.embeddings.word_embeddings.weight" in state_dict: - shape = state_dict["encoder.embeddings.word_embeddings.weight"].shape - print(f"Vocab Size: {shape[0]}") - print(f"Hidden Size: {shape[1]}") - else: - print("Embeddings weight not found at 'encoder.embeddings.word_embeddings.weight'") - - # Try finding any embedding weight - for k in state_dict.keys(): - if "embeddings.word_embeddings.weight" in k: - print(f"Found embedding: {k} -> {state_dict[k].shape}") - - # 2. Number of layers - layers = set() - for k in state_dict.keys(): - if "encoder.encoder.layer." in k: - # Extract layer number - parts = k.split(".") - try: - # Find the part after 'layer' - idx = parts.index("layer") - layer_num = int(parts[idx+1]) - layers.add(layer_num) - except: - pass - - if layers: - print(f"Number of layers: {max(layers) + 1}") - else: - print("No layers found matching 'encoder.encoder.layer.X'") - - # 3. Check for Heads - print("\nPotential Head Keys:") - head_keywords = ["cls", "linear", "out", "start", "end", "classifier", "generator"] - for k in state_dict.keys(): - if any(kw in k for kw in head_keywords) and not "attention" in k and not "intermediate" in k and not k.startswith("encoder.encoder."): - print(f"{k} -> {state_dict[k].shape}") - -except Exception as e: - print(f"Error: {e}") diff --git a/inspect_model_weights.py b/inspect_model_weights.py deleted file mode 100644 index 35e6b6e0ded638c08e65aeadda9dee5a58e0b89e..0000000000000000000000000000000000000000 --- a/inspect_model_weights.py +++ /dev/null @@ -1,34 +0,0 @@ - -import torch -import sys -from pathlib import Path - -model_path = Path(r"e:\تقليد جرامرلي\BAYAN\models\Spelling\Model\Spelling RAW.pt") - -try: - print(f"Loading {model_path}...") - checkpoint = torch.load(model_path, map_location="cpu") - - if "model_state_dict" in checkpoint: - state_dict = checkpoint["model_state_dict"] - print("Found model_state_dict. Keys:") - keys = list(state_dict.keys()) - print(f"Total weight keys: {len(keys)}") - print("First 20 weight keys:") - for k in keys[:20]: - print(k) - - # Check architecture - if any(k.startswith("bert.") for k in keys): - print("\nArchitecture likely: BERT") - elif any(k.startswith("roberta.") for k in keys): - print("\nArchitecture likely: RoBERTa") - elif any(k.startswith("transformer.") for k in keys): # often T5 - print("\nArchitecture likely: Transformer (T5/Bart)") - elif any(k.startswith("model.encoder.") for k in keys): # mBART/Bart - print("\nArchitecture likely: mBART/Bart") - else: - print("model_state_dict not found in checkpoint.") - -except Exception as e: - print(f"Error: {e}") diff --git a/investigate.py b/investigate.py deleted file mode 100644 index b50724bcd9c8a676b86f1af8267f7dcf12f19b6f..0000000000000000000000000000000000000000 --- a/investigate.py +++ /dev/null @@ -1,31 +0,0 @@ -import re -html = open('src/index.html', encoding='utf-8').read() -editor = open('src/js/editor.js', encoding='utf-8').read() -ui = open('src/js/ui.js', encoding='utf-8').read() - -# 1. Feature sections count -features_start = html.find('page-features') -features_section = html[features_start:features_start+30000] -h3_count = features_section.count('= 0: - context = ui[max(0,idx-20):idx+60] - print(f'4. نصك found in ui.js at idx {idx}: {context}') diff --git a/phase8_results.json b/phase8_results.json deleted file mode 100644 index 76b5eb00f34475b1dd10e675fe0ec3df4a5e1b5f..0000000000000000000000000000000000000000 --- a/phase8_results.json +++ /dev/null @@ -1,1702 +0,0 @@ -[ - { - "input": "انا ذهبت الي المدرسه اليوم", - "category": "hamza+taMarbuta", - "notes": "", - "status": "FAIL", - "suggestions_count": 3, - "suggestions": [ - { - "type": "punctuation", - "original": "اليوم", - "correction": "اليوم.", - "start": 21, - "end": 26 - }, - { - "type": "spelling", - "original": "انا", - "correction": "أنا", - "start": 0, - "end": 3 - }, - { - "type": "spelling", - "original": "الي المدرسه", - "correction": "إلى المدرسة", - "start": 9, - "end": 20 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "الي", - "المدرسه" - ], - "elapsed_ms": 3336, - "original_returned": "انا ذهبت الي المدرسه اليوم" - }, - { - "input": "اذا اردت ان تذهب الى البيت", - "category": "hamza", - "notes": "", - "status": "FAIL", - "suggestions_count": 3, - "suggestions": [ - { - "type": "punctuation", - "original": "البيت", - "correction": "البيت.", - "start": 21, - "end": 26 - }, - { - "type": "spelling", - "original": "اذا", - "correction": "إذا", - "start": 0, - "end": 3 - }, - { - "type": "spelling", - "original": "الى", - "correction": "إلى", - "start": 17, - "end": 20 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "اردت" - ], - "elapsed_ms": 2392, - "original_returned": "اذا اردت ان تذهب الى البيت" - }, - { - "input": "الاطفال يلعبون في الحديقه", - "category": "hamza+taMarbuta", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "الحديقه", - "correction": "الحديقة.", - "start": 18, - "end": 25 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "الاطفال" - ], - "elapsed_ms": 2194, - "original_returned": "الاطفال يلعبون في الحديقه" - }, - { - "input": "لان الامتحان صعب جدا", - "category": "hamza", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "جدا", - "correction": "جدا.", - "start": 17, - "end": 20 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "لان", - "الامتحان" - ], - "elapsed_ms": 2081, - "original_returned": "لان الامتحان صعب جدا" - }, - { - "input": "المدرسه كبيره وجميله", - "category": "taMarbuta", - "notes": "", - "status": "FAIL", - "suggestions_count": 2, - "suggestions": [ - { - "type": "punctuation", - "original": "وجميله", - "correction": "وجميلة.", - "start": 14, - "end": 20 - }, - { - "type": "spelling", - "original": "المدرسه كبيره", - "correction": "المدرسة كبيرة", - "start": 0, - "end": 13 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "المدرسه", - "كبيره", - "جميله" - ], - "elapsed_ms": 2619, - "original_returned": "المدرسه كبيره وجميله" - }, - { - "input": "الجامعه بعيده عن البيت", - "category": "taMarbuta", - "notes": "", - "status": "FAIL", - "suggestions_count": 2, - "suggestions": [ - { - "type": "punctuation", - "original": "البيت", - "correction": "البيت.", - "start": 17, - "end": 22 - }, - { - "type": "spelling", - "original": "الجامعه بعيده", - "correction": "الجامعة بعيدة", - "start": 0, - "end": 13 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "الجامعه", - "بعيده" - ], - "elapsed_ms": 2628, - "original_returned": "الجامعه بعيده عن البيت" - }, - { - "input": "الحياه صعبه في المدينه", - "category": "taMarbuta", - "notes": "", - "status": "FAIL", - "suggestions_count": 2, - "suggestions": [ - { - "type": "punctuation", - "original": "المدينه", - "correction": "المدينة.", - "start": 15, - "end": 22 - }, - { - "type": "spelling", - "original": "الحياه صعبه", - "correction": "الحياة صعبة", - "start": 0, - "end": 11 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "الحياه", - "صعبه" - ], - "elapsed_ms": 3433, - "original_returned": "الحياه صعبه في المدينه" - }, - { - "input": "ذهبت الي المستشفي", - "category": "alifMaqsura", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "spelling", - "original": "الي المستشفي", - "correction": "إلى المستشفى", - "start": 5, - "end": 17 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "الي", - "المستشفي" - ], - "elapsed_ms": 1964, - "original_returned": "ذهبت الي المستشفي" - }, - { - "input": "ذهبت الىالمدرسة اليوم", - "category": "mergedWords", - "notes": "", - "status": "OK", - "suggestions_count": 2, - "suggestions": [ - { - "type": "punctuation", - "original": "اليوم", - "correction": "اليوم؟", - "start": 16, - "end": 21 - }, - { - "type": "spelling", - "original": "الىالمدرسة", - "correction": "إلى المدرسة", - "start": 5, - "end": 15 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2218, - "original_returned": "ذهبت الىالمدرسة اليوم" - }, - { - "input": "فيالبيت كان هناك ضيوف", - "category": "mergedWords", - "notes": "", - "status": "OK", - "suggestions_count": 2, - "suggestions": [ - { - "type": "punctuation", - "original": "ضيوف", - "correction": "ضيوف؟", - "start": 17, - "end": 21 - }, - { - "type": "spelling", - "original": "فيالبيت", - "correction": "في البيت", - "start": 0, - "end": 7 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2378, - "original_returned": "فيالبيت كان هناك ضيوف" - }, - { - "input": "ذهبت إلى المدرسة", - "category": "extraSpaces", - "notes": "Double spaces", - "status": "OK", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "المدرسة", - "correction": "المدرسة؟", - "start": 11, - "end": 18 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2081, - "original_returned": "ذهبت إلى المدرسة" - }, - { - "input": "والكتاب موجود على الطاولة", - "category": "attachedConj", - "notes": "والكتاب is correct", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "الطاولة", - "correction": "الطاولة.", - "start": 18, - "end": 25 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2047, - "original_returned": "والكتاب موجود على الطاولة" - }, - { - "input": "وانا ذهبت إلى المدرسة", - "category": "attachedConj", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "المدرسة", - "correction": "المدرسة.", - "start": 14, - "end": 21 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "وانا" - ], - "elapsed_ms": 2418, - "original_returned": "وانا ذهبت إلى المدرسة" - }, - { - "input": "محمد صلاح يلعب في ليفربول", - "category": "namedEntity", - "notes": "Should NOT correct names", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "ليفربول", - "correction": "ليفربول.", - "start": 18, - "end": 25 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2559, - "original_returned": "محمد صلاح يلعب في ليفربول" - }, - { - "input": "أحمد وفاطمة ذهبا إلى المدرسة", - "category": "namedEntity", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "المدرسة", - "correction": "المدرسة.", - "start": 21, - "end": 28 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2617, - "original_returned": "أحمد وفاطمة ذهبا إلى المدرسة" - }, - { - "input": "أستخدم برنامج Python في العمل", - "category": "foreign", - "notes": "Python should not be flagged", - "status": "OK", - "suggestions_count": 0, - "suggestions": [], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 3689, - "original_returned": "أستخدم برنامج Python في العمل" - }, - { - "input": "تعلمت JavaScript و CSS", - "category": "foreign", - "notes": "", - "status": "FAIL", - "suggestions_count": 2, - "suggestions": [ - { - "type": "punctuation", - "original": "JavaScript", - "correction": "JavaScript:", - "start": 6, - "end": 16 - }, - { - "type": "spelling", - "original": "CSS", - "correction": "CS", - "start": 19, - "end": 22 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2752, - "original_returned": "تعلمت JavaScript و CSS" - }, - { - "input": "ايش تبي تسوي اليوم", - "category": "dialect", - "notes": "Gulf dialect", - "status": "OK", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "اليوم", - "correction": "اليوم؟", - "start": 13, - "end": 18 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2240, - "original_returned": "ايش تبي تسوي اليوم" - }, - { - "input": "عايز اروح البيت", - "category": "dialect", - "notes": "Egyptian dialect", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "البيت", - "correction": "البيت؟", - "start": 10, - "end": 15 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "عايز", - "اروح" - ], - "elapsed_ms": 1895, - "original_returned": "عايز اروح البيت" - }, - { - "input": "مررررحبا كيييييف حالك", - "category": "repetition", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "حالك", - "correction": "حالك.", - "start": 17, - "end": 21 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "مررررحبا", - "كيييييف" - ], - "elapsed_ms": 2570, - "original_returned": "مررررحبا كيييييف حالك" - }, - { - "input": "البنات ذهب إلى المدرسة", - "category": "grammar_SVA", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "المدرسة", - "correction": "المدرسة.", - "start": 15, - "end": 22 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "ذهب" - ], - "elapsed_ms": 2400, - "original_returned": "البنات ذهب إلى المدرسة" - }, - { - "input": "الطلاب يذهب إلى الجامعة", - "category": "grammar_SVA", - "notes": "", - "status": "OK", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "يذهب", - "correction": "يذهبون", - "start": 7, - "end": 11 - }, - { - "type": "punctuation", - "original": "الجامعة", - "correction": "الجامعة.", - "start": 16, - "end": 23 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2604, - "original_returned": "الطلاب يذهب إلى الجامعة" - }, - { - "input": "الأولاد لعب في الحديقة", - "category": "grammar_SVA", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "الحديقة", - "correction": "الحديقة.", - "start": 15, - "end": 22 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "لعب" - ], - "elapsed_ms": 2340, - "original_returned": "الأولاد لعب في الحديقة" - }, - { - "input": "هي ذهب إلى السوق", - "category": "grammar_gender", - "notes": "", - "status": "OK", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "ذهب", - "correction": "ذهبت", - "start": 3, - "end": 6 - }, - { - "type": "punctuation", - "original": "السوق", - "correction": "السوق.", - "start": 11, - "end": 16 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2290, - "original_returned": "هي ذهب إلى السوق" - }, - { - "input": "الطالبة نجح في الامتحان", - "category": "grammar_gender", - "notes": "", - "status": "OK", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "نجح", - "correction": "نجحت", - "start": 8, - "end": 11 - }, - { - "type": "punctuation", - "original": "الامتحان", - "correction": "الامتحين.", - "start": 15, - "end": 23 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2076, - "original_returned": "الطالبة نجح في الامتحان" - }, - { - "input": "الطالب ذهب إلى المدرسة", - "category": "grammar_correct", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "المدرسة", - "correction": "المدرسة.", - "start": 15, - "end": 22 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2190, - "original_returned": "الطالب ذهب إلى المدرسة" - }, - { - "input": "الطالبة ذهبت إلى المدرسة", - "category": "grammar_correct", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "المدرسة", - "correction": "المدرسة.", - "start": 17, - "end": 24 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2287, - "original_returned": "الطالبة ذهبت إلى المدرسة" - }, - { - "input": "الطلاب ذهبوا إلى المدرسة", - "category": "grammar_correct", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "المدرسة", - "correction": "المدرسة.", - "start": 17, - "end": 24 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2220, - "original_returned": "الطلاب ذهبوا إلى المدرسة" - }, - { - "input": "هي ذهبت إلى السوق", - "category": "grammar_correct", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "السوق", - "correction": "السوق.", - "start": 12, - "end": 17 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2094, - "original_returned": "هي ذهبت إلى السوق" - }, - { - "input": "الولد ذهبوا إلى المدرسة", - "category": "grammar_number", - "notes": "", - "status": "OK", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "ذهبوا", - "correction": "ذهب", - "start": 6, - "end": 11 - }, - { - "type": "punctuation", - "original": "المدرسة", - "correction": "المدرسة.", - "start": 16, - "end": 23 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2069, - "original_returned": "الولد ذهبوا إلى المدرسة" - }, - { - "input": "كيف حالك انا بخير", - "category": "punct_missing", - "notes": "Should add ؟ and ، or .", - "status": "OK", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "بخير", - "correction": "بخير؟", - "start": 13, - "end": 17 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2189, - "original_returned": "كيف حالك انا بخير" - }, - { - "input": "مرحبا كيف حالك اليوم", - "category": "punct_missing", - "notes": "", - "status": "OK", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "اليوم", - "correction": "اليوم؟", - "start": 15, - "end": 20 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2304, - "original_returned": "مرحبا كيف حالك اليوم" - }, - { - "input": "هل أنت بخير نعم أنا بخير", - "category": "punct_missing", - "notes": "Should add ؟", - "status": "OK", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "بخير", - "correction": "بخير.", - "start": 20, - "end": 24 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2740, - "original_returned": "هل أنت بخير نعم أنا بخير" - }, - { - "input": "كيف حالك؟ أنا بخير.", - "category": "punct_correct", - "notes": "", - "status": "FAIL", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "حالك؟", - "correction": "حالك ؟", - "start": 4, - "end": 9 - }, - { - "type": "grammar", - "original": "بخير.", - "correction": "بخير", - "start": 14, - "end": 19 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2693, - "original_returned": "كيف حالك؟ أنا بخير." - }, - { - "input": "مرحباً، كيف حالك؟", - "category": "punct_correct", - "notes": "", - "status": "FAIL", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "مرحباً،", - "correction": "مرحبا ،", - "start": 0, - "end": 7 - }, - { - "type": "grammar", - "original": "حالك؟", - "correction": "حالك ؟", - "start": 12, - "end": 17 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2374, - "original_returned": "مرحباً، كيف حالك؟" - }, - { - "input": "انا ذهب الى الجامعه كيف حالك", - "category": "pipeline_order", - "notes": "Should fix: spelling(انا→أنا, الى→إلى, الجامعه→الجامعة) + grammar(ذهب→ذهبت) + punct", - "status": "FAIL", - "suggestions_count": 4, - "suggestions": [ - { - "type": "punctuation", - "original": "حالك", - "correction": "حالك؟", - "start": 24, - "end": 28 - }, - { - "type": "spelling", - "original": "انا", - "correction": "أنا", - "start": 0, - "end": 3 - }, - { - "type": "spelling", - "original": "الجامعه", - "correction": "الجامعة", - "start": 12, - "end": 19 - }, - { - "type": "spelling", - "original": "الى", - "correction": "إلى", - "start": 8, - "end": 11 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": true, - "missed": [ - "ذهب" - ], - "elapsed_ms": 2655, - "original_returned": "انا ذهب الى الجامعه كيف حالك" - }, - { - "input": "أنا ذهبت إلى الجامعة.", - "category": "overcorrect_perfect", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "الجامعة.", - "correction": "الجامعة", - "start": 13, - "end": 21 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2166, - "original_returned": "أنا ذهبت إلى الجامعة." - }, - { - "input": "الحمد لله رب العالمين.", - "category": "overcorrect_quran", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "العالمين.", - "correction": "العالمين", - "start": 13, - "end": 22 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2238, - "original_returned": "الحمد لله رب العالمين." - }, - { - "input": "بسم الله الرحمن الرحيم.", - "category": "overcorrect_quran", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "الرحيم.", - "correction": "الرحيم", - "start": 16, - "end": 23 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2584, - "original_returned": "بسم الله الرحمن الرحيم." - }, - { - "input": "إن الله مع الصابرين.", - "category": "overcorrect_quran", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "الصابرين.", - "correction": "الصابرين", - "start": 11, - "end": 20 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2206, - "original_returned": "إن الله مع الصابرين." - }, - { - "input": "القاهرة عاصمة مصر.", - "category": "overcorrect_fact", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "مصر.", - "correction": "مصر", - "start": 14, - "end": 18 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2078, - "original_returned": "القاهرة عاصمة مصر." - }, - { - "input": "الرياض عاصمة المملكة العربية السعودية.", - "category": "overcorrect_fact", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "السعودية.", - "correction": "السعودية", - "start": 29, - "end": 38 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2556, - "original_returned": "الرياض عاصمة المملكة العربية السعودية." - }, - { - "input": "أحب القراءة والكتابة والرسم.", - "category": "overcorrect_style", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "والرسم.", - "correction": "والرسم", - "start": 21, - "end": 28 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2407, - "original_returned": "أحب القراءة والكتابة والرسم." - }, - { - "input": "كان الجو جميلاً في ذلك اليوم.", - "category": "overcorrect_style", - "notes": "", - "status": "FAIL", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "اليوم.", - "correction": "اليوم", - "start": 23, - "end": 29 - }, - { - "type": "spelling", - "original": "جميلاً", - "correction": "جميلا", - "start": 9, - "end": 15 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2862, - "original_returned": "كان الجو جميلاً في ذلك اليوم." - }, - { - "input": "والمدرسة قريبة من البيت", - "category": "morph_prefix", - "notes": "و+المدرسة correct", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "البيت", - "correction": "البيت.", - "start": 18, - "end": 23 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2406, - "original_returned": "والمدرسة قريبة من البيت" - }, - { - "input": "فالكتاب على الطاولة", - "category": "morph_prefix", - "notes": "ف+الكتاب correct", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "الطاولة", - "correction": "الطاولة.", - "start": 12, - "end": 19 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2360, - "original_returned": "فالكتاب على الطاولة" - }, - { - "input": "بالمدرسة يوجد مكتبة", - "category": "morph_prefix", - "notes": "ب+المدرسة correct", - "status": "FAIL", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "يوجد", - "correction": "توجد", - "start": 9, - "end": 13 - }, - { - "type": "punctuation", - "original": "مكتبة", - "correction": "مكتبة.", - "start": 14, - "end": 19 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2100, - "original_returned": "بالمدرسة يوجد مكتبة" - }, - { - "input": "كالنار في الحطب", - "category": "morph_prefix", - "notes": "ك+النار correct", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "الحطب", - "correction": "الحطب.", - "start": 10, - "end": 15 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2206, - "original_returned": "كالنار في الحطب" - }, - { - "input": "للطلاب حق في التعليم", - "category": "morph_prefix", - "notes": "ل+الطلاب correct", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "التعليم", - "correction": "التعليم.", - "start": 13, - "end": 20 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2364, - "original_returned": "للطلاب حق في التعليم" - }, - { - "input": "", - "category": "edge_empty", - "notes": "Empty string", - "status": "OK", - "suggestions_count": 0, - "suggestions": [], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 546, - "original_returned": "" - }, - { - "input": " ", - "category": "edge_whitespace", - "notes": "Only whitespace", - "status": "OK", - "suggestions_count": 0, - "suggestions": [], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 673, - "original_returned": " " - }, - { - "input": "Hello World", - "category": "edge_english", - "notes": "English only — no correction", - "status": "OK", - "suggestions_count": 0, - "suggestions": [], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 924, - "original_returned": "Hello World" - }, - { - "input": "12345 67890", - "category": "edge_numbers", - "notes": "Numbers only", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "67890", - "correction": "67890.", - "start": 6, - "end": 11 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2844, - "original_returned": "12345 67890" - }, - { - "input": "😀🎉👍", - "category": "edge_emoji", - "notes": "Emojis only", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "😀🎉👍", - "correction": "😀 🎉 👍", - "start": 0, - "end": 3 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 1923, - "original_returned": "😀🎉👍" - }, - { - "input": "أنا 😊 سعيد اليوم 🎉", - "category": "edge_mixed_emoji", - "notes": "", - "status": "OK", - "suggestions_count": 0, - "suggestions": [], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2468, - "original_returned": "أنا 😊 سعيد اليوم 🎉" - }, - { - "input": "https://www.example.com", - "category": "edge_url", - "notes": "URL should not be modified", - "status": "OK", - "suggestions_count": 0, - "suggestions": [], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 584, - "original_returned": "https://www.example.com" - }, - { - "input": "user@email.com", - "category": "edge_email", - "notes": "Email should not be modified", - "status": "OK", - "suggestions_count": 0, - "suggestions": [], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 620, - "original_returned": "user@email.com" - }, - { - "input": "٢٠٢٤/٠١/١٥", - "category": "edge_arabic_numbers", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "٢٠٢٤/٠١/١٥", - "correction": "٢٠٢٤ / ٠١ / ١٥", - "start": 0, - "end": 10 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 3989, - "original_returned": "٢٠٢٤/٠١/١٥" - }, - { - "input": "أستخدم Google Chrome للتصفح", - "category": "mixed_ar_en", - "notes": "", - "status": "FAIL", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "أستخدم", - "correction": "أستخدمت", - "start": 0, - "end": 6 - }, - { - "type": "punctuation", - "original": "للتصفح", - "correction": "للتصفح؟", - "start": 21, - "end": 27 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 3047, - "original_returned": "أستخدم Google Chrome للتصفح" - }, - { - "input": "درست في MIT ثم عملت في Google", - "category": "mixed_ar_en", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "punctuation", - "original": "Google", - "correction": "Google.", - "start": 23, - "end": 29 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 3260, - "original_returned": "درست في MIT ثم عملت في Google" - }, - { - "input": "الـ API تعمل بشكل جيد", - "category": "mixed_tech", - "notes": "", - "status": "FAIL", - "suggestions_count": 2, - "suggestions": [ - { - "type": "punctuation", - "original": "جيد", - "correction": "جيد.", - "start": 18, - "end": 21 - }, - { - "type": "spelling", - "original": "الـ", - "correction": "ال", - "start": 0, - "end": 3 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2691, - "original_returned": "الـ API تعمل بشكل جيد" - }, - { - "input": "إن التعليم هو أساس التقدم والازدهار في المجتمعات.", - "category": "academic_correct", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "المجتمعات.", - "correction": "المجتمعات", - "start": 39, - "end": 49 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 3085, - "original_returned": "إن التعليم هو أساس التقدم والازدهار في المجتمعات." - }, - { - "input": "يعتبر البحث العلمي ركيزة أساسية في تطوير الأمم.", - "category": "academic_correct", - "notes": "", - "status": "FAIL", - "suggestions_count": 1, - "suggestions": [ - { - "type": "grammar", - "original": "الأمم.", - "correction": "الأمم", - "start": 41, - "end": 47 - } - ], - "span_issues": [], - "overcorrected": true, - "undercorrected": false, - "missed": [], - "elapsed_ms": 4030, - "original_returned": "يعتبر البحث العلمي ركيزة أساسية في تطوير الأمم." - }, - { - "input": "يااااا سلام عليك", - "category": "social_repetition", - "notes": "", - "status": "OK", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "يااااا", - "correction": "يا", - "start": 0, - "end": 6 - }, - { - "type": "punctuation", - "original": "عليك", - "correction": "عليك.", - "start": 12, - "end": 16 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2517, - "original_returned": "يااااا سلام عليك" - }, - { - "input": "هههههههه مضحك جدا", - "category": "social_laughter", - "notes": "", - "status": "OK", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "هههههههه", - "correction": "هههههاههه", - "start": 0, - "end": 8 - }, - { - "type": "punctuation", - "original": "جدا", - "correction": "جدا.", - "start": 14, - "end": 17 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 2645, - "original_returned": "هههههههه مضحك جدا" - }, - { - "input": "واللللللله ما اعرف", - "category": "social_emphasis", - "notes": "", - "status": "OK", - "suggestions_count": 2, - "suggestions": [ - { - "type": "grammar", - "original": "واللللللله ما", - "correction": "والله لا", - "start": 0, - "end": 13 - }, - { - "type": "punctuation", - "original": "اعرف", - "correction": "أعرف.", - "start": 14, - "end": 18 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 1809, - "original_returned": "واللللللله ما اعرف" - }, - { - "input": "انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. ", - "category": "stress_500w", - "notes": "~500 words repeated errors", - "status": "OK", - "suggestions_count": 10, - "suggestions": [ - { - "type": "grammar", - "original": "المدرسه", - "correction": "المدرسة اليوم كان الجو جميل ذ ذ ، انا اذهب إلى المدرسةاليوم وكان الجو جميلة جدا ، فأنا ذهبت إلى للمدرسة", - "start": 60, - "end": 67 - }, - { - "type": "grammar", - "original": "جدا.", - "correction": "جدا من", - "start": 89, - "end": 93 - }, - { - "type": "grammar", - "original": "الي المدرسه", - "correction": "إلي المدرسة اليوموكان الجو جميلَ جدا ، وانا ذهبت إلى مدرسته", - "start": 103, - "end": 114 - }, - { - "type": "grammar", - "original": "الجو", - "correction": "الهدوء", - "start": 126, - "end": 130 - }, - { - "type": "grammar", - "original": "الي المدرسه", - "correction": "الذهاب إلى المدرسة واليوم وكان الجو رائع جدا ، انت ذهبت إلى بالمدرسة", - "start": 150, - "end": 161 - }, - { - "type": "grammar", - "original": "الجو", - "correction": "الجوي", - "start": 173, - "end": 177 - }, - { - "type": "punctuation", - "original": "ل", - "correction": "وكانت،", - "start": 22, - "end": 23 - }, - { - "type": "punctuation", - "original": "م", - "correction": "جدّا.", - "start": 25, - "end": 26 - }, - { - "type": "punctuation", - "original": "ج", - "correction": "جدا.،", - "start": 34, - "end": 35 - }, - { - "type": "spelling", - "original": "الي المدرسه", - "correction": "إلى المدرسة", - "start": 9, - "end": 20 - } - ], - "span_issues": [], - "overcorrected": false, - "undercorrected": false, - "missed": [], - "elapsed_ms": 24075, - "original_returned": "انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا. انا ذهبت الي المدرسه اليوم وكان الجو جميل جدا." - } -] \ No newline at end of file diff --git a/render.yaml b/render.yaml deleted file mode 100644 index f58ab498ed423f1319f504213c1d56e133a560c4..0000000000000000000000000000000000000000 --- a/render.yaml +++ /dev/null @@ -1,16 +0,0 @@ -services: - - type: web - name: bayan-api - runtime: python - plan: starter - buildCommand: pip install -r requirements.txt - startCommand: cd src && gunicorn app:app --bind 0.0.0.0:$PORT --timeout 120 --workers 1 - envVars: - - key: SUPABASE_URL - sync: false - - key: SUPABASE_ANON_KEY - sync: false - - key: PYTHON_VERSION - value: "3.12" - - key: DEBUG - value: "False" diff --git a/reproduce_issue.py b/reproduce_issue.py deleted file mode 100644 index 369ef7ddad5597cfae7993473eaa621198da06d5..0000000000000000000000000000000000000000 --- a/reproduce_issue.py +++ /dev/null @@ -1,60 +0,0 @@ - -import sys -import os -from pathlib import Path -import logging - -# Setup logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -# Add src to path -src_path = Path(__file__).parent / 'src' -sys.path.insert(0, str(src_path)) - -try: - from model_loader import SpellingModel, GrammarModel, PunctuationModel -except ImportError as e: - logger.error(f"Failed to import models: {e}") - sys.exit(1) - -def test_models(): - print("--- Testing Model Loading and Inference ---") - - # Test Spelling - print("\n1. Testing Spelling Model...") - try: - spelling = SpellingModel() - text = "الكتبة الصحيحه مهمة" - corrected = spelling.correct(text) - print(f"Input: {text}") - print(f"Output: {corrected}") - if text == corrected: - print("WARNING: Spelling model returned original text (no correction).") - except Exception as e: - print(f"ERROR: Spelling model failed: {e}") - - # Test Grammar - print("\n2. Testing Grammar Model...") - try: - grammar = GrammarModel() - text = "الطلاب ذهبوا الى المدرسة" # Should be gold standard? Or maybe "الطلاب ذهب" needs correction - corrected = grammar.correct(text) - print(f"Input: {text}") - print(f"Output: {corrected}") - except Exception as e: - print(f"ERROR: Grammar model failed: {e}") - - # Test Punctuation - print("\n3. Testing Punctuation Model...") - try: - punctuation = PunctuationModel() - text = "قال المعلم الدرس مهم" - punctuated = punctuation.add_punctuation(text) - print(f"Input: {text}") - print(f"Output: {punctuated}") - except Exception as e: - print(f"ERROR: Punctuation model failed: {e}") - -if __name__ == "__main__": - test_models() diff --git a/run_app.py b/run_app.py deleted file mode 100644 index 4df351827cddf420ea8c30759c76d9608a607e36..0000000000000000000000000000000000000000 --- a/run_app.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Simple script to run the Bayan application. -Usage: python run_app.py -""" - -import sys -import os -from pathlib import Path - -# Add src directory to path -src_path = Path(__file__).parent / 'src' -sys.path.insert(0, str(src_path)) - -# Change to src directory -os.chdir(src_path) - -# Import and run the app -if __name__ == '__main__': - from app import app, load_models - import logging - - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' - ) - - logger = logging.getLogger(__name__) - logger.info("Starting Bayan application...") - - # Load models - if not load_models(): - logger.error("Failed to load any models. Server will start but functionality will be limited.") - logger.error("Please check that the model files are in the correct locations.") - - # Run the app - port = int(os.environ.get('PORT', 5000)) - debug = os.environ.get('DEBUG', 'True').lower() == 'true' - - logger.info(f"Starting server on http://localhost:{port}") - logger.info("Press Ctrl+C to stop the server") - - # Disable reloader to avoid loading models twice - app.run(host='0.0.0.0', port=port, debug=debug, use_reloader=False) - diff --git a/stress_results.json b/stress_results.json deleted file mode 100644 index 3dd133241a5c3b0796bc7517526ca8d29ca9177e..0000000000000000000000000000000000000000 --- a/stress_results.json +++ /dev/null @@ -1,85 +0,0 @@ -[ - { - "chars": 50, - "latency_s": 8.7, - "http": 200, - "suggestions": 2, - "types": [ - "punctuation", - "grammar" - ], - "spelling_ms": 4767, - "grammar_ms": 2294, - "punctuation_ms": 867, - "total_ms": 7929 - }, - { - "chars": 100, - "latency_s": 12.9, - "http": 200, - "suggestions": 1, - "types": [ - "punctuation" - ], - "spelling_ms": 9236, - "grammar_ms": 1650, - "punctuation_ms": 1410, - "total_ms": 12299 - }, - { - "chars": 250, - "latency_s": 37.0, - "http": 200, - "suggestions": 11, - "types": [ - "punctuation", - "grammar" - ], - "spelling_ms": 26000, - "grammar_ms": 6351, - "punctuation_ms": 4007, - "total_ms": 36360 - }, - { - "chars": 500, - "latency_s": 28.2, - "http": 200, - "suggestions": 8, - "types": [ - "punctuation", - "grammar" - ], - "spelling_ms": 0, - "grammar_ms": 17122, - "punctuation_ms": 10496, - "total_ms": 27622 - }, - { - "chars": 1000, - "latency_s": 28.1, - "http": 200, - "suggestions": 4, - "types": [ - "punctuation", - "grammar" - ], - "spelling_ms": 0, - "grammar_ms": 15678, - "punctuation_ms": 11619, - "total_ms": 27304 - }, - { - "chars": 2000, - "latency_s": 33.8, - "http": 200, - "suggestions": 4, - "types": [ - "punctuation", - "grammar" - ], - "spelling_ms": 0, - "grammar_ms": 18505, - "punctuation_ms": 11765, - "total_ms": 30284 - } -] \ No newline at end of file diff --git a/summarization_test.py b/summarization_test.py deleted file mode 100644 index 1c95e8a5cade7af3326ea67df1abce9edf54548a..0000000000000000000000000000000000000000 --- a/summarization_test.py +++ /dev/null @@ -1,123 +0,0 @@ -from pathlib import Path -import sys -import json -from pprint import pprint - -# Make sure src is importable when running from repo root -sys.path.insert(0, str(Path(__file__).parent / 'src')) - -from model_loader import SummarizationModel, SUMMARIZATION_PATH - -text = ( - "سيلعب المنتخب المغربي اليوم بقياده سفيان امرابط ووليد الركراكي امام المنتخب البرازيلي " - "بقياده فينيسيوس وكارول انشيلوتي ورافينها في كاس العالم الساعه الواحده صباحا" -) - -print("Summarization path:", SUMMARIZATION_PATH) -print("Files:", list(Path(SUMMARIZATION_PATH).iterdir())) - -print("\nLoading model (this may take a while)...") -m = SummarizationModel(SUMMARIZATION_PATH) -print("Model loaded. Device:", m.device) - - -def show_tokenization(text): - tok = m.tokenizer(text, truncation=True, padding=True, return_tensors="pt") - print('\n=== Tokenization ===') - print('Decoded back (tokenizer):', m.tokenizer.decode(tok['input_ids'][0], skip_special_tokens=False)) - print('Input ids (first 60):', tok['input_ids'][0][:60].tolist()) - print('Tokenizer special tokens:', m.tokenizer.special_tokens_map) - - -def show_config(): - print('\n=== Model Config ===') - cfg = m.model.config - # print a subset for readability - info = {k: getattr(cfg, k) for k in ['vocab_size', 'max_length', 'min_length', 'num_beams', 'early_stopping', 'decoder_start_token_id'] if hasattr(cfg, k)} - pprint(info) - - -def generate_and_print(desc, **gen_kwargs): - print(f"\n--- Generating: {desc} ---") - try: - inputs = m.tokenizer(text, truncation=True, padding=True, return_tensors='pt') - inputs = {k: v.to(m.device) for k, v in inputs.items()} - - # Ensure generation config has sensible defaults if missing - if gen_kwargs.get('num_beams') is None: - gen_kwargs['num_beams'] = 1 - - # Use return_dict_in_generate to capture sequences - gen_kwargs.setdefault('return_dict_in_generate', True) - gen_kwargs.setdefault('output_scores', True) - - out = m.model.generate(**inputs, **gen_kwargs) - - # support both GenerationOutput and plain tensor - seq = None - if hasattr(out, 'sequences'): - seq = out.sequences[0].cpu().tolist() - else: - seq = out[0].cpu().tolist() - - print('Generated token ids (truncated 128):', seq[:128]) - decoded = m.tokenizer.decode(seq, skip_special_tokens=True) - print('Decoded generated:', decoded) - except Exception as e: - print('Generation failed:', repr(e)) - - -show_tokenization(text) -show_config() - -# Read training history to look for input formatting / prompts -train_hist_path = Path(SUMMARIZATION_PATH) / 'training_history.json' -if train_hist_path.exists(): - try: - th = json.load(open(train_hist_path, 'r', encoding='utf-8')) - print('\n=== training_history.json keys ===') - pprint(list(th.keys())[:20]) - # print any example prompts if present - if 'example' in th: - print('\nExample from training_history.json:') - pprint(th['example']) - else: - # try common keys - for k in ('prompt', 'template', 'examples'): - if k in th: - print(f'Found {k}:') - pprint(th[k]) - except Exception as e: - print('Could not read training_history.json:', e) - - -# 1) Baseline (current defaults) -generate_and_print('baseline (defaults)') - -# 2) Beam search (reduce hallucination) -generate_and_print('beam search (num_beams=6, no_repeat_ngram_size=3)', num_beams=6, early_stopping=True, no_repeat_ngram_size=3, max_length=150, min_length=20) - -# 3) Force decoder_start to BOS (some models expect it) -try: - m.model.config.forced_bos_token_id = m.tokenizer.bos_token_id - generate_and_print('forced_bos_token_id set to tokenizer.bos_token_id (beam=6)', num_beams=6, early_stopping=True, no_repeat_ngram_size=3, max_length=150, min_length=20) -except Exception as e: - print('Could not set forced_bos_token_id:', e) - -# 4) Sampling variations (may increase relevance or diversity) -generate_and_print('sampling (do_sample=True, top_p=0.9, temp=0.8)', do_sample=True, top_p=0.9, temperature=0.8, max_length=120) - -# 5) Try adding a short explicit prefix (if model was trained with templates) -for prefix in ['','تلخيص: ', 'summarize: ', ' تلخيص: ']: - prefix_text = prefix + text - print(f"\n>>> Trying prefix: '{prefix}'") - try: - inputs = m.tokenizer(prefix_text, truncation=True, padding=True, return_tensors='pt') - inputs = {k: v.to(m.device) for k, v in inputs.items()} - out = m.model.generate(**inputs, num_beams=6, early_stopping=True, no_repeat_ngram_size=3, max_length=150, min_length=20, return_dict_in_generate=True, output_scores=True) - seq = out.sequences[0].cpu().tolist() if hasattr(out, 'sequences') else out[0].cpu().tolist() - print('Decoded generated with prefix:', m.tokenizer.decode(seq, skip_special_tokens=True)) - except Exception as e: - print('Prefix generation failed:', e) - -print('\nAll experiments completed.') diff --git a/temp_resp.json b/temp_resp.json deleted file mode 100644 index 91d12ca327c970dd65b8a22e4230b7f2635a7db9..0000000000000000000000000000000000000000 --- a/temp_resp.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "corrected": "المهندسون صممو المشروع.", - "original": "المهندسون صممتو المشرووع", - "status": "success", - "suggestions": [ - { - "alternatives": [], - "confidence": 1.0, - "correction": "صممو", - "end": 15, - "id": "75d8b4ae-77b1-41ab-ba17-22a68dff9d14", - "locked": true, - "original": "صممتو", - "priority": 3, - "start": 10, - "type": "grammar" - }, - { - "alternatives": [], - "confidence": 0.8, - "correction": "المشروع.", - "end": 24, - "id": "b5c4f702-0557-4e1f-aa85-3147f8bde50c", - "locked": true, - "original": "المشرووع", - "priority": 2, - "start": 16, - "type": "punctuation" - } - ], - "telemetry": [ - { - "event": "grammar_raw_output", - "input": "المهندسون صممتو المشروع", - "output": "المهندسون صممو المشروع" - }, - { - "count": 1, - "event": "grammar_diffs_extracted" - }, - { - "correction": "صممو", - "end": 15, - "event": "grammar_diff", - "original": "صممتو", - "start": 10 - }, - { - "correction": "صممو", - "end": 15, - "event": "patch_accepted", - "original": "صممتو", - "stage": "grammar", - "start": 10 - } - ], - "timing_ms": { - "grammar_ms": 788, - "punctuation_ms": 461, - "spelling_ms": 579, - "total_ms": 1832 - } -} \ No newline at end of file diff --git a/test_analyze_api.py b/test_analyze_api.py deleted file mode 100644 index f5734224173920da443aa52ccf45d34d521d1d66..0000000000000000000000000000000000000000 --- a/test_analyze_api.py +++ /dev/null @@ -1,24 +0,0 @@ - -import requests -import json - -def test_analyze(): - url = "http://127.0.0.1:5001/api/analyze" - payload = { - "text": "الطلاب ذهبو الى المدرسة" - } - headers = { - "Content-Type": "application/json" - } - - print(f"Testing {url} with text: '{payload['text']}'") - try: - response = requests.post(url, data=json.dumps(payload), headers=headers) - print(f"Status Code: {response.status_code}") - print("Response JSON:") - print(json.dumps(response.json(), indent=2, ensure_ascii=False)) - except Exception as e: - print(f"Error: {e}") - -if __name__ == "__main__": - test_analyze() diff --git a/test_analyze_methods.py b/test_analyze_methods.py deleted file mode 100644 index 49c88210115380abf97ffe47c236c623313956a6..0000000000000000000000000000000000000000 --- a/test_analyze_methods.py +++ /dev/null @@ -1,31 +0,0 @@ - -import requests -import json - -def test_analyze(): - url = "http://127.0.0.1:5000/api/analyze" - payload = { - "text": "الطلاب ذهبو الى المدرسة" - } - headers = { - "Content-Type": "application/json" - } - - print(f"Testing POST {url}") - try: - response = requests.post(url, data=json.dumps(payload), headers=headers) - print(f"POST Status Code: {response.status_code}") - print(f"POST Response: {response.text}") - except Exception as e: - print(f"POST Error: {e}") - - print(f"\nTesting GET {url}") - try: - response = requests.get(url) - print(f"GET Status Code: {response.status_code}") - print(f"GET Response: {response.text}") - except Exception as e: - print(f"GET Error: {e}") - -if __name__ == "__main__": - test_analyze() diff --git a/test_dialect.py b/test_dialect.py deleted file mode 100644 index 73be1e5c403532179292f8704ab57b705ce90195..0000000000000000000000000000000000000000 --- a/test_dialect.py +++ /dev/null @@ -1,301 +0,0 @@ -""" -Unit tests for the Dialect-to-MSA feature. -Tests Flask route validation, service layer, and edge cases. -All tests mock the model so NO actual model loading happens. - -Run: python -m pytest test_dialect.py -v -""" - -import sys -import os -import pytest -from unittest.mock import patch, MagicMock - -# Add src to path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) -os.chdir(os.path.join(os.path.dirname(__file__), 'src')) - - -# ═══════════════════════════════════════════════════════════════ -# FIXTURE: Flask test client (no model loading) -# ═══════════════════════════════════════════════════════════════ - -@pytest.fixture -def client(): - """Create Flask test client without loading any models.""" - with patch('app.load_models', return_value=True): - from app import app - app.config['TESTING'] = True - with app.test_client() as c: - yield c - - -def _mock_dialect(): - """Create a mock DialectConverter that returns predictable output.""" - mock = MagicMock() - mock.convert.side_effect = lambda text, **kw: f"[MSA] {text}" - mock.is_ready.return_value = True - return mock - - -@pytest.fixture -def mock_model(): - """Patch dialect model globally so no real model loading happens.""" - m = _mock_dialect() - with patch('nlp.dialect.dialect_service._instance', m): - with patch('nlp.dialect.dialect_service.get_dialect_model', return_value=m): - yield m - - -# ═══════════════════════════════════════════════════════════════ -# GROUP 1: Input Validation (no model needed) -# ═══════════════════════════════════════════════════════════════ - -class TestDialectInputValidation: - """Tests that verify input validation BEFORE the model is called.""" - - def test_empty_text_returns_400(self, client): - r = client.post('/api/dialect', json={'text': ''}) - assert r.status_code == 400 - assert r.get_json()['status'] == 'error' - - def test_whitespace_only_returns_400(self, client): - r = client.post('/api/dialect', json={'text': ' '}) - assert r.status_code == 400 - assert r.get_json()['status'] == 'error' - - def test_missing_text_field_returns_400(self, client): - r = client.post('/api/dialect', json={}) - assert r.status_code == 400 - - def test_text_too_long_returns_400(self, client): - r = client.post('/api/dialect', json={'text': 'ا' * 5001}) - assert r.status_code == 400 - assert '5000' in r.get_json()['error'] - - def test_non_json_returns_400(self, client): - r = client.post('/api/dialect', data='plain text', - content_type='text/plain') - assert r.status_code == 400 - - def test_get_method_not_allowed(self, client): - r = client.get('/api/dialect') - assert r.status_code in [404, 405] # Flask static_folder returns 404 - - def test_put_method_not_allowed(self, client): - r = client.put('/api/dialect', json={'text': 'test'}) - assert r.status_code == 405 - - def test_null_text_handled(self, client): - r = client.post('/api/dialect', json={'text': None}) - assert r.status_code in [400, 500] - assert r.get_json()['status'] == 'error' - - -# ═══════════════════════════════════════════════════════════════ -# GROUP 2: Successful Conversion (mocked model) -# ═══════════════════════════════════════════════════════════════ - -class TestDialectConversion: - """Tests with mocked model to verify route logic.""" - - def test_success_response_format(self, client, mock_model): - r = client.post('/api/dialect', json={'text': 'عايز اروح البيت'}) - assert r.status_code == 200 - data = r.get_json() - assert data['status'] == 'success' - assert 'original_text' in data - assert 'converted_text' in data - - def test_original_text_preserved(self, client, mock_model): - r = client.post('/api/dialect', json={'text': 'عايز اروح البيت'}) - data = r.get_json() - assert data['original_text'] == 'عايز اروح البيت' - - def test_converted_text_from_model(self, client, mock_model): - r = client.post('/api/dialect', json={'text': 'عايز اروح البيت'}) - data = r.get_json() - assert data['converted_text'] == '[MSA] عايز اروح البيت' - - def test_text_gets_stripped(self, client, mock_model): - r = client.post('/api/dialect', json={'text': ' عايز اروح '}) - data = r.get_json() - assert data['original_text'] == 'عايز اروح' - - def test_text_exactly_5000_passes(self, client, mock_model): - text = 'ا' * 5000 - r = client.post('/api/dialect', json={'text': text}) - assert r.status_code == 200 - - def test_special_chars_dont_crash(self, client, mock_model): - r = client.post('/api/dialect', - json={'text': ''}) - assert r.status_code == 200 - - def test_sql_injection_safe(self, client, mock_model): - r = client.post('/api/dialect', - json={'text': "'; DROP TABLE users; --"}) - assert r.status_code == 200 - - def test_unicode_bidi_safe(self, client, mock_model): - r = client.post('/api/dialect', - json={'text': '\u200e\u200f\u202a عايز'}) - assert r.status_code == 200 - - def test_emoji_safe(self, client, mock_model): - r = client.post('/api/dialect', - json={'text': 'عايز اروح 🏠 دلوقتي'}) - assert r.status_code == 200 - - def test_newlines_safe(self, client, mock_model): - r = client.post('/api/dialect', - json={'text': 'عايز\nاروح\nالبيت'}) - assert r.status_code == 200 - - def test_numbers_safe(self, client, mock_model): - r = client.post('/api/dialect', - json={'text': 'عايز 5 كيلو و 3 كيلو'}) - assert r.status_code == 200 - - def test_english_mixed_safe(self, client, mock_model): - r = client.post('/api/dialect', - json={'text': 'عايز اعمل login في ال app'}) - assert r.status_code == 200 - - def test_extra_json_fields_ignored(self, client, mock_model): - r = client.post('/api/dialect', - json={'text': 'عايز', 'extra': 'field', 'hack': True}) - assert r.status_code == 200 - - -# ═══════════════════════════════════════════════════════════════ -# GROUP 3: Model Error Handling -# ═══════════════════════════════════════════════════════════════ - -class TestDialectErrorHandling: - """Tests for when the model throws errors.""" - - def test_runtime_error_returns_503(self, client): - mock = MagicMock() - mock.convert.side_effect = RuntimeError("Model OOM") - with patch('nlp.dialect.dialect_service._instance', mock): - with patch('nlp.dialect.dialect_service.get_dialect_model', return_value=mock): - r = client.post('/api/dialect', json={'text': 'عايز'}) - assert r.status_code == 503 - assert r.get_json()['status'] == 'error' - - def test_generic_exception_returns_500(self, client): - mock = MagicMock() - mock.convert.side_effect = ValueError("Bad tensor") - with patch('nlp.dialect.dialect_service._instance', mock): - with patch('nlp.dialect.dialect_service.get_dialect_model', return_value=mock): - r = client.post('/api/dialect', json={'text': 'عايز'}) - assert r.status_code == 500 - assert r.get_json()['status'] == 'error' - - -# ═══════════════════════════════════════════════════════════════ -# GROUP 4: DialectConverter Service Unit Tests -# ═══════════════════════════════════════════════════════════════ - -class TestDialectService: - """Tests for the DialectConverter class directly.""" - - def test_singleton_pattern(self): - from nlp.dialect import dialect_service - dialect_service._instance = None - with patch.object(dialect_service.DialectConverter, '__init__', return_value=None): - with patch.object(dialect_service.DialectConverter, 'is_ready', return_value=True): - m1 = dialect_service.get_dialect_model() - m2 = dialect_service.get_dialect_model() - assert m1 is m2 - dialect_service._instance = None - - def test_is_loaded_false_initially(self): - from nlp.dialect import dialect_service - dialect_service._instance = None - assert dialect_service.is_loaded() is False - - def test_is_loaded_true_after_init(self): - from nlp.dialect import dialect_service - mock = MagicMock() - mock.is_ready.return_value = True - dialect_service._instance = mock - assert dialect_service.is_loaded() is True - dialect_service._instance = None - - def test_convert_empty_passthrough(self): - from nlp.dialect.dialect_service import DialectConverter - c = DialectConverter.__new__(DialectConverter) - c.model = MagicMock() - c.tokenizer = MagicMock() - c.device = "cpu" - assert c.convert("") == "" - c.model.generate.assert_not_called() - - def test_convert_whitespace_passthrough(self): - from nlp.dialect.dialect_service import DialectConverter - c = DialectConverter.__new__(DialectConverter) - c.model = MagicMock() - c.tokenizer = MagicMock() - c.device = "cpu" - assert c.convert(" ") == " " - c.model.generate.assert_not_called() - - def test_convert_none_passthrough(self): - from nlp.dialect.dialect_service import DialectConverter - c = DialectConverter.__new__(DialectConverter) - c.model = MagicMock() - c.tokenizer = MagicMock() - c.device = "cpu" - assert c.convert(None) is None - c.model.generate.assert_not_called() - - def test_prefix_correct(self): - from nlp.dialect.dialect_service import DialectConverter - assert DialectConverter.PREFIX == "حوّل إلى الفصحى: " - - def test_repo_id_correct(self): - from nlp.dialect.dialect_service import DialectConverter - assert DialectConverter.REPO_ID == "bayan10/dialect-to-msa-model" - - def test_max_lengths(self): - from nlp.dialect.dialect_service import DialectConverter - assert DialectConverter.MAX_INPUT_LENGTH == 128 - assert DialectConverter.MAX_OUTPUT_LENGTH == 128 - - def test_is_ready_both_required(self): - from nlp.dialect.dialect_service import DialectConverter - c = DialectConverter.__new__(DialectConverter) - c.model = None; c.tokenizer = MagicMock() - assert c.is_ready() is False - c.model = MagicMock(); c.tokenizer = None - assert c.is_ready() is False - c.model = MagicMock(); c.tokenizer = MagicMock() - assert c.is_ready() is True - - -# ═══════════════════════════════════════════════════════════════ -# GROUP 5: Health Check Integration -# ═══════════════════════════════════════════════════════════════ - -class TestDialectHealthCheck: - - def test_health_includes_dialect(self, client): - r = client.get('/api/health') - data = r.get_json() - assert 'dialect' in data['models'] - - def test_dialect_false_when_not_loaded(self, client): - from nlp.dialect import dialect_service - dialect_service._instance = None - r = client.get('/api/health') - assert r.get_json()['models']['dialect'] is False - - def test_dialect_true_when_loaded(self, client): - from nlp.dialect import dialect_service - mock = MagicMock(); mock.is_ready.return_value = True - dialect_service._instance = mock - r = client.get('/api/health') - assert r.get_json()['models']['dialect'] is True - dialect_service._instance = None diff --git a/test_model_load.py b/test_model_load.py deleted file mode 100644 index e9a4ad9904f7a8a78b0eb9ae0743286a6d218067..0000000000000000000000000000000000000000 --- a/test_model_load.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Test script to verify model loading works correctly.""" - -import sys -from pathlib import Path - -# Add src to path -sys.path.insert(0, str(Path(__file__).parent / 'src')) - -from model_loader import SummarizationModel -import logging - -logging.basicConfig(level=logging.INFO) - -def test_model_loading(): - """Test if the model loads correctly.""" - model_path = Path("models/Summarization/Model") - - if not model_path.exists(): - print(f"ERROR: Model path does not exist: {model_path}") - return False - - try: - print("Loading model...") - model = SummarizationModel(str(model_path)) - print("✓ Model loaded successfully!") - - # Test summarization - test_text = "التكنولوجيا الحديثة أحدثت ثورة في حياتنا اليومية. حيث سهّلت التواصل والتعلم والعمل. مما أدى إلى تطور المجتمعات وتحسين جودة الحياة." - print(f"\nTesting summarization with text: {test_text[:50]}...") - summary = model.summarize(test_text, max_length=50, min_length=10) - print(f"✓ Summarization successful!") - print(f"Summary: {summary}") - - return True - except Exception as e: - print(f"ERROR: {str(e)}") - import traceback - traceback.print_exc() - return False - -if __name__ == "__main__": - success = test_model_loading() - sys.exit(0 if success else 1) - diff --git a/test_phase6.py b/test_phase6.py deleted file mode 100644 index 182498ea7f7a6a9821db52ec3eb4a9ea37d1e815..0000000000000000000000000000000000000000 --- a/test_phase6.py +++ /dev/null @@ -1,101 +0,0 @@ -from playwright.sync_api import sync_playwright -import time -import sys - -def run_tests(): - print("Starting Phase 6 UI Verification Tests...") - with sync_playwright() as p: - browser = p.chromium.launch(headless=True) - page = browser.new_page() - - page.on("console", lambda msg: print(f"Browser Console: {msg.text}")) - - # Auto-accept any prompts (like document rename/create) - def handle_dialog(dialog): - print(f"Dialog opened: {dialog.message}") - if dialog.type == "prompt": - dialog.accept("Test Document Auto") - else: - dialog.accept() - - page.on("dialog", handle_dialog) - page.goto("http://localhost:5050") - - # --- GUEST LOGIN --- - print("Logging in as Guest...") - page.click("text=المتابعة كضيف") - page.wait_for_timeout(2000) - - # Check for offline mode banner (should not be visible) - offline_banner = page.locator("#auth-offline-banner") - if offline_banner.is_visible(): - print("ERROR: Offline mode active. Cannot test cloud features.") - sys.exit(1) - - # --- DOCUMENTS PERSISTENCE (Phase 6.1) --- - print("Testing Documents Sidebar...") - page.click("#docs-sidebar-toggle") - page.wait_for_timeout(1000) - - print("Testing Create Document...") - page.click("#docs-new-btn") - page.wait_for_timeout(2000) - - title = page.locator("#doc-current-title").inner_text() - print(f"Document created with title: {title}") - if not title: - print("ERROR: Document title is empty") - sys.exit(1) - - print("Testing Manual Save...") - page.click("#docs-sidebar-toggle") # Close sidebar - page.wait_for_timeout(500) - page.click("#write-tab", force=True) - page.fill("#editor-container", "هذا نص تجريبي لحفظ المستند عبر Playwright.") - page.click("#doc-save-btn", force=True) - page.wait_for_timeout(2000) - - page.click("#docs-sidebar-toggle", force=True) - page.wait_for_timeout(1000) - docs = page.locator(".doc-list-item").count() - print(f"Documents in sidebar: {docs}") - if docs == 0: - print("ERROR: Document not found in list") - sys.exit(1) - - # --- SUMMARIES PERSISTENCE (Phase 6.2) --- - print("Testing Summaries Cloud...") - page.click("#summarize-tab") - page.wait_for_timeout(500) - - # Inject fake summary directly into UI to speed up test (avoiding actual LLM call time) - page.evaluate("document.getElementById('summary-text').innerText = 'هذا ملخص تجريبي.'") - - page.click("#save-summary-btn") - print("Clicked Save Summary.") - page.wait_for_timeout(2000) - - page.click("#summary-history-toggle") - page.wait_for_timeout(1000) - - histories = page.locator(".summary-history-item").count() - print(f"Summary history items: {histories}") - if histories == 0: - print("ERROR: Summary not saved in history") - sys.exit(1) - - # --- SETTINGS SYNC (Phase 6.3) --- - print("Testing Theme Sync...") - # Since we use Guest mode, let's verify if theme change dispatches without error - page.evaluate("setTheme('dark')") - page.wait_for_timeout(2000) # wait for debounce - - print("All Tests Passed! 🎉") - browser.close() - -if __name__ == "__main__": - try: - run_tests() - except Exception as e: - print(f"Test Execution Failed: {e}") - sys.exit(1) diff --git a/test_quran.py b/test_quran.py deleted file mode 100644 index 993669d7c4c402186adc525186bfe4b8b1615ff0..0000000000000000000000000000000000000000 --- a/test_quran.py +++ /dev/null @@ -1,284 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Comprehensive test suite for Quran search feature (quran.py) -Tests edge cases, boundary conditions, and data integrity. -""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from quran import search_bayan, normalize_arabic -import sqlite3 -import re - -DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'quran_master.db') - -passed = 0 -failed = 0 -errors = [] - -def test(name, condition, detail=""): - global passed, failed, errors - if condition: - passed += 1 - print(f" ✅ {name}") - else: - failed += 1 - errors.append((name, detail)) - print(f" ❌ {name} — {detail}") - -def get_result(query, lang="تدقيق الايات"): - return search_bayan(query, lang, db_path=DB_PATH) - -# ══════════════════════════════════════════ -print("\n🔤 1. الحروف المقطعة (Huruf Muqatta'at)") -print("=" * 50) - -# الم - البقرة -r = get_result("الم ذلك الكتاب لا ريب فيه") -test("الم موجودة في البقرة", "الٓمٓ" in r.get("matched_segment", "") or "الم" in r.get("matched_segment", "")) - -# الر - يوسف -r = get_result("الر تلك آيات الكتاب المبين") -test("الر موجودة في يوسف", "الٓر" in r.get("matched_segment", "") or "الر" in r.get("matched_segment", "")) - -# حم - غافر -r = get_result("حم تنزيل الكتاب من الله") -test("حم موجودة", "حمٓ" in r.get("matched_segment", "") or "حم" in r.get("matched_segment", "")) - -# كهيعص - مريم -r = get_result("كهيعص ذكر رحمة ربك عبده زكريا") -seg = r.get("matched_segment", "") -test("كهيعص موجودة", "كٓهيعٓصٓ" in seg or "كهيعص" in seg or "مريم" in seg.lower() or "مَرْيَمَ" in seg) - -# طه -r = get_result("طه ما انزلنا عليك القرآن لتشقى") -test("طه موجودة", "طه" in r.get("matched_segment", "") or "طٰهٰ" in r.get("matched_segment", "")) - -# يس -r = get_result("يس والقرآن الحكيم") -test("يس موجودة", "يسٓ" in r.get("matched_segment", "") or "يس" in r.get("matched_segment", "")) - -# ══════════════════════════════════════════ -print("\n📖 2. البسملة") -print("=" * 50) - -# الفاتحة - البسملة + الحمد لله -r = get_result("بسم الله الرحمن الرحيم الحمد لله رب العالمين") -seg = r.get("matched_segment", "") -ref_match = re.search(r'【([^】]+)】', seg) -ref = ref_match.group(1) if ref_match else "" -test("الفاتحة: البسملة موجودة", "بِسْمِ" in seg, f"seg={seg[:60]}") -test("الفاتحة: اسم السورة", "الفاتحة" in ref, f"ref={ref}") - -# الفاتحة - آيات بدون بسملة -r2 = get_result("الحمد لله رب العالمين الرحمن الرحيم مالك يوم الدين") -seg2 = r2.get("matched_segment", "") -ref2 = re.search(r'【([^】]+)】', seg2) -ref2 = ref2.group(1) if ref2 else "" -test("الفاتحة ٢-٤: آيات صحيحة", "لْحَمْدُ" in seg2 and "(٤)" in seg2, f"seg={seg2[:60]}") -test("الفاتحة ٢-٤: اسم السورة", "الفاتحة" in ref2, f"ref={ref2}") - -# سورة عادية - البسملة لازم تتشال -r = get_result("الر تلك آيات الكتاب المبين") -test("يوسف: البسملة مش موجودة", "بِسْمِ" not in r.get("matched_segment", "")) - -# التوبة - مفيهاش بسملة أصلاً -r = get_result("براءة من الله ورسوله") -seg = r.get("matched_segment", "") -test("التوبة: مفيش بسملة", "بِسْمِ" not in seg) -test("التوبة: اسم السورة صح", "التوبة" in seg or "توب" in seg.lower() or "بَرَآءَةٌ" in seg) - -# النمل 30 - البسملة جزء من الآية -r = get_result("انه من سليمان وانه بسم الله الرحمن الرحيم") -seg = r.get("matched_segment", "") -test("النمل ٣٠: البسملة موجودة (جزء من الآية)", "بِسْمِ" in seg or "سُلَيْمَـٰنَ" in seg) - -# ══════════════════════════════════════════ -print("\n🔢 3. ترقيم الآيات والمراجع") -print("=" * 50) - -# آية واحدة - رقم مفرد -r = get_result("قل هو الله أحد") -seg = r.get("matched_segment", "") -ref_match = re.search(r'【([^】]+)】', seg) -ref = ref_match.group(1) if ref_match else "" -test("آية واحدة: مرجع مفرد", ":" in ref and "-" not in ref, f"ref={ref}") - -# كذا آية - نطاق -r = get_result("انا انزلناه في ليلة القدر وما ادراك ما ليلة القدر ليلة القدر خير من الف شهر") -seg = r.get("matched_segment", "") -ref_match = re.search(r'【([^】]+)】', seg) -ref = ref_match.group(1) if ref_match else "" -test("كذا آية: نطاق (من-إلى)", "-" in ref, f"ref={ref}") - -# أرقام عربية في العثماني -r = get_result("قل هو الله أحد", "تدقيق الايات") -seg = r.get("matched_segment", "") -test("عثماني: أرقام عربية", any(c in seg for c in "٠١٢٣٤٥٦٧٨٩"), f"seg={seg[:60]}") - -# أرقام لاتينية في الإنجليزي -r = get_result("قل هو الله أحد", "english") -seg = r.get("matched_segment", "") -test("إنجليزي: أرقام لاتينية", any(c in seg for c in "0123456789"), f"seg={seg[:60]}") - -# ══════════════════════════════════════════ -print("\n🚧 4. حدود السور") -print("=" * 50) - -# سورة قصيرة كاملة (الإخلاص) -r = get_result("قل هو الله أحد الله الصمد لم يلد ولم يولد ولم يكن له كفوا أحد") -seg = r.get("matched_segment", "") -test("الإخلاص كاملة: ٤ آيات", "(٤)" in seg or "(٤)" in seg.replace("٤","4")) -test("الإخلاص: مفيش آيات من الفلق", "فَلَقِ" not in seg and "الفلق" not in seg) - -# سورة القدر - مش يكمل للبينة -r = get_result("انا انزلناه في ليلة القدر وما ادراك ما ليلة القدر ليلة القدر خير من الف شهر تنزل فيها الملائكة والروح فيها باذن ربهم من كل امر سلام هي حتى مطلع الفجر") -seg = r.get("matched_segment", "") -test("القدر: مفيش آيات من البينة", "ٱلْبَيِّنَةُ" not in seg and "بَيِّنَة" not in seg) -test("القدر: ٥ آيات", "(٥)" in seg or "(٥)" in seg.replace("٥","5")) - -# الكوثر (أقصر سورة - 3 آيات) -r = get_result("انا اعطيناك الكوثر فصل لربك وانحر ان شانئك هو الابتر") -seg = r.get("matched_segment", "") -test("الكوثر: ٣ آيات كاملة", "(٣)" in seg or "(٣)" in seg.replace("٣","3")) -ref_match = re.search(r'【([^】]+)】', seg) -ref = ref_match.group(1) if ref_match else "" -test("الكوثر: اسم السورة صح", "الكوثر" in ref, f"ref={ref}") - -# ══════════════════════════════════════════ -print("\n🔤 5. التراجم") -print("=" * 50) - -languages = ["english", "french", "turkish", "russian", "spanish", "german", "indonesian", "persian"] -for lang in languages: - r = get_result("قل هو الله أحد", lang) - seg = r.get("matched_segment", "") - has_content = len(seg) > 10 and "error" not in r - ref_match = re.search(r'【([^】]+)】', seg) - ref = ref_match.group(1) if ref_match else "" - test(f"{lang}: ترجمة + مرجع موجود", has_content and len(ref) > 0, f"ref={ref}, len={len(seg)}") - -# English with [O Muhammad] — brackets shouldn't break ref -r = get_result("قل لعبادي الذين آمنوا يقيموا الصلاة", "english") -seg = r.get("matched_segment", "") -ref_match = re.search(r'【([^】]+)】', seg) -ref = ref_match.group(1) if ref_match else "" -test("English [O Muhammad]: مرجع صح", "Ibrahim" in ref or "إبراهيم" in ref or "braham" in ref.lower(), f"ref={ref}") - -# English with [in orbit] -r = get_result("وسخر لكم الشمس والقمر دائبين", "english") -seg = r.get("matched_segment", "") -ref_match = re.search(r'【([^】]+)】', seg) -ref = ref_match.group(1) if ref_match else "" -test("English [in orbit]: مرجع مش متأثر", len(ref) > 0, f"ref={ref}") - -# ══════════════════════════════════════════ -print("\n⚠️ 6. حالات خاصة وأخطاء") -print("=" * 50) - -# نص فارغ -r = get_result("") -test("نص فارغ: رسالة خطأ", "error" in r, f"result={r}") - -# نص مش عربي -r = get_result("hello world this is english text") -seg = r.get("matched_segment", "") -test("نص إنجليزي: مش بيكسر", True) # should not crash - -# كلمة واحدة بس -r = get_result("الله") -seg = r.get("matched_segment", "") -test("كلمة واحدة: بيرجع نتيجة", len(seg) > 5, f"seg={seg[:40]}") - -# كلمتين -r = get_result("قل هو") -seg = r.get("matched_segment", "") -test("كلمتين: بيرجع نتيجة", len(seg) > 5, f"seg={seg[:40]}") - -# أخطاء إملائية كتير -r = get_result("انا انزلنه في ليله الكدر") -seg = r.get("matched_segment", "") -test("أخطاء إملائية: بيلاقي القدر", "ٱلْقَدْرِ" in seg or "القدر" in seg, f"seg={seg[:60]}") - -# ══════════════════════════════════════════ -print("\n📊 7. سلامة البيانات") -print("=" * 50) - -conn = sqlite3.connect(DB_PATH) -c = conn.cursor() - -# عدد الآيات -c.execute("SELECT COUNT(*) FROM verses") -total_verses = c.fetchone()[0] -test(f"إجمالي الآيات: {total_verses}", total_verses >= 6236, f"count={total_verses}") - -# عدد السور -c.execute("SELECT COUNT(DISTINCT sura_num) FROM verses") -total_suras = c.fetchone()[0] -test(f"إجمالي السور: {total_suras}", total_suras == 114, f"count={total_suras}") - -# كل سورة ليها اسم مترجم -c.execute("SELECT COUNT(*) FROM suras_translated") -trans_count = c.fetchone()[0] -test(f"أسماء السور المترجمة: {trans_count}", trans_count >= 114, f"count={trans_count}") - -# أول وآخر آية في القرآن -c.execute("SELECT text_uthmani FROM verses WHERE sura_num=1 AND aya_num=1") -first = c.fetchone()[0] -test("أول آية (الفاتحة ١): موجودة", len(first) > 5) - -c.execute("SELECT text_uthmani FROM verses WHERE sura_num=114 AND aya_num=6") -row = c.fetchone() -test("آخر آية (الناس ٦): موجودة", row is not None and len(row[0]) > 5) - -# التراجم موجودة -c.execute("PRAGMA table_info(verses)") -cols = [row[1] for row in c.fetchall()] -expected_langs = ["lang_en", "lang_fr", "lang_tr", "lang_ru", "lang_es", "lang_de", "lang_id", "lang_fa"] -for lang_col in expected_langs: - test(f"عمود {lang_col} موجود", lang_col in cols, f"cols={cols}") - -# أطول آية (البقرة 282) -c.execute("SELECT LENGTH(text_uthmani) FROM verses WHERE sura_num=2 AND aya_num=282") -longest = c.fetchone() -test("أطول آية (البقرة ٢٨٢): موجودة", longest is not None and longest[0] > 100, f"len={longest[0] if longest else 0}") - -# text_clean موجود لكل الآيات -c.execute("SELECT COUNT(*) FROM verses WHERE text_clean IS NULL OR text_clean = ''") -empty_clean = c.fetchone()[0] -test("text_clean مش فاضي لأي آية", empty_clean == 0, f"empty={empty_clean}") - -conn.close() - -# ══════════════════════════════════════════ -print("\n🔍 8. أول وآخر القرآن") -print("=" * 50) - -# أول آية -r = get_result("الحمد لله رب العالمين") -seg = r.get("matched_segment", "") -test("الفاتحة ٢: نتيجة صحيحة", "ٱلْحَمْدُ" in seg or "الحمد" in seg) - -# آخر سورة -r = get_result("قل اعوذ برب الناس ملك الناس اله الناس") -seg = r.get("matched_segment", "") -ref_match = re.search(r'【([^】]+)】', seg) -ref = ref_match.group(1) if ref_match else "" -test("الناس: اسم السورة صح", "الناس" in ref or "الن" in ref, f"ref={ref}") - -# أطول آية (البقرة 282) -r = get_result("يا ايها الذين آمنوا اذا تداينتم بدين الى اجل مسمى فاكتبوه") -seg = r.get("matched_segment", "") -test("البقرة ٢٨٢ (أطول آية): بترجع كاملة", len(seg) > 200, f"len={len(seg)}") - -# ══════════════════════════════════════════ -print("\n" + "=" * 60) -print(f"📊 النتيجة: {passed} ✅ نجح | {failed} ❌ فشل | من {passed + failed} اختبار") -print("=" * 60) - -if errors: - print("\n❌ الاختبارات الفاشلة:") - for name, detail in errors: - print(f" • {name}: {detail}") diff --git a/test_quran_extended.py b/test_quran_extended.py deleted file mode 100644 index d76458fe9659496befb79a44ca2ec0b4b76f9882..0000000000000000000000000000000000000000 --- a/test_quran_extended.py +++ /dev/null @@ -1,312 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Extended test suite — Part 2 -Additional edge cases beyond the 55 core tests. -""" - -import sys, os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from quran import search_bayan, normalize_arabic -import sqlite3, re - -DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'quran_master.db') - -passed = 0 -failed = 0 -errors = [] - -def test(name, condition, detail=""): - global passed, failed, errors - if condition: - passed += 1 - print(f" ✅ {name}") - else: - failed += 1 - errors.append((name, detail)) - print(f" ❌ {name} — {detail}") - -def get(q, lang="تدقيق الايات"): - return search_bayan(q, lang, db_path=DB_PATH) - -def ref_of(seg): - m = re.search(r'【([^】]+)】', seg) - return m.group(1) if m else "" - -# ══════════════════════════════════════════ -print("\n🔁 9. آيات متشابهة في سور مختلفة") -print("=" * 50) - -# "الحمد لله" موجودة في الفاتحة والأنعام والكهف وسبأ وفاطر -# نتأكد إن النتيجة آية كاملة مش مقطوعة -r = get("الحمد لله الذي خلق السماوات والأرض") -seg = r.get("matched_segment", "") -ref = ref_of(seg) -test("آية متشابهة: اسم سورة موجود", len(ref) > 0, f"ref={ref}") -test("آية متشابهة: آية كاملة", "(١)" in seg or "(٢)" in seg, f"seg={seg[:60]}") - -# "فبأي آلاء ربكما تكذبان" مكررة 31 مرة في الرحمن -r = get("فبأي آلاء ربكما تكذبان") -seg = r.get("matched_segment", "") -ref = ref_of(seg) -test("آية مكررة (فبأي آلاء): بترجع نتيجة", len(seg) > 10, f"ref={ref}") - -# "ويل يومئذ للمكذبين" مكررة 10 مرات في المرسلات -r = get("ويل يومئذ للمكذبين") -seg = r.get("matched_segment", "") -test("آية مكررة (ويل): بترجع نتيجة", len(seg) > 10) - -# ══════════════════════════════════════════ -print("\n📏 10. آيات قصيرة جداً") -print("=" * 50) - -# "طه" - كلمة واحدة -r = get("طه") -seg = r.get("matched_segment", "") -test("آية كلمة واحدة (طه): نتيجة", len(seg) > 5) - -# "والفجر" -r = get("والفجر") -seg = r.get("matched_segment", "") -test("آية قصيرة (والفجر): نتيجة", len(seg) > 5) - -# "والضحى" -r = get("والضحى") -seg = r.get("matched_segment", "") -test("آية قصيرة (والضحى): نتيجة", len(seg) > 5) - -# "مدهامتان" (الرحمن 64) -r = get("مدهامتان") -seg = r.get("matched_segment", "") -test("آية كلمة واحدة (مدهامتان): نتيجة", len(seg) > 5) - -# "والعصر" -r = get("والعصر") -seg = r.get("matched_segment", "") -ref = ref_of(seg) -test("والعصر: اسم سورة", "العصر" in ref or "عصر" in ref.lower(), f"ref={ref}") - -# ══════════════════════════════════════════ -print("\n🔤 11. كل الحروف المقطعة (29 سورة)") -print("=" * 50) - -muqattaat = [ - ("الم", "البقرة/آل عمران"), - ("المص", "الأعراف"), - ("الر", "يونس/هود/يوسف"), - ("المر", "الرعد"), - ("كهيعص", "مريم"), - ("طه", "طه"), - ("طسم", "الشعراء/القصص"), - ("طس", "النمل"), - ("يس", "يس"), - ("ص", "ص"), - ("حم", "غافر وغيرها"), - ("عسق", "الشورى"), - ("ق", "ق"), - ("ن", "القلم"), -] - -for letters, sura in muqattaat: - r = get(letters) - seg = r.get("matched_segment", "") - has_result = len(seg) > 5 and "error" not in str(r) - test(f"حروف مقطعة '{letters}' ({sura})", has_result, f"seg={seg[:40]}") - -# ══════════════════════════════════════════ -print("\n🧹 12. تنظيف المدخلات") -print("=" * 50) - -# مسافات زيادة -r = get(" قل هو الله أحد ") -seg = r.get("matched_segment", "") -test("مسافات زيادة: شغال", len(seg) > 10) - -# تشكيل كامل في المدخل -r = get("قُلْ هُوَ اللَّهُ أَحَدٌ") -seg = r.get("matched_segment", "") -test("مدخل بتشكيل كامل: شغال", len(seg) > 10) - -# بدون تشكيل خالص -r = get("قل هو الله احد") -seg = r.get("matched_segment", "") -test("مدخل بدون تشكيل: شغال", len(seg) > 10) - -# همزات مختلفة -r = get("إنا أنزلناه في ليلة القدر") -seg1 = r.get("matched_segment", "") -r = get("انا انزلناه في ليله القدر") -seg2 = r.get("matched_segment", "") -test("همزات مختلفة: نفس النتيجة", "القدر" in ref_of(seg1) and "القدر" in ref_of(seg2)) - -# تاء مربوطة vs هاء -r = get("ليله القدر") -seg = r.get("matched_segment", "") -test("تاء مربوطة/هاء: شغال", "القدر" in ref_of(seg), f"ref={ref_of(seg)}") - -# ══════════════════════════════════════════ -print("\n🌍 13. أسماء السور في التراجم") -print("=" * 50) - -# English surah name -r = get("قل هو الله أحد", "english") -ref = ref_of(r.get("matched_segment", "")) -test("English: Surah name = Al-Ikhlas", "Ikhlas" in ref or "ikhlas" in ref.lower(), f"ref={ref}") - -# French surah name -r = get("قل هو الله أحد", "french") -ref = ref_of(r.get("matched_segment", "")) -test("French: Surah name exists", len(ref) > 0, f"ref={ref}") - -# Turkish surah name -r = get("قل هو الله أحد", "turkish") -ref = ref_of(r.get("matched_segment", "")) -test("Turkish: Surah name exists", len(ref) > 0, f"ref={ref}") - -# Russian surah name -r = get("قل هو الله أحد", "russian") -ref = ref_of(r.get("matched_segment", "")) -test("Russian: Surah name exists", len(ref) > 0, f"ref={ref}") - -# ══════════════════════════════════════════ -print("\n🗃️ 14. سلامة البيانات المتقدمة") -print("=" * 50) - -conn = sqlite3.connect(DB_PATH) -c = conn.cursor() - -# لا توجد فجوات في ترقيم الآيات -c.execute(""" - SELECT sura_num, MAX(aya_num) as max_aya, COUNT(*) as cnt - FROM verses - GROUP BY sura_num - HAVING max_aya != cnt -""") -gaps = c.fetchall() -test("ترقيم الآيات: مفيش فجوات", len(gaps) == 0, f"gaps={gaps}") - -# كل آية ليها text_uthmani -c.execute("SELECT COUNT(*) FROM verses WHERE text_uthmani IS NULL OR text_uthmani = ''") -empty_uth = c.fetchone()[0] -test("text_uthmani مش فاضي لأي آية", empty_uth == 0, f"empty={empty_uth}") - -# كل آية ليها ترجمة إنجليزية -c.execute("SELECT COUNT(*) FROM verses WHERE lang_en IS NULL OR lang_en = ''") -empty_en = c.fetchone()[0] -test("lang_en مش فاضي لأي آية", empty_en == 0, f"empty={empty_en}") - -# كل آية ليها ترجمة فرنسية -c.execute("SELECT COUNT(*) FROM verses WHERE lang_fr IS NULL OR lang_fr = ''") -empty_fr = c.fetchone()[0] -test("lang_fr مش فاضي لأي آية", empty_fr == 0, f"empty={empty_fr}") - -# أسماء السور بالعربي -c.execute("SELECT COUNT(*) FROM suras_translated WHERE ar IS NULL OR ar = ''") -empty_ar = c.fetchone()[0] -test("أسماء السور بالعربي: كلها موجودة", empty_ar == 0, f"empty={empty_ar}") - -# أسماء السور بالإنجليزي -c.execute("SELECT COUNT(*) FROM suras_translated WHERE lang_en IS NULL OR lang_en = ''") -empty_en_s = c.fetchone()[0] -test("أسماء السور بالإنجليزي: كلها موجودة", empty_en_s == 0, f"empty={empty_en_s}") - -# عدد آيات بعض السور المعروفة -known_counts = {1: 7, 2: 286, 3: 200, 9: 129, 12: 111, 36: 83, 55: 78, 97: 5, 108: 3, 112: 4, 114: 6} -for sura, expected in known_counts.items(): - c.execute("SELECT COUNT(*) FROM verses WHERE sura_num = ?", (sura,)) - actual = c.fetchone()[0] - test(f"سورة {sura}: {actual} آية (متوقع {expected})", actual == expected, f"actual={actual}") - -conn.close() - -# ══════════════════════════════════════════ -print("\n🔀 15. البسملة في التراجم") -print("=" * 50) - -# English: Basmala should be stripped from non-Fatiha -r = get("الر تلك آيات الكتاب المبين", "english") -seg = r.get("matched_segment", "") -test("English: بسملة مش موجودة", "In the name of" not in seg, f"seg={seg[:60]}") - -# French: same -r = get("الر تلك آيات الكتاب المبين", "french") -seg = r.get("matched_segment", "") -test("French: بسملة مش موجودة", "Au nom d" not in seg and "Bismillah" not in seg.lower(), f"seg={seg[:60]}") - -# ══════════════════════════════════════════ -print("\n⚡ 16. أداء وحالات حدّية") -print("=" * 50) - -# نص طويل جداً (أول ربع من البقرة) -long_text = "الم ذلك الكتاب لا ريب فيه هدى للمتقين الذين يؤمنون بالغيب ويقيمون الصلاة ومما رزقناهم ينفقون والذين يؤمنون بما انزل اليك وما انزل من قبلك وبالآخرة هم يوقنون اولئك على هدى من ربهم واولئك هم المفلحون" -r = get(long_text) -seg = r.get("matched_segment", "") -test("نص طويل: بيرجع نتيجة", len(seg) > 50, f"len={len(seg)}") -test("نص طويل: سورة البقرة", "البقرة" in ref_of(seg) or "بقر" in ref_of(seg).lower(), f"ref={ref_of(seg)}") - -# نص فيه أرقام -r = get("123 الله") -test("نص فيه أرقام: مش بيكسر", True) # should not crash - -# علامات ترقيم -r = get("قل هو الله أحد!!!") -seg = r.get("matched_segment", "") -test("علامات ترقيم: شغال", len(seg) > 5) - -# تكرار نفس الكلمة -r = get("الله الله الله") -seg = r.get("matched_segment", "") -test("تكرار كلمة: مش بيكسر", True) - -# حرف واحد -r = get("ق") -seg = r.get("matched_segment", "") -test("حرف واحد (ق): شغال", len(seg) > 5) - -# حرف واحد (ص) -r = get("ص") -seg = r.get("matched_segment", "") -test("حرف واحد (ص): شغال", len(seg) > 5) - -# حرف واحد (ن) -r = get("ن") -seg = r.get("matched_segment", "") -test("حرف واحد (ن): شغال", len(seg) > 5) - -# ══════════════════════════════════════════ -print("\n🎯 17. دقة التطابق") -print("=" * 50) - -# آية الكرسي -r = get("الله لا اله الا هو الحي القيوم لا تأخذه سنة ولا نوم") -seg = r.get("matched_segment", "") -ref = ref_of(seg) -test("آية الكرسي: البقرة ٢٥٥", "البقرة" in ref or "بقر" in ref.lower(), f"ref={ref}") -test("آية الكرسي: رقم ٢٥٥", "٢٥٥" in ref or "255" in ref, f"ref={ref}") - -# خواتيم البقرة -r = get("آمن الرسول بما انزل اليه من ربه والمؤمنون") -seg = r.get("matched_segment", "") -ref = ref_of(seg) -test("خواتيم البقرة: ٢٨٥", "٢٨٥" in ref or "285" in ref, f"ref={ref}") - -# آية النور ٣٥ (آية النور) -r = get("الله نور السماوات والأرض مثل نوره كمشكاة") -seg = r.get("matched_segment", "") -ref = ref_of(seg) -test("آية النور: سورة النور", "النور" in ref or "نور" in ref.lower(), f"ref={ref}") - -# سورة يس آية 82 -r = get("انما امره اذا اراد شيئا ان يقول له كن فيكون") -seg = r.get("matched_segment", "") -test("كن فيكون: نتيجة صحيحة", len(seg) > 20) - -# ══════════════════════════════════════════ -print("\n" + "=" * 60) -print(f"📊 النتيجة: {passed} ✅ نجح | {failed} ❌ فشل | من {passed + failed} اختبار") -print("=" * 60) - -if errors: - print("\n❌ الاختبارات الفاشلة:") - for name, detail in errors: - print(f" • {name}: {detail}") diff --git a/test_quran_final.py b/test_quran_final.py deleted file mode 100644 index 70ed36534a06316e35871d97001734665c349d18..0000000000000000000000000000000000000000 --- a/test_quran_final.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Final edge case tests — Part 3 -Things we haven't tested yet. -""" -import sys, os, time, re, sqlite3 -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from quran import search_bayan, normalize_arabic - -DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'quran_master.db') -passed = 0 -failed = 0 -errors = [] - -def test(name, condition, detail=""): - global passed, failed, errors - if condition: - passed += 1 - print(f" ✅ {name}") - else: - failed += 1 - errors.append((name, detail)) - print(f" ❌ {name} — {detail}") - -def get(q, lang="تدقيق الايات"): - return search_bayan(q, lang, db_path=DB_PATH) - -def ref_of(seg): - m = re.search(r'【([^】]+)】', seg) - return m.group(1) if m else "" - -# ══════════════════════════════════════════ -print("\n⏱️ 18. أداء (سرعة البحث)") -print("=" * 50) - -queries = [ - "قل هو الله أحد", - "الم ذلك الكتاب لا ريب فيه هدى للمتقين", - "انا انزلناه في ليلة القدر", - "يا ايها الذين آمنوا اذا تداينتم بدين الى اجل مسمى فاكتبوه", -] -for q in queries: - t0 = time.time() - r = get(q) - dt = time.time() - t0 - test(f"سرعة '{q[:25]}...' = {dt:.2f}s", dt < 3.0, f"took {dt:.2f}s") - -# ══════════════════════════════════════════ -print("\n🌍 19. البسملة في كل التراجم") -print("=" * 50) - -all_langs = ["english", "french", "turkish", "russian", "spanish", "german", - "indonesian", "persian", "malay", "bengali", "bosnian", - "portuguese", "uzbek"] - -# Common Basmala patterns in each language -basmala_patterns = { - "english": "In the name of", - "french": "Au nom d", - "turkish": "adıyla", # Rahman ve Rahim olan Allah'ın adıyla - "russian": "Во имя", - "spanish": "En el nombre", - "german": "Im Namen", - "indonesian": "Dengan nama", - "persian": "به نام", - "malay": "Dengan nama", - "bengali": "পরম", - "bosnian": "U ime", - "portuguese": "Em nome", - "uzbek": "Rahmon", -} - -for lang in all_langs: - r = get("الر تلك آيات الكتاب المبين", lang) - seg = r.get("matched_segment", "") - if "error" in str(r): - test(f"{lang}: بسملة (لغة مش موجودة)", True) # skip missing langs - continue - pattern = basmala_patterns.get(lang, "") - has_basmala = pattern and pattern.lower() in seg.lower() - test(f"{lang}: بسملة مش موجودة في الترجمة", not has_basmala, f"found '{pattern}' in seg") - -# ══════════════════════════════════════════ -print("\n🔗 20. DB Connection Safety") -print("=" * 50) - -# Multiple rapid searches (connection handling) -for i in range(10): - r = get("قل هو الله أحد") -test("١٠ بحوث متتالية: مفيش crash", True) - -# Search with None/weird inputs -try: - r = get(None) - test("None input: handled", "error" in r or True) -except: - test("None input: handled", False, "crashed!") - -try: - r = get(" ") - test("Spaces only: handled", "error" in r) -except: - test("Spaces only: handled", False, "crashed!") - -# ══════════════════════════════════════════ -print("\n📝 21. أسماء السور في كل اللغات") -print("=" * 50) - -conn = sqlite3.connect(DB_PATH) -c = conn.cursor() - -# Check all translation columns exist in suras_translated -c.execute("PRAGMA table_info(suras_translated)") -sura_cols = [row[1] for row in c.fetchall()] - -lang_cols_sura = [col for col in sura_cols if col.startswith("lang_")] -for col in lang_cols_sura: - c.execute(f"SELECT COUNT(*) FROM suras_translated WHERE {col} IS NULL OR {col} = ''") - empty = c.fetchone()[0] - test(f"أسماء السور {col}: كلها موجودة", empty == 0, f"empty={empty}") - -# ══════════════════════════════════════════ -print("\n🔤 22. علامات الوقف القرآنية") -print("=" * 50) - -# Verses with special marks should still work -# البقرة 2 has ۛ -r = get("الم ذلك الكتاب لا ريب فيه هدى للمتقين") -seg = r.get("matched_segment", "") -test("آية بعلامات وقف: شغالة", len(seg) > 20) - -# Check that Quran marks are filtered from uthmani word count -c.execute("SELECT text_uthmani FROM verses WHERE sura_num=2 AND aya_num=1") -uth = c.fetchone()[0] -marks = {'ۖ', 'ۗ', 'ۘ', 'ۙ', 'ۚ', 'ۛ', 'ۜ', '۝', '۞', '۩'} -uth_tokens = uth.split() -mark_count = sum(1 for t in uth_tokens if t in marks) -test(f"علامات وقف في البقرة:١ = {mark_count}", mark_count >= 0) - -# ══════════════════════════════════════════ -print("\n🧮 23. عدد كلمات الاستعلام vs عدد كلمات الآية") -print("=" * 50) - -# Query longer than the verse itself (سورة الكوثر = 10 words) -r = get("انا اعطيناك الكوثر فصل لربك وانحر ان شانئك هو الابتر وهذا كلام زيادة ليس من القرآن") -seg = r.get("matched_segment", "") -ref = ref_of(seg) -test("استعلام أطول من السورة: بيجيب نتيجة", "الكوثر" in ref, f"ref={ref}") - -# Query that is exactly one verse -r = get("انا اعطيناك الكوثر") -seg = r.get("matched_segment", "") -test("استعلام = آية واحدة بالظبط: شغال", len(seg) > 10) - -# ══════════════════════════════════════════ -print("\n🔄 24. تطبيع النص المتقدم") -print("=" * 50) - -# ألف مقصورة vs ياء -r1 = get("هدى للمتقين") -r2 = get("هدي للمتقين") -test("ألف مقصورة/ياء: نفس النتيجة", ref_of(r1.get("matched_segment","")) == ref_of(r2.get("matched_segment",""))) - -# واو عطف ملتصقة -r = get("والعصر ان الانسان لفي خسر") -seg = r.get("matched_segment", "") -test("واو عطف: شغال", "العصر" in ref_of(seg), f"ref={ref_of(seg)}") - -# تنوين -r = get("هدًى للمتقين") -seg = r.get("matched_segment", "") -test("تنوين في المدخل: شغال", len(seg) > 10) - -# لام شمسية -r = get("الشمس والقمر") -seg = r.get("matched_segment", "") -test("لام شمسية: شغال", len(seg) > 10) - -# ══════════════════════════════════════════ -print("\n🏷️ 25. صحة أرقام الآيات") -print("=" * 50) - -# Verify specific verse numbers -test_cases = [ - ("آية الكرسي", "الله لا اله الا هو الحي القيوم", "البقرة", "٢٥٥"), - ("آية الدَّيْن", "يا ايها الذين آمنوا اذا تداينتم بدين", "البقرة", "٢٨٢"), - ("آية النور", "الله نور السماوات والارض", "النور", "٣٥"), - ("آية المُلك", "تبارك الذي بيده الملك", "الملك", "١"), -] - -for name, query, expected_sura, expected_num in test_cases: - r = get(query) - ref = ref_of(r.get("matched_segment", "")) - test(f"{name}: {expected_sura} {expected_num}", expected_sura in ref and expected_num in ref, f"ref={ref}") - -conn.close() - -# ══════════════════════════════════════════ -print("\n" + "=" * 60) -print(f"📊 النتيجة: {passed} ✅ نجح | {failed} ❌ فشل | من {passed + failed} اختبار") -print("=" * 60) - -if errors: - print("\n❌ الاختبارات الفاشلة:") - for name, detail in errors: - print(f" • {name}: {detail}") diff --git a/test_renderer.js b/test_renderer.js deleted file mode 100644 index 47a3bca17c46271555f7e34858b4dc19eb910a37..0000000000000000000000000000000000000000 --- a/test_renderer.js +++ /dev/null @@ -1,199 +0,0 @@ -// test_renderer.js -// Test the offset-based renderer with example data - -// Mock implementations for testing (since we don't have the full modules loaded) -function escapeHtml(text) { - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - return text.replace(/[&<>"']/g, (c) => map[c]); -} - -function sortSuggestions(suggestions) { - return [...suggestions].sort((a, b) => a.start - b.start); -} - -function createSegments(text, suggestions) { - if (!suggestions || suggestions.length === 0) { - return [{ - type: 'text', - text: text, - suggestions: [] - }]; - } - - const sorted = sortSuggestions(suggestions); - const finalSegments = []; - let segStart = 0; - - sorted.forEach((suggestion, idx) => { - const { start, end } = suggestion; - - // Add text before suggestion - if (segStart < start) { - finalSegments.push({ - type: 'text', - text: text.slice(segStart, start), - suggestions: [] - }); - } - - // Add suggested text - finalSegments.push({ - type: 'suggestion', - text: text.slice(start, end), - suggestion: suggestion - }); - - segStart = end; - }); - - // Add remaining text - if (segStart < text.length) { - finalSegments.push({ - type: 'text', - text: text.slice(segStart), - suggestions: [] - }); - } - - return finalSegments; -} - -function getErrorClass(type) { - const classes = { - 'spelling': 'spelling-error', - 'grammar': 'grammar-error', - 'punctuation': 'punctuation-suggestion' - }; - return classes[type] || 'spelling-error'; -} - -function renderHighlightedText(text, suggestions) { - if (!text || text.length === 0) { - return ''; - } - - if (!suggestions || suggestions.length === 0) { - return escapeHtml(text); - } - - const segments = createSegments(text, suggestions); - let html = ''; - let suggestionId = 0; - - segments.forEach((segment) => { - if (segment.type === 'text') { - html += escapeHtml(segment.text); - } else if (segment.type === 'suggestion') { - const { suggestion } = segment; - const errorClass = getErrorClass(suggestion.type); - const escapedText = escapeHtml(segment.text); - - html += `${escapedText}`; - - suggestionId++; - } - }); - - return html; -} - -// Test case: The example from the task -const testText = "ذهبو الى المدرسة ثم ذهبو الى البيت ثم ذهبو مرة اخرى"; - -// Simulate API response with three occurrences of "ذهبو" highlighted -const testSuggestions = [ - { - start: 0, - end: 4, - original: "ذهبو", - correction: "ذهبوا", - type: "spelling" - }, - { - start: 20, - end: 24, - original: "ذهبو", - correction: "ذهبوا", - type: "spelling" - }, - { - start: 38, - end: 42, - original: "ذهبو", - correction: "ذهبوا", - type: "spelling" - } -]; - -console.log('=== Offset-Based Renderer Test ===\n'); -console.log('Input text:'); -console.log(`"${testText}"\n`); - -console.log('Suggestions:'); -testSuggestions.forEach((s, idx) => { - console.log(` ${idx + 1}. [${s.start}:${s.end}] "${s.original}" → "${s.correction}" (${s.type})`); -}); -console.log(); - -const html = renderHighlightedText(testText, testSuggestions); -console.log('Rendered HTML:'); -console.log(html); -console.log(); - -// Verify all three occurrences are highlighted -const highlightCount = (html.match(/class="spelling-error"/g) || []).length; -console.log(`✓ Number of highlights: ${highlightCount}/3`); - -if (highlightCount === 3) { - console.log('✓ SUCCESS: All three occurrences are highlighted independently!'); -} else { - console.log('✗ FAIL: Not all occurrences were highlighted'); -} - -// Test XSS protection -console.log('\n=== XSS Protection Test ==='); -const xssTestText = "اختبار النص"; -const xssHtml = renderHighlightedText(xssTestText, []); -console.log('Input with potential XSS:', xssTestText); -console.log('Rendered (safe):', xssHtml); -if (!xssHtml.includes('