#!/usr/bin/env python3
"""
09_build_report.py -- FINAL_REPORT.md (markdown twin) + the client PDF, built from the
vendored report_style chrome. Both are emitted from the same data so they stay in parity.
Outputs: scripts/FINAL_REPORT.md, output/TP53_mCherry_Knockin_Report.pdf
"""
import json, os
from report_style import (make_doc, notice_box, callout, make_table, status_para,
                          references_block, bullet, section, subsection, embed_image,
                          PALETTE, STYLES, CONTENT_W)
from reportlab.platypus import Spacer, PageBreak, KeepTogether, Paragraph
from reportlab.lib.units import mm

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "..", "output")
FIG = os.path.join(OUT, "figures")
L = json.load(open(os.path.join(HERE, "locus.json")))
gd = {g["id"]: g for g in json.load(open(os.path.join(HERE, "guide_candidates.json")))["guides"]}
cris = json.load(open(os.path.join(HERE, "crispor_summary.json")))["guides"]
offt = json.load(open(os.path.join(HERE, "offtarget_results.json")))
FF = json.load(open(os.path.join(HERE, "flashfry_summary.json")))["guides"]
VS = json.load(open(os.path.join(HERE, "variant_screen.json")))
DON = json.load(open(os.path.join(HERE, "donor.json")))
PR = json.load(open(os.path.join(HERE, "primers.json")))["sets"]
MCH = json.load(open(os.path.join(HERE, "mcherry_opt.json")))
g3, g2 = gd["TP53ct_g3"], gd["TP53ct_g2"]
p5, p3, pin = PR["5_prime_junction"], PR["3_prime_junction"], PR["internal_across_insert"]
DATE = "29 June 2026"

# ============================================================ shared content
REFS = [
    "O'Leary NA, et al. (2016). Reference sequence (RefSeq) database at NCBI. <i>Nucleic Acids Res</i> 44:D733-D745. TP53 transcript NM_000546.6.",
    "Shaner NC, Campbell RE, Steinbach PA, Giepmans BNG, Palmer AE, Tsien RY (2004). Improved monomeric red, orange and yellow fluorescent proteins derived from <i>Discosoma</i> sp. red fluorescent protein. <i>Nat Biotechnol</i> 22:1567-1572. (mCherry; GenBank AY678264)",
    "Concordet JP, Haeussler M (2018). CRISPOR: intuitive guide selection for CRISPR/Cas9 genome editing experiments and screens. <i>Nucleic Acids Res</i> 46:W242-W245.",
    "Bae S, Park J, Kim JS (2014). Cas-OFFinder: a fast and versatile algorithm that searches for potential off-target sites of Cas9 RNA-guided endonucleases. <i>Bioinformatics</i> 30:1473-1475.",
    "Doench JG, et al. (2016). Optimized sgRNA design to maximize activity and minimize off-target effects of CRISPR-Cas9. <i>Nat Biotechnol</i> 34:184-191. (CFD / Rule Set 2)",
    "Ran FA, Hsu PD, Wright J, Agarwala V, Scott DA, Zhang F (2013). Genome engineering using the CRISPR-Cas9 system. <i>Nat Protoc</i> 8:2281-2308.",
    "Lilyestrom W, Klein MG, Zhang R, Joachimiak A, Chen XS (2006). Crystal structure of SV40 large T-antigen bound to p53: interplay between a viral oncoprotein and a cellular tumor suppressor. <i>Genes Dev</i> 20:2373-2382.",
    "Kruse JP, Gu W (2009). Modes of p53 regulation. <i>Cell</i> 137:609-622. (C-terminal domain: ubiquitination, acetylation, PTMs)",
    "Lahav G, Rosenfeld N, Sigal A, Geva-Zatorsky N, Levine AJ, Elowitz MB, Alon U (2004). Dynamics of the p53-Mdm2 feedback loop in individual cells. <i>Nat Genet</i> 36:147-150.",
]
QC_ROWS = [
    ("Guide cross-validation across 3 tools (Cas-OFFinder + CRISPOR + FlashFry)", "PASS", "All rank g3 most specific; counts + top-OT CFD agree"),
    ("No off-target at <=1 mismatch (primary guide g3)", "PASS", "0 hits in all three tools"),
    ("No off-target in a TP53 pseudogene / p53 family gene", "PASS", "0 hits (prior-failure mode); annotating tools Cas-OFFinder + CRISPOR"),
    ("mCherry human codon-optimized, no internal BsaI/BsmBI", "PASS", f"708 nt, GC {MCH['mcherry_gc']}%, 0 sites"),
    ("Donor re-cut protection", "PASS", "g3 split by insert; g2 silent PAM-block (Gly)"),
    ("In-frame C-terminal fusion, native stop + 3'UTR reused", "PASS", "single stop; ...MDELYK-stop verified"),
    ("Junction primers KI-specific (no WT / no donor product)", "PASS", "isPcr unique; in-silico verified"),
    ("No common SNP under guide binding sites (Ensembl, MAF>=1%)", "PASS", f"{VS['n_variants']} cataloged variants in window, all rare"),
    ("Cell line supports physiological p53 dynamics", "WARN", "HEK293T SV40 large T antigen perturbs p53"),
    ("C-terminal tag and p53 CTD-dependent regulation", "WARN", "Validate tagged-p53 function"),
]

def guide_row(g, cr, role):
    return [f"{g['id']}\n({role})", g["spacer"], g["pam"], g["strand"],
            f"{g['dist_to_insert']:+d}", f"{g['gc']:.0f}", str(cr["mit_spec"]),
            str(cr["cfd_spec"]), str(cr["doench16"])]

# ============================================================ MARKDOWN twin
def build_md():
    g3o, g2o = offt["TP53ct_g3"], offt["TP53ct_g2"]
    md = []
    A = md.append
    A(f"# TP53 C-terminal mCherry Knock-in -- Design Report\n")
    A(f"**AI Bioinformatician by Labterna**  |  {DATE}  |  GRCh38/hg38  |  TP53 NM_000546.6\n")
    A("> **Design delivered; one biology caveat to read first.** A complete, cross-validated "
      "SpCas9 + HDR design for endogenous C-terminal mCherry tagging of TP53 is provided: a primary "
      "and backup guide at the stop codon (validated across three independent off-target tools, **no "
      "pseudogene/p53-family off-targets**), an 800/800 bp HDR donor with a codon-optimized BsaI/BsmBI-free "
      "mCherry and built-in re-cut protection, and a full genotyping primer set. **Caveat:** HEK293T "
      "expresses SV40 large T antigen, which binds and functionally inactivates p53 -- p53 dynamics under "
      "genotoxic stress in this line are non-physiological (Section 7).\n")

    A("## 1. Objective\n")
    A("Design an endogenous C-terminal mCherry knock-in at TP53 (NM_000546.6, GRCh38) in HEK293T for "
      "live-cell imaging of p53 under genotoxic stress, using SpCas9 and an HDR donor. Deliverables requested: "
      "(i) guide(s) at the TP53 stop codon cross-validated across >=2 tools (a previous design failed on an "
      "undetected pseudogene off-target); (ii) an HDR donor with ~800 bp homology arms, a GS linker before "
      "mCherry, and PAM-blocking silent mutations; (iii) junction genotyping primers (5' and 3', anchored "
      "outside the arms) plus an internal Sanger pair across the insert; (iv) a full off-target report for the "
      "top guides. mCherry to be human codon-optimized with no internal BsaI/BsmBI sites.\n")

    A("## 2. Target biology and tagging strategy\n")
    A(f"TP53 sits on chr17p13.1 on the **minus strand**; NM_000546.6 encodes the canonical 393-aa p53 "
      f"(isoform a). The C-terminus (...Lys-Leu-Met-Phe-Lys-Thr-Glu-Gly-Pro-Asp-Ser-Asp{chr(0x2089) if False else '393'}) lies in the "
      "last exon (exon 11), and the entire 1,188-nt 3'UTR is intronless and contiguous with the stop codon "
      "in the genome -- so both homology arms can be taken directly from genomic sequence with no intron "
      "complications. mCherry is inserted **in-frame immediately before the native TGA stop** "
      f"(chr17:{L['native_stop_chr17_1based'][0]}-{L['native_stop_chr17_1based'][1]}), so the fusion reuses the "
      "endogenous stop codon and 3'UTR, preserving native post-transcriptional regulation. The resulting ORF "
      "is p53(1-393)-(GGGGS)x3-mCherry-stop.\n")
    A("**Why C-terminal, and the caveat it carries.** The N-terminus carries the transactivation domains and "
      "the MDM2-binding site (residues ~17-29); an N-terminal tag would block MDM2 binding and artificially "
      "stabilize p53. The C-terminus is therefore the conventional choice, but it abuts the **C-terminal "
      "regulatory domain (CTD, ~363-393)** -- the site of MDM2-mediated ubiquitination (Lys370/372/373/381/382/386) "
      "and numerous regulatory PTMs (Kruse & Gu 2009). The tag is fused after residue 393 (the CTD lysines are "
      "preserved), and a flexible (GGGGS)x3 linker is used to minimize steric interference, but tagged-p53 "
      "function must be validated experimentally (Section 7).\n")
    A("Endogenous fluorescent tagging of p53 for live single-cell dynamics is well precedented (the p53-Mdm2 "
      "pulsing literature, Lahav et al. 2004 [9], and later endogenous reporter lines), which motivates tagging at "
      "the native locus rather than overexpressing. The guide+donor set here is a de novo design for that purpose; "
      "a formal parameter-by-parameter comparison against a specific published TP53 knock-in line is noted as a "
      "possible extension (Section 7).\n")

    A("## 3. Guide RNA design and selection\n")
    A("All NGG protospacers with a predicted blunt cut within 35 bp of the insertion point were enumerated on "
      "both strands; 7 candidates were scored. On-target efficiency and specificity were obtained from "
      "**CRISPOR** (Doench-2016, MIT and CFD specificity) and genome-wide off-targets independently from "
      "**Cas-OFFinder** (Section 4). Two guides are recommended:\n")
    A("| Guide (role) | Spacer 5'->3' | PAM | Strand | Cut vs insert (bp) | GC% | MIT spec | CFD spec | Doench'16 |")
    A("|---|---|---|---|---|---|---|---|---|")
    A(f"| {g3['id']} (PRIMARY) | {g3['spacer']} | {g3['pam']} | {g3['strand']} | {g3['dist_to_insert']:+d} | {g3['gc']:.0f} | {cris['TP53ct_g3']['mit_spec']} | {cris['TP53ct_g3']['cfd_spec']} | {cris['TP53ct_g3']['doench16']} |")
    A(f"| {g2['id']} (backup) | {g2['spacer']} | {g2['pam']} | {g2['strand']} | {g2['dist_to_insert']:+d} | {g2['gc']:.0f} | {cris['TP53ct_g2']['mit_spec']} | {cris['TP53ct_g2']['cfd_spec']} | {cris['TP53ct_g2']['doench16']} |")
    A("")
    A("**g3 (primary)** cuts just 6 bp from the insertion point (high HDR efficiency), is 50% GC, begins with "
      "G (U6 transcription-ready), and has the **highest MIT (70) and CFD (87) specificity of any guide at the "
      "stop codon**. Critically, its protospacer straddles the insertion point, so the inserted cassette splits "
      "the target -- the edited allele is no longer a Cas9 substrate, requiring no PAM-blocking mutation. Its "
      "on-target efficiency is moderate (Doench'16 49); it was chosen for specificity, cut-to-insert proximity, "
      "and intrinsic re-cut immunity rather than peak predicted cutting.\n")
    A("**On the backup.** For a single-point knock-in both guides must sit at the stop codon to drive HDR there, "
      "so the standard \"backup amplicon in a different region, >=100 bp away\" rule does not apply -- g3 and g2 "
      "necessarily overlap (cuts ~12 bp apart). g2's genuine value as a backup is its **different seed, strand and "
      "PAM**: it is immune to a SNP under g3's seed or to g3-specific inefficiency, and it has the higher predicted "
      "efficiency (Doench'16 63), so it is the natural fallback if g3 cutting proves weak in validation. It does "
      "**not** protect against a locus-level failure (a deletion/structural variant spanning the stop codon would "
      "disable both); that residual shared-failure mode is inherent to point knock-ins. Because g2's target lies "
      "entirely within the 5' arm, a silent PAM-block is built into the donor (Section 5). Guides g4-g7 scored "
      "lower (MIT 30-61) and the GC-rich g5-g7 are markedly promiscuous (622-908 off-target sites); they are "
      "tabulated in the workbook but not recommended.\n")

    A("## 4. Off-target / specificity analysis (three independent tools)\n")
    A("Because the client's prior design failed on an off-target pseudogene match missed by a single tool, "
      "specificity was assessed with **three independent genome-wide methods** and the results cross-checked:\n")
    A("- **Cas-OFFinder** (local, exhaustive enumeration against hg38): NGG up to 4 mismatches plus NAG up to "
      "3 mismatches; every hit annotated to RefSeq genes (exon/intron/intergenic).\n")
    A("- **CRISPOR** (web, BWA-based, hg38): independent off-target search with CFD/MIT off-target scoring and "
      "gene annotation.\n")
    A("- **FlashFry** (local, against a purpose-built hg38 SpCas9-NGG database): a third, independent algorithm "
      "reporting the same community metrics (CFD, Hsu/MIT, Doench).\n")
    A(f"For the primary guide **g3**, all three tools agree: **{g3o['n_offtargets']} (Cas-OFFinder) / "
      f"{cris['TP53ct_g3']['offtarget_count']} (CRISPOR) / {FF['TP53ct_g3']['otCount']} (FlashFry) off-target "
      "sites, with zero at <=1 mismatch in every tool**. The few 2-mismatch sites are intronic/intergenic; all "
      f"exonic off-targets occur only at 4 mismatches with multiple seed mismatches (max CFD "
      f"{cris['TP53ct_g3']['exonic_max_cfd']}, predicted inactive), and the same exonic loci (UBR1, FGA, "
      "NAALADL2) are recovered by the annotating tools. CRISPOR and FlashFry independently identify the **same "
      f"single highest-risk off-target with an identical CFD of {cris['TP53ct_g3']['max_cfd_offtarget']}** (g3) / "
      f"{cris['TP53ct_g2']['max_cfd_offtarget']} (g2). **No off-target falls in a TP53 pseudogene or any p53-family "
      "gene (TP63/TP73)** -- the specific failure mode from the previous design is excluded. g2 shows the same "
      "clean pattern. Guide-level specificity is concordant: FlashFry Hsu/MIT 74 (g3) vs CRISPOR MIT 70.\n")
    A("| Tool (hg38) | g3 off-targets | g3 <=1 mm | g3 top-OT CFD | g2 off-targets | g2 <=1 mm | p53-family / pseudogene |")
    A("|---|---|---|---|---|---|---|")
    A(f"| Cas-OFFinder (local) | {g3o['n_offtargets']} | 0 | n/a | {g2o['n_offtargets']} | 0 | 0 |")
    A(f"| CRISPOR (web) | {cris['TP53ct_g3']['offtarget_count']} | 0 | {cris['TP53ct_g3']['max_cfd_offtarget']} | {cris['TP53ct_g2']['offtarget_count']} | 0 | 0 |")
    A(f"| FlashFry (local) | {FF['TP53ct_g3']['otCount']} | 0 | {FF['TP53ct_g3']['max_cfd_offtarget']} | {FF['TP53ct_g2']['otCount']} | 0 | 0* |")
    A("\n*FlashFry confirms the off-target count and mismatch distribution on a third algorithm; gene/pseudogene "
      "annotation is from the two annotating tools (Cas-OFFinder, CRISPOR), which agree on zero. Cas-OFFinder does "
      "not compute CFD (it enumerates exhaustively); top-OT CFD is from CRISPOR/FlashFry, which match exactly. "
      "(FlashFry's table value is genomic off-target sites (otCount); Figure 2B's bars count unique off-target "
      "sequences, slightly fewer because some map to several loci.)\n")
    A(f"**Binding-site variation.** A common SNP in a spacer seed or PAM would cause allele-specific cutting "
      f"failure -- relevant here because the edit is in HEK293T's own genome. Screening Ensembl Variation (GRCh38) "
      f"across the g3 and g2 protospacer+PAM windows returned {VS['n_variants']} cataloged variants, **none common "
      f"(MAF >= 1%)** under either guide -- no allele-dropout risk (g3 {VS['g3']['verdict']}, g2 {VS['g2']['verdict']}). "
      "The region is densely catalogued with rare single-submission variants, none of which threaten the assay.\n")

    A("## 5. HDR donor design\n")
    A(f"The donor is a **{DON['donor_len']} bp dsDNA** (plasmid or synthetic long dsDNA) with the architecture "
      "5' arm (800 bp) - (GGGGS)x3 linker - mCherry - 3' arm (800 bp):\n")
    A(f"- **Homology arms (~800 bp each), genomic.** 5' arm {DON['arm5_chr17']}; 3' arm {DON['arm3_chr17']}. "
      "The 5' arm ends at the last p53 sense codon (Asp393); the 3' arm begins with the native TGA stop and 3'UTR.\n")
    A("- **Linker.** Flexible (GGGGS)x3 (15 aa), codon-diversified to avoid a tandem repeat that would complicate "
      "synthesis/recombination.\n")
    A(f"- **mCherry.** 236-aa mCherry (Shaner 2004; GenBank AY678264, translation verified byte-identical), "
      f"human codon-optimized to {len(MCH['mcherry_nt'])} nt (GC {MCH['mcherry_gc']}%), **0 internal BsaI and 0 "
      "BsmBI sites** as requested, carrying no stop (it reads through into the native TGA).\n")
    A("- **Re-cut protection (PAM-blocking).** g3 needs none (the insertion splits its target). For g2, a single "
      "**silent** change in the 5' arm (codon 389 GGG->GGT, both Glycine) destroys the NGG PAM; the CDS "
      "translation is unchanged and g2 can no longer cut the donor or edited allele. One donor therefore serves "
      "either guide.\n")
    A(f"- **Restriction note.** The insert (linker+mCherry) is BsaI/BsmBI-free. The genomic homology arms contain "
      f"{len(DON['donor_bsaI'])} BsaI and {len(DON['donor_bsmBI'])} BsmBI site(s) (native TP53 sequence); these "
      "do not affect the requested re-clonability of mCherry but should be noted if Golden Gate assembly of the "
      "whole donor is planned.\n")

    A("## 6. Genotyping and validation strategy\n")
    A("Three primer pairs are provided; all genomic primers are unique on hg38 (isPcr) and the logic was "
      "confirmed by in-silico PCR on WT, donor and knock-in templates:\n")
    A(f"- **5' junction PCR** ({p5['product_size_ki']} bp): forward outside the 5' arm (genomic) + reverse in the "
      "insert. Product only on correct 5' integration -- no product on WT or on the donor plasmid.\n")
    A(f"- **3' junction PCR** ({p3['product_size_ki']} bp): forward in the insert + reverse outside the 3' arm "
      "(genomic). Confirms 3' integration; KI-specific.\n")
    A(f"- **Internal / zygosity pair** (across the insert): **WT {pin['product_size_wt']} bp vs knock-in "
      f"{pin['product_size_ki']} bp**, distinguishing wild-type / heterozygous / homozygous clones and providing "
      "a Sanger amplicon across the whole insert and both junctions.\n")
    A("Anchoring the junction primers outside the homology arms is deliberate: the donor carries only ~800 bp of "
      "arm, so a primer beyond the arm cannot prime on residual donor -- a band proves genuine genomic integration.\n")
    A("Note: the internal/zygosity pair sits **within** the homology arms, so it also amplifies residual donor "
      "plasmid/dsDNA (same knock-in-size band). Treat the **junction PCRs as the definitive integration proof** and "
      "account for donor carryover when scoring zygosity.\n")

    A("## 7. Critical caveats and recommendations\n")
    A("**(a) HEK293T expresses SV40 large T antigen -- a confound for p53 dynamics.** 293T cells stably express "
      "SV40 large T antigen, which binds p53 directly, **stabilizes** it (raising basal levels) while "
      "**inhibiting its transactivation** and blocking p53-dependent MDM2 induction (Lilyestrom 2006). p53 "
      "stress dynamics (stabilization pulses, target-gene feedback) in 293T are therefore not representative of "
      "normal p53 signaling. If the goal is physiological p53 dynamics, a p53-wild-type line without a viral "
      "oncoprotein (e.g. MCF7, RPE1-hTERT, A549, U2OS, HCT116) is strongly preferable. If HEK293T is required, "
      "interpret dynamics with the large-T caveat in mind. **This is the single most important point in this report.**\n")
    A("**(b) C-terminal tag vs p53 regulation.** The CTD (363-393) governs MDM2-dependent turnover and PTM-based "
      "regulation. Validate that tagged p53 still: stabilizes after genotoxic stress (e.g. doxorubicin, "
      "nutlin-3a), localizes to the nucleus, and induces canonical targets (CDKN1A/p21, MDM2) -- ideally against "
      "an untagged control.\n")
    A("**(c) HEK293 ploidy.** HEK293/293T is hypotriploid (~3 copies of chr17/TP53). Expect multiple alleles to "
      "edit; the internal/zygosity PCR and Sanger reads will reveal allele dosage. Homozygous (all-allele) "
      "tagging may require screening more clones.\n")
    A("**(d) Delivery & HDR.** Deliver the donor as a plasmid or long dsDNA (arms are 800 bp -- too long for "
      "ssODN). Co-deliver SpCas9 + g3 sgRNA (RNP or plasmid). HDR is enhanced by cell-cycle synchronization "
      "and small-molecule NHEJ inhibition if needed. Validate with the junction PCRs, then Sanger across the "
      "insert, then confirm mCherry fluorescence and tagged-p53 function.\n")
    A("**(e) Multiplex/specificity scope.** Specificity here is for the human genome (host). No multiplex panel "
      "applies. The guide cloning oligos provided are for pX330/pSpCas9(BB)-family BbsI vectors.\n")

    A("## 8. Methods, databases and auditability\n")
    A(f"TP53 NM_000546.6 retrieved from NCBI; genomic sequence from the local GRCh38/hg38 2bit. Stop codon "
      "anchored by exact sequence match (build-independent). Guides enumerated with a custom script; on-target "
      "and specificity scores from CRISPOR v5.2 (hg38; batch retained); exhaustive off-targets from Cas-OFFinder "
      "v2.4.1 (hg38, CPU) and, independently, FlashFry against a purpose-built hg38 SpCas9-NGG database "
      "(cached locally for reuse). mCherry codon optimization with DNAChisel (h_sapiens table) enforcing BsaI/BsmBI "
      "absence. Primers with primer3; specificity by isPcr against hg38; binding-site variant screen via Ensembl "
      "Variation (GRCh38). Data retrieved 2026-06-29/30. The numbered scripts/ pipeline ships as the reproducible "
      "record.\n")

    A("## 9. Output files\n")
    A("- `TP53_mCherry_Knockin_Report.pdf` -- this report\n")
    A("- `TP53_mCherry_Knockin_Design.xlsx` -- guides, off-targets, donor components, primers, cloning oligos\n")
    A("- `sequences.fasta` -- guides, mCherry/insert, donor + arms, primers, BbsI cloning oligos\n")
    A("- `figures/` -- knock-in strategy; specificity evidence; donor + genotyping\n")
    A("- `scripts/` -- reproducible numbered pipeline (ships with the project)\n")

    A("## 10. References\n")
    for i, r in enumerate(REFS, 1):
        A(f"{i}. {r.replace('<i>','*').replace('</i>','*')}")
    open(os.path.join(HERE, "FINAL_REPORT.md"), "w").write("\n".join(md) + "\n")
    print("[md] FINAL_REPORT.md written")

# ============================================================ PDF
def P(t, style="body"): return Paragraph(t, STYLES[style])
def build_pdf():
    g3o, g2o = offt["TP53ct_g3"], offt["TP53ct_g2"]
    doc = make_doc(os.path.join(OUT, "TP53_mCherry_Knockin_Report.pdf"),
                   title="TP53 C-terminal mCherry Knock-in -- Design Report",
                   subtitle="Endogenous tagging in HEK293T  |  SpCas9 + HDR  |  GRCh38 / NM_000546.6",
                   meta=f"{DATE}  |  GRCh38/hg38",
                   footer_left="TP53-mCherry knock-in", footer_center=DATE)
    s = []
    s.append(notice_box("warn",
        "Design complete and cross-validated -- one biology caveat to read first",
        "A primary + backup SpCas9 guide at the TP53 stop codon (validated across three independent tools -- "
        "Cas-OFFinder + CRISPOR + FlashFry; <b>no pseudogene / p53-family off-targets</b>), an 800/800 bp HDR donor with codon-optimized "
        "BsaI/BsmBI-free mCherry and built-in re-cut protection, and a full genotyping set are delivered. "
        "<b>Caveat:</b> HEK293T expresses SV40 large T antigen, which binds and functionally inactivates p53 -- "
        "p53 stress dynamics in this line are non-physiological (Section 7)."))
    s.append(Spacer(1, 5))

    s.append(section("1. Objective"))
    s.append(P("Design an endogenous C-terminal mCherry knock-in at TP53 (NM_000546.6, GRCh38) in HEK293T for "
        "live-cell imaging of p53 under genotoxic stress, using SpCas9 + HDR. Requested: (i) guide(s) at the TP53 "
        "stop codon cross-validated across &ge;2 tools (a prior design failed on an undetected pseudogene "
        "off-target); (ii) an HDR donor with ~800 bp arms, a GS linker before mCherry, and PAM-blocking silent "
        "mutations; (iii) 5' and 3' junction genotyping primers anchored outside the arms plus an internal Sanger "
        "pair; (iv) a full off-target report. mCherry: human codon-optimized, no internal BsaI/BsmBI."))

    s.append(section("2. Target biology and tagging strategy"))
    s.append(P("TP53 lies on chr17p13.1 (<b>minus strand</b>); NM_000546.6 encodes the canonical 393-aa p53. The "
        "C-terminus (...EGPDSD<sub>393</sub>) is in the last exon, and the entire 1,188-nt 3'UTR is intronless and "
        "contiguous with the stop codon, so both homology arms come cleanly from genomic sequence. mCherry is "
        f"inserted <b>in-frame immediately before the native TGA stop</b> (chr17:{L['native_stop_chr17_1based'][0]}-"
        f"{L['native_stop_chr17_1based'][1]}), reusing the endogenous stop and 3'UTR. Fusion ORF: "
        "p53(1-393)-(GGGGS)&times;3-mCherry-stop."))
    s.append(P("<b>Why C-terminal (and its caveat).</b> An N-terminal tag would block the MDM2-binding site "
        "(~residues 17-29) and artificially stabilize p53, so the C-terminus is conventional. It abuts the "
        "<b>C-terminal regulatory domain (CTD, ~363-393)</b> -- the site of MDM2 ubiquitination (Lys370-386) and "
        "many PTMs (Kruse &amp; Gu 2009). The tag is placed after residue 393 (CTD lysines preserved) with a "
        "flexible (GGGGS)&times;3 linker, but tagged-p53 function must be validated (Section 7)."))
    s.append(P("Endogenous fluorescent tagging of p53 for live single-cell dynamics is well precedented (the "
        "p53-Mdm2 pulsing literature, Lahav et al. 2004 [9], and later endogenous reporter lines), which motivates "
        "tagging at the native locus over overexpression. This is a de novo guide+donor design for that purpose; a "
        "parameter-by-parameter comparison against a specific published TP53 knock-in line is a possible extension."))
    s.append(Spacer(1, 4))
    s.append(KeepTogether([embed_image(os.path.join(FIG, "figure_A_knockin_strategy.png"), max_width=CONTENT_W*0.84),
        P("Figure 1. SpCas9 guides at the TP53 stop codon (top) and the C-terminal mCherry fusion relative to "
          "p53 domain structure (bottom). The insertion sits just downstream of the regulatory CTD.", "caption")]))

    s.append(section("3. Guide RNA design and selection"))
    s.append(P("All NGG protospacers cutting within 35 bp of the insertion point were enumerated on both strands "
        "(7 candidates). On-target/specificity scores are from CRISPOR; genome-wide off-targets independently from "
        "Cas-OFFinder (Section 4). Two guides are recommended:"))
    gtbl = make_table(
        ["Guide (role)", "Spacer 5'->3'", "PAM", "Str.", "Cut vs ins.", "GC%", "MIT", "CFD", "Doench16"],
        [[P(f"{g3['id']}<br/>(PRIMARY)","cell"), P(g3["spacer"],"cell_mono"), g3["pam"], g3["strand"],
          f"{g3['dist_to_insert']:+d} bp", f"{g3['gc']:.0f}", str(cris['TP53ct_g3']['mit_spec']),
          str(cris['TP53ct_g3']['cfd_spec']), str(cris['TP53ct_g3']['doench16'])],
         [P(f"{g2['id']}<br/>(backup)","cell"), P(g2["spacer"],"cell_mono"), g2["pam"], g2["strand"],
          f"{g2['dist_to_insert']:+d} bp", f"{g2['gc']:.0f}", str(cris['TP53ct_g2']['mit_spec']),
          str(cris['TP53ct_g2']['cfd_spec']), str(cris['TP53ct_g2']['doench16'])]],
        col_widths=[60, 110, 32, 28, 44, 34, 30, 30, 56])
    s.append(gtbl)
    s.append(Spacer(1, 4))
    s.append(P("<b>g3 (primary)</b> cuts 6 bp from the insertion (efficient HDR), is 50% GC, starts with G "
        "(U6-ready), and has the highest MIT (70) and CFD (87) specificity of any guide at the stop codon. Its "
        "protospacer straddles the insertion point, so the insert splits the target -- the edited allele is no "
        "longer a Cas9 substrate and <b>no PAM-block is needed</b>. On-target efficiency is moderate (Doench'16 49); "
        "g3 is chosen for specificity, cut-to-insert proximity and intrinsic re-cut immunity rather than peak "
        "predicted cutting."))
    s.append(P("<b>On the backup.</b> For a single-point knock-in both guides must sit at the stop codon, so the "
        "standard \"backup &ge;100 bp away\" rule does not apply -- g3 and g2 overlap (cuts ~12 bp apart). g2's value "
        "is its <b>different seed, strand and PAM</b> (immune to a SNP under g3's seed or to g3-specific "
        "inefficiency) and its higher predicted efficiency (Doench'16 63) -- the natural fallback if g3 cuts weakly. "
        "It does <b>not</b> cover a locus-level deletion/SV spanning the stop codon (a residual shared-failure mode "
        "inherent to point knock-ins). g2's target lies in the 5' arm, so a silent PAM-block is built into the donor "
        "(Section 5). g4-g7 scored lower; the GC-rich g5-g7 are promiscuous (622-908 sites) and not recommended."))

    s.append(section("4. Off-target / specificity analysis (three independent tools)"))
    s.append(P("Because the prior design failed on a pseudogene off-target missed by a single tool, specificity "
        "was assessed with <b>three independent genome-wide methods</b>: <b>Cas-OFFinder</b> (local exhaustive "
        "enumeration vs hg38; NGG&le;4 mm + NAG&le;3 mm, every hit annotated to RefSeq genes), <b>CRISPOR</b> "
        "(web, BWA-based, CFD/MIT scoring + annotation), and <b>FlashFry</b> (local, against a purpose-built hg38 "
        "SpCas9-NGG database; same community metrics). All three agree closely."))
    ottbl = make_table(
        ["Tool (hg38)", "g3 off-targets", "g3 &le;1 mm", "g3 top-OT CFD", "g2 off-targets", "g2 &le;1 mm", "p53-family / pseudogene"],
        [["Cas-OFFinder (local)", str(g3o['n_offtargets']), status_para("0"), "n/a", str(g2o['n_offtargets']), status_para("0"), status_para("0")],
         ["CRISPOR (web)", str(cris['TP53ct_g3']['offtarget_count']), status_para("0"), str(cris['TP53ct_g3']['max_cfd_offtarget']), str(cris['TP53ct_g2']['offtarget_count']), status_para("0"), status_para("0")],
         ["FlashFry (local)", str(FF['TP53ct_g3']['otCount']), status_para("0"), str(FF['TP53ct_g3']['max_cfd_offtarget']), str(FF['TP53ct_g2']['otCount']), status_para("0"), P("0 *","cell")]],
        col_widths=[96, 70, 52, 64, 70, 52, 89])
    s.append(ottbl)
    s.append(P("* FlashFry confirms the count and mismatch distribution on a third algorithm; gene/pseudogene "
        "annotation is from Cas-OFFinder + CRISPOR (both 0). Cas-OFFinder enumerates exhaustively and does not "
        "compute CFD; the g3/g2 top-off-target CFD (0.61 / 0.65) is reported identically by CRISPOR and FlashFry. "
        "FlashFry's tabulated count is genomic sites (otCount); Figure 2B bins unique off-target sequences (slightly "
        "fewer, as some map to several loci).", "caption"))
    s.append(callout("pass", "<b>Pseudogene check (the prior-failure mode): cleared.</b> For g3, all three tools "
        "report <b>0 off-targets at &le;1 mismatch</b>, and the two annotating tools find <b>0 hits in any TP53 "
        "pseudogene or p53-family gene (TP63/TP73)</b>. All exonic off-targets occur only at 4 mismatches (max CFD "
        "0.17, predicted inactive); the same loci (UBR1, FGA, NAALADL2) are recovered independently. CRISPOR and "
        "FlashFry flag the identical single highest-risk off-target (CFD 0.61)."))
    s.append(P(f"<b>Binding-site variation.</b> A common SNP in a spacer seed or PAM would cause allele-specific "
        f"cutting failure -- relevant because the edit is in HEK293T's own genome. Screening Ensembl Variation "
        f"(GRCh38) across the g3 and g2 protospacer+PAM windows returned {VS['n_variants']} cataloged variants, "
        f"<b>none common (MAF &ge; 1%)</b> under either guide -- no allele-dropout risk (g3 {VS['g3']['verdict']}, "
        f"g2 {VS['g2']['verdict']}). The locus is densely catalogued with rare single-submission variants that do "
        "not threaten the assay."))
    s.append(Spacer(1, 4))
    s.append(KeepTogether([embed_image(os.path.join(FIG, "figure_B_specificity_evidence.png")),
        P("Figure 2. (A) CRISPOR specificity across the seven candidates -- g3 is the most specific guide at the "
          "stop codon. (B) g3 off-target counts by mismatch agree across Cas-OFFinder, CRISPOR and FlashFry; no "
          "hit below 2 mismatches in any tool and none in p53-family/pseudogenes.", "caption")]))

    s.append(section("5. HDR donor design"))
    s.append(P(f"The donor is a <b>{DON['donor_len']} bp dsDNA</b> (plasmid or synthetic long dsDNA): "
        "5' arm (800 bp) - (GGGGS)&times;3 linker - mCherry - 3' arm (800 bp)."))
    dtbl = make_table(
        ["Component", "Length", "Location / context", "Notes"],
        [["5' homology arm", "800 bp", DON['arm5_chr17'].replace(' (minus strand, 5\' arm)',''), "genomic; ends at Asp393; carries g2 silent PAM-block"],
         ["(GGGGS)x3 linker", f"{len(MCH['linker_nt'])} bp", "insert", "flexible, codon-diversified (no tandem repeat)"],
         ["mCherry CDS (no stop)", f"{len(MCH['mcherry_nt'])} bp", "insert", f"human codon-opt, GC {MCH['mcherry_gc']}%, 0 BsaI / 0 BsmBI"],
         ["3' homology arm", "800 bp", DON['arm3_chr17'].replace(' (minus strand, 3\' arm)',''), "genomic; native TGA stop + 3'UTR"],
         [P("<b>Full donor</b>","cell"), P(f"<b>{DON['donor_len']} bp</b>","cell"), "5'arm + insert + 3'arm", f"insert BsaI/BsmBI = 0; arms carry {len(DON['donor_bsaI'])} BsaI + {len(DON['donor_bsmBI'])} BsmBI (native genomic)"]],
        col_widths=[92, 48, 150, 203])
    s.append(dtbl)
    s.append(Spacer(1, 4))
    s.append(P("<b>Re-cut protection.</b> g3 needs none (the insertion splits its target). For g2, a single "
        "<b>silent</b> change in the 5' arm (codon 389 GGG&rarr;GGT, both Glycine) destroys the NGG PAM; the CDS "
        "translation is unchanged and g2 can no longer cut the donor or the edited allele -- one donor serves "
        "either guide. <b>mCherry</b> (Shaner 2004; GenBank AY678264, verified byte-identical) is human "
        f"codon-optimized to {len(MCH['mcherry_nt'])} nt with 0 internal BsaI/BsmBI as requested."))
    s.append(Spacer(1, 4))
    s.append(KeepTogether([embed_image(os.path.join(FIG, "figure_C_donor_genotyping.png")),
        P("Figure 3. (A) HDR donor architecture with built-in re-cut protection. (B) Genotyping/Sanger primers; "
          "junction PCRs give a product only on correct genomic integration (not on WT or donor plasmid).", "caption")]))

    s.append(section("6. Genotyping and validation strategy"))
    ptbl = make_table(
        ["Assay", "Forward / Reverse (Tm)", "Product"],
        [[P("5' junction PCR","cell"), P(f"F {p5['fwd']} ({p5['fwd_tm']}C, outside 5' arm)<br/>R {p5['rev']} ({p5['rev_tm']}C, in insert)","cell_mono"), f"{p5['product_size_ki']} bp (KI only; none on WT/donor)"],
         [P("3' junction PCR","cell"), P(f"F {p3['fwd']} ({p3['fwd_tm']}C, in insert)<br/>R {p3['rev']} ({p3['rev_tm']}C, outside 3' arm)","cell_mono"), f"{p3['product_size_ki']} bp (KI only; none on WT/donor)"],
         [P("Internal / zygosity","cell"), P(f"F {pin['fwd']} ({pin['fwd_tm']}C, 5' arm)<br/>R {pin['rev']} ({pin['rev_tm']}C, 3' arm)","cell_mono"), f"WT {pin['product_size_wt']} bp vs KI {pin['product_size_ki']} bp"]],
        col_widths=[78, 285, 130])
    s.append(ptbl)
    s.append(P("All genomic primers are unique on hg38 (isPcr); junction-PCR logic was confirmed by in-silico PCR "
        "on WT, donor and knock-in templates. Junction primers are anchored outside the arms so they cannot prime "
        "on residual donor -- a band proves genuine genomic integration. The internal pair distinguishes "
        "wild-type / heterozygous / homozygous clones and provides a Sanger amplicon across the whole insert -- "
        "but its primers lie within the arms, so it also amplifies residual donor (same KI-size band); use the "
        "junction PCRs as the definitive integration proof and account for donor carryover when scoring zygosity."))

    s.append(section("7. Critical caveats and recommendations"))
    s.append(P("<b>(a) HEK293T expresses SV40 large T antigen.</b> Large T binds p53 directly, <b>stabilizes</b> it "
        "(raising basal levels) while <b>inhibiting transactivation</b> and blocking p53-dependent MDM2 induction "
        "(Lilyestrom 2006). p53 stress dynamics in 293T are therefore not physiological. For physiological "
        "dynamics, a p53-wild-type line without a viral oncoprotein (MCF7, RPE1-hTERT, A549, U2OS, HCT116) is "
        "preferable; if 293T is required, interpret with this caveat. <b>This is the most important point in this "
        "report.</b>"))
    s.append(P("<b>(b) C-terminal tag vs p53 regulation.</b> Validate that tagged p53 stabilizes after genotoxic "
        "stress (doxorubicin, nutlin-3a), localizes to the nucleus, and induces canonical targets (CDKN1A/p21, "
        "MDM2), against an untagged control."))
    s.append(P("<b>(c) HEK293 ploidy.</b> 293/293T is hypotriploid (~3 TP53 copies); expect multiple alleles to "
        "edit. The internal/zygosity PCR reveals allele dosage; all-allele tagging may need more clones."))
    s.append(P("<b>(d) Delivery &amp; HDR.</b> Deliver the donor as plasmid or long dsDNA (800 bp arms are too long "
        "for ssODN); co-deliver SpCas9 + g3 (RNP or plasmid). Validate by junction PCR &rarr; Sanger across the "
        "insert &rarr; mCherry fluorescence and tagged-p53 function. Guide cloning oligos are provided for "
        "pX330/pSpCas9(BB)-family BbsI vectors."))
    s.append(Spacer(1, 4))
    s.append(subsection("Design quality-control summary"))
    s.append(make_table(["Check", "Status", "Detail"],
        [[c[0], status_para(c[1]), c[2]] for c in QC_ROWS],
        col_widths=[250, 50, 193]))

    s.append(section("8. Methods, databases and auditability"))
    s.append(P("TP53 NM_000546.6 from NCBI; genomic sequence from local GRCh38/hg38 2bit; stop codon anchored by "
        "exact sequence match (build-independent). On-target/specificity from CRISPOR v5.2 (hg38; batch retained); "
        "exhaustive off-targets from Cas-OFFinder v2.4.1 (hg38, CPU) and independently from FlashFry (local hg38 "
        "SpCas9-NGG database, cached for reuse). mCherry codon optimization with DNAChisel "
        "(h_sapiens) enforcing BsaI/BsmBI absence; primers with primer3; specificity by isPcr vs hg38; binding-site "
        "variant screen via Ensembl Variation (GRCh38). Data retrieved 2026-06-29/30. The numbered scripts/ pipeline "
        "ships as the reproducible record."))

    s.append(section("9. Output files"))
    for t in ["<b>TP53_mCherry_Knockin_Report.pdf</b> -- this report",
              "<b>TP53_mCherry_Knockin_Design.xlsx</b> -- guides, off-targets, donor components, primers, cloning oligos",
              "<b>sequences.fasta</b> -- guides, mCherry / insert, donor + arms, primers, BbsI cloning oligos",
              "<b>figures/</b> -- knock-in strategy; specificity evidence; donor + genotyping"]:
        s.append(bullet(t))
    s.append(P("The <b>scripts/</b> folder ships as the reproducible numbered pipeline.", "caption"))

    s.append(section("10. References"))
    s.extend(references_block(REFS))
    doc.build(s)
    print("[pdf] TP53_mCherry_Knockin_Report.pdf written")

if __name__ == "__main__":
    build_md()
    build_pdf()
